Apache2 and HTTP/2

Apache HTTP/2

最近 Apache HTTPD 2.4.17 出了,內建 HTTP/2 的支援,不免俗的要來測試一下,在 Ubuntu 14.04 LTS 下安裝其實也是蠻辛苦的,最主要的問題是 OpenSSL 內建的是 1.0.1,但是要完整支援 HTTP/2 ,還需要 TLS 的 ALPN ,然後就會需要 OpenSSL 1.0.2,因此第一件事情就是下載編譯安裝 OpenSSL,目前最新的是 1.0.2d,如果是 15.10 就是內建 1.0.2 的,問題會少很多,總之先自己下載解壓縮然後編譯:

./config
make
sudo make install

會把檔案裝到/usr/local/ssl這個位置,大概是因為 OpenSSL 影響比較大所以預設沒有裝到平常的/usr/local下面,不過也因此造就後續比較麻煩的地方。

Apache 的部分比較麻煩,要分兩個部分,第一個部分是先把 APR 部分的程式碼和 HTTPD 的部分拉下來放一起,這邊參考 あすのかぜ 上的指令:

wget http://ftp.jaist.ac.jp/pub/apache//httpd/httpd-2.4.17.tar.gz
tar zxvf ./httpd-2.4.17.tar.gz

wget http://ftp.yz.yamagata-u.ac.jp/pub/network/apache//apr/apr-1.5.2.tar.gz
tar zxvf ./apr-util-1.5.4.tar.gz
mv ./apr-1.5.2 ./httpd-2.4.17/srclib/apr

wget http://ftp.yz.yamagata-u.ac.jp/pub/network/apache//apr/apr-util-1.5.4.tar.gz
ar zxvf ./apr-util-1.5.4.tar.gz 
mv ./apr-util-1.5.4 ./httpd-2.4.17/srclib/apr-util

然後進去httpd-2.4.17目錄下指令編譯安裝:

env PKG_CONFIG_PATH=/usr/local/ssl/lib/pkgconfig ./configure --enable-http2
make
sudo make install

執行的時候也需要注意,需要帶個環境變數,不然他找不到/usr/local/ssl/下的 Library:

env LD_LIBRARY_PATH=/usr/local/ssl/lib /usr/local/apache2/bin/httpd

或是:

env LD_LIBRARY_PATH=/usr/local/ssl/lib /usr/local/apache2/bin/apachectl start

不過其實執行前還需要先修改一下設定,我是加在 VirtualHost 裡面:

<VirtualHost *:80>
    Protocols h2c http/1.1
    ServerName blog.othree.net
    ServerAdmin othree@gmail.com

還有

<VirtualHost *:443>
    Protocols h2 http/1.1
    ServerName blog.othree.net
    ServerAdmin othree@gmail.com

重點是Protocols那行,h2指的是 HTTPS(TLS) 下的 HTTP/2 連線,h2c則是 HTTP 下的,在其他地方還會看到h-15h-17之類的,那些其實是之前草稿的版本,無視就好,另外還有一個地方需要設定的是加密用的 CipherSuite 和溝通協定,可以參考 How to h2 in apache 提供的設定:

SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK
SSLProtocol All -SSLv2 -SSLv3

不確定 CipherSuite 的選擇影響多大,不過我猜主要是要把 SSL 全部關掉吧,然後其實這組設定就是 Mozilla 推薦的 SSL 設定的 Modern 那組,等於是要放棄很多舊瀏覽器了,支援的瀏覽器最低版本如下:

  • Firefox 27
  • Chrome 22
  • IE 11
  • Opera 14
  • Safari 7
  • Android 4.4
  • Java 8

都弄好開啟 Apache 就可以了,不過記得如果之前是用 apt 裝的 Apache 的話,設定要自己搬過來。

驗證工具可以用 Curl 或是 nghttp,我是在 OSX 上用 brew 裝的,Curl 比較麻煩,不過可以看到一開始溝通的部分:

brew install curl --with-nghttp2
brew link curl

/usr/local/bin/curl -v --http2 https://othree.net

然後就會出現最上面那張圖那樣的資訊,可以看到有 ALPN 的選項出現,另外一個工具是 nghttp,其實 Curl 的 http2 支援就是用這套 library:

brew install nghttp2

nghttp -uv https://othree.net

Apache HTTP/2

Apache HTTP/2

可以看到上色整理得很漂亮的資訊,另外用瀏覽器也可以判斷,Firefox 的話會寫上 HTTP 版本號,Chrome 我測試是要 Canary 才支援,然後我網站連線的 SSL 握手那段花的時間反而更多,不過還是比較快全部下載完,只是 render 畫面影響的還有其他檔案,雖然大部分檔案都提早開始下載,不過也同時開始下載重要度比較低的圖形檔案,結果反而 DOM ready 的時間點更晚~~~,下圖是 HTTP/1.1 的時候:

http1.1-1

然後是 2.0:

http2-1

最後就是還不支援 Server Push,這個東西其實技術上不難,可是和 Cache 搞在一起就變很複雜了,目前好像還沒有比較好的解法出現,也只有一些非開源的系統支援的樣子。


前端測試入門

Test Well

這篇也是之前花一些時間搞清楚的觀念,想著要記錄下來一陣子了,不過最近很忙碌,一直到這幾個連假才有時間寫下來。

其實身為工程師,我一直沒什麼寫測試,只有在少數幾個工具的 library 中有加上 unit test,大概的原因是因為前端的測試沒這麼好做起來,如果是單一 JavaScript 模組的單元測試還好,不過要做整合測試,或是在瀏覽器上真的測試就麻煩很多了,總之前陣子一方面為了工作需要,一方面幫 Moztw 做了下載檔案的自動檢查,就順便把相關的名詞和觀念弄清楚。

之前最搞不清楚的其實就是 Mocha(摩卡咖啡) 和 Chai(印度拉茶) 到底分別是什麼定位,後來終於弄清楚了,Chai 只是提供 BDD 語法的測試用的 斷言 函數庫(assert library),什麼是斷言呢,英文是 assert,例如明確知道某個函數的結果是什麼,把他說出來,就是斷言,如果結果和說的不一樣,就是測試到錯誤,一般的情形,這些 assert library 就會 throw error,至於 Mocha 則是 Test Framework,用來組織和管理你的測試的程式碼,Mocha 本身的設計是不含 assert library,所以可以自己挑選喜歡的 assert library,只要它在出錯誤時會 throw error 就好,Mocha 網站上就列出了四套 assert library 供大家選擇,除此之外,像我之前在介紹 TypeScript 時提過的 assert.js 也可以使用,不過 assert.js 只能檢查型別就是。至於要挑選哪套 assert library 就看各人喜好了,主要是看要怎麼寫斷言,像我挑選 Chai 的原因是他的語法,支援 BDD ,可以寫的看起來很像一句英語:

foo.should.be.a('string');
foo.should.equal('bar');
foo.should.have.length(3);
tea.should.have.property('flavors').with.length(3);

很容易就知道是什麼意思,而且自由度還蠻大的。另外一個原因則是他有支援 Promise,就是所謂的 chai-as-promised,為什麼這個很重要呢,因為 JS 很常遇到需要非同步的操作流程,如果沒有支援,Test Framework 當下把他的 function 跑完,沒有 catch 到 error 就認為沒有錯誤了,當然像 mocha 是有支援非同步的,內建有個等待的機制,done

describe('User', function() {
  describe('#save()', function() {
    it('should save without error', function(done) {
      var user = new User('Luna');
      user.save(function(err) {
        if (err) throw err;
        done();
      });
    });
  });
});

就是每個it區塊裡面,其實都會收到一個函數done,如果有要測試非同步的程式,可以在非同步的部分測試完後,才執行done(),這樣 Mocha 才有機會知道你的測試是不是有非同步的部分,還有什麼時候才是測試完成,不過 Chai 是 BDD,不會容許這樣不直觀的寫法的,所以 Domenic Denicola 開發了 chai-as-promised

promise.should.be.fulfilled;
promise.should.eventually.deep.equal("foo");
promise.should.become("foo"); // same as `.eventually.deep.equal`
promise.should.be.rejected;

只是要這樣簡潔的寫法,還需要先設定一下:

var chai = require("chai");
var chaiAsPromised = require("chai-as-promised");

chai.use(chaiAsPromised);

其實 chai-as-promised 是 chai 的 plugin,然後用chai.use來使用它,底層怎樣運作我還沒深入研究,覺得還有點 magic,不過還算是想的到怎樣實做出來的程度,猜測可能有用到 function 的toString來判斷有沒有引用next參數。

再來,測試蠻常會用到的假物件,mock 和 stub,兩者的差異其實蠻多文章有說明了,我個人覺得簡單分法就是 stub 沒有副作用,mock 則是有副作用的假物件,至於要說要用哪種物件來完成測試的話,基本上就是 stub 可以達成你的測試需求的話就用 stub,在 JavaScript 的測試環境下,好像只有看到 Sinon.js 這套比較多人用,去查了一下名稱典故,覺得一個比較可能的來源是特洛伊木馬故事中,騙特洛伊人把木馬搬進去城裡的那位(Mocha 和 Chai 的名稱應該是互相影響的,不過不確定誰先出來的)。另外還有個角色和 mock、stub 很常一起提到的叫 spy(常見用複數形 spies),最常用來當 callback 之類的,在非同步測試案例中,可以用來確保 callback 有被執行到,甚至可以偷看(spy)被執行了幾次,收到什麼參數等等,總之就是個可以測試函數被執行的次數和方式的物件。

最後要說的則是 e2e test,因為 JS 很多時候都是用來在瀏覽器端實做 UI 和使用者行為的 handler,其實要做完整整合的測試不太容易,e2e 指的是 End to End,端點到端點,通常是說一個流程的起點到終點的意思,例如上網站註冊帳號,這樣算是一個流程,或是上網登入購買東西到結帳完成,這樣也是一個流程,由於 Web App 的環境下,跑 JS 的是瀏覽器,沒辦法簡單的介入,所以以往真的要做 e2e 測試幾乎都是要靠人工,後來有了 Selenium 和 WebDriver,才開始可以讓這些測試自動化。

以前的 Selenium 要控制瀏覽器靠的是 Selenium RC,用比較暴力的方式介入瀏覽器,不過現在的 Selenium 2 則是透過 WebDriver 這個 API 來操作,WebDriver 能進 W3C 標準化其實也是 Selenium 貢獻者的努力,背後也是有些大公司的影子在,目前主流的瀏覽器包括微軟最新的 Edge 也都支援,不過其實 Selenium 因為是 Java 寫的,雖然控制瀏覽器的 script 沒有限制要用 Java,我還是一直不太習慣,所以都沒深入,直到前陣子開始看到 Paul 在 Facebook 上連載介紹 Protractor,才又開始有想嘗試的動力,Protractor 的名稱由來也還蠻有趣的,意思是量角器,而 AngularJS 則有諧音 angle 的感覺在,當初出來也是為了要測試 AngularJS 的,Github 上 Protractor 是 AngularJS 下的一個專案,Protractor 和 Selenium 的差別就在於,Protractor 是一個 test framework,然後建好了 WebDriver binding,可以直接透過 WebDriver 來跟瀏覽器溝通,不再需要 Selenium 介面那塊了。

後來 Carl 跟我說到有 WebdriverIO 這個專案,是只有 WebDriver 介面的部分,可以寫 node script 來叫瀏覽器做事,當然也可以做測試,可以挑自己喜歡的 test framework 和 assert library 來搭配使用,於是我就做了一個可以去 moztw.org 下載安裝檔回來驗證正確性的專案,在這個專案中,還用了一個特殊的寫法:

it('Download OSX Installer', function* () {
  var data = yield hashes;
  ...

其實就是 async function 加上yield來代替 ES2016 的await,要達成這樣的效果其實會需要一個 async function runner,不是 node 可以直接跑起來的,實際測試過也是跑不起來,所以就只能 WebdriverIO 提供的wdio執行檔來執行。

這篇還差一點東西沒講到,就是 test coverage,JS 這邊比較常看到的是 istanbul.js,名稱的來源是 carpet coverage,然後 Istanbul 是個生產優質地毯的地方~


Transducer

今天在 JSDC 講的題目是 Transducer , 是目前講過數學和程式碼最多的題目了,不過還是希望能用盡量簡單的範例來說明什麼是 Transducer。


更之前的文章