1. 程式人生 > >開源HTTP解析器---http-parser和fast-http

開源HTTP解析器---http-parser和fast-http

轉載地址:https://www.cnblogs.com/arnoldlu/p/6497837.html

由於專案中遇到需要傳送http請求,然後再解析接收到的響應。大概在網上搜索了一下,有兩個比較不錯,分別是http-parserfast-http

http-parser是由C編寫的工具;fast-http是大部分移植自http-parser,用lisp語言編寫的,不太適合目前的專案。fast-http介紹文件《fast-http》。

有一篇文章《HTTP 協議解析庫:fast-http》介紹了fast-http,並且和http-parser進行了簡單比較,結論是fast-http更快,快一倍。

另一個關於HTTP請求的簡單介紹《為你詳細解讀HTTP請求頭的具體含意》,可以作為背景知識簡要閱讀。

關於http-parser有一篇不錯的實戰文件《【slighttpd】基於lighttpd架構的Server專案實戰(7)—http-parser》。

HTTP請求和響應格式

關於HTTP請求和響應的科普文件:《HTTP請求和響應格式》、《HTTP請求格式和http響應格式》、《#HTTP協議學習# (一)request 和response 解析》、《HTTP協議詳解》。

Request格式

先看看請求纖細的結構,request訊息分為3部分:Request line、Request header、Body。Request header和Body之間有個空行:

第一行中的Method表示請求方法,比如"POST","GET",  Path-to-resoure表示請求的資源, Http/version-number 表示HTTP協議的版本號。

當使用的是"GET" 方法的時候, body是為空的。

Request line

請求的第一行是“方法 URL 議 / 版本”: GET/sample.jsp HTTP/1.1
以上程式碼中“ GET ”代表請求方法,“ /sample.jsp ”表示 URI ,“ HTTP/1.1 代表協議和協議的版本。
根據 HTTP 標準, HTTP 請求可以使用多種請求方法。例如: HTTP1.1 目前支援 7 種請求方法: GET 、 POST 、 HEAD、 OPTIONS 、 PUT 、 DELETE 和 TARCE 。

GET 請求獲取由Request-URI所標識的資源。
POST Request-URI所標識的資源後附加新的資料。
HEAD 請求獲取由Request-URI所標識的資源的響應訊息報頭。
OPTIONS 請求查詢伺服器的效能,或查詢與資源相關的選項和需求。
PUT 請求伺服器儲存一個資源,並用Request-URI作為其標識。
DELETE 請求伺服器刪除由Request-URI所標識的資源。
TRACE 請求伺服器回送收到的請求資訊,主要用語測試或診斷。

 

在Internet應用中,最常用的方法是GET和POST。

URI完整地指定了要訪問的網路資源,通常只要給出相對於伺服器的根目錄的相對目錄即可,因此總是以“/”開頭,最後,協議版本聲明瞭通訊過程中使用HTTP的版本。

Request header

請求頭包含許多有關的客戶端環境和請求正文的有用資訊。例如,請求頭可以宣告瀏覽器所用的語言,請求正文的長度等。
Accept:image/gif.image/jpeg.*/*
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible:MSIE5.01:Windows NT5.0)
Accept-Encoding:gzip,deflate.

Request body

請求頭和請求正文之間是一個空行,這個行非常重要,它表示請求頭已經結束,接下來的是請求正文。請求正文中可以包含客戶提交的查詢字串資訊:
username=jinqiao&password=1234
在以上的例子的HTTP請求中,請求的正文只有一行內容。當然,在實際應用中,HTTP請求正文可以包含更多的內容。

HTTP請求方法我這裡只討論GET方法與POST方法
l   GET方法
GET方法是預設的HTTP請求方法,我們日常用GET方法來提交表單資料,然而用GET方法提交的表單資料只經過了簡單的編碼,同時它將作為URL的一部分向Web伺服器傳送,因此,如果使用GET方法來提交表單資料就存在著安全隱患上。例如:Http://127.0.0.1/login.jsp?Name=zhangshi&Age=30&Submit=%cc%E+%BD%BB
從上面的URL請求中,很容易就可以辯認出表單提交的內容。(?之後的內容)另外由於GET方法提交的資料是作為URL請求的一部分所以提交的資料量不能太大
l  POST方法
POST方法是GET方法的一個替代方法,它主要是向Web伺服器提交表單資料,尤其是大批量的資料。POST方法克服了GET方法的一些缺點。通過POST方法提交表單資料時,資料不是作為URL請求的一部分而是作為標準資料傳送給Web伺服器,這就克服了GET方法中的資訊無法保密和資料量太小的缺點。因此,出於安全的考慮以及對使用者隱私的尊重,通常表單提交時採用POST方法。
  從程式設計的角度來講,如果使用者通過GET方法提交資料,則資料存放在QUERY_STRING環境變數中,而POST方法提交的資料則可以從標準輸入流中獲取。

Response格式

Response訊息的結構和Request結構基本一樣。同樣也為三部分:Response Line、Response header、Body。Response header和Body之間也有個空行

Response line

HTTP/version-number表示HTTP協議的版本號,status-code和message請看下面詳細解釋。

Response 訊息中的第一行叫做狀態行,由HTTP協議版本號, 狀態碼, 狀態訊息 三部分組成。

狀態行由協議版本、數字形式的狀態程式碼、及相應的狀態描述,各元素之間以空格分隔。

狀態碼用來告訴HTTP客戶端,HTTP伺服器是否產生了預期的Response.

HTTP/1.1中定義了5類狀態碼, 狀態碼由三位數字組成,第一個數字定義了響應的類別

1XX  提示資訊 - 表示請求已被成功接收,繼續處理

2XX  成功 - 表示請求已被成功接收,理解,接受

3XX  重定向 - 要完成請求必須進行更進一步的處理

4XX  客戶端錯誤 -  請求有語法錯誤或請求無法實現

5XX  伺服器端錯誤 -   伺服器未能實現合法的請求

200 OK:最常見的就是成功響應狀態碼200了, 這表明該請求被成功地完成,所請求的資源傳送回客戶端。

302 Found:重定向,新的URL會在response 中的Location中返回,瀏覽器將會自動使用新的URL發出新的Request。

304 Not Modified:代表上次的文件已經被快取了, 還可以繼續使用。

400 Bad Request:  客戶端請求與語法錯誤,不能被伺服器所理解。

401         Unauthonzed   請求未經授權。這個狀態程式碼必須和WWW-Authenticate報頭域一起使用

403 Forbidden: 伺服器收到請求,但是拒絕提供服務。伺服器通常會在響應正文中給出不提供服務的原因。

404 Not Found:請求的資源不存在,例如,輸入了錯誤的URL。

500 Internal Server Error: 伺服器發生不可預期的錯誤,導致無法完成客戶端的請求。

503 Server Unavailable: 伺服器當前不能夠處理客戶端的請求,在一段時間之後,伺服器可能會恢復正常。

Response header

Location:Location響應報頭域用於重定向接受者到一個新的位置。例如:客戶端所請求的頁面已不存在原先的位置,為了讓客戶端重定向到這個頁面新的位置,伺服器端可以發回Location響應報頭後使用重定向語句,讓客戶端去訪問新的域名所對應的伺服器上的資源。當我們在JSP中使用重定向語句的時候,伺服器端向客戶端發回的響應報頭中,就會有Location響應報頭域。

Server:Server響應報頭域包含了伺服器用來處理請求的軟體資訊。它和User-Agent請求報頭域是相對應的,前者傳送伺服器端軟體的資訊,後者傳送客戶端軟體(瀏覽器)和作業系統的資訊。下面是Server響應報頭域的一個例子:Server: Apache-Coyote/1.1


WWW-Authenticate:WWW-Authenticate響應報頭域必須被包含在401(未授權的)響應訊息中,這個報頭域和前面講到的Authorization請求報頭域是相關的,當客戶端收到401響應訊息,就要決定是否請求伺服器對其進行驗證。如果要求伺服器對其進行驗證,就可以傳送一個包含了 Authorization報頭域的請求,下面是WWW-Authenticate響應報頭域的一個例子:WWW-Authenticate: Basic realm="Basic Auth Test!"從這個響應報頭域,可以知道伺服器端對我們所請求的資源採用的是基本驗證機制。


Content-Encoding:Content-Encoding實體報頭域被使用作媒體型別的修飾符,它的值指示了已經被應用到實體正文的附加內容編碼,因而要獲得Content- Type報頭域中所引用的媒體型別,必須採用相應的解碼機制。Content-Encoding主要用語記錄文件的壓縮方法,下面是它的一個例子: Content-Encoding: gzip。如果一個實體正文采用了編碼方式儲存,在使用之前就必須進行解碼。


Content-Language:Content-Language實體報頭域描述了資源所用的自然語言。Content-Language允許使用者遵照自身的首選語言來識別和區分實體。如果這個實體內容僅僅打算提供給丹麥的閱讀者,那麼可以按照如下的方式設定這個實體報頭域:Content-Language: da。如果沒有指定Content-Language報頭域,那麼實體內容將提供給所以語言的閱讀者。


Content-Length:  Content-Length實體報頭域用於指明正文的長度,以位元組方式儲存的十進位制數字來表示,也就是一個數字字元佔一個位元組,用其對應的ASCII碼儲存傳輸。
要注意的是:這個長度僅僅是表示實體正文的長度,沒有包括實體報頭的長度。


Content-Type實體報頭域用語指明發送給接收者的實體正文的媒體型別。例如:Content-Type: text/html;charset=ISO-8859-1

PS:這裡主要關注Content-Length和Content-Type兩種型別,用於提取Body實體。


Last-Modified實體報頭域用於指示資源最後的修改日期及時間。


Expires實體報頭域給出響應過期的日期和時間。通常,代理伺服器或瀏覽器會快取一些頁面。當用戶再次訪問這些頁面時,直接從快取中載入並顯示給使用者,這樣縮短了響應的時間,減少伺服器的負載。為了讓代理伺服器或瀏覽器在一段時間後更新頁面,我們可以使用Expires實體報頭域指定頁面過期的時間。當用戶又一次訪問頁面時,如果Expires報頭域給出的日期和時間比Date普通報頭域給出的日期和時間要早(或相同),那麼代理伺服器或瀏覽器就不會再使用快取的頁面而是從伺服器上請求更新的頁面。不過要注意,即使頁面過期了,也並不意味著伺服器上的原始資源在此時間之前或之後發生了改變。

http-parser

下載程式碼:

git clone https://github.com/arnoldlu/http-parser.git

編譯

 

編譯安裝

make
make parsertrace
make url_parser
sudo make install  (會將lib安裝到/usr/lib/libhttp_parser.so.2.7.1和/usr/lib/libhttp_parser.so,在使用的時候編譯中增加-lhttp_parser)

 

一個例項測試

make http_parser.o
gcc -Wall -Wextra -Werror -Wno-error=unused-but-set-variable -O3 http_parser.o demo.c -o demo
./demo

程式碼如下:

#include "http_parser.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>

static http_parser *parser;

int on_message_begin(http_parser* _) {
  (void)_;
  printf("\n***MESSAGE BEGIN***\n\n");
  return 0;
}

int on_headers_complete(http_parser* _) {
  (void)_;
  printf("\n***HEADERS COMPLETE***\n\n");
  return 0;
}

int on_message_complete(http_parser* _) {
  (void)_;
  printf("\n***MESSAGE COMPLETE***\n\n");
  return 0;
}

int on_url(http_parser* _, const char* at, size_t length) {
  (void)_;
  printf("Url: %.*s\n", (int)length, at);
  return 0;
}

int on_header_field(http_parser* _, const char* at, size_t length) {
  (void)_;
  printf("Header field: %.*s\n", (int)length, at);
  return 0;
}

int on_header_value(http_parser* _, const char* at, size_t length) {
  (void)_;
  printf("Header value: %.*s\n", (int)length, at);
  return 0;
}

int on_body(http_parser* _, const char* at, size_t length) {
  (void)_;
  printf("Body: %.*s\n", (int)length, at);
  return 0;
}

static http_parser_settings settings_null =  http_parser的回撥函式,需要獲取HEADER後者BODY資訊,可以在這裡面處理。
  {.on_message_begin = on_message_begin
  ,.on_header_field = on_header_field
  ,.on_header_value = on_header_value
  ,.on_url = on_url
  ,.on_status = 0
  ,.on_body = on_body
  ,.on_headers_complete = on_headers_complete
  ,.on_message_complete = on_message_complete
  };

int
main (void)
{
  const char *buf;
  int i;
  float start, end;
  size_t parsed;

  parser = malloc(sizeof(http_parser));  分配一個http_parser

  buf = "GET http://admin.omsg.cn/uploadpic/2016121034000012.png HTTP/1.1\r\nHost: admin.omsg.cn\r\nAccept: */*\r\nConnection: Keep-Alive\r\n\r\n";

  start = (float)clock()/CLOCKS_PER_SEC;
  for (i = 0; i < 1; i++) {
    http_parser_init(parser, HTTP_REQUEST);  初始化parser為Request型別
    parsed = http_parser_execute(parser, &settings_null, buf, strlen(buf));  執行解析過程
  }
  end = (float)clock()/CLOCKS_PER_SEC;


  buf="HTTP/1.1 200 OK\r\n"
         "Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n"
         "Server: Apache\r\n"
         "X-Powered-By: Servlet/2.5 JSP/2.1\r\n"
         "Content-Type: text/xml; charset=utf-8\r\n"
         "Connection: close\r\n"
         "\r\n"
         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
         "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
         "  <SOAP-ENV:Body>\n"
         "    <SOAP-ENV:Fault>\n"
         "       <faultcode>SOAP-ENV:Client</faultcode>\n"
         "       <faultstring>Client Error</faultstring>\n"
         "    </SOAP-ENV:Fault>\n"
         "  </SOAP-ENV:Body>\n"
         "</SOAP-ENV:Envelope>";

  http_parser_init(parser, HTTP_RESPONSE);  初始化parser為Response型別
  parsed = http_parser_execute(parser, &settings_null, buf, strlen(buf));  執行解析過程

  free(parser);
  parser = NULL;

  printf("Elapsed %f seconds.\n", (end - start));

  return 0;
}

結果如下:

***MESSAGE BEGIN***

Url: http://admin.omsg.cn/uploadpic/2016121034000012.png
Header field: Host
Header value: admin.omsg.cn
Header field: Accept
Header value: */*
Header field: Connection
Header value: Keep-Alive

***HEADERS COMPLETE***


***MESSAGE COMPLETE***


***MESSAGE BEGIN***

Header field: Date
Header value: Tue, 04 Aug 2009 07:59:32 GMT
Header field: Server
Header value: Apache
Header field: X-Powered-By
Header value: Servlet/2.5 JSP/2.1
Header field: Content-Type
Found Content-Type
Header value: text/xml; charset=utf-8
http_png.type = text/xml; charset=utf-8
Header field: Connection
Header value: close

***HEADERS COMPLETE***

Elapsed 0.000091 seconds.