- 微信公眾號:鄭爾多斯
- 關注「鄭爾多斯」公眾號 ,回復「領取資源」,獲取IT資源500G干貨。
升職加薪、當上總經理、出任CEO、迎娶白富美、走上人生巔峰!想想還有點小激動- 關注可了解更多的
Nginx
知識。任何問題或建議,請公眾號留言;
關注公眾號,有趣有內涵的文章第一時間送達!
狀態機解析請求行
HTTP請求行格式為: [請求方法][空格][URL][空格][協議版本][回車符][換行符]
一般來說,HTTP請求行不會很長,但是依舊有可能在單次系統調用中無法讀取完整的請求行,所以可能需要多次調用ngx_http_process_request_line才能讀取和處理完請求行;也正因如此,Nginx在請求行的解析過程中對狀態進行了記錄。我覺得,能否在讀取到/r/n時才對請求行進行解析呢?這種做法好處是比較簡單,但是會有性能問題,因為在讀取到非法的請求行內容時,就不需要在讀取之后的內容呢!而Nginx是以追求高性能著稱的,顯然一邊讀取一遍解析會更優。
閑話少說,直接上代碼:
第一遍只關注happy path,關注流程的實現方式。等到整個流程走通了,第二遍或者第三遍學習的時候,再抓住一個點學習具體的函數,比如解析request line的函數等。避免第一次就陷入到具體函數的內部。
Nginx的HTTP模塊中使用ngx_http_parse_request_line函數來對讀取的請求行進行解析,HTTP請求行的格式不是很復雜,但是要注意HTTP 0.9與1.0、1.1之間的區別;我覺得只要關注http1.1就行了,抓住主干功能。
/*?http/ngx_http_parse.c?*/
/*?解析HTTP請求行
?*?param?r:?待處理的HTTP請求
?*???????b:?存放請求行內容的緩沖區
?*?return?:?成功解析完整的請求行時返回NGX_OK;
?*??????????成功解析了部分請求行時返回NGX_AGAIN;
?*??????????否則返回其他
?*/
ngx_int_t?ngx_http_parse_request_line(ngx_http_request_t?*r,?ngx_buf_t?*b)
{
????//?HTTP?0.9????請求行格式:?[請求方法][空格..空格][URL](空格..空格)(回車符)[換行符]
????//?HTTP?>=?1.0?請求行格式:?[請求方法][空格..空格][URL][空格..空格][協議版本][回車符][換行符]
????u_char??ch,?*p,?*m;
????enum?{
????????sw_start?=?0,?????????????//?初始狀態
????????sw_method,????????????????//?解析請求方法
????????sw_space_after_method,????//?解析請求方法后緊跟的一個空格
????????sw_spaces_before_uri,?????//?解析URL前可能存在的多余空格
????????sw_schema,????????????????//?解析schema(http/https)
????????sw_schema_slash,??????????//?解析<schema>:后緊跟的一個/
????????sw_schema_slash_slash,????//?解析<schema>:/后緊跟的一個/
????????sw_host,??????????????????//?解析<schema>://后緊跟的主機(域名/IP)
????????sw_port,??????????????????//?解析<schema>://<host>:后緊跟的端口
????????sw_after_slash_in_uri,????//?解析URL路徑中/后的內容
????????sw_check_uri,?????????????//??
????????sw_uri,???????????????????//??
????????sw_http_09,???????????????//?解析URL后緊跟空格后的內容
????????sw_http_H,????????????????//?解析協議版本的第二個字符T
????????sw_http_HT,???????????????//?解析協議版本的第三個字符T
????????sw_http_HTT,??????????????//?解析協議版本的第四個字符P
????????sw_http_HTTP,?????????????//?解析協議版本的第五個字符/
????????sw_first_major_digit,?????//?解析協議版本的主版本號的第一個數字
????????sw_major_digit,???????????//?解析協議版本的主版本號第一個數字后的數字或者.
????????sw_first_minor_digit,?????//?解析協議版本的次版本號的第一個數字
????????sw_minor_digit,???????????//?解析協議版本的次版本號第一個數字后的數字
????????sw_almost_done,???????????//?解析結束的\n
????????sw_done???????????????????//?解析完成
????}?state;??????????????????????//?枚舉變量:?HTTP請求行解析狀態
????//?獲取請求r的當前狀態state
????state?=?r->state;
????//?獲取緩沖區b的有效內容起始地址p
????p?=?b->pos;
????while?(p?<?b->last?&&?state?<?sw_done)?{
????????//?p小于b->last時,?表明緩沖區內的有效內容不為空;
????????//?state小于sw_done,?表明未解析完成
????????//?ch指向緩沖區有效內容的第一個字符,?p后移一位
????????ch?=?*p++;
????????switch?(state)?{
????????/*?HTTP?methods:?GET,?HEAD,?POST?*/
????????case?sw_start:
????????????//?當前狀態為sw_start即起始狀態
????????????//?置r->request_start為p-1,?也就是當前字符的位置
????????????r->request_start?=?p?-?1;
????????????if?(ch?==?CR?||?ch?==?LF)?{
????????????????//?如果當前字符為\r或者\n
????????????????//?跳過,這里的意思是如果遇到了回車或者換行,那么這些字符仍然是請求行的一部分
????????????????break;
//?這里還有一層意思,每當遇到?CR或者LF的時候,break了,解析下一個字符的時候又會
//?執行?r->request_start?=?p?-?1,?其實就相當于過濾掉了請求行最前面的?CR和LF
????????????}
????????????if?(ch?<?'A'?||?ch?>?'Z')?{
????????????????//?如果當前字符不是大寫字母
????????????????//?請求方法必須是由大寫字母組成的,?所以返回NGX_HTTP_PARSE_INVALID_METHOD,
????????????????//?從字面上可以看出,?這個返回值表示無效的請求方法
????????????????return?NGX_HTTP_PARSE_INVALID_METHOD;
????????????}
????????????//?置state為sw_method,?表示解析請求方法
????????????state?=?sw_method;
????????????break;
????????case?sw_method:
????????????//?當前狀態為解析請求方法?
????????????if?(ch?==?'?')?{
????????????????//?如果當前字符為空格
????????????????//?說明遇到了請求方法后面的空格了,?p-2即為請求方法的最后一個字符
????????????????//?置r->method_end為p-1,?記錄請求方法的結束位置
????????????????r->method_end?=?p?-?1;
????????????????//?r->request_start此時指向的是請求方法的第一個字符
????????????????m?=?r->request_start;
????????????????if?(r->method_end?-?m?==?3)?{
????????????????????//?如果請求方法子字符串的長度為3
????????????????????if?(m[0]?==?'G'?&&?m[1]?==?'E'?&&?m[2]?==?'T')?{
????????????????????????//?如果請求方法子字符串為GET
????????????????????????//?置r->method為NGX_HTTP_GET
????????????????????????r->method?=?NGX_HTTP_GET;
????????????????????}
????????????????}?else?if?(r->method_end?-?m?==?4)?{
????????????????????//?如果請求方法子字符串的長度為4
????????????????????if?(m[0]?==?'P'?&&?m[1]?==?'O'
????????????????????????&&?m[2]?==?'T'?&&?m[3]?==?'T')
????????????????????{
????????????????????????//?如果請求方法子字符串為POST
????????????????????????//?置r->method為NGX_HTTP_POST
????????????????????????r->method?=?NGX_HTTP_POST;
????????????????????}?else?if?(m[0]?==?'H'?&&?m[1]?==?'E'
???????????????????????????????&&?m[2]?==?'A'?&&?m[3]?==?'D')
????????????????????{
????????????????????????//?如果請求方法子字符串為HEAD
????????????????????????//?置r->method為NGX_HTTP_HEAD
????????????????????????r->method?=?NGX_HTTP_HEAD;
????????????????????}
????????????????}
????????????????//?解析完請求方法,?置state為sw_spaces_before_uri,?表示解析URL前面的空格
????????????????//?因為此處已經解析到一個請求方法后的空格,?所以跳過狀態sw_space_after_method,
????????????????state?=?sw_spaces_before_uri;
????????????????break;
????????????}
????????????if?(ch?<?'A'?||?ch?>?'Z')?{
????????????????//?如果當前字符不是大寫字母
????????????????//?返回NGX_HTTP_PARSE_INVALID_METHOD
????????????????return?NGX_HTTP_PARSE_INVALID_METHOD;
????????????}
????????????break;
????????case?sw_space_after_method:
????????????//?當前狀態為解析請求方法后緊跟的一個空格
????????????switch?(ch)?{
????????????case?'?':
????????????????//?如果當前字符為空格
????????????????//?置state為sw_spaces_before_uri,?URL前面可能還有空格
????????????????state?=?sw_spaces_before_uri;
????????????????break;
????????????default:
????????????????//?如果當前字符為非空格的字符
????????????????//?請求方法和URL之間至少需要一個空格,
????????????????//?返回NGX_HTTP_PARSE_INVALID_METHOD
????????????????return?NGX_HTTP_PARSE_INVALID_METHOD;
????????????}
????????????break;
????????case?sw_spaces_before_uri:
????????????//?當前狀態為解析URL前可能存在的多余空格
????????????switch?(ch)?{
????????????case?'/':
????????????????//?如果當前字符為/,?說明遇到URL的第一個字符
????????????????//?置r->uri_start為p-1,?記錄URL的起始位置
????????????????r->uri_start?=?p?-?1;
????????????????//?置state為sw_after_slash_in_uri,?表示解析URL路徑中/后的內容
????????????????state?=?sw_after_slash_in_uri;
????????????????break;
????????????case?'?':
????????????????//?如果當前字符為空格
????????????????//?直接跳過
????????????????break;
????????????default:
????????????????if?((ch?>=?'A'?&&?ch?<=?'Z')?||?(ch?>=?'a'?&&?ch?<=?'z'))?{
????????????????????//?如果當前字符為大小寫字母,?說明遇到schema(http/https)的第一個字符了
????????????????????//?置r->schema_start為p-1,?記錄schema的起始位置
????????????????????r->schema_start?=?p?-?1;
????????????????????//?置state為sw_schema,?表示解析schema
????????????????????state?=?sw_schema;
????????????????????break;
????????????????}
????????????????//?當前字符為其他字符,?表示請求有誤,?返回NGX_HTTP_PARSE_INVALID_REQUEST,
????????????????//?即無效請求
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????break;
????????case?sw_schema:
????????????//?當前狀態為解析schema
????????????switch?(ch)?{
????????????case?':':
????????????????//?如果當前字符為:,?說明遇到schema的后一個字符了
????????????????//?置r->schema_end為p-1,?記錄schema的結束位置
????????????????r->schema_end?=?p?-?1;
????????????????//?置state為sw_schema_slash,?表示解析<schema>:后緊跟的一個/
????????????????state?=?sw_schema_slash;
????????????????break;
????????????default:
????????????????if?((ch?>=?'A'?&&?ch?<=?'Z')?||?(ch?>=?'a'?&&?ch?<=?'z'))?{
????????????????????//?如果當前字符是大小寫字符,?說明是我們想要的
????????????????????//?直接跳過
????????????????????break;
????????????????}
????????????????//?當前字符為其他字符,?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????break;
????????case?sw_schema_slash:
????????????//?當前狀態為解析<schema>:后緊跟的一個/
????????????switch?(ch)?{
????????????case?'/':
????????????????//?如果當前字符正是/
????????????????//?置state為sw_schema_slash_slash,?解析緊跟的一個/
????????????????state?=?sw_schema_slash_slash;
????????????????break;
????????????default:
????????????????//?當前字符不為/,?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????break;
????????case?sw_schema_slash_slash:
????????????//?當前狀態為解析<schema>:/后緊跟的一個/
????????????switch?(ch)?{
????????????case?'/':
????????????????//?如果當前字符正是/
????????????????//?置r->host_start為p-1,?記錄URL中主機的起始位置
????????????????r->host_start?=?p?-?1;
????????????????//?置state為sw_host,?表示解析<schema>://后緊跟的主機
????????????????state?=?sw_host;
????????????????break;
????????????default:
????????????????//?當前字符不為/,?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????break;
????????case?sw_host:
????????????//?當前狀態為解析<schema>://后緊跟的主機
????????????switch?(ch)?{
????????????case?':':
????????????????//?如果當前字符為:,?說明遇到主機后緊跟的一個:了
????????????????//?置r->host_end為p-1,?記錄主機的結束位置
????????????????r->host_end?=?p?-?1;
????????????????//?置state為sw_port,?因為遇到主機后緊跟的:了,?那么此:后需要跟著端口號
????????????????state?=?sw_port;
????????????????break;
????????????case?'/':
????????????????//?如果當前字符是/,?因為主機后的:<port>不是必須的,
????????????????//?說明遇到主機后緊跟的一個/了
????????????????//?置r->host_end為p-1,?記錄主機的結束位置
????????????????r->host_end?=?p?-?1;
????????????????//?置r->uri_start為p-1,?記錄URL中路徑的起始地址
????????????????r->uri_start?=?p?-?1;
????????????????//?置state為sw_after_slash_in_uri,?表示解析URL路徑中/后的內容
????????????????state?=?sw_after_slash_in_uri;
????????????????break;
????????????default:
????????????????if?((ch?>=?'A'?&&?ch?<=?'Z')?||?(ch?>=?'a'?&&?ch?<=?'z')
????????????????????||?(ch?>=?'0'?&&?ch?<=?'9')?||?ch?==?'.'?||?ch?==?'-')
????????????????{
????????????????????//?如果當前字符為大小寫字母、數字、.、-,?說明是主機(域名/IP)的有效字符????
????????????????????//?直接跳過
????????????????????break;
????????????????}
????????????????//?當前字符為其他字符,?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????break;
????????case?sw_port:
????????????//?當前狀態為解析<schema>://<host>:后緊跟的端口
????????????switch?(ch)?{
????????????case?'/':
????????????????//?如果當前字符為/,?說明遇到端口后緊跟的一個/了
????????????????//?置r->port_end為p-1,?記錄端口的結束位置
????????????????r->port_end?=?p?-?1;
????????????????//?置r->uri_start為p-1,?記錄URL中路徑的起始位置
????????????????r->uri_start?=?p?-?1;
????????????????//?置state為sw_after_slash_in_uri,?表示解析URL路徑中/后的內容
????????????????state?=?sw_after_slash_in_uri;
????????????????break;
????????????default:
????????????????if?(ch?<?'0'?&&?ch?>?'9')?{
????????????????????//?如果當前字符不為數字,?端口必須由數字組成,?說明是非法字符
????????????????????//?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????????}
????????????????break;
????????????}
????????????break;
????????case?sw_after_slash_in_uri:
????????????//?當前狀態為解析URL路徑中/后的內容
????????????switch?(ch)?{
????????????case?CR:
????????????????//?如果當前字符為\r,?說明可能是HTTP?0.9
????????????????//?置r->uri_end為p-1,?記錄URL中路徑的結束位置
????????????????r->uri_end?=?p?-?1;
????????????????//?置r->http_minor為9
????????????????r->http_minor?=?9;
????????????????//?置state為sw_almost_done,?表示解析結束的\n
????????????????state?=?sw_almost_done;
????????????????break;
????????????case?LF:
????????????????//?如果當前字符為\n,?說明可能是HTTP?0.9
????????????????//?置r->uri_end為p-1,?記錄URL中路徑的結束位置
????????????????r->uri_end?=?p?-?1;
????????????????//?置r->http_minor為9
????????????????r->http_minor?=?9;
????????????????//?置state為sw_done,?表示解析完成
????????????????state?=?sw_done;
????????????????break;
????????????case?'?':
????????????????//?如果當前字符為空格,?表示遇到URL(或者路徑)后緊跟的一個空格
????????????????//?置r->uri_end為p-1,?記錄URL中路徑的結束位置
????????????????r->uri_end?=?p?-?1;
????????????????//?置state為sw_http_09,?表示解析URL后緊跟空格后的內容
????????????????state?=?sw_http_09;
????????????????break;
????????????case?'.':
????????????case?'%':
????????????????//?如果當前字符為.或者%,?說明是復雜的URL
????????????????//?置r->complex_uri為1
????????????????r->complex_uri?=?1;
????????????????//?置state為sw_uri
????????????????state?=?sw_uri;
????????????????break;
????????????case?'/':
????????????????//?如果當前字符為/
????????????????//?置r->complex_uri為1
????????????????//?因為仍要解析/后的內容,?因此state不變
????????????????r->complex_uri?=?1;
????????????????break;
????????????case?'?':
????????????????//?如果當前字符為?,?說明遇到了URL中的參數
????????????????//?置r->args_start為p,?記錄參數的起始位置
????????????????r->args_start?=?p;
????????????????//?置state為sw_uri
????????????????state?=?sw_uri;
????????????????break;
????????????default:
????????????????//?如果當前字符為其他字符
????????????????//?置state為sw_check_uri
????????????????state?=?sw_check_uri;
????????????????break;
????????????}
????????????break;
????????case?sw_check_uri:
????????????//?當前狀態為sw_check_uri
????????????switch?(ch)?{
????????????case?CR:
????????????????//?如果當前字符為\r,?說明遇到了URL后緊跟的\r
????????????????//?置r->uri_end為p-1,?記錄URL的結束位置
????????????????r->uri_end?=?p?-?1;
????????????????//?顯然是HTTP?0.9,?置r->http_minor為9
????????????????r->http_minor?=?9;
????????????????//?置state為sw_almost_done,?表示解析結束的\n
????????????????state?=?sw_almost_done;
????????????????break;
????????????case?LF:
????????????????//?如果當前字符為\n,?說明遇到了URL后緊跟的\n
????????????????//?置r->uri_end為p-1,?記錄URL的結束位置
????????????????r->uri_end?=?p?-?1;
????????????????//?顯然是HTTP?0.9,?置r->http_minor為9
????????????????r->http_minor?=?9;
????????????????//?置state為sw_done,?表示解析完成
????????????????state?=?sw_done;
????????????????break;
????????????case?'?':
????????????????//?如果當前字符為空格,?表明遇到URL后緊跟的一個空格
????????????????//?置r->uri_end為p-1,?記錄URL的結束位置
????????????????r->uri_end?=?p?-?1;
????????????????//?置state為sw_http_09,?表示解析URL后緊跟空格后的內容
????????????????state?=?sw_http_09;
????????????????break;
????????????case?'.':
????????????????//?如果當前字符為.,?表明遇到擴展名
????????????????//?置r->uri_ext為p,?記錄擴展名的起始位置
????????????????r->uri_ext?=?p;
????????????????break;
????????????case?'/':
????????????????//?如果當前字符為/?
????????????????//?那么之前記錄的"擴展名"其實不是真的擴展名,?置r->uri_ext為空
????????????????r->uri_ext?=?NULL;
????????????????//?置state為sw_after_slash_in_uri,?因為仍在解析URL且遇到了/
????????????????state?=?sw_after_slash_in_uri;
????????????????break;
????????????case?'%':
????????????????//?如果當前字符為%,?表明是復雜的URL
????????????????//?置r->complex_uri為1
????????????????r->complex_uri?=?1;
????????????????//?置state為sw_uri
????????????????state?=?sw_uri;
????????????????break;
????????????case?'?':
????????????????//?如果當前字符為?,?表明遇到了參數
????????????????//?置r->args_start為p,?記錄參數的起始位置
????????????????r->args_start?=?p;
????????????????//?置state為sw_uri
????????????????state?=?sw_uri;
????????????????break;
????????????}
????????????break;
????????case?sw_uri:
????????????//?當前狀態為sw_uri
????????????switch?(ch)?{
????????????case?CR:
????????????????//?如果當前字符為\r,?說明遇到了URL后緊跟的\r
????????????????//?置r->uri_end為p-1,?記錄URL的結束位置
????????????????r->uri_end?=?p?-?1;
????????????????//?顯然是HTTP?0.9,?置r->http_minor為9
????????????????r->http_minor?=?9;
????????????????//?置state為sw_almost_done,?表示解析結束的\n
????????????????state?=?sw_almost_done;
????????????????break;
????????????case?LF:
????????????????//?如果當前字符為\n,?說明遇到了URL后緊跟的\n
????????????????//?置r->uri_end為p-1,?記錄URL的結束位置
????????????????r->uri_end?=?p?-?1;
????????????????//?顯然是HTTP?0.9,?置r->http_minor為9
????????????????r->http_minor?=?9;
????????????????//?置state為sw_done,?表示解析完成
????????????????state?=?sw_done;
????????????????break;
????????????case?'?':
????????????????//?如果當前字符為空格,?表明遇到URL后緊跟的一個空格
????????????????//?置r->uri_end為p-1,?記錄URL的結束位置
????????????????r->uri_end?=?p?-?1;
????????????????//?置state為sw_http_09,?表示解析URL后緊跟空格后的內容
????????????????state?=?sw_http_09;
????????????????break;
????????????}
????????????break;
????????case?sw_http_09:
????????????//?當前狀態為解析URL后緊跟空格后的內容
????????????switch?(ch)?{
????????????case?'?':
????????????????//?如果當前字符為空格,?直接跳過
????????????????break;
????????????case?CR:
????????????????//?如果當前字符為\r,?說明是HTTP?0.9
????????????????//?置r->http_minor為9
????????????????r->http_minor?=?9;
????????????????//?置state為sw_almost_done,?表示解析結束的\n
????????????????state?=?sw_almost_done;
????????????????break;
????????????case?LF:
????????????????//?如果當前字符為\n,?說明是HTTP?0.9
????????????????//?置r->http_minor為9
????????????????r->http_minor?=?9;
????????????????//?置state為sw_done,?表示解析完成
????????????????state?=?sw_done;
????????????????break;
????????????case?'H':
????????????????//?如果當前字符是H,?說明是HTTP?>=?1.0
????????????????//?置state為sw_http_H,?表示解析協議版本的第二個字符T
????????????????state?=?sw_http_H;
????????????????break;
????????????default:
????????????????//?當前字符為其他字符,?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????break;
????????case?sw_http_H:
????????????//?當前狀態為解析協議版本的第二個字符T
????????????switch?(ch)?{
????????????case?'T':
????????????????//?如果當前字符正是T
????????????????//?置state為sw_http_HT
????????????????state?=?sw_http_HT;
????????????????break;
????????????default:
????????????????//?當前字符不為T,?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????break;
????????case?sw_http_HT:
????????????//?當前狀態為解析協議版本的第三個字符T
????????????switch?(ch)?{
????????????case?'T':
????????????????//?如果當前字符正是
????????????????//?置state為sw_http_HTTP
????????????????state?=?sw_http_HTT;
????????????????break;
????????????default:
????????????????//?當前字符不為T,?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????break;
????????case?sw_http_HTT:
????????????//?當前狀態為解析協議版本的第四個字符P
????????????switch?(ch)?{
????????????case?'P':
????????????????//?如果當前字符正是P??
????????????????//?置state為sw_http_HTTP
????????????????state?=?sw_http_HTTP;
????????????????break;
????????????default:
????????????????//?當前字符不為P,?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????break;
????????case?sw_http_HTTP:
????????????//?當前狀態為解析協議版本的第五個字符/
????????????switch?(ch)?{
????????????case?'/':
????????????????//?如果當前字符正是/
????????????????//?置state為sw_first_major_digit
????????????????state?=?sw_first_major_digit;
????????????????break;
????????????default:
????????????????//?當前字符不為/,?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????break;
????????case?sw_first_major_digit:
????????????//?當前狀態為解析協議版本的主版本號的第一個數字
????????????if?(ch?<?'1'?||?ch?>?'9')?{
????????????????//?如果當前字符不為數字1-9,?說明是無效字符;
????????????????//?協議版本應該是在HTTP?1.0后才有的,?因此主版本號應該不小于1;
????????????????//?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????//?置r->http_major為ch-'0',?記錄主版本號
????????????r->http_major?=?ch?-?'0';
????????????//?置state為sw_major_digit,?表示解析協議版本的主版本號第一個數字后的數字或者.
????????????state?=?sw_major_digit;
????????????break;
????????case?sw_major_digit:
????????????//?當前狀態為解析協議版本的主版本號第一個數字后的數字或者.
????????????if?(ch?==?'.')?{
????????????????//?如果當前字符為.,?說明遇到主版本號后緊跟的.了
????????????????//?置state為sw_first_minor_digit,?表示解析次版本號的第一個數字
????????????????state?=?sw_first_minor_digit;
????????????????break;
????????????}
????????????if?(ch?<?'0'?||?ch?>?'9')?{
????????????????//?如果當前字符不為數字,?說明是非法字符,?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????//?更新主版本號r->http_major
????????????r->http_major?=?r->http_major?*?10?+?ch?-?'0';
????????????break;
????????case?sw_first_minor_digit:
????????????//?當前狀態為解析協議版本的次版本號的第一個數字
????????????if?(ch?<?'0'?||?ch?>?'9')?{
????????????????//?如果當前字符不為數字,?說明是非法字符,?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????//?置r->http_minor為ch-'0',?記錄次版本號
????????????r->http_minor?=?ch?-?'0';
????????????//?置state為sw_minor_digit,?表示解析協議版本的次版本號第一個數字后的數字
????????????state?=?sw_minor_digit;
????????????break;
????????case?sw_minor_digit:
????????????//?當前狀態為解析協議版本的次版本號第一個數字后的數字
????????????if?(ch?==?CR)?{
????????????????//?如果當前字符為\r,?說明遇到次版本號后緊跟的\r
????????????????//?置state為sw_almost_done,?表示解析結束的\n
????????????????state?=?sw_almost_done;
????????????????break;
????????????}
????????????if?(ch?==?LF)?{
????????????????//?如果當前字符為\n,?說明遇到次版本號后的\n
????????????????//?置state為sw_done,?表示解析完成
????????????????state?=?sw_done;
????????????????break;
????????????}
????????????if?(ch?<?'0'?||?ch?>?'9')?{
????????????????//?如果當前字符不為數字,?說明是非法字符,?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????//?更新次版本號r->http_minor
????????????r->http_minor?=?r->http_minor?*?10?+?ch?-?'0';
????????????break;
????????case?sw_almost_done:
????????????//?當前狀態為解析結束的\n
????????????//?置r->request_end為p-2,?記錄請求行有效內容的結束位置
????????????r->request_end?=?p?-?2;
????????????switch?(ch)?{
????????????case?LF:
????????????????//?如果當前字符正是\n
????????????????//?置state為sw_done,?表示解析完成
????????????????state?=?sw_done;
????????????????break;
????????????default:
????????????????//?如果當前字符不是\n,?那么就是非法字符,?返回NGX_HTTP_PARSE_INVALID_REQUEST
????????????????return?NGX_HTTP_PARSE_INVALID_REQUEST;
????????????}
????????????break;
????????case?sw_done:
????????????//?當前狀態為解析完成,?直接退出循環
????????????break;
????????}
????}
????//?置緩沖區的pos為p
????b->pos?=?p;
????if?(state?==?sw_done)?{
????????//?如果state為sw_done,?表明解析完成
????????if?(r->request_end?==?NULL)?{
????????????//?如果r->request_end為空
????????????//?置r->request_end為p-1,?p-1即為請求行的結束位置
????????????r->request_end?=?p?-?1;
????????}
????????//?求取HTTP版本,?規則為:?主版本號*1000+次版本號
????????//?所以,0.9->9,?1.0->1000,?1.1->1001
????????r->http_version?=?r->http_major?*?1000?+?r->http_minor;
????????//?重置請求r的state為sw_start
????????r->state?=?sw_start;
????????if?(r->http_version?==?9?&&?r->method?!=?NGX_HTTP_GET)?{
????????????//?如果為HTTP?0.9且請求方法不為GET
????????????//?返回NGX_HTTP_PARSE_INVALID_09_METHOD,?說明HTTP?0.9只支持GET方法
????????????return?NGX_HTTP_PARSE_INVALID_09_METHOD;
????????}
????????return?NGX_OK;
????}?else?{
????????//?沒有解析完
????????//?記錄當前解析狀態
????????r->state?=?state;
????????//?返回NGX_AGAIN
????????return?NGX_AGAIN;
????}
}
復制代碼
HTTP使用統一資源標識符(Uniform Resource Identifiers, URI)來傳輸數據和建立連接。URL是一種特殊類型的URI,包含了用于查找某個資源的足夠的信息
URL,全稱是UniformResourceLocator, 中文叫統一資源定位符,是互聯網上用來標識某一處資源的地址。以下面這個URL為例,介紹下普通URL的各部分組成:http://www.aspxfans.com:8080/news/index.asp?boardID=5&ID=24618&page=1#name
從上面的URL可以看出,一個完整的URL包括以下幾部分:
1.協議部分:該URL的協議部分為“http:”,這代表網頁使用的是HTTP協議。在Internet中可以使用多種協議,如HTTP,FTP等等本例中使用的是HTTP協議。在"HTTP"后面的“//”為分隔符
2.域名部分:該URL的域名部分為“www.aspxfans.com”。一個URL中,也可以使用IP地址作為域名使用
3.端口部分:跟在域名后面的是端口,域名和端口之間使用“:”作為分隔符。端口不是一個URL必須的部分,如果省略端口部分,將采用默認端口
4.虛擬目錄部分:從域名后的第一個“/”開始到最后一個“/”為止,是虛擬目錄部分。虛擬目錄也不是一個URL必須的部分。本例中的虛擬目錄是“/news/”
5.文件名部分:從域名后的最后一個“/”開始到“?”為止,是文件名部分,如果沒有“?”,則是從域名后的最后一個“/”開始到“#”為止,是文件部分,如果沒有“?”和“#”,那么從域名后的最后一個“/”開始到結束,都是文件名部分。本例中的文件名是“index.asp”。文件名部分也不是一個URL必須的部分,如果省略該部分,則使用默認的文件名
6.錨部分:從“#”開始到最后,都是錨部分。本例中的錨部分是“name”。錨部分也不是一個URL必須的部分
7.參數部分:從“?”開始到“#”為止之間的部分為參數部分,又稱搜索部分、查詢部分。本例中的參數部分為“boardID=5&ID=24618&page=1”。參數可以允許有多個參數,參數與參數之間用“&”作為分隔符
根據代碼我們分析一下ngx_http_request_t結構體中一些字段的含義:
// HTTP 0.9 請求行格式: [請求方法][空格..空格]URL(回車符)[換行符]
// HTTP >= 1.0 請求行格式: [請求方法][空格..空格][URL][空格..空格][協議版本][回車符][換行符]
ngx_http_request_t
結構體中在解析請求行時候設置的字段:
以下面的請求為例來說明:GET http://www.mytest.com:8011/abc.php?q=1#frag HTTP/1.1
request_start
: 指向了請求行最開始的位置,也即請求方法開始的地方(過濾了請求方法之前的回車和換行)request_end
: 指向了當前請求行的最后面字符
method_end
: 指向了http
請求行中method
最后一個字符method
: http請求方法的數字表示形式,每個http請求方法都對應一個特殊的字段
scheme_start
: 指向了請求行中method
后面的第一個非斜線字母(即A~Z或者a~z,或者下劃線,如果method后面直接跟著以下劃線開始的內容,那么scheme_start
就為空,表示此時沒有scheme
),正常的來說,比如本例子中scheme_start
就指向了 "http"
中的 "h"
scheme_end
: 指向了method后面的第一個冒號符號,表示schme的結束(比如http:)
host_start
: 指向了http請求行中兩個slash后面的第一個字符(比如在本例中host_start指向了第一個w字母)host_end
: 指向了http請求行中host結束的部分,nginx中允許host中包含大寫字母,小寫字母,數字,點,橫線,而host_end就指向了第一個非這些字符的位置。比如在http://www.baidu.com:80/,那么host_start指向第一個w字符,host_end指向com后面的冒號port_start
:port_end
: 指向了port后面的第一個slash或者空格,因為這兩個字符就表示了端口號的結束。如果沒有指定port,那么該字段為0
uri_start
: 指向http請求行port后面的第一個slash符號(即 "/")(比如,GET http://www.mytest.com:8011/abc.php)。如果沒有指定port,那么uri指向host后面的第一個slash符號(比如,GET http://www.mytest.com/abc.php)。如果連host也沒有指定,那么就指向method后面的slash符號(比如 GET /abc.php)。uri_end
: 指向了http請求行中錨點的后面的空格(如果有錨點的話),本例中指向了 "#frag"后面的空格,表示uri的結束位置。
http_major
: 指向了HTTP請求行中http版本的高位(點號前面的值)http_minor
: 指向了HTTP請求行中http版本的低位(點號后面的值)http_version
: 真正的版本號,http_version = 1000 * http_major + http_minor
exten
: 表示uri中的擴展名,比如 /abc.php?q=1#fra 中,exten就是 "php"
args_start
: 指向了請求行中問號("?")后面的第一個字符,表示請求參數的開始位置args
: 表示url中的請求參數(問號?后面,#前面的部分),比如 /abc.php?q=1#fra 中,args就是 q=1
complex_uri
: 如果uri中包含了小數點符號("."),slash符號("/"),井號("#"),那么該字段為 1plus_in_uri
: 如果uri中包含了加號("+"),那么該字段為 1quoted_uri
: 如果uri中出現了百分號("%"),那么該字段為 1uri_ext
: 指向了uri中最后一個斜線后面的小數點(".")后面的字符,在解析的過程中,每當碰到斜線的時候都會清空 r->uri_ext,所以該字段只能指向最后面的斜線后的小數點后的字符。如 GET /a.txt/b.json , 那么 uri_ext 指向了 "json"。而 GET /a.txt/b 這個請求中,uri_ext 為空。
當整個請求行解析結束之后,r->state
被重置為為 sw_start
, 這點非常重要,后面會用到。