狀態機解析請求行

  • 微信公眾號:鄭爾多斯
  • 關注「鄭爾多斯」公眾號 ,回復「領取資源」,獲取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符號("/"),井號("#"),那么該字段為 1
plus_in_uri : 如果uri中包含了加號("+"),那么該字段為 1
quoted_uri : 如果uri中出現了百分號("%"),那么該字段為 1
uri_ext : 指向了uri中最后一個斜線后面的小數點(".")后面的字符,在解析的過程中,每當碰到斜線的時候都會清空 r->uri_ext,所以該字段只能指向最后面的斜線后的小數點后的字符。如 GET /a.txt/b.json , 那么 uri_ext 指向了 "json"。而 GET /a.txt/b 這個請求中,uri_ext 為空。

當整個請求行解析結束之后,r->state被重置為為 sw_start, 這點非常重要,后面會用到。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/536447.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/536447.shtml
英文地址,請注明出處:http://en.pswp.cn/news/536447.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

GO 從零開始的語法學習二

for循環 if條件里不需要括號 err ! nil 判斷是否為空 func main(){const filename "abc.txt"contents , err : ioutil.ReadFile(filename); err ! nil{fmt.Println(err)} else{fmt.Printf("%s\n",contents)} } 復制代碼if的條件里可以進行賦值if的條件里…

7個有用的Vue開發技巧

1 狀態共享 隨著組件的細化&#xff0c;就會遇到多組件狀態共享的情況&#xff0c;Vuex當然可以解決這類問題&#xff0c;不過就像Vuex官方文檔所說的&#xff0c;如果應用不夠大&#xff0c;為避免代碼繁瑣冗余&#xff0c;最好不要使用它&#xff0c;今天我們介紹的是vue.js …

Kewail-郵件短信接口的基礎教程

短信接口接入流程開始接入手機短信接口接入操作流程&#xff1a;申請短信簽名 → 申請短信模板 → 生成AccessKey → 下載DEMO/攢寫接口調用文檔 → 免費測試發送 → 購買發信量正式使用。一、申請短信簽名接入API接口&#xff0c;通過1069通道發送驗證碼等短信&#xff0c;必須…

傳百度無人車計劃分拆,百度回復:不實信息,目前未有分拆計劃

據《財經》報道&#xff0c;百度無人車項目正在籌備分拆(spin off)當中&#xff0c;且正在尋找外部投資機構融資。一位接近百度無人車項目人士對《財經》表明&#xff0c;分拆就是時間問題。對于無人車項目分拆一事&#xff0c;百度對 36 氪表示&#xff0c;媒體報道不實。目前…

又見回文

又見回文 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Discuss Problem Description “回文串”是一個正讀和反讀都一樣的字符串&#xff0c;比如“level”或者“noon”等等就是回文串。現在呢&#xff0c;就是讓你判斷輸入的字符串是否是回文串。 Inpu…

Fighting_小銀考呀考不過四級【遞推】

Fighting_小銀考呀考不過四級 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Discuss Problem Description 四級考試已經過去好幾個星期了&#xff0c;但是小銀還是對自己的英語水平擔心不已。 小銀打算好好學習英語&#xff0c;爭取下次四級考試和小學弟小…

從xml中返回的對象,和new 返回的對象時不同的。

public BigDecimal getTax() {return tax null ? BigDecimal.ZERO : tax;} 這是自定義的一個類 對null 做出了處理。 但是如果是直接從xml 查詢返回的該對象&#xff0c; tax() 字段還是會產生null <resultMap id"twoToNine" type"" ><result …

三國佚事——巴蜀之危【遞推】

三國佚事——巴蜀之危 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Discuss Problem Description 話說天下大勢&#xff0c;分久必合&#xff0c;合久必分。。。卻道那魏蜀吳三國鼎力之時&#xff0c;多少英雄豪杰以熱血譜寫那千古之絕唱。古人誠不我欺…

HTTP Authentication(HTTP認證)(轉)

HTTP協議規范中有兩種認證方式&#xff0c;一種是Basic認證&#xff0c;另外一種是Digest認證&#xff0c;這兩種方式都屬于無狀態認證方式&#xff0c;所謂無狀態即服務端都不會在會話中記錄相關信息&#xff0c;客戶端每次訪問都需要將用戶名和密碼放置報文一同發送給服務端&…

們--加強斐波那契【遞推】

們--加強斐波那契 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Discuss Problem Description 對于斐波那契數列想必各位已經見過了。這里給出一個加強版。 F[i] i (i < 3); F[i] F[i-1] F[i-2] F[i-3](i > 4); Input 多組輸入。每組輸入一…

inux CentOS 7 修改內核啟動默認順序

2019獨角獸企業重金招聘Python工程師標準>>> inux CentOS 7 修改內核啟動默認順序 2018年12月07日 09:53:32 XueShengke 閱讀數&#xff1a;781 轉載于&#xff1a;21運維 Linux CentOS 7.X 如何修改內核啟動默認順序 我們知道&#xff0c;centos 6.x是通過/etc/gr…

快速掌握ajax!

ajax是什么&#xff1f;ajax——asynchronous JavaScript and xml&#xff1a;異步的js和xml它能使用js訪問服務器&#xff0c;而且是異步訪問服務器給客戶端的響應一般是整個頁面&#xff0c;一個html完整頁面&#xff01;但在ajax中因為是局部刷新&#xff0c;那么服務器就不…

鎖底層之內存屏障與原語指令

Java內存模型1&#xff0e;工作內存和主內存Java內存模型規定所有的變量都存儲在主內存中&#xff08;JVM內存的一部分&#xff09;&#xff0c;每個線程有自己獨立的工作內存&#xff0c;它保存了被該線程使用的變量的主內存復制。線程對這些變量的操作都在自己的工作內存中進…

微信點擊鏈接,用默認瀏覽器中打開指定網址鏈接!

2019獨角獸企業重金招聘Python工程師標準>>> 最近有客戶咨詢&#xff0c;自己的鏈接在微信種推廣&#xff0c;經常會被無緣無故封殺&#xff0c;有沒有一種功能&#xff0c;用戶在微信中點擊我們推廣的鏈接&#xff0c;可以自動強制跳轉到手機默認瀏覽器中打開指定的…

elasticsearch存儲空間不足導致索引只讀,不能創建

問題描述 1.添加數據時&#xff0c;報錯&#xff0c;原因是&#xff0c;一旦在存儲超過95&#xff05;的磁盤中的節點上分配了一個或多個分片的任何索引&#xff0c; 該索引將被強制進入只讀模式 ClusterBlockException[blocked by: [FORBIDDEN/12/index read-only / allow del…

java版spring cloud+spring boot 社交電子商務平臺:服務消費(基礎)

使用LoadBalancerClientSpring cloud b2b2c電子商務社交平臺源碼請加企鵝求求&#xff1a;一零三八七七四六二六。在Spring Cloud Commons中提供了大量的與服務治理相關的抽象接口&#xff0c;包括DiscoveryClient、這里我們即將介紹的LoadBalancerClient等。對于這些接口的定義…

Monthly Expense【二分】

B - Monthly Expense POJ - 3273 Farmer John is an astounding accounting wizard and has realized he might run out of money to run the farm. He has already calculated and recorded the exact amount of money (1 ≤ moneyi ≤ 10,000) that he will need to spend …

關于HTTP協議,一篇就夠了

原文地址&#xff1a;https://www.cnblogs.com/ranyonsue/p/5984001.html HTTP簡介 HTTP協議是Hyper Text Transfer Protocol&#xff08;超文本傳輸協議&#xff09;的縮寫,是用于從萬維網&#xff08;WWW:World Wide Web &#xff09;服務器傳輸超文本到本地瀏覽器的傳送協議…

Oracle關聯查詢-數據類型不一致問題 ORA-01722: 無效數字

一、存在表A和表B&#xff0c;都包含字段user_no&#xff0c;但數據類型不一致&#xff0c;如下&#xff1a; create table A ( user_id varchar2(20), user_no number(12,0), xxx ); create table B ( user_name varchar2(60), user_no varchar2(20), xxx ); 二、現有某項業務…

1096: 字符逆序

1096: 字符逆序 Time Limit: 1 Sec Memory Limit: 64 MB Submit: 2017 Solved: 1059 [Submit][Status][Web Board] Description 將一個字符串str的內容顛倒過來&#xff0c;并輸出。str的長度不超過100個字符。 Input 輸入包括一行。 第一行輸入的字符串。 Output 輸出…