Google Chrome 63存取.dev網域強制使用HTTPS

前陣子一直困擾我的問題

明明本機的開發環境沒有設定錯誤卻一直連不到網站

但換個瀏覽器(從Chrome改用Firefox)或是掛上SSL使用HTTPS就可以

其實以前也有發生過,後來換個網域名稱就可以所以就沒有深究原因了

原本我的開發環境的網域名稱是:

myweb.dev

基本上在開發環境中我不常使用HTTPS,照裡來說用以下的URL應該要可以連到:

http://myweb.dev

實際上確不行,而且Chrome會擅自幫我導到

https://myweb.dev

經過幾次測試後我發現一個輪廓

Chrome + HTTP (X)

Chrome + HTTPS(O)

Firefox + HTTP (O)

Firefox + HTTPS (O)

wget + HTTP (O)

從以上的測試結果來看應該可以確定是Chrome的問題

其實,從Google Chrome 63開始會強制將.dev網域的網站導入https存取

如果你使用Google Chrome連到http://xxx.dev,Chrome會強制幫你導到https://xxx.dev

source code

至於為何會有這樣的機制?其實就是為了防止中間人攻擊的駭客手法

有興趣更深入瞭解的話可以搜尋一下HSTS

 

解決方案

1.放棄使用.dev domain改用.lh或.localhost

2.到chrome://net-internals/#hsts > Delete domain security policies 輸入你的開發網域 ex: myweb.dev 刪掉它

 

參考來源:

Chrome & Firefox now force .dev domains to HTTPS via preloaded HSTS

Preload HSTS for the .dev gTLD

Advertisements

[GNOME] putting your application in the menus

這類型的Menu呈現方式,是我愛用GNOME的原因之一

但如果要將自己的application加入到這個menu要怎麼做呢?

身為一個開發者經常需要執行各種程式,有些常用的程式我會希望可以放在menu中

那麼,要如何將程式放在menu中呢?

首先,你需要寫一個GNOME desktop file,內容看起來會像是這樣:

[Desktop Entry]                                                                                                                        
Encoding=UTF-8
Name=MyApplication
Exec=/home/tony/an-executable-file 
Icon=/home/tony/a_icon.png
Terminal=false
Type=Application
  1. [Desktop Entry] 基本上每個gnome desktop file的第1行一定要是這個
  2. Encoding 設定這個desktop file實際的編碼
  3. Name application的名稱
  4. Exec 你希望這個application執行的檔案
  5. Icon application的圖示
  6. Terminal 是否要在terminal中執行
  7. Type 告訴gnome這個desktop file是一個application,也可以是Link or Directory

完成後,我們可以將這個desktop file放在以下兩個路徑其中一個:

放在系統目錄下,可以讓每個使用者都能看見

/usr/share/applications

或是放在家目錄,只有自己可以看見

~/.local/share/applications

 

參考來源:

https://developer.gnome.org/integration-guide/stable/desktop-files.html.en

[Git]讓不同的Repository使用不同的private key

有時候我們需要在不同的Repository使用不同的private key,尤其是工作和私人的Repository

目標

我們希望切換到不同的Local Repository下git pull或git push這些和remote repository的指令它就自動使用指定的private key

第一步:到~/.ssh/config新增config

如果有兩個不同的Repository使用不同的key,就需要新增兩個設定

這裡用your-repo-ayour-repo-b來表示

再把Host複製起來,下一步會用到

Host your-repo-a
HostName github.com
User git
IdentityFile /home/tony/.ssh/your_repo_a_id_rsa

Host your-repo-b
HostName github.com
User git
IdentityFile /home/tony/.ssh/your_repo_b_id_rsa

第二步:到Local Repository的.git/config

依照上面的設定host,修改remote hostname


[remote "origin"]
    url = git@your-repo-a:tonyciou/a-project.git

這樣就搞定了!

[MySQL] 讓mysql識別table 不分大小寫(case insensitive)

以下兩種SQL,MySQL是否會將他們識別程不同的資料表呢?

select * from users
select * from Users

答案是:可以,也不可以

一般來說資料表的名稱都會使用小寫來命名,雖然我也有遇過一些是用大寫命名,但第一次遇到有混用的,才發現我需要在開發環境的my.cnf加上一個設定,才能讓以上兩個sql查到同一張資料表,而且我有發現只有我在建立開發環境的時候碰到這個問題,因為團隊的其他人是用Mac,在預設的情況下在Mac和Windows安裝MySQL,它會將SQL的table name用(case insensitive)的方式來看,而Linux預設則會用(case sensitive)的方式,這和OS的File system有關,但有沒有方式可以讓MySQL在Linux上跑的時候也case insensitive呢?

只要在my.cnf將lower_case_table_names設1

[mysqld]
lower_case_table_names = 1

不管你的table是大寫或小寫mysql都會將他們視為小寫

參考來源:

https://dev.mysql.com/doc/refman/5.6/en/identifier-case-sensitivity.html

[Debian]升級Debian 9後無法使用觸控版

趁這週末有空趕緊把我的筆電從Debian 8升到Debian 9

升級的過程都還算滿順利的,但升級後觸控版沒辦法使用

稍微查了一下發現是因為Debian 9預設改用新的 libinput driver

雖然它不支援一些舊的driver提供的功能

但如果你的筆電是新的應該就沒有這個問題

解法1:

直接把舊的driver移除

sudo apt-get remove xserver-xorg-input-synaptics

解法2:

sudo mkdir /etc/X11/xorg.conf.d
sudo cp /usr/share/X11/xorg.conf.d/40-libinput.conf /etc/X11/xorg.conf.d

以上兩種方式都需要重新登入才會生效

雖然這兩種都可以解決觸控版無法正常使用的問題

但我比較建議使用解法1畢竟synaptic Xorg會漸漸被libinput取代

直接移除掉以後升級應該就不會遇到類似的問題了

 

參考來源:

https://wiki.debian.org/SynapticsTouchpad#Debian_9_.22Stretch.22

[Laravel5.1踩雷紀錄] 進行整合測試發生1205 Lock wait timeout exceeded; try restarting transaction

前陣子不知道為什麼在我的Laravel app進行整合測試的時候開始出現這個錯誤,而且隨著TestCase開始慢慢增加,問題就越來越嚴重!因為這造成我們在跑測試的時候花費非常多的時間!而且會因為Lock wait timeout導致無法測完某個TestCase就failure

使用情境

版本:

  • PHP 7.0
  • PHPUnit 5.7
  • Laravel 5.1
  • MySQL 5.6

執行phpunit進行測試

vendor/bin/phpunit --stop-on-failure

跑了一部份的TestCase就出現

PDOException: SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction

如果把上面的錯誤訊息直接丟去問Google大神,那找到的解法可能無法解決這個情境發生的問題,因為有太多的原因會造成Lock wait timeout還好這個情況是在測試時發生的,需要確認的因素單純很多,其實追根究底Lock wait timeout exceeded就是transaction一直在等待等到timeout,如果我們可以觀察目前transaction的狀態那一定可以幫助我們釐清這個問題

在MySQL內建資料庫information_schema的INNODB_TRX 資料表可以讓我們觀察目前正在執行或等待的transaction

The INNODB_TRX table contains information about every transaction (excluding read-only transactions) currently executing inside InnoDB, including whether the transaction is waiting for a lock, when the transaction started, and the SQL statement the transaction is executing, if any.

MySQL Documentation

從這張gif看就可以很明顯的發現,隨著測試開始進行時transaction的數量竟然慢慢飆高,而且一直都在running的狀態

因為我是用Laravel提供的DatabaseTransactions Trait,照理來說跑完某個Test就要rollback才對,但竟然沒有!實際去看DatabaseTransactions.php也確實有rollback,有趣的是它在beforeApplicationDestroyed的時候才執行

    public function beginDatabaseTransaction()
    {
        $this->app->make('db')->beginTransaction();

        $this->beforeApplicationDestroyed(function () {
            $this->app->make('db')->rollBack();
        });
    }

在從beforeApplicationDestroyed()找到vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php這隻檔案

abstract class TestCase extends PHPUnit_Framework_TestCase
{
    use ApplicationTrait, AssertionsTrait, CrawlerTrait;

    /**
     * The callbacks that should be run before the application is destroyed.
     *
     * @var array
     */
    protected $beforeApplicationDestroyedCallbacks = [];

    /**
     * Creates the application.
     *
     * Needs to be implemented by subclasses.
     *
     * @return \Symfony\Component\HttpKernel\HttpKernelInterface
     */
    abstract public function createApplication();

    /**
     * Setup the test environment.
     *
     * @return void
     */
    public function setUp()
    {
        if (! $this->app) {
            $this->refreshApplication();
        }
    }

    /**
     * Clean up the testing environment before the next test.
     *
     * @return void
     */
    public function tearDown()
    {
        if ($this->app) {
            foreach ($this->beforeApplicationDestroyedCallbacks as $callback) {
                call_user_func($callback);
            }

            $this->app->flush();

            $this->app = null;
        }

        if (property_exists($this, 'serverVariables')) {
            $this->serverVariables = [];
        }

        if (class_exists('Mockery')) {
            Mockery::close();
        }
    }

    /**
     * Register a callback to be run before the application is destroyed.
     *
     * @param  callable  $callback
     * @return void
     */
    protected function beforeApplicationDestroyed(callable $callback)
    {
        $this->beforeApplicationDestroyedCallbacks[] = $callback;
    }
}

看完以上的程式碼應該就可以瞭解DatabaseTransactions Trait的運作流程

  1. 在呼叫beginDatabaseTransaction()的時候, 開啟transaction並將rollback callback存入beforeApplicationDestroyedCallbacks陣列
  2. 在呼叫tearDown()的時候才會取出rollback callback執行

到目前為止幾乎可以確定是transaction開啟後但沒有rollback,重新去檢查之後才發現某些TestCase覆寫了tearDown method卻沒有呼叫parent::tearDown()

public function tearDown()
{
    //some code here.
}

改成

public function tearDown()
{
    parent::tearDown();
    //some code here.
}

就可以讓transaction rollback

總結前因後果

tearDown()被覆寫了,但在child method沒有呼叫parent::tearDown()導致Test跑完了transaction卻一直沒有被釋放掉,進而發生transaction wait timeout的問題,如果TestCase數量很少的時候這個問題其實不會太明顯,但隨著Test的數量越來越多這個問題就很難被忽視

補充

其實還有另一種方法可以解決,但還是建議使用第一個解法!畢竟有些時候會需要用同一個PHP process進行測試,PHPUnit提供了–process-isolation參數,這個參數讓PHPUnit在跑每個測試的時候都是用新的php process執行

vendor/bin/phpunit --stop-on-failure --process-isolation

[Nginx]接收自定義header需要注意的事情

我們都知道HTTP的header可以塞自定義的內容,身為一個Web Develop Developer開發的API在大部分的情況下都會要求client side提供一個token或key來讓server side驗證這個request是否允許存取這個API,當然這個token必須是由server side產生的,這時候我們就需要請client side把token塞在request header裡面,server side會去驗證這個token是不是合法的

Nginx會將含有底線的header視為不合法

Nginx預設的設定會將含有底線(underscore)的欄位名稱視為不合法的header,導致server side的程式碼無法取得自定義的header內容

解決這個問題有兩個方法

1.不要在header field name使用底線

2.在nginx.conf啟用underscores_in_headers:

underscores_in_headers on;

為了查明這個問題還特地去看了一下nginx的source code

        rc = ngx_http_parse_header_line(r, r->header_in,
                                        cscf->underscores_in_headers);

        if (rc == NGX_OK) {

            r->request_length += r->header_in->pos - r->header_name_start;

            if (r->invalid_header && cscf->ignore_invalid_headers) {

                /* there was error while a header line parsing */

                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                              "client sent invalid header line: \"%*s\"",
                              r->header_end - r->header_name_start,
                              r->header_name_start);
                continue;
            }

            /* a header line has been parsed successfully */

            h = ngx_list_push(&r->headers_in.headers);
            if (h == NULL) {
                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }

            h->hash = r->header_hash;

            h->key.len = r->header_name_end - r->header_name_start;
            h->key.data = r->header_name_start;
            h->key.data[h->key.len] = '\0';

            h->value.len = r->header_end - r->header_start;
            h->value.data = r->header_start;
            h->value.data[h->value.len] = '\0';

            h->lowcase_key = ngx_pnalloc(r->pool, h->key.len);
            if (h->lowcase_key == NULL) {
                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }

            if (h->key.len == r->lowcase_index) {
                ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);

            } else {
                ngx_strlow(h->lowcase_key, h->key.data, h->key.len);
            }

            hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
                               h->lowcase_key, h->key.len);

            if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
                return;
            }

            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "http header: \"%V: %V\"",
                           &h->key, &h->value);

            continue;
        }

從上面的程式碼可以看到,如果被nginx視為不合法的(invalid)header就會將client sent invalid header line xxx寫入log,並且不會繼續往下執行而是直接continue

所以如果在server side的程式碼無法取得自定義的header內容且滿足以下條件:

  1. server side使用的web server是nginx
  2. 確定client side有給server side自定義的header而且這個header含有底線的名稱
  3. 在server side的程式碼無法取得自定義的header內容
  4. underscores_in_header沒開

就打開underscores_in_headerreload nginx

含有底線的header被視為不合法的原因

這是為了避免將header fileds name傳遞到CGI變數的時候造成衝突,因為nginx將header映設給CGI變數的時候會將破折號(dashes)和底線(underscores)都轉成底線,如果自定義的header有兩個MyTokenMy_Token

GET /
Host: tonyhao.net
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120
Pragma: no-cache
Cache-Control: no-cach
My-Token: my-token1
My_Token: my-token2

映設到CGI變數的時候都會變成My_Token,在這個情況下就會發生衝突了

參考資料

https://github.com/nginx/nginx/blob/master/src/http/ngx_http_request.c

http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_header

https://code.tutsplus.com/tutorials/http-headers-for-dummies–net-8039