17 四月

AngularJS + SVG 地雷

情況是這樣的。由於 html5mode 很漂亮, 不再有醜陋的 hash 在網址裡頭, 所以理所當然的就想辦法用這個模式。然而, IE9 並不支援 History API, 因此依照 AngularJS 的文件, 要弄個

<base href="/blabla" />

在 head 裡頭。

這都還不打緊, 但是當我想要使用 SVG 的 textPath 的時候, 他就爆炸了。textPath 是一個可以讓文字彎彎曲曲, 依照先前定義的 path 來編排的一個功能, 他的語法大致如下

<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs>
        <path id="myTextPath" d="M75,20 a1,1 0 0,0 100,0" />
    </defs>

    <text x="10" y="100" style="stroke: #000000;">
      <textPath xlink:href="#myTextPath" >
            Text along a curved path...
      </textPath>
    </text>
</svg>

Reference: The SVG textpath element

在 <defs> 裡頭定義了一個 path, 然後接著在 textPath 裡頭會藉由 xlink:href 來指向這個 path。

此時麻煩來了, 由於 angularjs 定義了 base, 因此 xlink:href 就會自動解讀為 /blabla#myTextPath, 而造成失效 (原因是這段 svg 是動態產生的), 他確實是會想要透過網路抓取外部資料, 而我沒有任何方式可以把 base 的設定取消。

於是, 就造成了一種死結無解的狀態… orz

最後我用了爛招, 就是我把 SVG 搬移到外面, 並且給他參數讓他自動產生我想要的向量… 真的滿弱的… 只是, 這種組合又有多少人會碰到呢?

13 六月

Django, celery, gevent 實作 long polling

我們的一個新的網站平台, 有讓使用者上傳圖片的功能。當照片上傳過後, 我們會縮放圖片至適當的比例, 並且貼上一些浮水印之類的東西。如果我是貼上一個單眼拍下來的相片, 使用 PIL 縮圖是挺耗費時間的, 因此為了有更好的 User Experience, 我覺得這些耗費時間變成 offline 來做。然而, 我希望 server 在做完之後, browser 能夠被通知到並且將縮圖抓回來。

一開始, 我打定的主義是使用 node.js 的 socketio, 可惜我完全沒碰過 node.js, 雖然 socketio 在前後台之間的結合很完美, 但有下列問題要先解決:

  1. Ubuntu 12.04 LTS 上的 nginx 版本還沒有支援 websocket。當然也是可以更新版本。
  2. nginx 的後端有兩種 solution, django + node.js。
  3. 我要想辦法讓 django 跟 node.js 可以一起溝通, 因為網站整個都是 django, user upload 完照片後, 整個都是在 django/python 裡頭, task 的資訊要在兩邊共享

基於上述會耗費太多開發時間, 因此我多 survey 了一下, 發現 long polling 基本上就是把 request 卡住, 等到一有結果就直接回傳。用最基本的東西就可以實作。

Celery

Celery 之前就大概用過, 這一次想說直接用 SQS 作為 Message Queue, 文件上有很基本的說明, 但是有一行講得非常模糊

If you specify AWS credentials in the broker URL, then please keep in mind that the secret access key may contain unsafe characters that needs to be URL encoded.

看完沒有 sample 真的很痛苦, 因為 python urllib 的 API 文件好像找不到合用的, 一開始看大概會選擇 urllib.urlencode(query[, doseq]), 但其實這是用在 GET parameter, 會有 key 跟 value, 而 urllib.quote(string[, safe]) 用起來卻不會將 / (slash) 做 encode。搞了半天, 原來要把 safe 變成空的才 ok:

BROKER_URL = 'sqs://%s:%s@' % (AWS_ACCESS_KEY_ID, urllib.quote(AWS_SECRET_ACCESS_KEY, ''))

然而, 實作之後, 發現有不穩定的狀況, 才發現在 Brokers 的頁面上有寫著, SQS 的 status 是 experimental, 因此就火速換掉使用之前用的 rabbitMQ。(不過後來發現不穩定的狀況應該是我自己耍腦殘, 請看下面段落敘述)

gevent & long polling

查了很多 long polling, python 還有 django 的資料, 後來發現可以使用 Django+gevent 來實作。如果不使用 gevent, Django 是不太適合直接拿來做這件事情, 因為如果有多個 request 都在等待, 很快就會把 worker 全部都佔滿 (如果有錯請糾正 >”<), 而使用 gevent, 則可以使用底層的 libevent 來做多工的切換, Gevent Tutorial 的這張圖有很清楚的表達他的運作方式。因此, 如果有多個 user 在做 long polling, 也不致於會卡住。

結合在一起

接在一起的流程很簡單, 步驟如下:

  1. User 上傳檔案到後端的某隻 view。
  2. 這個 view 製作一個 celery task (apply_async), 然後將 task_id 回傳給 browser
  3. Browser 定期帶著這個 task_id 去 check 另一個叫做 poll 的 view
  4. poll 的 view 只做一件事情就是 get celery task result, 並且設定一個 timeout。所以, 只要一 timeout, browser 就會再發一次 request 卡在 poll 裡頭。

當然, 如果一次上傳多個圖片, 就可以使用 gevent 的 group, 一次開多個 greenlet 去等待, 哪一個先回傳就把整個 group 幹掉回給 browser。我寫了一個很簡單的 django-longpolling-example 放在 github, 就只是簡單的示範這個概念。

Debug 很久之自己耍笨

前天下午在睡午覺的時候, 收到 E-mail 說有使用者講無法上傳圖片, 我就跳起來 debug, 用了非長久, 發現會有漏 task 的事情發生。於是乎我 trace code trace 到了 celery、kombu、amqp 等 python library, 才發現 message 是真的都有丟出去, 但是為什麼我的 celery worker 就是會掉 task 呢? 我今天才突然驚覺, 原來我 dev、stage、production 用到了同一個 queue, 所以我在測試機器上傳的圖片, 都委派給其他 server 去做了, 而這邊 code 沒有 sync, 或是檔案也不在那台 instance 上, 所以就無法完成 task。

這告訴了我們, follow tutorial 還滿重要的。在 Using RabbitMQ 的 Setting up RabbitMQ 提到, 要設定 virtual host, 才會把 queue 給隔開。

Deployment

在 django 使用 gevent, 若是使用 gunicorn, 可以直接將 worker 換成 gevent


gunicorn app.wsgi:application --worker-class gevent

如果直接執行的話, 可以參考 django-longpolling-examplerungevent.py

01 十月

console.log in IE

測試功夫做得還不夠。今天早上同事打來說, 還是有很多客戶反應 IE 註冊都沒有任何的提示。我上次想說就已經看過也修了, 為什麼還是一樣。今天到公司自己測了一下, 都 ok。直到我跑到同事的電腦請他做一次給我看, 才發現我跟他測的差別。我每一次都開了 developer tools 在做測試 (既然是在 debug, 當然會把這個打開), 殊不知就因為我有打開就看不到 bug。

此時我大概猜到了, 我應該是用了 console.log 所以開 developer tools 的時候都正常, 一關掉就爛掉了。

所以加了一個避免爆炸的小 code

// For IE
if (typeof console === "undefined" || typeof console.log === "undefined") {
console = {};
console.log = function() { };
window.console = console;
}

因為有做 js compress, 所以要把 console 給 expose 到 window 這個物件, 不然這段 code 會被 cage 住而讀不到資料。

20 九月

bootstrap 的 navbar-fixed-top

如果使用 navbar-fixed-top 的話, 要記得在 body 加上 padding。沒有看文件直接參考別人的 code 真的是會碰到問題阿! Trace 了半天才發現 style 加在 body 上。

body {
padding-top: 60px;
}

緣由其實是我想要看有沒有 fluid + fixed 的 bootstrap layout 可以參考, 於是乎找到了 Bootstrap: Fixed gutter width in fluid layout? 這篇文章。他的方式就是讓中間的 fluid layout 空出左邊或右邊的 margin 達成。