- 微信公衆號:鄭爾多斯
- 關注「鄭爾多斯」公衆號 ,回覆「領取資源」,獲取IT資源500G乾貨。
升職加薪、當上總經理、出任CEO、迎娶白富美、走上人生巔峯!想一想還有點小激動- 關注可瞭解更多的
Nginx
知識。任何問題或建議,請公衆號留言;
關注公衆號,有趣有內涵的文章第一時間送達!
HTTP請求行格式爲: [請求方法][空格][URL][空格][協議版本][回車符][換行符]
php
通常來講,HTTP請求行不會很長,可是依舊有可能在單次系統調用中沒法讀取完整的請求行,因此可能須要屢次調用ngx_http_process_request_line才能讀取和處理完請求行;也正因如此,Nginx在請求行的解析過程當中對狀態進行了記錄。我以爲,可否在讀取到/r/n時纔對請求行進行解析呢?這種作法好處是比較簡單,可是會有性能問題,由於在讀取到非法的請求行內容時,就不須要在讀取以後的內容呢!而Nginx是以追求高性能著稱的,顯然一邊讀取一遍解析會更優。nginx
閒話少說,直接上代碼:
第一遍只關注happy path,關注流程的實現方式。等到整個流程走通了,第二遍或者第三遍學習的時候,再抓住一個點學習具體的函數,好比解析request line的函數等。避免第一次就陷入到具體函數的內部。git
Nginx的HTTP模塊中使用ngx_http_parse_request_line函數來對讀取的請求行進行解析,HTTP請求行的格式不是很複雜,可是要注意HTTP 0.9與1.0、1.1之間的區別;我以爲只要關注http1.1就好了,抓住主幹功能。json
/* 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結構體中一些字段的含義:app
// 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請求方法都對應一個特殊的字段url
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
, 這點很是重要,後面會用到。