JavaScript Parameter Complete

前陣子在 FB 上說過突然有想做的 Vim Plugin 已經開發的差不多,所以把 1.0 放上了,這個又是自動補完的 plugin,架構設計上是沒針對 JavaScript,不過目前我只有針對 JavaScript 做好補完選項,這個 Plugin 一開始是為了處理Web Crypto API,這組 API 在做加解密的時候是需要指定演算法,不過演算法的名稱裡面,有一些有-在裡面,像是SHA-256之類的,以前這種字串的關鍵字,還可以加到 syntax 裡面,然後用 syntax complete 來處理,不過-不是 JavaScript 的關鍵字,會被忽略掉,為了處理這個問題,就開發了這個 complete function,簡稱jspc.vim

這個 complete function 的功能自動補完各種函數內的字串參數,像是剛剛提到的 crypto algorithm,還有各種事件名稱,Media Type 等,其實越做發現越多這類的選項,目前支援的東西包括:

  • Web Crypto Algorithm Name
  • Event Name for DOM, jQuery, Backbone
  • HTTP Methods for XHR, fetch
  • HTTP Headers, some values
  • Locales for Intl
  • HTML Tag Name for DOM API
  • Image Type for canvas.toBlob, canvas.toDataURL

這個 function 的原理還蠻簡單的,如果抓到游標位置看起來在字串內(以下用底線代表游標位置):

n.addEventListener('cl_

然後就會往前找看看這是不是一個 function call 的內部,是的話去看看 function name,像是上面的範例就會找addEventListener,然後就去內部先定義好的表單找看有沒有候選的項目,有的話就會根據字串內容去做過濾後然後給回來。

如果前面有其他參數是不會受影響的,不過 function name 和字串要在同一行,然後除了 JavaScript 的語法用(做為判斷是不是 function call 之外,其實還有支援 CoffeeScript 那種只用空白接餐處的寫法:

n.addEventListener 'cl_

其實判斷方法很簡單,就是找空白前面不是,的話就是了,一時還想不到是不是有其他種語言的 function call 的語法跳脫這兩種寫法的,我想應該可以支援大部分的程式語言了,不過裡面的候選資料還需要人來做苦工,所以我只弄了 JavaScript 的 Web 相關的東西,然後同時給 CoffeeScript、LiveScript、TypeScript 用,安裝方法就照用 Pathogen 那套裝法就可以了裝起來直接用,因為實做上是把原本的 omni complete function 包起來,所以不用特別做什麼設定,就可以和其它自動補完的輔助工具合作,像是vim-autocomplpopneocomplcache.vim,其實本來是獨立一個 function 的,但是這樣要和 Neo Complete Cache 整合太難了,乾脆就走 Vim 的 omnicomplete 路線,結果意外的方便,最後就是有什麼問題歡迎提出摟~


前端工程師都應該知道的 fetch

之前介紹 ES6 Promise 的時候就有提到一些過去的標準應該也可以更新到來支援 Promise,沒想到就看到 WHATWG 的fetch了,fetch 就是個XMLHttpRequest(XHR)的 替代品,幾乎是集了這幾年前端領域 Pattern 之大成。

首先是命名很簡單,和 XHR 完全不一樣,那個時期的網路標準的命名都很繁雜,尤其像是 XML Schema 的那個時期,聽說是找了些語言學家來一起制訂的,那個時期的東西很多都名稱弄的很冗長,當然不可否認這樣有個好處是比較容易理解東西的源由,像 XHR 看名字就可以知道其實主要目的是為了抓 XML,而那個時期會想要抓 XML 大概就是為了 SOAP 協定的 Web Service 吧,只是真的用來抓 XML 的已經很少了,一直用這個名稱早就已經覺得很奇怪了,至於新的 fetch 顧名思義就是為了抓東西用的,反而和現在 XHR 使用的情境很符合,而且命名很簡單,好記,就像是 jQuery 的on取代了addEventListener一樣。(PS: 另外有一個叫sendBeacon的是只管送出,不管回來的東西的。)

第二個特點是使用了Options Object,不過 XHR 倒也不是收很多參數,他的設計是先產生物件後才對它操作:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'test.html');
xhr.setRequestHeader('Tester-Name', 'mike');
xhr.setRequestHeader('Tester-Name ', 'peter');
xhr.send();

雖然沒有搞不清楚參數順序的問題,卻也是多了很多步驟才能達成目標,不過其實產生了 XHR 物件但是卻不送出 request 的使用情境我實在想不太到,大概是因此,新的 fetch 才改成像是 jQuery 的$.ajax那樣,產生物件時直接就發出 request 了吧。

第三個特點當然就是回傳的是 ES6 Promise 物件,另外也支援 FormData 等等新東西,不過要說能不能完全取代 XHR 呢?目前看起來是不行的,最主要是因為 ES6 Promise 並沒有支援 progress 的機制,而且已經不是 event-based 的物件了,所以沒辦法抓上傳進度之類的資訊。

因為這個 spec 還很新,目前是沒瀏覽器支援,不過 Github 有提供一個polyfill了,把基本的功能都做好了(還有缺一些比較少用到的細節),有興趣想開始用的人可以從這邊開始,大概要注意的有兩個,第一個是因為它是用 ES6 Promise,所以還要引入 ES6 Promise polyfill,第二個是回傳資料的處理,雖然 fetch 在發 request 的時候和 jQuery 的設計很像,不過回傳的資料處理方式就差距比較大了。

jQuery 的 ajax 收到 Response Body 時,會自動根據 Header 的 Content-Type 來處理,像是 JSON 會自動用JSON.parse把文字轉成 JS 物件,不過 fetch 不會,根據 spec 所說, fetch 算是一個底層的 library,所以這種事情就要自己來了:

fetch("https://pk.example/berlin-calling.json", {mode:"cors"})
  .then(res => {
  if(res.headers.get("content-type") == "application/json") {
    return res.json()
  } else {
    throw new TypeError()
  }
}).then(processJSON)

fetch 需要你自己在程式碼裡面判斷回傳資料的格式是什麼,然後可以用它提供的 method 擷取到相對應格式的資料,像是這個例子中抓的是 JSON 格式的資料,就直接執行 Response 物件的json這個 method,當然你也可以不判斷就直接執行json(),只是無法 parse 時會直接 throw error 出來,又因為在 Promise 串接過程中,後面就會跑到 reject 的 callback function 那邊去,除了json外,其他支援的還有arrayBufferblobformDatatext。這些從 response 物件中讀取 body 資料出來的動作(spec 中稱為 consume)只能操作一次,如果真的很想讀很多次,建議是直接把回傳資料的那個 Promise 儲存起來,還有一個方法是用 clone 複製 Response 物件,不確定那個方法好就是了,這部分這樣設計的原因似乎是為了處理少一點事情,讓效能比較好。

而除了 Response Body 外,其它的回傳資訊像是 Response Header 等,都有新定義的物件來儲存,不過沒有很複雜,設計的很直覺,和送出去的 Options Object 很接近。不過講到 Header 就有一點還是要說一下,其實 HTTP Header 是可以重複送出一樣的 key 的,先不管合不合規範,現實是 HTTP Protocol 的實作都還可以處理這種狀況,以前的 XHR 也可以做出這樣的行為,印象中也有 framework 會這樣用,不過不太確定,總之 fetch 因為 Header 是給 Options Object 中的一個物件,而物件的 key 不能重複,所以不會允許這種行為出現,我個人是覺得這樣其實也比較好啦。

目前這個標準還未廣為人知,但是我是覺得前景非常看好,Spec 寫的也異常詳細,雖然不能把 XHR 的所有功能都取代,不過大部分的 XHR 應用都可以用的上了,也有 Github 提供的 polyfill,應該很容易吸引人進去使用,加上也沒其它的類似候選標準,除了沒有 progress 和回來的資料格式要自己判斷外,應該是沒什麼缺點了,而且判斷資料格式的部分也是可以自己寫點程式碼把他處理掉,所以嚴格一點說的話,問題就剩下沒有 progress 可以看這點了。


OSX 10.10 紅綠燈

OSX 10.10

有用 OSX 的人可能都會有個感覺,就是左上角紅綠燈的 icon 裡面東西歪歪的,我會感覺紅色的往左上,綠色的往左下,不過用抓圖的抓下來放大看卻又發現其實是置中的,所以我想到的可能性就是螢幕的 subpixel 問題,找了手上能拍最大的鏡頭拍來看看:

OSX 10.10

發現排列是 RGB 從左到右,不過其實這只解釋了紅色叉叉往左偏而已,往上和綠色的完全無法解釋,尤其是綠色,綠色的點剛好是在中間,我覺得應該只剩下視覺錯覺的可能性了吧,不過其實我是想說,這個錯覺的感覺不知道是只有少部分人有,還是美國人都不會有感覺,最糟的情形是他們雖然有感覺,但是因為給的 asset 是正確的所以就沒進一步處理了。

在 Steve Jobs 過世之後,其實我不太喜歡網路上很多人說 Tim Cook 表現的差很多之類的言論,不過對於這個問題,如果是最後一種情形,我真的覺得少了 Jobs 有差,然後仔細想了一下,好像現在蘋果真的少了個對軟體介面細節這麼在意的人了,John Ive 是硬體工業設計那塊的,其他比較高層的好像就沒有聽說有誰是對這塊特別要求的人,突然覺得對蘋果未來有點擔心...


ES6 Promise

ES6 Promise目前各家瀏覽器的支援程度雖然還沒到可以直接用的程度,但是目前已經有非常多的 polyfill,差不多是可以開始使用的時候了。

如果習慣了 jQuery 設計的 Deferred 物件,應該會對於 ES6 的 Promise 設計很不習慣吧,相較於 jQuery 是產生好物件然後提供一些 method 做操作,要不要封裝起來也是開發者的事,ES6 Promise 的設計更是強調了封裝的特性,要怎麼 resolve、reject,在一開始就要決定好了,只有在建構函數裡面碰的到 resolve 和 reject 的觸發點:

new Promise(function (resolve, reject) {
  //...
});

這樣的設計雖然在物件封裝上比較嚴謹,但是其實會讓一些程式碼多了一層的縮排,例如本來用 jQuery Deferred 包起來的 XHR:

function JQXHR(url) {
  var xhr = new XMLHttpRequest();
  var dfd = new $.Deferred();
  xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) { dfd.resolve(xhr.response); }
  }
  xhr.open('GET', url);
  xhr.send();
  return dfd.promise();
}

用 ES6 Promise 就要改寫成:

function ESXHR(url) {
  var xhr = new XMLHttpRequest();
  var dfd = new Promise(function (resolve) {
    xhr.onreadystatechange = function () {
      if (xhr.readyState == 4) { resolve(xhr.response); }
    }
  });
  xhr.open('GET', url);
  xhr.send();
  return dfd;
}

第二個差異就在於回傳的 thenable 物件要怎麼把結果改掉,以前 jQuery 的時候可以在 callback 裡面回傳新的 Deferred 物件改結果:

dfd.then(null, function () {
  return $.Deferred().resolve();
});

沒特別傳 Deferred 物件的話不管用then串接幾次的話結果都不會改變,不過 ES6 Promise 就不一樣了,不管是 resolve 狀況還是 reject 狀況,then回傳的預設就是一個新的 resolved 狀態的 Promise 物件。那要怎樣改變狀態呢?這裡就要用throw new Error()了。在 ES6 Promise 的then裡面,不管是 resolve 還是 reject 的 handler,都是回傳任意值會讓後面拿到新的 resolved 的 Promise 物件,而如果在執行中 throw error 出去,就會讓後面拿到 rejected 的 Promise 物件:

dfd.then(function (val) {
  if (val === 0) {
    throw new Error('');
  }
});

然後就是 rejected 狀態的 Promise 也是有值的,如果是 throw error 產生的,那就是看 throw 什麼東西,那個東西就會變成新的 Promise 物件的值,而以往手動 throw error 時,為了相容性都會產生 Error 物件,在這邊就不必如此了,其實可以隨便傳想要給後面使用的值。

再來,有時候只想要處理 reject 的狀況,會寫成:

dfd.then(null, function () {
  //rejected handler
});

有個 null 放前面其實蠻討厭的,不過 ES6 有提供一個catch可以用:

dfd.catch(function () {
  //rejected handler
});

這個以前倒是沒有類似的東西,蠻方便的。大概瞭解到這樣就可以使用 ES6 Promise 了,另外還有Promise.allPromise.race可以做和jQuery.when類似的事情(race 是新的控制),至於我為什麼說現在差不多可以開始使用呢,除了 Polyfill 齊全外,其實還有一個原因是一些新的網路標準也開始使用 ES6 Promise 了,像是Web Crypto,看 spec 比較不明顯,不過看 MDN 的介紹,就有列出 encrypt、decrypt、sign 等花時間的 method 回傳都是 Promise 物件,實做就可以丟到背景的 Worker 去處理,才不會把 UI 卡住。而除了 Web Crypto 外,還有像是 ES7 可能會有的await語法也是要接 Promise 物件,總之是個未來趨勢,我覺得現在比較尷尬的是在 ES6 Promise 出來前的一堆標準都是用 event base 設計的,像是File Reader之類的,不知道有沒有機會慢慢都改到 Promise。


Ubuntu 14.04 與 MovableType 4.x

前一篇文章提到我為 SSL 安全性升級到 Ubuntu 14.04,其實升級之後遇到一些 MovableType 的相容性問題,Ubuntu 在 12.04 的時候還是用 Perl 5.14,不過到 14.04 時,Perl 升級到 5.18 了,順便提一下現在最新的穩定版是 5.20,不過 5.18 和 5.20 是同時都有在維護的。

然後我的 MovableType 是用很久以前的 4.38,用新的 Perl 會跑不起來,不過我也不太想升級,一來是 License 問題,二來是新的 MT 一個很大的架構改變是他變成是多 blog 系統,我也不太喜歡這點。總之剩下的方法就是想辦法修 bug,或是用舊的 Perl 跑,顯然後者容易許多,然後我也找到 gugod 開發的perlbrew這工具,類似於Ruby 的RVM,c9s 也有寫一篇文章介紹

比較有趣的是我用了 perlbrew 安裝好 Perl 5.14 後,用which perl找到 Perl 5.14 執行檔位置然後手動修改 mt.cgi 等檔案,用 mt-check 檢查發現還缺 DBI 的套件,就用升級前就已經裝好的 cpanminus 裝了,不過怎麼裝都顯示正常但是 MT 就是一直抓不到,看了一下 cpanm 檔案原始碼才發現它用的 perl 是:

#!/usr/bin/perl

心想 gugod 怎麼可能不處理這個問題,於是搜尋一下發現可以用perlbrew 安裝 cpanm

perlbrew install-cpanm

看了安裝出來的 cpanm 用的 perl 來源是不一樣的:

#!/usr/bin/env perl

這樣用 cpanm 裝的 Perl 套件總算可以用了,之後還有一個 5.14.2 和 5.14.4 的差異造成的錯誤,就照網路上找到的文章修正了。


SSL 設定更新

SSL Lab test

因為剛好我的 StartSSL 免費憑證要過期了,所以趁這次更新憑證的同時順便把一些過時的設定都改掉,不然之前我的評等已經新漏洞的關係掉到 F 了,做的事情也不複雜,第一個是把 SSLv3 也關掉:

SSLProtocol all -SSLv2 -SSLv3

然後限制 Cipher Suite:

SSLCipherSuite AES256+EECDH:AES256+EDH

這組 Cipher Suite 也是網路上找的,不過忘了留下連結,Cipher Suite 的建議組合網路上還蠻好找的,我用的這組基本上就是限制比較多,所以不少舊環境會無法建立連線,像是 Android 2、IE 6、Java 環境等,不過是很可以接受的程度。

光這樣的設定評等已經不錯了,不過會有個橘色字樣說不支援 TLS_FALLBACK_SCSV,會有被攻擊的危險,研究過後發現要升級 OpenSSL,然後我的系統是 Ubuntu 12.04 LTS,沒有新版的 OpenSSL,雖然也可以自己編譯,不過我還是決定升上 14.04 LTS。

另外有個特別被 highlight 的項目是我的憑證的 trust chain 中有一個是用 SHA1 簽章的,然後那個其實是 CA 的,就是從 Start SSL 抓的:

http://www.startssl.com/certs/sub.class1.server.ca.pem

在他的 cert 目錄下找了一下看到疑似是 SHA256 簽的:

http://www.startssl.com/certs/class1/sha2/pem/sub.class1.server.sha2.ca.pem

改成這個後看來就沒警告了,我想應該沒搞錯才是 :P

到這樣我發現總評等已經到 A 了,但是分數卻很難更高,所以先研究了一陣子怎樣到 A+,後來發現是我曾經開過的Strict Transport Security(HSTS),暫時拿掉後忘了放回來,加上 HSTS Header 後總評等就可以上到 A+ 了。

設定到這樣其實已經很足夠了,不過我還是對於其中的 Protocol Support 這項沒有滿分感到很好奇,搜尋一陣子發現到要把 Protocol 關到只剩下 TLS 1.2 才會滿分,可是這樣支援度會很慘烈,因為還很多客戶端是只有到 TLS 1.0 的,所以就只抓個圖紀念一下。最後還花了點時間設定OCSP Stapling,可以提昇效能,因為把OCSP上的資訊都抓下來放在本主機給客戶端用,不過目前感覺不到差異就是,設定也不難:

SSLUseStapling on
SSLStaplingCache shmcb:/tmp/stapling_cache(128000)

好像放這兩行就可以了。


➡ 前一個月的文章