?
Mochiweb的設計分析
轉自: http://erlang-china.org/misc/mochiweb-inside.html
Web服務器 的基本工作大致分3步:?
- 接收HTTP請求;?
- 處理HTTP請求,生成響應內容;
- 發送響應
一、處理請求和發送響應 ?
模塊mochiweb_request可說是Mochiweb處理HTTP請求的核心部分,它總共負責了第2步和第3步工作。因此參數化模塊mochiweb_request的實例不像它的模塊名那樣單純:它還負責將請求的響應(通過Socket連接,調用gen_tcp:send)發還(response)給瀏覽器。這個模塊是一個 參數化模塊 。這意味著它具有基于對象(Object-based)的特點,它的每個實例化對象代表一個用戶請求,用戶可以像OO那樣操作HTTP請求:獲取請求信息,生成響應內容等。?
對靜態內容(如html文件,靜態圖片)來說,無非是讀取文件系統中靜態文件的內容作為響應消息體(Body)發送給客戶端瀏覽器。?
對動態內容來說,生成響應內容的業務邏輯是用戶通過編程實現的:解析請求(包括URL路徑和HTTP請求頭),然后生成相應的響應內容。用戶編寫業務邏輯處理函數,然后將函數插入到mochiweb中。此函數帶有一參數,該參數是參數化模塊mochiweb_request的實例(假設為Req),在這個定制函數中可以通過這個Req實例對象獲取瀏覽器請求的所有信息(包括URL路徑和HTTP請求頭),處理后的響應數據也通過Req實例提供的函數發還給瀏覽器,有好幾種方式:?
-
通過Req實例對象直接發還給瀏覽器
:
?
a)? 通過這個Req實例對象的response/1函數直接發還給瀏覽器:
Req:response({Code, ResponseHeaders, Body})
這個函數接收一個tuple參數,參數的第一個元素是HTTP狀態碼(比如200),第二個元素是響應消息頭列表(一個二元tuple,key和value都是字符串,如{“Content-Type”, “image/jpeg”}),第三個元素即為HTTP響應內容;如果是動態內容,采用response函數直接發還響應數據還是比較方便的。b)? 如果是靜態文件,通過Req的serve_file(…)函數可以直接將文件發還給瀏覽器,告訴此函數文件的Web根目錄(doc_root)和相對路徑就可以將指定的靜態文件發給瀏覽器了。doc_root目錄一般在配置文件中設置,這個目錄下的所有文件都可以通過HTTP訪問。
c)? 一些常見的例行響應:
不存在的URL請求返回404錯誤,可以直接調用Req:not_found() 若一切正常,調用Req:ok({…, Body})直接返回狀態碼為200的HTTP響應
b,c兩種情況的內部其實還是調用的Req:response() -
通過Response模塊對象將響應發還給瀏覽器
:
?
Req:response函數會返回一個mochiweb_response參數化模塊實例對象(假設為Response),Response實例對象包含有對應的Req實例對象。通過Response對象可以得到響應的相關信息(如響應狀態碼,響應消息頭,對應的Req對象),它還有一個send函數可以將響應數據發還給瀏覽器(它的實現其實還是調用Req對象的send函數進行的)。Response之所以還要有send函數是為了發送chunked數據(HTTP 1.1)的方便,在第一次響應完成后,后繼的chunk數據就可以通過最初返回的Response對象繼續進行發送了,為此Response有個函數write_chunk()專門干這事,write_chunk檢查了請求消息頭中的HTTP 版本消息后就調用Response:send。
因此,響應內容最終都是由參數化模塊mochiweb_request的response/1函數發送的。而這個response(…)函數內部最后調用了Req:send(Data)函數將響應通過socket連接(調用gen_tcp:send)返還給瀏覽器,這一發送過程又分成兩個階段:響應消息頭(Headers)的發送和消息體(Body)的發送,這兩步都是通過Req:send完成的。
小結:對于程序員來說,他編寫的服務器端處理HTTP請求的函數必須帶有一個參數,這個參數代表了mochiweb_request參數化模塊的一個實例對象。通過這個實例程序員可以得到HTTP請求的所有信息(包括路徑、請求參數以及HTTP請求消息頭),然后生成響應,他還可以通過該mochiweb_request實例對象將響應數據發還給瀏覽器。?
- 如果響應的是靜態文件,可以通過Request:server_file()函數發送響應;
- 如果響應是動態生成的內容,通過Request:response()函數發送響應數據(文本數據和二進制數據都可以);
- 如果是chunk響應,第一次調用Request:response()函數發送數據后,該函數會返回一個Response實例對象(參數化模塊mochiweb_response的一個實例),以后的響應數據可以通過這個Response實例對象(調用Response:write_chunk(Data))發送后續的響應數據。
二、Web服務器的業務處理邏輯如何嵌入到Mochiweb ?
業務處理邏輯被程序員編寫成一個函數(函數式編程中函數也是一種數據,以下稱為HttpLoop),處理函數HttpLoop是作為mochiweb啟動時的參數之一進入Mochiweb,Mochiweb是這樣啟動的:?
Request實例對象最終要調用gen_tcp:send(Socket,…)將響應數據由socket連接發給瀏覽器,因此,Request實例對象應該持有HTTP請求的socket連接。這個目標是這樣實現的:?
在啟動過程中,mochiweb_http又對用戶的HttpLoop函數進行了重新包裝?
?? ? mochiweb_http : loop ( Socket ,? HttpLoop )
end ,
每個瀏覽器連接請求對應著一個Socket連接,新的Loop函數以此Socket作為參數,然后通過mochiweb_http:loop函數對Socket連接和用戶自定義的HttpLoop函數進行了處理,簡單的說,這個函數做了如下工作:?
- 將Socket連接設置成能處理HTTP數據包 (inet:setopts(Socket, [{packet, http}]));
- 準備接收socket連接傳來的數據(gen_tcp:recv(Socket,…));
當收到數據時:
- 將HTTP消息頭數據提出(成為{HeaderName, HeaderValue}的tuple列表)
- 生成一個參數化模塊mochiweb_request的實例對象,并將Socket連接、HTTP請求信息(路徑、請求方法、HTTP版本)以及請求消息頭列表包裝到此實例對象中,
- 然后調用用戶的HttpLoop對請求進行處理和響應(如前所述,處理得到的響應數據也在用戶編寫的HttpLoop函數中被發送給瀏覽器)
- 最后根據HTTP請求信息決定是簡單的關閉Socket連接,還是清理一下Req對象并保持連接(例如對keep-alive,chunk等類型的HTTP請求,以及還沒完成數據傳送的HTTP請求),以便繼續讓HttpLoop函數進行處理,
這都是在headers函數中進行的,還是看mochiweb_http模塊的代碼:?
- headers ( Socket ,? Request ,? Headers ,? HttpLoop ,? HeaderCount ) ?->?
- ? ?? case ? gen_tcp : recv ( Socket ,? 0 , ? IDLE_TIMEOUT ) ? of ?
- ? ? ? ? { ok ,? http_eoh } ->?
- ? ? ? ? ? ?? inet : setopts ( Socket ,? [ { packet ,? raw } ]) ,?
- ? ? ? ? ? ?? % 將Socket連接、HTTP請求信息(路徑、請求方法、HTTP版本)以及請求消息頭列表打包成參數化模塊(mochiweb_request)的實例對象?
- ? ? ? ? ? ?? Req ?=? mochiweb : new_request ( { Socket ,? Request ,?
- ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? lists : reverse ( Headers ) } ) ,? ?
- ? ? ? ? ? ?? HttpLoop ( Req ) ,? % 讓用戶編寫的函數處理HTTP請求?
- ? ? ? ? ??? case ? Req : should_close () ? of ?
- ? ? ? ? ? ? ? ?? true ?->??
- ? ? ? ? ? ? ? ? ? ?? gen_tcp : close ( Socket ) ,?
- ? ? ? ? ? ? ? ? ? ?? exit ( normal ) ;?
- ? ? ? ? ? ? ? ?? false ?->??
- ? ? ? ? ? ? ? ? ? ?? Req : cleanup () ,?
- ? ? ? ? ? ? ? ? ? ?? mochiweb_http : loop ( Socket ,? HttpLoop ) ?
- ? ? ? ? ? ?? end ;?
- ? ? ? ? { ok , { http_header ,? _ ,? Name ,? _ ,? Value }} ->?
- ? ? ? ? ? ?? headers ( Socket ,? Request ,? [ { margin-
發表評論
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

評論