httpserver--如何解析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協議規定的內容去解析報文,獲取報文包含的資訊。
由於基礎知識較薄弱,程式碼還有很多錯誤以及很多地方需要優化。如果有看到錯誤的地方或有其它建議望各位大俠不吝賜教。^_^
原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。