1. 程式人生 > >httpserver--如何解析HTTP請求報文

httpserver--如何解析HTTP請求報文

上一篇文章中,講述瞭如何編寫一個最簡單的server,但該程式只是接受到請求之後馬上返回響應,實在不能更簡單。在正常的開發中,應該根據不同的請求做出不同的響應。要做到上述的功能,首先要解析客戶端發來的請求報文。報文在不同的上下文情景下有不同的理解,本文所說的報文都是在HTTP上下文中描述的名詞。

HTTP報文是什麼

在HTTP程式中,報文就是HTTP用來搬運東西的包裹,也可以理解為程式之間傳遞資訊時傳送的資料塊。這些資料塊以一些文字形式的元資訊開頭,這些資訊描述了報文的內容和含義,後面跟著可選的資料部分。

報文的流動

HTTP使用屬於流入和流出來描述報文的傳遞方向。HTTP報文會像合水一樣流動。不管時請求報文還是響應報文,都會向下遊流動,所有報文的傳送者都在接受者的上游。下圖展示了報文向下遊流動的例子。

報文向下遊流動

報文的組成

報文由三個部分組成:

  • 對報文進行描述的起始行
  • 包含屬性的首部塊
  • 可選的、包含資料的主體部分

起始行和首部是由行分隔的ASCII文字。每行都以一個由兩個字元(回車符–ASCII碼13和換行符–ASCII碼10)組成的行終止序列結束。可以寫做CRLF

儘管規範說明應該用CRLF來表示行終止,但穩健的應用程式也應該接受單個換行作為行的終止。筆者僅支援以CRLF換行的解析,因為我覺得既然有了規範,那就需要遵循,遵循相同的協議的程式才能互相通訊。

實體是一個可選的資料塊。與起始行和首部不同的是,主體中可以包含主體或二進位制資料,也可以為空(比如僅僅GET一個頁面或檔案)。

下面來看看報文的語法的格式和規則。

報文的語法

請求報文的語法:

1234567891011 <code><method><request-url><version><headers><entity-body/></headers></version></request-url></method></code>

響應報文的語法:

1234567891011 <code><version><status-code><reason-phrase><headers><entity-body/></headers></reason-phrase></status-code></version></code>

method,方法

客戶端希望伺服器對資源執行的操作。比如GET、POST

request-URL,請求URL

請求資源,或者URL路徑元件的完整URL。

version,版本

報文所使用的HTTP版本。格式:HTTP/.。其中major(主要版本號)和minor(次要版本號)都是整數。

status-code,狀態碼

描述請求過程所發生的情況的數字。

reason-phrase,原因短語

數字狀態碼的文字描述版本。

headers,首部

每個首部包含一個名字,後面跟著一個冒號(:),然後是一個可選的空格,接著是一個值,最後是一個CRLF。可以有零個或多個首部。首部由一個CRLF結束,表示首部結束和實體主體開始。

entity-body,實體的主體部分

包含一個由任意資料組成的資料塊。可以沒有,此時是以一個CRLF結束。

請求行

請求報文的起始行稱為請求行。所有的HTTP報文都以一行起始行作為開始。請求行包含一個方法和一個請求URL以及HTTP的版本三個欄位。每個欄位都以空格分隔。

比如:GET / HTTP/1.1

請求方法為GET,請求URL為/,HTTP版本為HTTP/1.1。

響應行

響應報文的起始行稱為響應行。響應行包含HTTP版本、數字狀態碼以及描述操作狀態的文字形式的原因短語。三個欄位也是以空格分隔。

比如:HTTP/1.1 200 OK

HTTP版本為HTTP/1.1,數字狀態碼是200,原因短語是OK。表示請求成功。

首部

首部是是包含在請求和響應報文的一些附加資訊。本質上,他們只是一些鍵值對的列表。

比如:Content-Length: 19

表示返回內容長度為19。

實體的主體部分

簡單地說,這部分就是HTTP要傳輸的內容。

解析請求報文

瞭解了報文是如何組成和各部分代表的內容之後,就對如何解析請求報文心裡有數了。

核心程式碼

C++
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 /* 解析請求行 */intparse_start_line(intsockfd,char*recv_buf,req_pack*rp){char*p=recv_buf;char*ch=p;inti=0;enumparts{method,url,ver}req_part=method;char*method_str;char*url_str;char*ver_str;intk=0;if(*ch'Z'){return-1;}while(*ch!=CR){if(*ch!=BLANK){k++;}elseif(req_part==method){method_str=(char*)malloc(k*sizeof(char*));memset(method_str,0,sizeof(char*));strncpy(method_str,recv_buf,k);k=0;req_part=url;}elseif(req_part==url){url_str=(char*)malloc(k*sizeof(char*));memset(url_str,0,sizeof(char*));strncpy(url_str,recv_buf+strlen(method_str)+1,k);k=0;req_part=ver;}ch++;i++;}if(req_part==url){if(k!=0){url_str=(char*)malloc(k*sizeof(char));memset(url_str,0,sizeof(char));strncpy(url_str,recv_buf+strlen(method_str)+1,k);k=0;}else{return-1;}}if(k==0){ver_str=(char*)malloc(8*sizeof(char));memset(ver_str,0,sizeof(char));strcpy(ver_str,"HTTP/1.1");}else{ver_str=(char*)malloc(k*sizeof(char));memset(ver_str,0,sizeof(char));strncpy(ver_str,recv_buf+strlen(method_str)+strlen(url_str)+2,k);}rp->method=method_str;rp->url=url_str;rp->version=ver_str;return(i+2);}/* 解析首部欄位 */intparse_header(intsockfd,char*recv_buf,header headers[]){char*p=recv_buf;char*ch=p;inti=0;intk=0;intv=0;inth_i=0;boolis_newline=false;char*key_str;char*value_str;header*tmp_header=(header*)malloc(sizeof(header*));memset(tmp_header,0,sizeof(header));while(1){if(*ch==CR&&*(ch+1)==LF){break;}while(*ch!=COLON){ch++;i++;k++;}if(*ch==COLON){key_str=(char*)malloc(k*sizeof(char*));memset(key_str,0,sizeof(char*));strncpy(key_str,recv_buf+i-k,k);k=0;ch++;i++;}while(*ch!=CR){ch++;i++;v++;}if(*ch==CR){value_str=(char*)malloc(v*sizeof(char*));memset(value_str,0,sizeof(char*));strncpy(value_str,recv_buf+i-v,v);v=0;i++;ch++;}i++;ch++;headers[h_i].key=key_str;headers[h_i].value=value_str;h_i++;}return(i+2);}

解析思想

遍歷recv接受到的請求字串,檢查是否遇到回車符\r判斷一行資料。

對於起始行,檢查是否遇到空格分隔不同的欄位;對於首部,檢查是否遇到冒號分隔鍵值對的欄位值;對於實體的主體部分,則先判斷是否遇到CRLF字串,然後將剩餘內容全部作為實體的主體部分。

返回值是告知程式下一次遍歷的起始位置。

如果遇到非法請求行則返回400的響應。

總結

解析報文的過程就是遵循HTTP協議規定的內容去解析報文,獲取報文包含的資訊。

由於基礎知識較薄弱,程式碼還有很多錯誤以及很多地方需要優化。如果有看到錯誤的地方或有其它建議望各位大俠不吝賜教。^_^

原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。