發表文章

目前顯示的是 2015的文章

multiple thread 存取共享資料的小技巧

最近看到不錯的寫法, 備忘一下。 通常我們不希望 main thread 被 block [*1], 所以會另外開 worker thread 處理耗 CPU 的工作。主要的需求是避免 block main thread, 其次是縮短完成工作的時間。 由於 main thread 和 worker thread 需要互相傳遞一些資料, 有可能造成 race condition, 導致程式行為有時不正確或是掛掉。這裡討論幾種避免 race condition 的作法。 用一個 "global" lock 保護整個 thread 的資料worker thread 全部函式都用同一個 lock 保護。 優點: 容易實作。 缺點: main thread 在 worker thread 忙的時候存取共享資料, 還是會被 worker thread block。不同群共享的資料用不同的 lock 保護和上個作法類似, 不過資料分群用不同 lock 保護。 優點: 減少 main thread 被 block 的機會。 缺點: 實作有些複雜, 可能會漏保護到新加的變數。並且 main thread 仍有可能被 block。避免共享資料如果資料只有一個 thread 會用到, 就直接轉交給另一個 thread (改變 ownership)。若兩個 thread 都會用到, 先複製一份再轉交給另一個 thread。沒有共享的資料, 就不需用 lock 保護了 (但仍需透過 message loop 或 queue 傳遞資料到另一個 thread)。 優點: 複製的時間不長, 且可以掌控占據 main thread 的時間, 完全不受 worker thread 影響。 缺點: 需要仔細設計複製和傳遞資料的機製, 確保沒有共享資料。 需要同步資料的時候, 記得只能允許一個方向的 block。比方說 main thread 可以透過 conditional variable 來 block worker thread, 但不允許反過來的操作, 這樣就不會有 dead lock。 不用 lock 的注意事項每個函式都要放 assert 確保函式在正確的 thread 執行。若一個 class 有兩份資料分別在兩個 thread 下執行的話, 可以考慮訂兩…

node.js 開發備忘

前陣子用 node.js 寫了一個上線的服務, 沒有意外的話接下來有一大段時間不會碰 node.js。備忘一下有用到和以後可能會用到的東西。 何時以及為什麼要用 node.js?fcamel 技術隨手記: 為什麼需要 async programming 以及相關技術的演進fcamel 技術隨手記: 使用 Node.js 實作 server pushnode.js 開發主要弱點 main loop 被 block: fcamel 技術隨手記: 找出 node.js 中 block main loop 的程式 CPU bound: 參考 Why you should use Node.js for CPU-bound tasks - Neil Kandalgaonkar。視不同需求可用不同解法: 用 multi-process 增加 throughput 還有減少被 slow response 拖慢其它 request: 用 cluster 用 multi-thread 避免 slow response 拖慢其它 request: 用 threads_a_gogo 甚至可以將 CPU bound 的工作丟給 browser 的 WebWorker 作, 不過這樣架構也會變複雜, 沒強烈必要不會走到這步吧。 callback hell: 用 Promise 解 (JS 6 新 API), bluebird 是效率不錯的 backport, 可用在目前的 JS 5 。對於既有沒用 Promise 的 module 和函式, 也有提供 API 直接轉換物件成使用 Promise API, 不用改程式。Why I am switching to promises 提到 bluebird 效率不錯以及其它好處。 還有 async 也有助於減少雜亂的 callback codes, 不確定 bluebird 是否可完全替代 async 提供的功能。 幾年後 JS 7 的新語法 async/await 普及後, 可以考慮用它們取代 promise。現階段要 transcompile 後才能在 JS 5 內用。 其它 coding style: callback 函式的第一個參數總是放 error, 沒有 error 時傳入 null。 用 nodemon 在更新程式後自動更新…

用 whois 查 IP 發行者的資訊

最近才知道可以用 whois IP 查 IP 的擁有者和擁有的網段, 不過 whois 回傳的內容沒有標準格式, 不方便寫程式直接處理。

觀察 HTTP/HTTPS 傳輸的內容以及防範 man-in-the-middle attack

看到許多強者推薦 mitmproxy (MITM 是 man-in-the-middle 的意思), 試用了一下的確超好用的。它用 man-in-the-middle attack 的方式觀察 (甚至更改) HTTP/HTTPS 傳輸的內容。 How To: Use mitmproxy to read and modify HTTPS traffic 介紹原理和用法, 設定很簡單。 官方文件也很清楚, 可交叉參考:mitmproxy 0.13 - How mitmproxy works: 有附圖解說明 proxy 連線過程mitmproxy 0.13 - Linuxmitmproxy 0.13 - OSX 這裡摘要一下 mitmproxy 作為 Transparent Proxy 的方式和原理: 備好一台機器 R 作為觀察目標裝置 D (如手機) 的 gateway, 這樣就完成 man-in-the-middle 的設置。 設定 R 的 firewall, 重導往外連往 port 80 和 443 的連線到在 R 上執行的 mitmproxy。 在 D 上安裝 mitmproxy 的 certificate, 這樣 D 會相信 mitmproxy 幫自己簽的 certificate。測試 Android 手機在 MITM 情況下 App 的反應我是用 ASUS Zenfone 2, 不過 App 的測試結果應該和手機無關。 結果如下: Android OS 有出現訊息說網路可能被監控了, 但清掉訊息後就沒再出現了, 使用者應該不會注意到。 沒裝 mitmproxy certificate 的情況, Google Play 會出現連線錯誤; 裝 certificate 後可以用 Google Play 安裝 app。 沒裝 mitmproxy certificate 的情況, Chrome 會警告有問題; 有裝 certificate 會放行。 沒裝 mitmproxy certificate 的情況, Gmail 會如常運作, 看不到 HTTPS 傳的內容。裝 certificate 的後可以看到 HTTPS 傳的內容。雖然 Gmail 用自己訂的資料格式, 在搜尋信件時, 可看到搜尋字、回傳信件的標題、內文前面幾句的文字。 另外在有裝 mitmproxy c…

HTTP library 支援 gzip response body 的情況

為了節省 HTTP response 的傳輸量, HTTP 有提供 request header Accept-Encoding: gzip 要求 server 「可以的話, 用 gzip 壓縮 body 再傳回來」, 還有 response header Content-Encoding: gzip 告知 client 「response body 有用 gzip 壓縮」。 在這樣的規定下, 推測 HTTP library 可能會有兩種實作方式: 不處理。使用者自己加 request header "Accept-Encoding: gzip", 然後自己解 response body 有個 flag 開啟後會自動處理。自動加上 "Accept-Encoding: gzip", 然後自動解 response body 並且移掉 response header "Content-Encoding: gzip"。另外要註明 API 取得的 content length 是否會受 gzip 影響, 結果如何變化。 不過實際情況是: iOS NSURLConnection 文件沒有提到這件事, 但是會自動加 request header 並自動解 response body, 不過沒有移掉 response header "Content-Encoding: gzip" (Ref., 實測結果確實如此, 不需手動加 Accept-Encoding: gzip 就有作用) Android HttpURLConnection 指明預設就會作, 並會清掉 Content-Encoding 和 Content-Length。可以用 Accept-Encoding: identity 覆寫預設值 (Ref.)。 curl 開啟 flag CURLOPT_ACCEPT_ENCODING 後會支援, 也是自動加 request header 並自動解 response body, 不過沒有移掉 response header "Content-Encoding: gzip" (Ref.) 結果只有 Android 的實作符合預期。使用前還是要先測過才準啊。 附帶一提, http:/…

Android 線上原始碼

線上搜尋: AndroidXRef, 進版還滿新的 官方線上列表: https://android.googlesource.com/ 更多說明: Where can I browse Android source code on-line?

偵測網路環境是否有 Captive Portal (使用 Wi-Fi 時會先導到登入網頁)

Captive Portal 是指在可以正常用網路前, 先導到一個登入網頁, 登入後才可以正常上網。通常用在需要付費上網的地方, 或是飯店提供免費但需帳號登入的 Wi-Fi。不過不限於 Wi-Fi 連線 這樣作的好處是使用者可以透過網頁看到比較詳細的說明, 像是「使用者同意書」、「使用限制」、「收費標準」等。對於 Wi-Fi 連線的供應者, 在連上 Wi-Fi 時登入 Wi-Fi 帳號和密碼, 就無法提供這些資訊。 但缺點是使用裝置會誤以為有網路可用 (因為 Wi-Fi 層級有通), 但其實還不能用。如果用的 app 沒有作進一步的偵測, 不會開啟登入網頁, 就無法上網了。使用者會覺得「Wi-Fi 有通 app 卻不能用」, 而覺得是 app 的問題。 Android 4.0.1 開始有內建一套偵測機制 [*1], 發現有 Captive Portal 時會通知使用者: private boolean isCaptivePortal(InetAddress server) { HttpURLConnection urlConnection = null; if (!mIsCaptivePortalCheckEnabled) return false; mUrl = "http://" + server.getHostAddress() + "/generate_204"; if (DBG) log("Checking " + mUrl); try { URL url = new URL(mUrl); urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setInstanceFollowRedirects(false); urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); urlConnection.setUseCaches(fa…

Web Proxy 是否能處理 HTTPS 連線?

在討論 HTTPS Proxy 前, 要先分清楚是在講那件事: Proxy 和 client 之間的連線用 HTTPS Proxy 允許 client 透過它往外用 HTTPS 連線 前者的好處是保護 client 和 Proxy 之間的連線, client 若是用 Wi-Fi 的話, 可藉此加密所有送出的 HTTP 連線, 防止有人偷聽 Wi-Fi 連線。 後者是本篇要討論的對象: Web Proxy 是否能支援 client 對外的 HTTPS 連線? 又是如何作到的? 畢竟 HTTPS 是基於 SSL 連線的 HTTP, 既然資料都用 SSL 加密了, Proxy 看不到 HTTP header 和 body, 要怎麼作好 Web Proxy 的角色? 用 HTTP tunnelProxy 接受 HTTP 1.1 的 CONNECT method, 讓 client 用 CONNECT 對外打通一個 TCP 連線 (又稱 HTTP tunnel), 然後只作 TCP packet forwarding, 於是 client 要作什麼都可以。比方說用 TCP 連線作 SSL handshake 轉成加密連線, 接著就可以走 HTTPS, SPDY 或 HTTP 2 了。 這個作法下, Proxy server 不知道 client 對外傳的內容, 只能經手封包, 不能作其它事。 置換 HTTPS 的 certificateProxy 一樣接受 CONNECT method 建立 HTTP tunnel, 不過在 SSL handshake 時, 換掉目的地 Web Server 回傳的 certificate, 改用 Proxy 自己的。然後分別和 client 和外部 Web Server 完成 SSL handshake, 形成這樣的連線: client <-> Proxy: 用 Proxy 的 certificate 建立 SSL 連線, 使用 key K1。 Proxy <-> 外部 Web Server: 用 Web Server 的 certificate 建立 SSL 連線, 使用 key K2。 Proxy 收到 client 的資料先用 K1 解密, 再用 K2 加密傳出去; 資料回來的時候反過來比照辦理。這樣 P…

用 Ubuntu 14.04 作為 Hotspot

最近需要模擬被擋封包的網路環境, 想想用筆電作為 hotspot 再設 firewall, 應該滿容易的。然後用手機連上 hotspot, 就可以模擬出各種擋封包的情況 (如立即回傳 connection error, 或只是 drop 封包)。 實際操作後的確很容易, 裝好 Ubuntu 14.04 後, 需要的東西都齊了, 內建的 Network Manager 有圖形介面, 點一點就可以分享 Wi-Fi。iPad/iPhone 可以連得上, 但是 Android 不行, 原因是 Ubuntu 預設 hotspot 用 Wi-Fi ad hoc mode, 但是 Android 不支援, 要改用 Wi-Fi infrastructure mode 才行。作法見 How do I create hotspots in Ubuntu 14.04?。 其它Android 4.0 後支援 Wi-Fi P2P, 可以讓 Android 裝置互連, 不過還是不支援 Wi-Fi ad hoc mode。 用 Wi-Fi ad hoc mode 的時候, iPad/iPhone 會在 Wi-Fi 列表的「裝置」看到筆電用的 SSID; 若改用 infrastructure mode 的時候, 會在 AP 列表看到筆電用的 SSID。 我有試過用 Mac OS X 作一樣的事, 但是新版 (我用 10.10.4) 內建只能用 pf 作 firewall, 和這東西不熟, 看起來沒 iptables 直覺。另外開啟 Internet Sharing 後 pf 就失效了, 就沒再深入研究。想想以後還是專心用 Linux 就好, 時間有限, 搞熟兩套 OS 的成本太高了。

HTTP POST 使用的編碼格式

三不五時會用到相關的東西, 所以花了點時間查清楚, 本篇重點是參考資料。 用 HTTP 的 POST 的時候, 參數會放在 request body, 然後用 HTTP header Content-Type 決定 request body 編碼方式。 常用的格式 ( Content-Type) 有兩種: application/x-www-form-urlencoded: HTML form 的預設值。和 GET 在 URL 後面帶的參數差不多, 用 URL encoding 編碼 (主要差異是空白字元用 '+' 取代), 適合用在簡單的資料。 multipart/form-data: 適合用在 binary data, 上傳檔案時要改用這個, 比較省空間 (檔案內容不需另外編碼)。 application/x-www-form-urlencoded 用 URL encoding 編碼, 遇到大量 binary 資料時, 資料會變大很多 (可能會到三倍)。若先用 base64 [*1] 編碼再套 URL encoding 會好一些 (變成 4/3 倍), 但還是沒有 multipart/form-data 省。 除了上述的格式也可以用 application/json 或其它格式, 不過得看 web server 是否支援。JSON 不支援 binary data, 所以有 binary data 時要先用 base64 編碼, binary 資料一樣會變 4/3 倍大。雖然 base64 會增加 1/3 倍的資料量, 但它簡單易用, 我個人滿喜歡 base64 的設計。 若目的是要壓縮上傳的文字資料, 除了 application layer 自己壓縮資料外, 也可以在 transport layer 作: 使用 HTTP header Content-Encoding: gzip 然後用 gzip 壓縮 request body [*2]。雖然 response 很常用 Content-Encoding: gzip, 但 request 很少這麼用的樣子, 需要改 web server 設定才能作用。如 apache2: 需另外設定 Input Decompression。 備註1. 嚴格來說, 要用 base64url (最後兩個…

Mac OS X 雜項常識

備忘用, 持續更新。硬體列表 (裝置沒反應時可看硬體形號找 driver) 左上角 Apple 圖示 -> About This Mac -> System Report -> Hardware 分享無線網路 (當作 hotspot )System Preference -> Internet -> Internet Sharing。開啟 Internet Sharing 後, 自訂的 pf rules 會失效。

備忘 nginx 設定

未來的某天應該會需要更了解 nginx, 現階段先隨便備忘用到的東西。request route 參考資料 Using nginx rewrite's to remove the file extension and still work with PHP-FastCGI — nullis.netModule ngx_http_core_moduleModule ngx_http_rewrite_module 用 try_files 設定 URI 對到不同 URI 的順序; 用 rewrite 改寫多種組合到同一位置。兩者都可用來實現 request route。

用 mtr 找出 routing 中有問題的節點

以前只知道用 traceroute 和 ping, 但用 traceroute 看完資訊後也不太清楚情況。用 "traceroute better" 搜了一下, 找到 mtr, 試了一下挺確不錯用。這裡有篇介紹: How To Use Traceroute and MTR to Diagnose Network Issues備忘指令 $ mtr HOSTNAME # 看 realtime 變化$ mtr -w HOSTNAME # 搜集一段時間內的資料

找出 node.js 中 block main loop 的程式

環境 Mac 用 node.js 0.10.36 Ubuntu (VM) 用 node.js 0.10.38問題描述node.js 的 single thread event loop 架構容易處理 I/O bound, 也避開 multi-thread 的各式問題, 但弱點是 main loop 被卡住了, 整個程式就不會動了。幸好找出錯誤原因的方法滿多的, 若確定是 CPU bound 而不是自己呼叫到 sync API, 也有很多成熟的解法。 在開發環境偵錯node.js 有很多種 profiling 的方法, 試過幾種作法, 我偏好這兩個作法:node-inspectorlook 兩者都會產生一個 web server 讓你可以連到指定的 port 用網頁作的 UI 看資料。也都不用傳資料到別人機器上。配合 stress test 可以找出潛在的 CPU bound。look 的介面比較簡單, 啟動速度較快。 在上線環境除錯但若 CPU bound 只有在上線後才會遇到, 就不容易找了。試了一些方法, 覺得用內建的 command line debugger 最管用。 node.js 已內建 debugger, 不需特殊設定就可以用 debugger attach 上線執行的程式。執行方法如下:$ kill -USR1 PID # PID 為 node.js pid $ node debug -p PID 然後執行 pause 暫停程式,接著執行 bt (backtrace) 才會有效。這樣就有線索找出 main loop 卡住的時候在作什麼了。 下面是一個完整的小例子:$ cat b.js function fib(n) { if (n <= 1) { return 1; } return fib(n - 1) + fib(n - 2); } setTimeout(function() { console.log(fib(100)); }, 10000); $ node b.js Hit SIGUSR1 - starting debugger agent. debugger listening on port 5858 在另一個 shell 用 debugger:$ ps auxw | grep node fca…

為什麼需要 async programming 以及相關技術的演進

幾種使用 async programming 的情境:GUI: 像是Web、iOS、Android apps。在 event-driven 的架構下用 callback 滿直覺的, 但是為了避免卡住 UI thread 而需要連續多個 non-blocking I/O 才能完成一件事時,寫起來就不直覺了。這時新增 thread 用 blocking I/O 寫起來比較直覺,但是不容易處理 thread-safety。 server: 像是 Web server、API server、reverse proxy。要能同時承受上千筆連線,必須用 non-blocking I/O。遇到 CPU 吃重的工作時,可以轉交給其它專門的 server (如向資料庫讀資料或用 Lucene 處理 text search)。 其它像是一些會用到多筆 http request 取資料的小程式,例如用 Gmail API 取出所有信件的附件,或是 stress test 的工具,或是 Web crawler,這些也會用 non-blocking I/O 加速程式 (或用 multi-thread/multi-process + blocking I/O 也行,最近才寫了個小工具 prunner.py)。 共通需求 用 non-blocking I/O 避免卡住服務或縮短完成時間。 用 single thread 降低實作複雜度。 為了滿足上述需求,主流的解法是用 single-thread event loop。但遇到 CPU bound 時還是得開新 thread。 注意這個共通需求對 Web page 和 server 是必要需求,但對其它情境只是「優先希望的作法」,Android、iOS 或 crawler 還是可以選擇新增 thread/process 使用 blocking I/O,然後自行承擔 multi-thread/multi-process 衍生的問題。non-blocking I/O 的問題single-thread event loop 只有幫忙監聽所有 non-blocking I/O 的動靜,用 callback 的方式通知結果。用 callback 處理 GUI 反應很自然,但是需要依序發數個 http request 的時候,在 callback 裡跳…

prunner.py: 連猴子都會用的加速框架

問題寫 python script 時有時會遇到 I/O bound (例如需發出大量 HTTP request) 或 CPU bound, 這時可用 multiprocessing 加速。但是自己寫 multi-process 有點麻煩:要留意 API 使用細節。 容易發生 race condition 或 dead lock。 程式較複雜時會切成多個階段作業, 有時會閒置部份 processes 而浪費了加速的機會。 辛苦寫好的平行架構不易重覆使用 (我至少重寫三次以上...泣)。解法prunner.py 是我寫好的框架, 中心思想是透過 task queue 提供容易重覆使用的架構, 還有協助減少閒置的 process。 以計算 sum( (i+1) * 2 ) 為例, 下面的程式會用很蠢的方式透過 10 個 processes 平行計算出結果: import prunner def begin(): prunner.get_dict()['sum'] = 0 prunner.post_task(init, range(2000)) def init(numbers): for i in numbers: prunner.post_task(add_one, i) def add_one(n): prunner.post_task(double, n + 1) def double(n): prunner.post_task(sum_up, n) prunner.post_task(sum_up, n) def sum_up(n): with prunner.global_lock(): prunner.get_dict()['sum'] += n def end(): print prunner.get_dict()['sum'] prunner.init(10, False, begin, end) prunner.start() 每個 function call 會在一個 process 上執行, 藉此增加使用到的 CPU。也可以盡情地用 blocking I/O, 反正有很多 process…

用 grequests 作 stress test

作了一個 C10K 的 server 後, 還要作 stress test 才能驗證 server 真的挺得住 C10K 的連線。試了幾套工具, 最後覺得 grequests 最好用 (用 gevent monkey patch 的 requests), 用法就這麼簡單: import grequests def exception_handler(request, exception): print "Request failed: %s" % exception url = 'SERVER_URL' rs = [grequests.post(url, data={...}) for i in range(10000)] rss = grequests.map(rs, size=1000, exception_handler=exception_handler) 這樣就寫好一個會同時產生 1k 個連線, 總共產生 10k 個連線的 client。因為是直接寫程式, 可以自訂各式各樣的內容, 測得出真正的效果, 不會被簡單的 cache 消掉絕大多數的 requeests。若要改用 get 就用 grequests.get(url) 產生 request, 詳細用法見 requests。 幾個測試情境和作法: Connection bound: 提升 size 數, 或多跑幾個 client CPU bound: 同上, 故意使用耗 CPU 的 API Memory bound: 依 API 的性質, 產生 1MB 或更大的資料, 由 data=... 傳過去 grequests.map() 不設 size 的話, 會盡可能產生最多連線, client 有可能自己先爆掉。依自己的網路環境和 server 狀況, 可試看看不同的值。 執行前記得先用 ulimit -n 提升 open file number, 不過要用 root 才能提升, 作法如下:$ sudo bash $ ulimit -n 50000 $ su YOUR_ID 這樣可以用一般帳號測試, 但保有 max open file number = 50k。 以下是我試過但不管用的方法, 憑印象寫下放棄使用的原因 (不見得是工具不好…

http 1.1 pipeline 失敗的原因

網路連線的基本指標是 latency 和 throughput。用 pipeline 同時提升 latency 和 throughput 還滿直覺的, 但在 http 1.1 上卻意外地以失敗收場, 所以花了點時間查一下為什麼。 What are the disadvantage(s) of using HTTP pipelining? 提到幾個問題: http 1.x 是 stateless 的 protocol, client 沒有能力知道 request 之間的 order (沒有流水號)。 因此, server 必須依 request order 回傳 response, 但是有些 server 實作錯了。 即使 server 作對了, 因為要照 order 回傳 response, server 可能需要暫存大量回應, 有被 DoS 的風險。 因為 response 要依順序回傳, client 有 head-of-line blocking 的風險, 比方說剛好先要一個大圖檔再要一個 JS 檔, 結果 JS 檔因此比較慢收到, 讓網頁比較慢取得較重要的檔案。 client 端的實作比想像中複雜, client 要先偵測 server 是否支援 pipeline 接著才能開始用 pipeline, 因此有些延遲。再者, 因為 client 不會真的只開一個連線, client 還需考慮如何分配 request 到不同連線作 pipeline。 因為 http 1.x 過往的限制, 網站早就依 domain 拆開回資料, client 也會同時發多個連線加速。結果是即使 pipeline 都作對了, 可能也沒賺到什麼。 此外, server 和 client 中間的 proxy 也可能不支援 (如擔心 DoS) 或是作錯, 又降低了可行的機率。 如果 http 裡面有含流水號, 由 http client library 自己匯合好再依順序回傳資料給上層程式 (像 TCP 那樣), 問題會少一些。設計協定時決定由 server 或 client 處理還挺關鍵的, 一但作錯決定, 協定普及後就很難改了。 HTTP/2SPDY 直接支援 multiplexing, 在一開始就作對了 pipeline。剩下的問題只有使用 TCP 造成 head-of…

gold 和 GNU ld 行為差異造成的問題

原因ToolChain/DSOLinking - Debian Wiki 介紹得很清楚, 主要的差異是處理 DT_NEEDED section 的行為不同。 對 program A 和 shared lib B 和 C 來說, 假設相依性如下: A 用到 B 和 C B 用到 C 照理說 link 的設定要如實反應出相依性。若 link 的設定如下: A: link B B: link C linker 是 GNU ld (BFD-based linker) 的時候會成功, 但用 gold 的時候會失敗。原因是 linker 會在目標的 DT_NEEDED section 記錄用到的 shared lib, 然後 GNU ld 預設會將 DT_NEEDED section 的值一併「往上傳」。所以 A link B 的時候不只會在 DT_NEEDED section 加上 B 的記錄, 還會取得 B 的 DT_NEEDED section 內的值, 所以沒寫 link C 也會有 C 的記錄。 這造成一個問題: 日後 B 沒有用到 C 的時候, 會因為升級 B 而造成 A 的 link error, 錯誤原因是沒有 link C。 解法是用 GNU ld link 時也要下 --no-add-needed 避免自動取用 shared lib 內的 DT_NEEDED section 的內容。這樣開發的時候才會注意到漏加 shared lib。 這種情況容易發生嗎? 就我個人的經驗來說, 比我預期的容易。比方說寫 GTK+ 也會用到 glib, 但 GTK+ 自己也有用 glib, 所以 GTK+ 的 DT_NEEDED section 有含 glib, 然後自己就忘了加。若用 GNU ld link, 沒加 glib 也會成功。或是很多 lib 用到 pthread, 自己也用到或加入的 static lib 間接用到, 然後忘了 link pthread。 改用 gold 為預設 linkergold link 的行為不但比較正確,速度比較快也比較省記憶體。可以的話, 改用 gold 作為系統預設 linker 比較省事。Mac OS X 已經用 gold 了, Ubuntu 12.04 要另裝 binutils-gold:$ sudo apt-ge…

PHP + JavaScript 提供 L10N 的文字

我希望的用法如下: PHP 和 JavaScript 共用同一套文字格式。 PHP 和 JavaScript 用同一套 API。 各國語系用字串 key 取出翻譯的內容。優點是可以隨意更改預設語系的內容; 缺點是網頁原始檔有一點難閱讀,無法像 L10N 前那樣直接看到內文。 選用的目標語言缺內容時,改用預設語言。 L10N 的文字存成 JSON, 方便用各種工具編輯。 函式庫相依性愈小愈好。 多數人使用的 gettext 不合我的需求, 查了一下沒找到順手的工具, 最後自己寫了一個。只有一個檔案 l10n.php, 程式不到 100 行。我不太熟 PHP 和 JavaScript, 可能寫得不怎麼道地。 用法是在網頁內 <?php require('l10n.php') ?>, 之後 PHP 和 JavaScript 都可以用 _m(KEY) 取得對到目前語言的字。可用 ?lang=LANG 切換語言,不然會以 Accept-Language 內 weight 最大的為目標語言。預設語言為 en。 API 還有一些可以改進的地方,像是支援替換文字內的變數,不用自己取出來後再替換;或是在 JSON 格式內加些輔助翻譯的描述。不過比較細微就是了,有這打算時再來參考 Chrome extension API 的作法, 還滿直覺的。

近期閱讀有物報告的心得

整理一下最近閱讀的心得,只是雜亂的片段想法。也許有天能串出更深的想法。一個預測未來的簡單方法原文: 有物報告 | 一個預測未來的簡單方法 用正面的方式描述是: 任何沒考慮 smartphone 的產業都會被衝擊。反過來說則是: 基於「不是人人都有 smartphone」的假設而發展出來的產業, 勢必會因 smartphone 普及而被衝擊。我比較喜歡後者的說法,前者聽起來比較空泛,後者有比較強的因果關係 (或著說比較強的推測)。 現行的例子是叫計程車。以前沒有 smartphone 要透過中間的服務人員對上雙方的位置, 幫忙湊合人和車。現在大家都有 smartphone, 定位和上網都很容易。建立平台的成本大幅下降,也不用透過服務人員配對。 去中介化的犧牲者 原文: 有物報告 | 親朋好友經濟、去中介化,與新中間人的誕生有物報告 | 去中介化的犧牲者 — 旅行社、出版社與書店推動聯合漲價的無用 能用數位傳遞的東西, 終將被網路取代。書店的定位必須轉型提供不同的服務,比方說營造店內的氣氛讓人想來逛, 像是誠品。或是經由個人品味挑選書本、寫評論分享自己挑選的書。 全程創業,販售服務原文: 有物報告 | 下一波傳統企業挑戰者 – 全程(end to end)創業有物報告 | 台灣最有野心的科技產品 – Gogoro有物報告 | 歡迎來到訂閱經濟時代有物報告 | 巨型城市襲來,百年福特的轉型策略:無人車與分享經濟有物報告 | [活動記錄] 賣鑽孔機是不完美的妥協,所以有了訂閱經濟販售長期的服務,金流比賣一次性的產品來得穩定。也因為更了解用戶,有機會撥掘和提供新服務取得更多獲利。理論上從上至下都自己來,使用者用起來會更順。像選用 iPhone,和 Apple TV 結合的很流暢 (其它 iCloud 之類的我沒有用, 不知效果如何)。Android phone 和 Miracast 則不容易作到流暢。什麼都自己來, 代價是要作更多事,分散火力。如今透過 smartphone 上搭配的「商城」 (App Store、Google Play), 傳遞產品和收取費用比以前簡單許多。至少降低了一個門檻。 Btw, 這段話讓我特別有感觸,注重在賣服務,而不是工具: 哈佛商學院教授 Theodore Levitt 說:「人們要的不是鑽孔機,而是牆上的洞。」他認為購買鑽孔機是種不完美的…

使用 user agent 和 meta viewport 的目的和造成的影響

對行動瀏覽器和網頁的開發者來說,都希望提供最佳的使用者體驗, 不過彼此在意的事有點不同。 瀏覽器在意的事: 用 user agent 告知網站自己是什麼瀏覽器, 讓網站有機會提供最佳的內容。 也提供切換 user agent 的選項,若網站自動提供行動版網頁, 而使用者想看桌機版網頁時, 可換用桌機瀏覽器的 user agent 「騙」網站提供桌機版的網頁。 如果頁面有用 meta viewport 設定 layout width=device width, 表示網頁有針對 device width 作最佳化, 照著辦就是了。 承上, 不過若網頁沒作好, 比方說在頁面裡寫死某個元件寬度, 寫死寬度的元件仍會超出螢幕寬度, 看起來怪怪的。 若沒有設 layout width=device with, 表示網頁開發者沒有針對行動裝置作最佳化, 至少要用夠寬的 layout width (>螢幕寬度), 不然網頁內容會縮成一團無法閱讀 (就像在桌機將瀏覽器視窗縮到很窄一樣)。常見的實作寬度是 980px 或 1024px。 承上, 因為手機螢幕比 layout width 小, 載入完頁面後使用者無法一眼看到網頁全貌, 瀏覽器會自動縮小 (zoom out) 頁面以貼齊螢幕寬度。預期使用者可以先看見全貌, 找到有興趣的地方,再自行放大 (zoom in) 想看的部份。 網站開發者在意的事: 透過 user agent 得知使用者用什麼裝置和瀏覽器。粗略分成行動裝置和桌機 (PC 或筆電) 兩種。後者通常計算能力強、網路快、螢幕大,可提供豐富內容。另外提供前者客制化的網頁,以提供更好的使用體驗。 桌機網頁也可使用 Responsive Web Design (RWD), 讓大螢幕有更好的使用體驗 (但不會在意螢幕太小的表現)。不需使用 meta viewport (用了也會被桌機瀏覽器忽略)。layout width 由視窗寬度決定。 行動網頁必須使用 meta viewport 要求瀏覽器用 device width 作為 layout width, 不然瀏覽器會用 980px 或 1024px 排版, 結果是字縮得太小 [*1],使用者得放大縮小外加水平捲動觀看內容, 用起來不方便。最好使用 RWD 應付不同手機的不同寬度。不然就要用比較窄的寬度為基準來排…

iOS kCFErrorHTTPSProxyConnectionFailure 和 Android ERR_TUNNEL_CONNECTION_FAILED 的解法

iOS kCFErrorHTTPSProxyConnectionFailure (錯誤代碼 310) 的意思是 proxy 不允許 HTTPS 的連線,可能是 client 用 https 連往 443 以外的 port, 然後 proxy 設定不允許這樣的連線。解法是改在 443 port 執行用 https 的 web server, 或是 client 不要使用 proxy。 我用同樣的設定 (在 port 443 以外跑 https + client 用 proxy) 改測 Chrome on Android, 結果顯示的錯誤代碼是 ERR_TUNNEL_CONNECTION_FAILED, 也許這個代碼有用在其它情況。遇到的時候也可以看看是否和這有關。

Python, JavaScript, C++, Java 和 Unicode 以及 UTF-8 等編碼

基本觀念Unicode 是一張表,定義每個文字對應的數字是什麼,比方說 'A' 是 65, '我'是 25105。 UTF-8, UTF-16, UTF-32, UCS-2, UCS-4 等則是 Unicode 的各種編碼。所謂的編碼,是某種定好的儲存格式 (或說 serialization/deserialization format 也許比較好懂?), 方便讓不同的應用程式之間傳遞資料。 比方說應用程式 A 寫入文字到檔案裡,應用程式 B 從檔案讀出文字,兩者要說好用的編碼格式 (或能從資料辨別使用的編碼格式), 才有辦法互通資料。舉例來說, 網路傳輸常用 HTTP 作為溝通協定, HTTP 有提供 client 或 server 指定傳輸內容編碼的方法 (Accept-Charset 和 Content-Type), 可以指明是用 UTF-8 或其它編碼。 寫程式的時候,基本上是假設外部讀來的資料是某種 Unicode 編碼 (最常用 UTF-8 ), 內部使用 Unicode 處理,要傳出去 (寫入硬碟/網路傳輸) 前再轉回 Unicode 編碼。用 Unicode 的好處是,可以正確計算字串長度, 這影響到使用 length, index, 取出 substring, 比對 substring (包含 regexp) 等字串 API。 這裡有以前寫的介紹,有多提一點相關的事。 PythonPython 2 就有定義 type unicode 和 type str。type str 可看成一般性的 binary, 以 byte 為單位。Python 2 的字串 '...' 的型別是 str, 要特別用 u'...' 才會改用 unicode。 有了 type unicode, 處理 unicode 比較輕鬆: 外部讀進來的字 (UTF-8) 存在 type str, 馬上轉成 unicode 函式的輸入輸出都用 unicode (len(), [] 的行為正確) 要輸出到螢幕、檔案、網路時再轉成 UTF-8。 參考 All About Python and UnicodeUnicode in Python: Common Pitfalls 了解細節。 另外, Python 可…

從舊有的子目錄建立新的 git repository

如果要搬移的目錄在 repository 的最上層,可以用 git subtree。若是要搬的目錄是中間某層目錄 (例: a/b/c 的 c), 可以用 filter-branch。 筆記一下我操作成功的流程:1. 在 git server 上建立新的 headless repository git-host$ cd /path/to/repo && mkdir new_repo && cd new_repo && git init --bare 2. 在自己的機器上從原本的 repository filter 出要搬移的目錄。注意: 目錄內的內容會變成只剩目標目錄,我是 clone 一份新的來作。 myhost$ git clone ssh://path/to/old_repo myhost$ cd old_repo # Filter the master branch to path/to/folder and remove empty commits myhost$ git filter-branch --prune-empty --subdirectory-filter path/to/folder master Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (89/89) Ref 'refs/heads/master' was rewritten 3. 合併舊有的 git commits 到新的 git repository 並更新回 server myhost$ cd /path/to/somewhere myhost$ git clone ssh://path/to/new_repo myhost$ cd new_repo myhost$ git pull /path/to/old_repo # 取得剛才 filter 過的 old_repo myhost$ git push --set-upstream origin master myhost$ rm -rf /path/to/old_repo # 移除用不到的目錄

使用 Node.js 實作 server push

前幾天才說用 Tornado 實作 server push 很簡單, 且可以直接用在上線環境裡。今天要來自打嘴巴說我要改用 Node.js 了。有興趣了解相關基本知識的人, 可以參閱之前的說明。 Python 圈 event-based framework 的問題如這篇文章所述, Python 天生不是 async, 既有函式庫自然也不是 async, 使用 event-based framework (*1) 後, 要另外找配套的函式庫。比方說 http client 用慣了 requests, 直接拿到 Tornado 裡用會出事, 因為 Tornado 是 single process single thread 的架構, 一發出網路連線就會 block 住全部動作。要嘛改用 Tornado 提供的 httpclient, 不然就要找別人用 Tornado 提供的 API 包好的 requests。 每套函式庫都這樣找頗麻煩的 (資料庫、第三方服務的 SDK、etc), 而且不見得找得到牢靠又有效率的版本, 比方說 Google API Python Client 就沒有 Tornado 版的, 要自己想辦法弄。以前用 gevent 時作過類似的事, 先搞定 Dropbox Python SDK, 再來搞不定 Google Drive API, 只好用 gevent monkey patch。感覺不是很可靠。 Node.js 的優勢反觀 Node.js 天生就是 async, 既有函式庫自然也是 async。Google 也有提供 Google API Node.js Client。既有的社群也滿龐大的, 可以用的套件很多。對我來說, 這點最重要。 效能方面, 因為使用 V8 轉成 native code 執行, 表現也不錯。從這裡這裡的 benchmark 來看, 數值計算大勝 CPython, 然後和 PyPy 持平。我自己比較常用 dictionary, 作個簡單的 benchmark 測試使用 for, if 和 dictionary, 結果 Node.js 小勝 PyPy, 然後大勝 CPython。整體來說執行速度夠用了, 到是記憶體有 1.7G 的限制 (64bit OS 預設 1G), 要多留意一下。 使用 Express 作 web…

server push by long polling

程式碼沒興趣讀落落長心得的人, 這裡有用 Tornado 實作的程式測試碼。 心得server push 是指從 server 主動送訊息給 client, 這裡有圖解一些達成 server push 的方式。其中我個人偏好 long polling 的作法,運作方式就和字面一樣: client 先發出一個連線, server 不要立即回應。等 server 需要主動通知 client 時, 再回傳資料。這個作法的好處是: 各種 client 都適用, 只要能用 http 連線即可, 不是瀏覽器也OK, 現在各個平台都有好用的 http 函式庫。 不用擔心 client 網路環境問題。client 能主動建立往 server 的連線, 反過來就很難說了。 另一方面, long polling 的缺點是 server 會有許多閒置的連線占資源。像 apache 這類每個 client 用獨立的 thread 處理連線的作法, 就不適合處理 long polling。假設一個 thread 占據 10MB 空間, 1k 個不作事的連線, 就占掉 10G 記憶體了。另外大量 thread 之間的 context switch 也是可觀的時間負擔。 但是改用 epoll 之類的 API 寫成 event-driven 的架構, 可用 single process single thread 的方式同時處理大量連線, 就沒有浪費記憶體和 context switch 的時間負擔。現在有不少現成的工具使用 epoll 包成 green thread, 使用上和 multi-thread 一樣容易上手 (而且還不用擔心 race condition, 因為實際上只有一個 native thread),降低實作門檻。其中 Tornado 是 Python 寫成的 web framework 並內建可上線用的 web server。我作了簡單的 benchmark, 的確可以快速反應一秒內同時擁入的一千個連線。用 Tornado 省去不少工夫 (若不想用 web server 而是自己從頭作 server, 也可用 gevent)。 實作 long polling 的另一個問題是: 如何在察覺資料更新時, 能立即通知 client? 想像 client 透過連線 S 連往…

apache ab 使用 post 傳資料

用法: $ cat post.txt key=value&key2=value2 $ ab -n 1000 -c 1000 -p post.txt -v4 -T 'application/x-www-form-urlencoded' http://.../ 注意使用 -p 指定 post data 時,同時必須用 -T 指定傳送 content type 為'application/x-www-form-urlencoded。反應不如預期時可用 -v 輸出收到的資料,協助除錯。

Chrome NaCL 開發心得小記

目前開發到一半, 先加減記一下。之後有多什麼心得再來補充。 為什麼要用 NaCL自然是方便重用既有的 C/C++ 程式啦。 雜項 NaCL 無法在本機檔案中執行 (file://...)。web pages、Chrome Web App、 Chrome Extension 才能用 NaCL。若要在本機試 web pages 的話, 需要跑 local web server。NaCL SDK 內有附一個 local web server, Getting Started 的範例用 make serve 執行 local web server。建議先玩 Getting Started 裡面的兩個範例, 了解 NaCL 的架構。 有支援 POSIX 和 ptherad, 還算堪用, 不過 port Linux 的程式時會遇到很多 Linux 才有但 POSIX 沒有的程式碼 (網路、時間、pthread 各式各樣都有)。 分成 PNaCL (編成 bitcode 執行檔, 使用前再轉成執行檔) 和 NaCL (native binary)。若需要放到 Chrome Web Store, 後者比較不方便, 得編多份不同平台的 binary 得用 NaCL SDK 附的工具, 我試過用 nm 和 ar 都無效, 改用 NaCL SDK 附的 llvm-nm 和 llvm-ar 才有正常結果。 若想使用 stdio 的函式 (如 FILE 相關函式), 要另外使用 nacl_io library。需要在 Makefile 補上 -I$(NACL_SDK_ROOT)/include -I$(NACL_SDK_ROOT)/include/pnacl 和 -lnacl_io除錯參考資料: 官方文件 1. 基本的除錯方式是透過 stderr 導到執行 Chrome 的 terminal (預設行為), 或寫到 DevTools 的 console (透過 /dev/console1 ~ 3)。 但是導到 DevTools console 的輸出會被限制行寬, 超出的字會被截掉。還有由於 stderr 是 unbuffered, 輸出會亂掉, 若想將 stderr 導到 /dev/console1, C/C++ 程式內記得改設 stderr 為 line buffer ( setl…

在 Ubuntu 14.04 上編 stable chromium 43

參考資料Get the Code: Checkout, Build, Run & Submit - The Chromium Projectsdepot_tools_tutorial(7)Chromium Development Calendar and Release Info - The Chromium Projects 步驟 1. 從這裡找到最近的 stable version VERSION 2. 取得程式和安裝相依套件 $ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git $ export PATH=$PATH:/path/to/depot_tools $ fetch --nohooks chromium $ fetch --nohooks blink $ cd src $ ./build/install-build-deps.sh $ git checkout VERSION # 可和 git tag 對照一下 $ git show VERSION # 取得 commit 編號 COMMIT $ (edit ../.gclient) # 設 "safesync_url" 為 COMMIT $ gclient sync --with_branch_heads # 沒有 --with_branch_heads 會出錯 3. 產生編譯所需的檔案和編譯 $ cd /path/to/chromium/src $ gclient runhooks $ ./build/gyp_chromium $ ninja -C out/Debug chrome

macports 和 aptitude / apt-get 指令對照

有用到再繼續補充: 操作Ubuntu apt-get / aptitudeMacports列出已安裝的套件dpkg -l port installed列出已安裝套件 PKG 包含的檔案pkg -L PKG port contents PKG搜尋包含 NAME 字串的套件aptitude search NAME port search NAME顯示套件 PKG 的詳細資訊aptitude show PKG port info PKG

JavaScript 測試 framework 評估小記

找了一下 javascript unit test 的 framework, 看了一陣子, 還是很久以前試過的 QUnit 最順眼, 載入一個 JS, 一個 CSS 檔就可以用了, 語法也簡單 (或著說比較習慣, BDD 風格看起來頗囉唆的)。 一開始不小心找到牛刀 Karma (是 test runner 不是 test framework), 糊里糊塗的裝來用, 看看預設用的 Jasmine, 還有 Jasmine, Mocha 和 QUnit 的比較文 (前兩者是 BDD 風格)。順便得知可搭配 PhantomJS 作 headless browser 測試。那天真的需要 Karma 時再來用吧。或是直接找個 QUnit + PhantomJS 的 runner, 大概也可滿足下一層級的需求。 參照這幾篇應該可以順利裝好 Karma。用Ubuntu 12.04 的話, 需要先手動升級 npm 至新版, 才能成功安裝 Karma。 Karma - InstallationKarma - ConfigurationKarma Test Runner with QUnit in 10 Minutes · MattSnider.com 雖然不會去用牛刀級的 framework, 看牛刀級的 framework 可以得知生態圈的全貌以及相關的熱門套件, 也是不錯的入門方法。

大幅提升開發時間的 vim 必備 plugin: syntastic

用 IDE 開發的一大好處是: 寫程式的當下, IDE 會即時編譯程式碼, 指出編譯錯誤的地方。可以大幅縮短「寫碼 -> 編譯失敗 -> 寫碼 -> 編譯失敗 -> ...」的流程。VIM 可透過 syntastic 達到一樣的效果, 存檔後立即編譯, 然後指出編譯錯誤的地方。Script language 也有同樣效果。安裝後我不需改任何設定就可以愉快地寫 C++ 和 Python。 通常寫 C/C++ 時, 會在編譯時加上 "-I/path/to/header", 這樣編譯器才知道上那找標頭檔。syntastic 同樣地也需要這個設定。設定方法是在程式碼的目錄下 (或父目錄, 或父父目錄...) 建立 .syntastic_cpp_config , 裡面直接寫 "-I/path/to/header", syntastic 就知道上那找標頭檔了。 參考資料: http://stackoverflow.com/a/19143873/278456

字型雜記

sans-serif vs. serif單字的字義就說明了一切: serif (襯線字) 是在字的邊端加裝飾 (如垂直線條), 用意是利於辨識相似的字, 而 sans-serif (無襯線字)則是沒有加裝飾。serif 最早是用在西方文字印刷體的內文, 如今在中文字或是電腦、行動裝置螢幕上, 情境大不同, 無此必要。 中文相對於的例子是明體的橫線右上角有個三角形, 類似 serif; 而黑體則沒有, 類似 sans-serif。字型的運用大家有不同的說法, 原本我以為內文該用明體, 標題用黑體, 就如同 serif 和 sans-serif 的用法。但是《字型散步:日常生活的中文字型學》用內文用黑體, UDN 改版後內文也用黑體。我現在也覺得黑體較順眼 [*1]。 另外, 明體也有可能用在大字, 《字型散步》舉例福音戰士用明體作為大字, 就用得很漂亮, 所以還是要看選用的字型。換句話說, 了解這些知識是清楚它們的特色。如同其它CS技術一樣, 要依配套措施決定如何使用。 明體特色除水平線的右上角有個三角形外, 還有水平細, 直筆粗, 避免文字擁擠。 黑體特色筆劃筆直, 大多無襯線。 康熙字典體直接取用康熙字典內的字, 沒有修正, 相較之下較破碎和零亂。原本應該是缺點, 但因為與眾不同, 反到成了優點, 每個字看起來比較「有感覺」, 比較文青。 定寬字和比例字中文字無此差異, 主要是西方文字、數字和符號有差。 細明體是定寬字, 裡面附的英數字每個字都占「半格」; 新細明體是比例字, 英數字是依字的實際大小決定寬度。用 terminal 或開發程式的 IDE, 習慣用定寬字。閱讀內文則是用比例字。 字體和字型字體是概念, 字型是實踐的產品格式。設計一套字體, 然後作成字型供電腦使用。類似音樂和 MP3。不過大多數情境兩者混用也通。 瀏覽器用字的順序和方塊字瀏覽器會依 CSS font-family 定的順序找字, 有找到就用, 沒找到就用下一個。依此規則, 一般會先放英文字型再放中文字型, 因為英文字型不會附中文字, 但中文字型會附英文字。藉此用比較好看的英文字型, 沒有的時候再套用中文字型附的英文字。看到方塊字的話, 我猜應該是字型裡沒作這個字的樣子, 卻填了個方塊, 瀏覽器就拿去用了。如果字型檔不附上方塊圖, 應該可以選用下一個字型。實際情況如何, 要研究字型…

跳掉檔案開頭數 bytes 的作法

假設有個大檔案 (e.g. 500MB) 在開頭存有 meta data, 後面存有內容, 要怎麼跳掉開頭的 meta data, 只留下內容呢? 命令列的作法從這裡看到兩個有效率的作法: 使用 subshell: 先用一個 dd 跳掉開頭 1131 bytes (block size=1131, read 1 block), 再用第二個 dd 用正常的 block size 讀寫檔, 這樣效率就和直接用 dd 複製檔案差不多了。$ ( dd bs=1131 count=1 of=dev_null && dd bs=4K of=out.mp3 ) < 100827_MR029_LobbyControl.mp3 使用 pipe 配合 a group of commands: 概念差不多, 只是改用 pipe。 $ dd if=filtered.dump bs=512k | { dd bs=1131 count=1 of=/dev/null; dd bs=512k of=trimmed.dump; } Python 不建立新檔的作法產生新檔案的時間成本有點高, 可能會花到數秒。如果是 Python 程式內要用的話, 可以直接用 file object 然後想辦法跳掉開頭資料。偷懶的作法是直接用 file object, 然後先呼叫 file.read(N) 跳掉開頭 N bytes。缺點是若 file object 會傳入 third-party lib 使用, 其它程式碼可能會用 seek() 回頭讀開頭的內容。保險的作法是自訂一個 file object wrapper。這個 wrapper 行為和內建 file object 一模一樣, 只是讀不到開頭數個 bytes。這樣效率和一般的 file object 幾乎沒差。 程式碼見這裡, 另外這裡是測試碼。關鍵是覆寫和移動檔案位置有關的 seek 和 tell。然後用 Python 提供的 __getattr__(self, attr) 實作剩下沒改到的 method。那麼, 要怎麼知道該覆寫那些 method 呢? 用 ipython 建一個 file object, 然後利用補字功能, 再一個個看 method 說明, 很快就知道該覆寫那些了。 以下是示意的操作過程: $ ipyth…

取得執行檔所在的目錄名稱

有時自己寫的程式, 會在執行檔所在的位置放其它資源檔或設定檔。如果偷懶直接用相對路徑讀檔, 可能因為先前有用 chdir 切換 process 所在的位置, 而讀不到檔案。保險見起, 可以改用執行檔所在的位置作為起始路徑來讀檔。這裡有取得執行檔所在的目錄的程式碼, 重點是利用 /proc/self/exe 找出產生目前 process 的執行檔的路徑。平時需要寫 shell script 也可用同樣方法取得路徑2015/02/06 更新原本寫用 /proc/PID/exe, 經 Kito Cheng 提醒, 改為 /proc/self/exe 更方便。

The Python Standard Library By Example

看到不錯的書: The Python Standard Library By Example, Table of Contents 有不少有用的關鍵字,知道要做什麼事的時候,可以用什麼內建模組。另外, 這裡有書上附的範例碼, 玩玩小程式比看文件快上手。

抓網站內容和使用 lxml.html 讀取 DOM 內容

lxml 功能強大, 不過提供太多 API, 不太容易在官網找資料 (或是我太沒耐性吧...)。記錄一下抓網站取內容常用的 code snippet: 下載網頁內容轉成 lxml.html 的 Element objectimport sys import requests import lxml.html DEBUG = False def get_web_content(link): if not link: return None, lxml.html.fromstring(u'<html></html>') try: r = requests.get(link) try: content = r.content.decode('UTF-8') except UnicodeDecodeError, ude: if DEBUG: msg = ( 'The content is not in UTF-8 (ude=%s). ' 'Try ISO-8859-1 instead.\n' % ude ) sys.stderr.write(msg) # Try another encoding. If fail, just let it fail. content = r.content.decode('ISO-8859-1') if DEBUG: sys.stderr.write('Get content of link %s: %s\n' % (link, content[:50])) return r.status_code, lxml.html.fromstring(content) except Exception, e: …

python multiprocessing 小記

因為 CPython 有 GIL 的緣故, 需要提升 CPU 效率時, 不會用 multi-thread, 會改用 multi-process。內建模組 multiprocessing 提供許多好東西, 實作 multi-process 簡單許多。習慣用 multiprocessing 後,有時不是 CPU bound 而是 I/O bound, 我還是用 multiprocessing。反正....不是 CPU bound 的話, 多耗 CPU 也無所謂。 之前寫 crawling 的程式,為了增加同時抓網頁的數量,就用 multiprocessing 加速 [*1]。以下是兩個例子: 取出台北熱門電影的 IMDB 分數 (使用 Pool)取出 Google Play 相似 app 的分數分佈 (沒用 Pool, 有用 Lock。Btw, Pool 感覺沒有很方便) 從例子裡找片段程式碼來用比看文件快。這裡記錄一下相關心得: 若要讓不同 process 共享資料,需要用 multiprocessing 訂的物件, 有含 list, dict 等,單一物件 (如 int, float, str) 則是用 Value 指定 typecode, 如: shared_integer = multiprocessing.Value('i', 0) 使用 shared data 會有 race condition, 要用 Lock 保護,不然就直接用 Queue 或 Pipe。還有 EventSemaphore 等物件,要作比較複雜的機制時,可以拿來用。 有提供跨機器的 multi-processing, 有需要再來細看。 程式架構照 producer-consumer 的方式寫比較容易, 也就是各個 process 共用 Queue, 然後 producer 用 blocking put, consumer 用 blocking get, 另有一個 main process 監控情況, 確保程式會結束 (這大概是最麻煩的地方)。 注意量大的時候 put 會因 queue full 卡住,所以不能在 main process 用 blocking put。即使很容易填入 Queue 的初始資料,用另外的 process 填入初始資料比較保險, 因…

用 jQuery load 跨 HTML 檔使用重覆的 HTML 內容

寫網頁到一定規模後, 會抽出重覆的 JavaScript 到獨立的 JavaScript 檔, 再用 <script src="FILE.js"></script> 載入, 以供不同 HTML 使用; CSS 則是用 <link rel="stylesheet" type="text/css" href="FILE.css"> 載入外部檔案。那 HTML 怎麼辦? 若是會寫 PHP 的人, 會使用 include 載入重覆使用的 HTML; 用 Python/Ruby/Perl 寫 CGI 的話, 會搭配 web framework 內 template language 的語法, 所以也沒問題。但若是只會寫 HTML + CSS + JavaScript 的人該怎麼辦? 查了一下, 發覺用 jQuery load 可以輕易做到 [*1]。唯一的問題是, 在本機電腦實作, 用瀏覽器開啟本機檔案後會發現行不通。有如下的 JavaScript 錯誤訊息:XMLHttpRequest cannot load file:///C:/.../FILE.html. Cross origin requests are only supported for HTTP. 這是因為瀏覽器基於安全考量, 禁止 JavaScript 讀取本機檔案。 解套方式是在本機跑 web server, 透過 http (而不是 file://) 讀取檔案。 於是問題變成: 如何讓這類不擅長程式設計或系統管理的人, 能在自己的電腦測試 jQuery load? 畢竟會有這種需求的人, 大概也不擅長在 Windows 或 Mac 裝 apache2 或 lighttpd。從這裡看到有人推薦用 mongoose。 作法如下: 下載 mongoose 執行檔 執行檔放在網頁目錄下 執行它就可以連到 port 8080 看到結果 (即連往 http://localhost:8080/ 瀏覽目錄下的網頁)。也可參考官網的教學了解更多設定。 要關掉 mongoose 的話, 可以從右下角的系統選單找到 mongoose 的圖示, 按右鍵再選 Exit。或從系統管理員直接結束它。 Bt…

網頁顯示彈出畫面同時禁止捲動主畫面的技巧

作法應該有相當多種,這裡提兩種我從一些網站中觀察到的作法。 設定 body 為 overflow:hidden線上觀看的例子完整原始碼 準備兩個 div, 一個是 main content (id=main), 一個是 pop-up (彈出畫面), 平常隱藏 pop-up, 要顯示 pop-up 時,設 body 的 overflow: hidden,並顯示 pop-up。這樣主頁面就不能捲了。 關掉 pop-up 時再設 body 的 overflow: visible (預設值), 主頁面就可以捲了。 程式碼像這樣:var popup = document.getElementById('popup'); function show_popup() { popup.style.display = 'block'; document.body.style.overflow = 'hidden'; } function close_popup() { popup.style.display = 'none'; document.body.style.overflow = 'visible'; } 優點: 簡單,很多網站這麼作 缺點: 部份瀏覽器不支援, 例如 iOS 6對 main content 為 position:fixed線上觀看的例子 (我有修改 #popup 的 height, 讓它可以在這網站正常顯示,其餘和下行的原始碼一樣)完整原始碼 這個作法比較複雜,是拆解 facebook 彈出照片的頁面得知的。 準備兩個 div, 一個是 main content (id=main), 一個是 pop-up (彈出畫面), 平常隱藏 pop-up, 要顯示 pop-up 時,設 main content 的 position: fixed,並顯示 pop-up。這樣主頁面就不能捲了。但是因為 scroll offset 歸零 (content height 改變的副作用), 要自己維護 scroll offset (後述)。 這個作法與上個作法有幾點差異: 網頁主動管理頁面大小, 造成主頁面不能捲; 上個作法是設 body 為 overflow: …

Python 處理 UTF-16 的 CSV

Python 內建的 csv 模組預設無法處理 UTF-16, 直接讀的話會出現Error: line contains NULL byte 自行轉換編碼成 UTF-8 即可,作法如下: import codecs import csv # 用 codecs 解碼 utf-16 為 unicode object f = codecs.open(filename, 'rU', 'utf-16') # 再轉回 utf-8 給 csv.reader 用 cr = csv.reader(line.encode('utf-8') for line in lines) for row in cr: # row 的內容就是 CSV parse 完的結果 或是先用 iconv 轉檔, 再用 Python csv 模組處理亦可: $ iconv -f utf-16 -t utf-8 file_in_utf16 > file_in_utf8 備註: Python csv 模組不接受 unicode 的輸入,若來源不是檔案,也要記得轉成 utf-8 再傳入 csv.reader()

自動統計 app 在 Google Play 內各版本的的分數和常見問題

最近想統計 app 在 Google Play 上各版的分數,了解最近版本是否比較不穩定。如果有問題的話,統計一下問題分成那幾類。透過 Google Play 觀看 review,如果沒有邊看邊統計分類,看過只會有個模糊印象,不知道那類問題比較嚴重。但是.......又懶得邊看邊記。 為了自動分類 reviews, 首先要抓下 Google Play 上的 review。幸好 Google Play 存放 reviews 和其它資料在 Google Cloud Storage, 並提供工具 gsutil 可以從命令列取出 Google Cloud Storage 的資料, 作起來並不費工。官方說明在這裡的 "Export ratings and reviews"。摘要步驟如下: 1. Mac 或 Linux 下載解開 gsutil: https://cloud.google.com/storage/docs/gsutil_install 2. 更新、設定 gsutil: $ export PATH=$PATH:/path/to/gsutil $ gsutil update $ gsutil config 其中 gsutil config 會認證帳號, 照著作即可。最後要求輸入 project id 的時候, 輸入 app package name,例如 com.mycompany.myapp。相關說明見這裡。 3. 下載 review 從 Google Play App Console -> App -> Ratings & Reviews 最下方得知 reviews 存放的位置 Ratings and reviews are also available for programmatic access through Google Cloud Storage and the gsutil tool. Your data, updated daily, is stored in this private bucket: pubsite_prod_rev_NUMBER 接著用 gsutil 列出目錄下的檔案$ gsutil ls gs://pubsite_prod_rev_NUMBER 再來就簡單了,gs…

線上刷卡流程的簡介

同事介紹關於線上刷卡流程的介紹文章, 滿淺顯易懂的, 改天有需要再複習一下:Credit Card Processing Diagram: How Credit Card Processing Works - Authorize.Netprimer:the_pieces [FoxyCart! Wiki

git 常用指令

有鑑於一兩年沒用 hg 就忘得差不多了, 還是備忘一下目前覺得好用的 git 指令, 比較長的就加到 alias 裡了。 搜尋全部內容包含 diff 中出現過的字: git log -G ( -S 速度較快, 但可能會漏東西, 見 "git log -S" does not show all commits 的說明 ) 顯示所有 branch: git log --graph --branches --decorate --oneline 顯示 utf-16 的字串: 讓 git diff 顯示 utf-16 (或其它binary) 檔案的差異 還沒實際用過, 先記著: 從目前的 git repository 裡切出一個子目錄成新的 git repository 並保留歷史記錄: Splitting a subfolder out into a new repository ( 聽 Wens 說的 )