狀態機解析請求行

  • 微信公衆號:鄭爾多斯
  • 關注「鄭爾多斯」公衆號 ,回覆「領取資源」,獲取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符號("/"),井號("#"),那麼該字段爲 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, 這點很是重要,後面會用到。

相關文章
相關標籤/搜索