關於如何寫一個Webserver,很多大咖都發表過類似的文章.趁著這個五一假期,我也來湊個份子.
我寫Webserver的原因,還得從如何將http協議傳送的訊息解析說起.當時,我只是想了解一下http的訊息解析過程,好能夠提高基於http協議的訊息處理效率,所以就在網上搜了一下,發現很多人都在用nodejs的http-parser,也許是智商上限封頂^_^!,我居然沒太看懂大神的程式碼邏輯.後來也考察過h2o這個專案的parser,無奈還是沒有能領悟大神的精神^_^!.
怎麼辦...,掙扎了半天,最終決定硬著頭皮自己寫一個http訊息的parser吧.就醬,就有了後來我寫Maestro Webserver的故事.
既然談到了http message的解析,那今天這第一篇隨筆就談這個東西吧.http協議的內容說起來歷史太久遠了,我不是歷史老師,網上很多講解都很棒,我就不多說了.此外,RFC2616, RFC7231等文件也明確的講解了協議的含義.不過還是應該吐槽一下RFC文件的晦澀難懂哈...
還是讓我引用一段相對清晰的關於http message的RFC講解吧
HTTP messages consist of requests from client to server and responses from server to client. HTTP-message = Request | Response ; HTTP/1.1 messages Request (section 5) and Response (section 6) messages use the generic message format of RFC 822 [9] for transferring entities (the payload of the message). Both types of message consist of a start-line, zero or more header fields (also known as "headers"), an empty line (i.e., a line with nothing preceding the CRLF) indicating the end of the header fields, and possibly a message-body. generic-message = start-line *(message-header CRLF) CRLF [ message-body ] start-line = Request-Line | Status-Line
從這段文字中,我們可以知道不論是request還是response,http message分三段,即start-line,message headers和message body.
那麼,在設計我的messge結構體時(對了,我是用C語言開發的),我會包含這三段內容.我並沒有把parser寫成獨立的單一函式,而是將他們分解成了一組能重複被呼叫的更小的函式.而從封裝的角度來說,我也沒有遵守儘量封裝資料結構體的原則.我的目的很簡單,那就是,簡單易懂,容易呼叫(這會不會被老師調打一頓:-).
還是看看定義的資料結構體吧.
typedef struct { int method; /* GET/POST... */ char *path; int ver_major; int ver_minor; int code; /* status code */ char *status; /* status text */ sllist_t *headers; int len_startline; int len_headers; unsigned char *body; unsigned char *body_zipped; unsigned char *body_s; /* point to the range start of the body */ size_t len_body; } httpmsg_t;
先不用看和body相關的部分,因為我會在後續如何寫Webserver中介紹相關的內容(涉及到body的壓縮,斷點續傳等等).
下面是相關的函式,
int msg_parse(sllist_t *headers, unsigned char **startline, unsigned char **body, size_t *len_body, const unsigned char *buf);
這個是對底層message進行解析的函式,再此之上,我用兩個函式封裝了它,分別用於解析http request和http response.
httpmsg_t *http_parse_req(const unsigned char *buf); httpmsg_t *http_parse_rep(const unsigned char *buf);
我寫這些底層函式的原則是,儘量利用上一步的結果,不做重複的計算,比如,同一字串的長度不要多次通過strlen計算,希望這樣應該能提高(微不足道^_^!)的效能吧.
在上面的httpmsg_t結構體中,我用了單連結串列來管理http headers,因為headers的數量不是很多,單連結串列輪詢反而速度更快.
至於上述函式如何實現,感興趣朋友可以請訪問我的github專案,連結https://github.com/grassroot72/Maestro2.
歡迎和我探討..
我會在第二篇內容裡介紹socket和epoll在Webserver中的應用...