Author: Tony Hao

[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

這樣就搞定了!

Advertisements

[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

[SQL]having的使用時機

一般來說having常常搭配group by一起使用

假設有一張articles的資料表,共有10筆資料如下:

現在有個情境是「取得文章分類的流覽次數超過5且依照次數由高到低排序」

若是一個初學SQL的新手又不認識having的用法,可能會寫成這樣:

SELECT
	category_id,
	SUM(view_cnt) AS view_cnt
FROM
	`articles`
WHERE
	view_cnt > 5;
GROUP BY
	category_id
ORDER BY
	view_cnt DESC

乍看之下好像是對的,但其實這個SQL變成「取得文章流覽次數超過5的文章分類流覽次數並依照次數由高到低排序」,這是因為WHERE會套用在GROUP BY之前,所以在GROUP BY前會把文章瀏覽次數小於5的排除掉才進行GROUP BY,這個結果不是我們要的!

正確的SQL是利用GROUP BY搭配HAVING

SELECT
	category_id, SUM(view_cnt)  AS total_view_cnt
FROM
	`articles`
GROUP BY category_id
HAVING total_view_cnt > 5
ORDER BY total_view_cnt DESC

如果不想使用HAVING的話也可以利用子查詢的方式,但比較不建議

SELECT * FROM (
SELECT
	category_id, SUM(view_cnt)  AS total_view_cnt
FROM
	`articles`
GROUP BY category_id
ORDER BY total_view_cnt DESC
) A
WHERE A.total_view_cnt > 5;

因為查兩次,多此一舉

[Linux]利用nslookup指定DNS查詢Domain

總該有個前因

今天遇到一個突發狀況,DNS(Domain Name Server)上的子網域設定突然失效導致客戶無法使用子網域下的服務

在查明是DNS的問題之前,腦海中有閃過一個指令是可以用來指定DNS查詢Domain

後來詢問Google大神才喚醒我的記憶

在解決這個突發狀況的時候nslookup幫了我很大的忙

正好趁這個機會重溫一下nslookup這個指令

我記得第一次接觸它是在大學修網路概論課的時候

一句話說明nslookup的作用

nslookup是用來查詢網路上的DNS

我們都知道網路上有非常多DNS,常見的有:

中華電信

  • 168.95.1.1
  • 168.95.192.1

Google

  • 8.8.8.8
  • 8.8.4.4

我們在本機可以設定要去哪一台DNS查詢Domain

那如果在不更動本機的DNS設定下,可以使用別台的DNS來查詢Domain嗎?

答案是Yes, 對!就是利用nslookup

如何使用nslookup

nslookup提供interactive和non-interactive兩種模式來查詢

non-interactive

nslookup google.com #使用本機設定的DNS查詢google.com這個domain
nslookup google.com 8.8.8.8 #到8.8.8.8這台DNS查詢google.com這個domain

interactive

nslookup
> server 8.8.8.8 #指定DNS
> google.com 8.8.8.8 #查詢google.com這個domain

以上兩種方式都有人在用,看個人習慣

自己比較偏好第二個