Category: Coding Life

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

[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

[vue.js] v-model.lazy的使用時機

vue.js的v-model directive是一個非常方便的東西

由於v-model是使用two way data binding

所以只要修改就會立即顯示

但有時候我們也會希望v-model不要這麼快就幫我們sync

尤其是使用者在填寫表單(form)的時候

我們會希望使用者填完之後才觸發

這時候就可以使用v-model.lazy

加上.lazy這個內建的modifier讓data sync在change event事件後才去處理

vuejs v2:

<input v-model.lazy="name">

參考來源:

https://vuejs.org/v2/guide/forms.html#lazy

javascript ES6 的箭頭函式(arrow function)

javascript ES6的arrow function是方便的syntax sugar,可以幫助我們寫出更簡潔的js code

在arrow function還沒出現前,針對特定element綁定事件大致會像這樣:

document
.getElementById('my-btn')
.addEventListener("click", function(e) => {
alert('before es6');
});

將以下的anonymous function替換成arrow function的寫法:

document
.getElementById('my-btn')
.addEventListener("click", (e) => {
alert('arrow function');
});

只有一個參數時,可省略括號:

document
.getElementById('my-btn')
.addEventListener("click", e => {
alert('arrow function');
});

若只有一行statement時,可以省略大括號

document
.getElementById('my-btn')
.addEventListener("click", e => alert('arrow function'));

其實從以上的範例還真看不出來arrow function強大的地方XD
頂多就只有省略function這個關鍵字和大括號而已
但其實這只是arrow function的其中一種寫法
更方便的用法還在後頭~

arrow function讓你不用.bind(this)

我們都知道javascript的this是一個很tricky的東西
和Java、C#、PHP等OOP的語言的this所代表的涵意是不太一樣的

言規正傳,如果不使用arrow function在setTimeout的callback中要取得object instance的this
就要使用.bind把object instance的this綁定給setTimeout的callback

function UserModel(id) {
    this.id = id;
    this.name = '';

    setTimeout(function() {
	this.name = 'from remote';
    }.bind(this), 1000);
}

var userModel = new UserModel(1);
console.log(userModel.name);

但如果使用arrow function就簡單的多了,不需要加上.bind(this)

function UserModel(id) {
    this.id = id;
    this.name = '';

    setTimeout(() => {
	this.name = 'from remote';
    }, 1000);
}

var userModel = new UserModel(1);
console.log(userModel.name);

結論

ES6的arrow function可以讓我們少寫一些程式碼
更容易處理this的指向,進而寫出更簡潔易懂的程式碼

[PHP] 為什麼應該使用MySQL Native Driver(mysqlnd)來取代MySQL Client Library(libmysqlclient)

還記得以前第一次自己架設LAMP開發環境的時候,就已經注意到安裝有這兩個package了

但那時候還不曉得有何差別,只記得書上叫我裝php5-mysql就照著裝XD

前陣子碰到一個需要安裝mysqlnd才能解決的問題,正好趁這個機會來瞭解一下差別

MySQL Native Driver is a replacement for the MySQL Client Library (libmysqlclient)

在PHP文件的myslqnd章節內一開頭就說明了mysqlnd是用來取代libmysqlclient

為何PHP官方會建議使用mysqlnd?

 

mysqlnd是屬於PHP Project的一部份

不像libmysqlclient是使用MySQL license,mysqlnd是使用PHP license這也避免了一些因為license所衍生的問題(看看前陣子Oracle告Google的新聞就知道為什麼了!)

mysqlnd的功能比libmysqlclient還多

從PHP 5.3.0開始mysqlnd library 已經是內建在PHP的library,mysqlnd提供的一些功能像是query caching、lazy connections、SSL這些在libmysqlclient的沒辦法用的

詳細差異請參閱Library feature comparison

mysqlnd的效能比libmysqlclient還好

由於mysqlnd是用C寫的PHP extension,所以它是使用PHP memory management system也支援PHP memory limit和memory_get_usage(),安裝mysqlnd可以使用memory_get_usage()來追蹤記憶體的使用情形,而是在libmysqlclient是不可能的,因為libmysqlclient要使用C語言的function malloc(),在官網有舉一個例子

One example of the memory efficiency is the fact that when using the MySQL Client Library, each row is stored in memory twice, whereas with the MySQL Native Driver each row is only stored once in memory.

libmysqlclient在儲存每一筆資料row到memory的時候會存兩次

而mysqlnd只會存一次

 

參考來源:

http://php.net/manual/en/mysqlnd.overview.php

http://php.net/manual/en/mysqlinfo.library.choosing.php

http://php.net/manual/en/intro.mysqlnd.php

https://dev.mysql.com/downloads/connector/php-mysqlnd/

淺談jQuery Event Handler 的 extraParameters 使用時機

最近常常需要使用jQuery來寫一些前端的功能
前陣子終於領悟到了jQuery API Document上寫的extraParameters的使用時機,趕緊記錄一下
其實jQuery API Document寫得滿清楚的

extraParamters是一個選擇性的參數,主要的用途就是傳遞額外參數給event Handler

extraParameters
Additional parameters to pass along to the event handler.

例如,有一個使用情境是使用者選擇完下拉選單會帶入預設的資料,但在修改時則帶入已儲存在DB的資料,如果在這個情況下直接trigger change預設的資料就會把應該顯示的資料蓋掉,這時候extraParameters就派上用場了!

在trigger change event handler的時候使用extraParameters,來傳遞需要顯示的資料(sourceText)

      $('.btn-load').click(function(e){
        var id = $(this).attr('id');
        $language.val(id).trigger('change', {
          sourceText: $(this).parent().parent().find('td')[1].innerHTML
        });
      });
 完整的JS code如下:
    $(function(){
      $language = $('#language');
      $language.change(function(e, o){
        if($(this).val() === ''){
          $('#desc').text('');
          return false;
        }
        
        $desc = $('#desc');
        
        if(typeof o === 'object'){
          if(o.sourceText){
            $desc.text(o.sourceText); 
          }          
        }
        else{          
          $desc.text($(this).find("option:selected").text() + ' is a programming language.');          
        }
        
      });
      $('.btn-load').click(function(e){
        var id = $(this).attr('id');
        $language.val(id).trigger('change', {
          sourceText: $(this).parent().parent().find('td')[1].innerHTML
        });
      });
    });

Demo Code

使用Gmail SMTP Server寄信需要注意的事情

前陣子因為工作的關係要使用Gmail SMTP Server來寄信,由於當時的情況有點緊急也有前輩和同事協助我,當下我也沒有搞懂整個設定的流程和需要注意的事項,這次因為開發新系統的關係要重新設定,剛好利用這次機會趕緊來紀錄一下,以Laravel Framework為例需要在.env檔加入以下設定:

MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=465
MAIL_FROM=test@gmail.com
MAIL_NAME=someone
MAIL_USERNAME=test@gmail.com
MAIL_PASSWORD=test
MAIL_ENCRYPTION=ssl

注意事項:

  1. encrytion的設定需要和port對應,若encrytion設為ssl則port須為465,若encrytion設為tls則port須為587
  2. encrytion的值須為小寫(ssl or tls)
  3. 使用Gmail SMTP Server每日寄送郵件上限數約為2000封
  4. 若以上設定確認無誤後,還是無法寄送信件並提示以下訊息,則需要啟用允許安全性較低的應用程式存取auth failed_gmail_smtp
  5. 啟用安全性較低的應用程式存取權限不會立即生效,大概要等30秒左右才會生效google_account_lesssecureapps

References:
https://support.google.com/a/answer/176600?hl=en

https://support.google.com/mail/answer/14257