Category Archives: HTTP

A brief introduction to SPDY – 邁向 HTTP/2.0 – 投影片

這是這次在 WebConf Taiwan 演講的投影片。從接到演講邀請就想說講什麼題目最切題,雖然自己比較熟的是 Rails,但是好像從前年開始就沒有在公開的場合講 Rails 了(咦?) anyway,這次一開始投的是「如何善用 REST 和 Hypermedia 設計 Web APIs」,後來看 SPDY 覺得這更實用且影響更大,更值得介紹給大家。加上我自己之前看 HTTP 連線管理 就覺得這議題蠻有趣的。REST 只是一種設計風格,而 HTTP 可是大家賴以為生的通訊協定啊,所以就毅然決然決定來改題目…

會後似乎獲得了不錯的回響,看來很多人都是第一次認識 SPDY 啊,題目真是挑對了 XD

HTTP Verbs: 談 POST, PUT 和 PATCH 的應用

在初學REST的這幾年,我都認為這幾個 HTTP Verbs 就是對應 CRUD:

  • POST = 新增
  • GET = 讀取
  • PUT = 更新
  • DELETE = 刪除

後來在設計 API only 的 Web service 時,常常搞不清楚到底要用 PUT 還是 POST,才發現我被 Rails 的鷹架範例誤導了(被框架框住想法了?),所謂的 PUT 其實也可以用到新增,而且還有一個蠻新的 HTTP Verb 叫做 PATCH,像 Github APIRails 4 都開始採用。

PUT 比較正確的定義是 Replace (Create or Update),例如PUT /items/1的意思是替換/items/1,如果已經存在就替換,沒有就新增。PUT 必須包含items/1的所有屬性資料。

但是這個行為似乎不怎麼好用,如果只是為了更新items/1的其中一個屬性,就需要重傳所有items/1的屬性也太浪費頻寬了,所以後來又有新的 PATCH Method 標準,可以用來做部分更新(Partial Update)。

用幾個 Ruby code 來舉例吧:

POST 新增:

# POST /items
def create
  @item = Item.new
  @item.attributes = { :name => params[:name],
                      :image => params[:image] }
  @item.save
end

PUT 替換(新增或完整更新),此例中如果image參數沒有傳,會被更新成空:

# PUT /items/{id}
def replace
   @item = Item.find_by_id(params[:id])

  unless @item  # if @item.nil?
      @item = Item.new
      @item.id = params[:id]
  end

  @item.attributes = { :name => params[:name],
                       :image => params[:image] }
  @item.save
end

PATCH 部分更新,此例中如果image參數沒有傳,就不會被更新:

# PATCH /items/{id}
def patch
   @item = Item.find(params[:id])
  @item.attributes = params.slice(:name, :image)
  @item.save
end

DELETE 刪除,此例中無論如何 items/1 最後都不存在了

# DELETE /items/{id}
def destroy
   @item = Item.find_by_id(params[:id])
  @item.destroy if @item
end

有時候拘泥於”語意”這件事情不容易想清楚設計 REST API 時要用哪一個 HTTP 方法,因為有時候不一定是CRUD的形式。我認為 9.1 Safe and Idempotent Methods 定義中的 “Idempotent” 特性蠻實用的。idempotent 的意思是如果相同的操作再執行第二遍第三遍,結果還是跟第一遍的結果一樣 (也就是說不管執行幾次,結果都跟只有執行一次一樣)。根據 HTTP 的規格,GET, HEAD, PUT 和 DELETE 是 idempotent,相同的 Request 再執行一次,結果還是一樣。只有 POST 和 PATCH 不是 idempotent,POST 再執行一遍,會再新增一筆資料,PATCH 則是有不能保證 idempotent 的可能性(徵求例子)。POST 和 PATCH 都不是 idempotent 的操作,這也是為什麼 Github API 裡用 POST 當做 PATCH 的相容取代方案。

另一個 HTTP Methods 特性是”Safe”,這比較簡單,只有 GET 和 HEAD 是 Safe 操作。Safe 特性會影響是否可以快取(POST/PUT/PATCH/DELETE 一定都不可以快取)。而 Idempotent 特性則是會影響可否 Retry (重試,反正結果一樣)。

Safe? Idempotent?
GET Y Y
POST N N
PATCH N N
PUT N Y
DELETE N Y

透過 Idempotent 的特性,有時候可以幫助你判斷該用哪一個 HTTP Methods。回到前面講 PUT 好像不太好用,例如以瀏覽器為主的 HTML 應用表單,要麻是 POST 新增資料,要麻就是用 PATCH 部分更新已經存在的資料(你不會希望用 PUT 修改個人資料的時候,每次都要重傳照片吧),因此比較少用到 PUT。這也是為什麼 Rails 4 把表單修改的 PUT 改成 PATCH Method,透過 Rails 鷹架產生出來的 update,其實符合的是 PATCH 行為而不是 PUT。

不過還是有一些我認為蠻適合用PUT的情境,例如訂閱東西該用POST還是PUT呢?

POST /subscriptions
# 還是
PUT /subscriptions/{something_id}

訂閱東西只有兩個狀態,”已訂閱”或”沒有訂閱”,這個訂閱的操作再重送幾次,還是”已訂閱”,所以我認為蠻符合 PUT 的 idempotent 特性。而對應的取消訂閱 API 想當然就是

DELETE /subscriptions/{something_id}

另外一個我覺得有趣又實用的 PUT 例子是,設計 API 给可以離線 offline 使用的行動裝置(例如iPhone)。支援 offline 產生的資料,通常會使用 UUID 來產生 ID,這樣就不需要透過中央伺服器管控 ID,方便裝置之間的同步。這樣的情境下,新增資料的 REST API 其實可以提供兩種:

POST /items # 參數帶 uuid=4937973d-e349-460a-a6ad-38625125b24a。如果不帶uuid則由server來產生uuid
# 和
PUT /items/4937973d-e349-460a-a6ad-38625125b24a

對行動裝置的 client 來說,用POST的問題在於離線環境的不穩定,有可能POST之後沒有收到回傳,因此行動裝置不確定有沒有同步成功,這時候要再重試(retry),但是用 POST 就爆炸了,因為 server 會再新建一筆 uuid 重複的資料。但是用 PUT 就沒有問題了,PUT 是 Idempotent 的操作,可以重送沒有關係 (可以再搭配 Conditional PUT 的機制,用 ETag 和 Last-Modified Headers 確保沒有覆蓋衝突)

如果是沒有 offline 需求的 client,例如第三方應用,那麼就可以用 POST /items 這個 API,交由 server 來產生 uuid。

其他參考資料

使用 httperf 做網站效能分析

Update(2008/7/8): 推薦Topfunky’s bong搭配使用,非常簡單。

做網站效能調校,一件重要的事情就是量測(measure)。調之前測一次,調之後再測一次才知有沒有藥效。

“Server 回應 requests 的速度有多快?” 就是一個最基本的問題。這技巧也叫做黑箱分析(Block-box analytic)。Advanced Rails 一書中給了幾點特別要注意的事項:

  1. 有台 front end server (Apache,Nginx等)先測試 static files,這個數據是一個上限。Rails 不會比這更快了。
  2. 找近一點的地方測,減少網路 latency 的變異性
  3. 不要用 server 同一台測試,雖然 CPU 不太會是問題,但是 I/O 可能會有影響。

觀測的數據除了平均,更要看標準差跟計算信賴區間,因為除了平均之外,低變異也很重要。基本的統計學告訴我們,相差平均一個標準差可以涵蓋 68% 的資料,相差兩個標準差就可以涵蓋 95% 的資料。因此我們可以算出 95% 的信賴區間,也就是 95% 發出的 requests 中,可以在幾秒到幾秒內回應。基本的統計知識是做 analytic 必備,Zed Shaw 為此還寫了篇Programmers Need To Learn Statistics Or I Will Kill Them All

在 Mac 上透過 MacPorts 安裝 httperf 很簡單,輸入 `sudo port install httperf` 即可。

httperf 的用法

基本用法是指定 Server、Port、URL和總共要發出多少個 requests:

httperf --server project1.local --port 3000 --uri /events --num-conns 1000

其中最重要的 output 資訊就是 Reply rate 有平均和標準差的那一行。要注意的是 httperf 是每五秒抓一次樣本(sample),根據 httperf 的建議是希望至少有 30 個樣本數才能得到準確的標準差,因此當 sample 數太少的時候,要記得把 –num-conns 往上加。

另一種算壓力測試,也就是去測試“Server 每秒可以承受多少 requests?”。請再加上 –rate 跟 –hog 參數:


httperf --server project1.local --port 3000 --uri /events --num-conns 5000 --rate 500 --hog

逐步把 rate 往上調,直到 server 超過極限時,就會開始少 replies (有 requests 被 drop off 了) 並出現 Errors。

Rails 的相關設定

有幾項設定可以增加更多相對比較數據:

  1. 透過 config.action_controller.perform_caching = false 的設定可以在 production mode 也把所有 caching Page, Action, Fragment 暫時關掉
  2. 暫時註解掉 before_filter 中的權限檢查
  3. 透過 session :off 關掉 session

本文參考資料

  1. Advanced Rails Chap.6 Performance
  2. Peepcode httperf screencast

Rails Front-End 優化

Update(2008/3/24): Yahoo 有份投影片值得一看:

因為 Registrano hosting 在國外的關係,先天速度就慢了一個太平洋。所以如何讓網頁能夠快點 loading 完並且 display 出來變得非常重要且感受明顯。我們可以使用 Firefox 的 YSlow 或 safari web inspector 來檢測實際下載的瓶頸在哪裡。教材則有 O’Reilly High Performance Web Sites 一書值得一讀,裡面的 guideline 有十四點如下:

  1. Make Fewer HTTP Requests
  2. Use a Content Delivery Network
  3. Add an Expires Header
  4. Gzip Components
  5. Put Stylesheets at the Top
  6. Put Scripts at the Bottom
  7. Avoid CSS Expressions
  8. Make JavaScript and CSS External
  9. Reduce DNS Lookups
  10. Minify JavaScript
  11. Avoid Redirects
  12. Remove Duplicates Scripts
  13. Configure ETags
  14. Make Ajax Cacheable

Continue reading Rails Front-End 優化

HTTP: The Definitive Guide Part 1

 雖然搞網站好幾年了,對於Client-side (如HTML,CSS,Javascript等)或Server-Side(如PHP)等技術都算熟悉,之前修過網路概論的課也對TCP/IP等網路架構有所了解,但是對Web中一項最重要的通訊協定HTTP卻所知有限,網路概論中對HTTP也講的非常簡單。

不過在看過蔡學鏞學長的書評之後,讓我燃起想深入了解的興趣,整個 HTTP Architecture 講起來也厚達六百多頁呢,而且最近剛好看到廖長輩貼的HTTP加速文,關於keep-alive,pipelined connections等這些我都不太清楚什麼作用的東西,也都在這本書的範疇內。

Part1 主講 HTTP 通訊協定本身,第一章是概論,第二章講Internet Resource的觀念(URI),其中有兩種實作方法URL及URN,URI的語法包含scheme(用什麼protocol來互動),server address跟path(放哪裡)。URN則是指定某resource名稱,不需要管放在哪裡,不過URN缺少實際架構,很少在用。

第三章則仔細講解 HTTP Messages,包含有哪些 Methods, Status Codes,Headers等,還有HTTP 0.9,1.0,1.1的各版本差異。不過 Headers 主要只是分類描述而已,本書各章在講解Web架構的同時,會在仔細說明相關的HTTP協定。

第四章講HTTP如何管理 TCP connections,一般講HTTP大都著墨在封包本身,對於HTTP connections卻講不多,但這卻是HTTP programming必須要了解的東西。這章有很多我以前不知道的東西,也複習了一下TCP協定,非常精采,值得另闢專文討論

另外這本書的插圖實在很棒,又多又好,把一些重要觀念都畫出來了。

接下來Part2則是HTTP Architecture,包含 Web Servers, Proxies,Caching,Gateway,Tunnels,Relays,SSL,Web Robots,HTTP-NG(HTTP/2.0)等。