./nginx-1.24.0/src/http/ngx_http_parse.c

ngx_int_t
ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
{
    u_char  c, ch, *p, *m;
    enum {
        sw_start = 0,
        sw_method,
        sw_spaces_before_uri,
        sw_schema,
        sw_schema_slash,
        sw_schema_slash_slash,
        sw_host_start,
        sw_host,
        sw_host_end,
        sw_host_ip_literal,
        sw_port,
        sw_after_slash_in_uri,
        sw_check_uri,
        sw_uri,
        sw_http_09,
        sw_http_H,
        sw_http_HT,
        sw_http_HTT,
        sw_http_HTTP,
        sw_first_major_digit,
        sw_major_digit,
        sw_first_minor_digit,
        sw_minor_digit,
        sw_spaces_after_digit,
        sw_almost_done
    } state;

    state = r->state;

    for (p = b->pos; p < b->last; p++) {
        ch = *p;

        switch (state) {

        /* HTTP methods: GET, HEAD, POST */
        case sw_start:
            r->request_start = p;

            if (ch == CR || ch == LF) {
                break;
            }

            if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') {
                return NGX_HTTP_PARSE_INVALID_METHOD;
            }

            state = sw_method;
            break;

        case sw_method:
            if (ch == ' ') {
                r->method_end = p - 1;
                m = r->request_start;

                switch (p - m) {

                case 3:
                    if (ngx_str3_cmp(m, 'G', 'E', 'T', ' ')) {
                        r->method = NGX_HTTP_GET;
                        break;
                    }

                    if (ngx_str3_cmp(m, 'P', 'U', 'T', ' ')) {
                        r->method = NGX_HTTP_PUT;
                        break;
                    }

                    break;

                case 4:
                    if (m[1] == 'O') {

                        if (ngx_str3Ocmp(m, 'P', 'O', 'S', 'T')) {
                            r->method = NGX_HTTP_POST;
                            break;
                        }

                        if (ngx_str3Ocmp(m, 'C', 'O', 'P', 'Y')) {
                            r->method = NGX_HTTP_COPY;
                            break;
                        }

                        if (ngx_str3Ocmp(m, 'M', 'O', 'V', 'E')) {
                            r->method = NGX_HTTP_MOVE;
                            break;
                        }

                        if (ngx_str3Ocmp(m, 'L', 'O', 'C', 'K')) {
                            r->method = NGX_HTTP_LOCK;
                            break;
                        }

                    } else {

                        if (ngx_str4cmp(m, 'H', 'E', 'A', 'D')) {
                            r->method = NGX_HTTP_HEAD;
                            break;
                        }
                    }

                    break;

                case 5:
                    if (ngx_str5cmp(m, 'M', 'K', 'C', 'O', 'L')) {
                        r->method = NGX_HTTP_MKCOL;
                        break;
                    }

                    if (ngx_str5cmp(m, 'P', 'A', 'T', 'C', 'H')) {
                        r->method = NGX_HTTP_PATCH;
                        break;
                    }

                    if (ngx_str5cmp(m, 'T', 'R', 'A', 'C', 'E')) {
                        r->method = NGX_HTTP_TRACE;
                        break;
                    }

                    break;

                case 6:
                    if (ngx_str6cmp(m, 'D', 'E', 'L', 'E', 'T', 'E')) {
                        r->method = NGX_HTTP_DELETE;
                        break;
                    }

                    if (ngx_str6cmp(m, 'U', 'N', 'L', 'O', 'C', 'K')) {
                        r->method = NGX_HTTP_UNLOCK;
                        break;
                    }

                    break;

                case 7:
                    if (ngx_str7_cmp(m, 'O', 'P', 'T', 'I', 'O', 'N', 'S', ' '))
                    {
                        r->method = NGX_HTTP_OPTIONS;
                    }

                    if (ngx_str7_cmp(m, 'C', 'O', 'N', 'N', 'E', 'C', 'T', ' '))
                    {
                        r->method = NGX_HTTP_CONNECT;
                    }

                    break;

                case 8:
                    if (ngx_str8cmp(m, 'P', 'R', 'O', 'P', 'F', 'I', 'N', 'D'))
                    {
                        r->method = NGX_HTTP_PROPFIND;
                    }

                    break;

                case 9:
                    if (ngx_str9cmp(m,
                            'P', 'R', 'O', 'P', 'P', 'A', 'T', 'C', 'H'))
                    {
                        r->method = NGX_HTTP_PROPPATCH;
                    }

                    break;
                }

                state = sw_spaces_before_uri;
                break;
            }

            if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') {
                return NGX_HTTP_PARSE_INVALID_METHOD;
            }

            break;

        /* space* before URI */
        case sw_spaces_before_uri:

            if (ch == '/') {
                r->uri_start = p;
                state = sw_after_slash_in_uri;
                break;
            }

            c = (u_char) (ch | 0x20);
            if (c >= 'a' && c <= 'z') {
                r->schema_start = p;
                state = sw_schema;
                break;
            }

            switch (ch) {
            case ' ':
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_schema:

            c = (u_char) (ch | 0x20);
            if (c >= 'a' && c <= 'z') {
                break;
            }

            if ((ch >= '0' && ch <= '9') || ch == '+' || ch == '-' || ch == '.')
            {
                break;
            }

            switch (ch) {
            case ':':
                r->schema_end = p;
                state = sw_schema_slash;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_schema_slash:
            switch (ch) {
            case '/':
                state = sw_schema_slash_slash;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_schema_slash_slash:
            switch (ch) {
            case '/':
                state = sw_host_start;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_host_start:

            r->host_start = p;

            if (ch == '[') {
                state = sw_host_ip_literal;
                break;
            }

            state = sw_host;

            /* fall through */

        case sw_host:

            c = (u_char) (ch | 0x20);
            if (c >= 'a' && c <= 'z') {
                break;
            }

            if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') {
                break;
            }

            /* fall through */

        case sw_host_end:

            r->host_end = p;

            switch (ch) {
            case ':':
                state = sw_port;
                break;
            case '/':
                r->uri_start = p;
                state = sw_after_slash_in_uri;
                break;
            case '?':
                r->uri_start = p;
                r->args_start = p + 1;
                r->empty_path_in_uri = 1;
                state = sw_uri;
                break;
            case ' ':
                /*
                 * use single "/" from request line to preserve pointers,
                 * if request line will be copied to large client buffer
                 */
                r->uri_start = r->schema_end + 1;
                r->uri_end = r->schema_end + 2;
                state = sw_http_09;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_host_ip_literal:

            if (ch >= '0' && ch <= '9') {
                break;
            }

            c = (u_char) (ch | 0x20);
            if (c >= 'a' && c <= 'z') {
                break;
            }

            switch (ch) {
            case ':':
                break;
            case ']':
                state = sw_host_end;
                break;
            case '-':
            case '.':
            case '_':
            case '~':
                /* unreserved */
                break;
            case '!':
            case '$':
            case '&':
            case '\'':
            case '(':
            case ')':
            case '*':
            case '+':
            case ',':
            case ';':
            case '=':
                /* sub-delims */
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_port:
            if (ch >= '0' && ch <= '9') {
                break;
            }

            switch (ch) {
            case '/':
                r->port_end = p;
                r->uri_start = p;
                state = sw_after_slash_in_uri;
                break;
            case '?':
                r->port_end = p;
                r->uri_start = p;
                r->args_start = p + 1;
                r->empty_path_in_uri = 1;
                state = sw_uri;
                break;
            case ' ':
                r->port_end = p;
                /*
                 * use single "/" from request line to preserve pointers,
                 * if request line will be copied to large client buffer
                 */
                r->uri_start = r->schema_end + 1;
                r->uri_end = r->schema_end + 2;
                state = sw_http_09;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        /* check "/.", "//", "%", and "\" (Win32) in URI */
        case sw_after_slash_in_uri:

            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
                state = sw_check_uri;
                break;
            }

            switch (ch) {
            case ' ':
                r->uri_end = p;
                state = sw_http_09;
                break;
            case CR:
                r->uri_end = p;
                r->http_minor = 9;
                state = sw_almost_done;
                break;
            case LF:
                r->uri_end = p;
                r->http_minor = 9;
                goto done;
            case '.':
                r->complex_uri = 1;
                state = sw_uri;
                break;
            case '%':
                r->quoted_uri = 1;
                state = sw_uri;
                break;
            case '/':
                r->complex_uri = 1;
                state = sw_uri;
                break;
#if (NGX_WIN32)
            case '\\':
                r->complex_uri = 1;
                state = sw_uri;
                break;
#endif
            case '?':
                r->args_start = p + 1;
                state = sw_uri;
                break;
            case '#':
                r->complex_uri = 1;
                state = sw_uri;
                break;
            case '+':
                r->plus_in_uri = 1;
                break;
            default:
                if (ch < 0x20 || ch == 0x7f) {
                    return NGX_HTTP_PARSE_INVALID_REQUEST;
                }
                state = sw_check_uri;
                break;
            }
            break;

        /* check "/", "%" and "\" (Win32) in URI */
        case sw_check_uri:

            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
                break;
            }

            switch (ch) {
            case '/':
#if (NGX_WIN32)
                if (r->uri_ext == p) {
                    r->complex_uri = 1;
                    state = sw_uri;
                    break;
                }
#endif
                r->uri_ext = NULL;
                state = sw_after_slash_in_uri;
                break;
            case '.':
                r->uri_ext = p + 1;
                break;
            case ' ':
                r->uri_end = p;
                state = sw_http_09;
                break;
            case CR:
                r->uri_end = p;
                r->http_minor = 9;
                state = sw_almost_done;
                break;
            case LF:
                r->uri_end = p;
                r->http_minor = 9;
                goto done;
#if (NGX_WIN32)
            case '\\':
                r->complex_uri = 1;
                state = sw_after_slash_in_uri;
                break;
#endif
            case '%':
                r->quoted_uri = 1;
                state = sw_uri;
                break;
            case '?':
                r->args_start = p + 1;
                state = sw_uri;
                break;
            case '#':
                r->complex_uri = 1;
                state = sw_uri;
                break;
            case '+':
                r->plus_in_uri = 1;
                break;
            default:
                if (ch < 0x20 || ch == 0x7f) {
                    return NGX_HTTP_PARSE_INVALID_REQUEST;
                }
                break;
            }
            break;

        /* URI */
        case sw_uri:

            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
                break;
            }

            switch (ch) {
            case ' ':
                r->uri_end = p;
                state = sw_http_09;
                break;
            case CR:
                r->uri_end = p;
                r->http_minor = 9;
                state = sw_almost_done;
                break;
            case LF:
                r->uri_end = p;
                r->http_minor = 9;
                goto done;
            case '#':
                r->complex_uri = 1;
                break;
            default:
                if (ch < 0x20 || ch == 0x7f) {
                    return NGX_HTTP_PARSE_INVALID_REQUEST;
                }
                break;
            }
            break;

        /* space+ after URI */
        case sw_http_09:
            switch (ch) {
            case ' ':
                break;
            case CR:
                r->http_minor = 9;
                state = sw_almost_done;
                break;
            case LF:
                r->http_minor = 9;
                goto done;
            case 'H':
                r->http_protocol.data = p;
                state = sw_http_H;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_http_H:
            switch (ch) {
            case 'T':
                state = sw_http_HT;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_http_HT:
            switch (ch) {
            case 'T':
                state = sw_http_HTT;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_http_HTT:
            switch (ch) {
            case 'P':
                state = sw_http_HTTP;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_http_HTTP:
            switch (ch) {
            case '/':
                state = sw_first_major_digit;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        /* first digit of major HTTP version */
        case sw_first_major_digit:
            if (ch < '1' || ch > '9') {
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }

            r->http_major = ch - '0';

            if (r->http_major > 1) {
                return NGX_HTTP_PARSE_INVALID_VERSION;
            }

            state = sw_major_digit;
            break;

        /* major HTTP version or dot */
        case sw_major_digit:
            if (ch == '.') {
                state = sw_first_minor_digit;
                break;
            }

            if (ch < '0' || ch > '9') {
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }

            r->http_major = r->http_major * 10 + (ch - '0');

            if (r->http_major > 1) {
                return NGX_HTTP_PARSE_INVALID_VERSION;
            }

            break;

        /* first digit of minor HTTP version */
        case sw_first_minor_digit:
            if (ch < '0' || ch > '9') {
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }

            r->http_minor = ch - '0';
            state = sw_minor_digit;
            break;

        /* minor HTTP version or end of request line */
        case sw_minor_digit:
            if (ch == CR) {
                state = sw_almost_done;
                break;
            }

            if (ch == LF) {
                goto done;
            }

            if (ch == ' ') {
                state = sw_spaces_after_digit;
                break;
            }

            if (ch < '0' || ch > '9') {
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }

            if (r->http_minor > 99) {
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }

            r->http_minor = r->http_minor * 10 + (ch - '0');
            break;

        case sw_spaces_after_digit:
            switch (ch) {
            case ' ':
                break;
            case CR:
                state = sw_almost_done;
                break;
            case LF:
                goto done;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        /* end of request line */
        case sw_almost_done:
            r->request_end = p - 1;
            switch (ch) {
            case LF:
                goto done;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
        }
    }

    b->pos = p;
    r->state = state;

    return NGX_AGAIN;

done:

    b->pos = p + 1;

    if (r->request_end == NULL) {
        r->request_end = p;
    }

    r->http_version = r->http_major * 1000 + r->http_minor;
    r->state = sw_start;

    if (r->http_version == 9 && r->method != NGX_HTTP_GET) {
        return NGX_HTTP_PARSE_INVALID_09_METHOD;
    }

    return NGX_OK;
}

该函数用于解析HTTP请求的第一行(请求行)


函数概述

ngx_int_t ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
  • r: 指向HTTP请求结构的指针,用于存储解析结果
  • b: 包含请求数据的缓冲区指针
  • 返回值: 状态码(NGX_OK表示成功,NGX_AGAIN需要更多数据,其他为错误码)

状态枚举

enum {
    sw_start = 0,          // 初始状态
    sw_method,             // 解析HTTP方法
    sw_spaces_before_uri,  // 方法后的空格
    sw_schema,             // 协议方案(如http)
    sw_schema_slash,       // 方案后的第一个斜杠
    sw_schema_slash_slash, // 方案后的第二个斜杠
    sw_host_start,         // 主机名开始
    sw_host,               // 解析主机名
    sw_host_end,           // 主机名结束
    sw_host_ip_literal,    // IPv6地址(方括号格式)
    sw_port,               // 端口号
    sw_after_slash_in_uri, // URI中的斜杠后
    sw_check_uri,          // 检查URI
    sw_uri,                // 解析URI
    sw_http_09,            // HTTP/0.9
    sw_http_H,             // 解析"H"
    sw_http_HT,            // 解析"HT"
    sw_http_HTT,           // 解析"HTT"
    sw_http_HTTP,          // 解析"HTTP"
    sw_first_major_digit,  // HTTP主版本号第一个数字
    sw_major_digit,        // HTTP主版本号
    sw_first_minor_digit,  // HTTP次版本号第一个数字
    sw_minor_digit,        // HTTP次版本号
    sw_spaces_after_digit, // 版本号后的空格
    sw_almost_done         // 即将完成
} state;

这段代码定义了一个枚举类型,用于表示HTTP请求行解析过程中的各个状态。
这是NGINX实现HTTP协议解析的核心状态机设计


状态机设计目的

这个状态机用于逐步解析HTTP请求的第一行(如 GET /index.html HTTP/1.1),将复杂的字符串解析过程分解为多个明确的阶段,每个阶段对应一个状态。通过状态转移,最终完成请求方法、URI、HTTP版本的解析。


状态分类与作用

1. 初始阶段
  • sw_start
    解析的起点,等待第一个有效字符(通常是HTTP方法的第一个字母,如G表示GET)。
2. HTTP方法解析
  • sw_method
    正在解析HTTP方法(如GETPOST)。通过字符匹配确定具体方法类型(最多支持9个字符的方法名)。
3. URI前的处理
  • sw_spaces_before_uri
    方法名后的空格(如GET[空格]/),可能直接进入URI解析,或检测到协议方案(如http:)。
4. 协议方案解析(可选)
  • sw_schema
    解析协议前缀(如http:https:)。
  • sw_schema_slashsw_schema_slash_slash
    严格匹配协议后的两个斜杠(://),确保格式正确(如http://)。
5. 主机名和端口解析
  • sw_host_start
    主机名解析开始(如example.com或IPv6的[::1])。
  • sw_host
    解析主机名字符(允许字母、数字、点、破折号)。
  • sw_host_ip_literal
    特殊处理IPv6地址(如[2001:db8::1])。
  • sw_host_end
    主机名结束,可能遇到端口号(:)、路径(/)或查询参数(?)。
  • sw_port
    解析端口号(如:8080)。
6. URI路径解析
  • sw_after_slash_in_uri
    路径开始的斜杠后(如/index.html),检查特殊字符(.%?等)。
  • sw_check_uri
    验证URI合法性(如是否包含非法字符)。
  • sw_uri
    通用URI字符处理,直到遇到空格或换行。
7. HTTP版本解析
  • sw_http_09
    兼容HTTP/0.9(无版本号的请求,如GET /)。
  • sw_http_Hsw_http_HTsw_http_HTTsw_http_HTTP
    逐步匹配字符串HTTP/(严格校验协议标识)。
  • sw_first_major_digitsw_major_digit
    解析主版本号(如HTTP/1.1中的1)。
  • sw_first_minor_digitsw_minor_digit
    解析次版本号(如HTTP/1.1中的1)。
8. 结束处理
  • sw_spaces_after_digit
    版本号后的空格(允许空格或直接换行)。
  • sw_almost_done
    请求行即将结束,等待最后的换行符(\n)。

状态机的关键特点

  1. 严格协议合规性
    每个状态严格校验字符合法性(如方法必须大写,版本号必须数字等),确保符合HTTP标准(RFC 2616/7230)。

  2. 性能优化

    • 通过状态跳转避免重复检查。
    • 使用sw_http_H等递进状态快速匹配固定字符串(HTTP/)。
    • 主机名和URI的解析采用最小化分支策略。
  3. 错误处理
    非法字符或顺序错误会立即返回错误码(如NGX_HTTP_PARSE_INVALID_METHOD)。

  4. 内存高效
    仅记录关键位置的指针(如r->uri_startr->host_end),不复制字符串。


状态转移示例

以解析 GET /index.html HTTP/1.1\r\n 为例:

  1. sw_startsw_method(遇到G)。
  2. sw_methodsw_spaces_before_uri(遇到空格)。
  3. sw_spaces_before_urisw_after_slash_in_uri(遇到/)。
  4. sw_after_slash_in_urisw_check_urisw_uri(解析路径)。
  5. sw_urisw_http_09(遇到空格)。
  6. sw_http_09sw_http_H → … → sw_http_HTTP(匹配HTTP/)。
  7. sw_http_HTTPsw_first_major_digitsw_major_digitsw_first_minor_digitsw_minor_digit(解析1.1)。
  8. sw_minor_digitsw_almost_done → 完成(遇到\n)。

state = r->state;

此时的 状态是
state=0


主循环

for (p = b->pos; p < b->last; p++) {
    ch = *p;  // 获取当前字符

此时
ch=G


状态机详细解析

1. sw_start - 初始状态

case sw_start:
    r->request_start = p;  // 记录请求行开始位置
    
    // 跳过回车换行(请求前的空行)
    if (ch == CR || ch == LF) break;
    
    // 验证方法首字符(必须是大写字母、下划线或破折号)
    if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') {
        return NGX_HTTP_PARSE_INVALID_METHOD;
    }
    
    state = sw_method;  // 转移到方法解析状态
    break;

2. sw_method - 解析HTTP方法

case sw_method:
    if (ch == ' ') {  // 遇到空格表示方法结束
        r->method_end = p - 1;
        m = r->request_start;
        
        // 根据方法长度进行分派
        switch (p - m) {
            case 3:  // GET, PUT等3字母方法
                if (ngx_str3_cmp(m, 'G', 'E', 'T', ' ')) {
                    r->method = NGX_HTTP_GET;
                    break;
                }
                // ... 其他3字母方法 ...
            case 4:  // POST, HEAD等4字母方法
                // ... 方法比较 ...
            // 其他长度情况...
        }
        
        state = sw_spaces_before_uri;  // 转移到URI前空格状态
        break;
    }
    
    // 验证方法字符
    if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') {
        return NGX_HTTP_PARSE_INVALID_METHOD;
    }
    break;

此时
r->method = NGX_HTTP_GET; 成立


3. sw_spaces_before_uri - 方法后的空格

case sw_spaces_before_uri:
    if (ch == '/') {  // 绝对路径URI
        r->uri_start = p;
        state = sw_after_slash_in_uri;
        break;
    }
    
    // 检查是否有协议方案(如http://)
    c = (u_char) (ch | 0x20);  // 快速转换为小写
    if (c >= 'a' && c <= 'z') {
        r->schema_start = p;
        state = sw_schema;
        break;
    }
    
    // 只允许空格
    switch (ch) {
        case ' ': break;
        default: return NGX_HTTP_PARSE_INVALID_REQUEST;
    }
    break;

case sw_spaces_before_uri:
    if (ch == '/') {  // 绝对路径URI
        r->uri_start = p;
        state = sw_after_slash_in_uri;
        break;
    }
  • 作用:处理HTTP方法后的空格,并检查URI的起始字符。
  • 逻辑
    • 如果当前字符是 /,表示这是一个绝对路径URI(如 GET /index.html)。
    • 记录URI的起始位置到 r->uri_start
    • 转移到 sw_after_slash_in_uri 状态,准备解析URI路径。
  • 意义:直接处理常见的不带协议前缀的URI(占大多数请求),快速进入路径解析状态。

此时
ch=/


    c = (u_char) (ch | 0x20);  // 快速转换为小写
    if (c >= 'a' && c <= 'z') {
        r->schema_start = p;
        state = sw_schema;
        break;
    }
  • 作用:检测是否包含协议方案(如 http:)。
  • 逻辑
    • ch | 0x20:通过位操作将字符强制转换为小写(例如 Hh),避免昂贵的 tolower() 调用。
    • 如果字符是字母(a-z),可能是协议前缀(如 http)的首字母:
      • 记录协议起始位置到 r->schema_start
      • 转移到 sw_schema 状态,开始解析协议(如 http:)。

    switch (ch) {
        case ' ': break;
        default: return NGX_HTTP_PARSE_INVALID_REQUEST;
    }
  • 作用:验证当前字符是否为合法空格。
  • 逻辑
    • 如果是空格( ),继续留在当前状态(可能还有连续空格)。
    • 如果是其他字符(如 \t% 等),返回错误码 NGX_HTTP_PARSE_INVALID_REQUEST
  • 意义
    • 协议严格性:HTTP标准要求方法后必须用空格分隔URI(不允许制表符等)。
    • 错误快速失败:遇到非法字符立即终止解析,避免无效后续处理。

    break;
  • 作用:结束当前 case 的处理,返回主循环继续读取下一个字符。
  • 逻辑
    • 如果没有触发任何转移条件(如仅读到空格),保持当前状态,继续检查下一个字符。
  • 意义:允许跳过多余的空格(如 GET[多个空格]/)。

4. URI相关状态

这些状态处理:

  • 协议方案(如http:)
  • 斜杠(//)
  • 主机名/IP地址
  • 端口号
  • URI路径
  • 查询字符串(?)
  • 片段标识(#)
// 协议方案(如http:)
case sw_schema:
    // 验证方案字符(a-z,0-9,+,-,.)
    if (ch == ':') {
        r->schema_end = p;
        state = sw_schema_slash;
    }
    break;

// 期待第一个斜杠(http:/)
case sw_schema_slash:
    if (ch == '/') state = sw_schema_slash_slash;
    else return error;
    break;

// 期待第二个斜杠(http://)
case sw_schema_slash_slash:
    if (ch == '/') state = sw_host_start;
    else return error;
    break;

// 主机名解析(支持IPv6地址[])
case sw_host_start:
    r->host_start = p;
    if (ch == '[') state = sw_host_ip_literal;
    else state = sw_host;
    // 继续执行下一个状态

// 主机名字符
case sw_host:
    // 有效字符: a-z,0-9,.,-
    if (无效字符) state = sw_host_end;
    break;

// 端口号解析(:后)
case sw_port:
    if (ch >= '0' && ch <= '9') break;  // 数字
    // 处理端口结束
    if (ch == '/') {
        r->port_end = p;
        r->uri_start = p;
        state = sw_after_slash_in_uri;
    }
    // 其他情况...
    break;

5. URI路径解析

        /* check "/.", "//", "%", and "\" (Win32) in URI */
        case sw_after_slash_in_uri:

            if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
                state = sw_check_uri;
                break;
            }

usual 是一个位图,标记哪些字符是"常规"URI字符
使用位运算快速检查当前字符ch是否是常规字符


ch >> 5:
这是右移5位操作,相当于除以32
作用:确定字符ch在usual数组中的索引位置


ch & 0x1f:
这是与操作,保留低5位(因为0x1f=31=二进制11111)
作用:确定字符在32位组内的具体位位置


1U << (ch & 0x1f):
创建一个掩码,将1左移到位图中对应的位置


位与操作&:
检查位图中对应位是否被设置
如果结果非零,表示该字符是常规字符


如果是常规字符,切换到sw_check_uri状态并跳出当前处理


            switch (ch) {
            case ' ':
                r->uri_end = p;
                state = sw_http_09;
                break;

在 HTTP 请求行中,空格用于分隔方法、URI 和协议版本(如 GET /index.html HTTP/1.1)
当在 URI 部分遇到空格时,它表示 URI 的结束


state = sw_http_09;:
将解析器状态切换为 sw_http_09,表示接下来要解析 HTTP 版本


            case CR:
                r->uri_end = p;
                r->http_minor = 9;
                state = sw_almost_done;
                break;
            case LF:
                r->uri_end = p;
                r->http_minor = 9;
                goto done;
            case '.':
                r->complex_uri = 1;
                state = sw_uri;
                break;
            case '%':
                r->quoted_uri = 1;
                state = sw_uri;
                break;
            case '/':
                r->complex_uri = 1;
                state = sw_uri;
                break;
#if (NGX_WIN32)
            case '\\':
                r->complex_uri = 1;
                state = sw_uri;
                break;
#endif
            case '?':
                r->args_start = p + 1;
                state = sw_uri;
                break;
            case '#':
                r->complex_uri = 1;
                state = sw_uri;
                break;
            case '+':
                r->plus_in_uri = 1;
                break;
            default:
                if (ch < 0x20 || ch == 0x7f) {
                    return NGX_HTTP_PARSE_INVALID_REQUEST;
                }
                state = sw_check_uri;
                break;
            }
            break;

6. HTTP版本解析

// 寻找"HTTP/"
        /* space+ after URI */
        case sw_http_09:
            switch (ch) {
            case ' ':
                break;
            case CR:
                r->http_minor = 9;
                state = sw_almost_done;
                break;
            case LF:
                r->http_minor = 9;
                goto done;
            case 'H':
                r->http_protocol.data = p;
                state = sw_http_H;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

这个状态处理 HTTP 请求行中 URI 之后的部分,主要做三件事:

  1. 处理 HTTP/0.9 简单请求(没有版本号的请求)
  2. 检测 HTTP/1.x 协议标识的开始
  3. 验证请求行格式的正确性

case sw_http_09:
    switch (ch) {
  • 状态上下文:进入这个状态表示已经解析完 URI,现在要解析 URI 后面的部分

    case ' ':
        break;
  • 作用:处理空格字符
  • 逻辑
    • 遇到空格时保持当前状态继续读取
    • 允许请求行中 URI 和 HTTP 版本之间有多个空格

    case CR:
        r->http_minor = 9;
        state = sw_almost_done;
        break;
  • 作用:处理回车符(CR),标识 HTTP/0.9 请求结束
  • 逻辑
    • 设置协议版本为 HTTP/0.9 (http_minor = 9)
    • 转移到 sw_almost_done 状态,等待最后的换行符(LF)
    • 对应 HTTP/0.9 的简单格式:GET <URI>\r\n

    case LF:
        r->http_minor = 9;
        goto done;
  • 作用:处理换行符(LF),直接完成 HTTP/0.9 请求解析
  • 逻辑
    • 设置协议版本为 HTTP/0.9
    • 使用 goto done 跳转到解析完成处理
    • 处理不标准的换行格式(缺少CR的情况)

    case 'H':
        r->http_protocol.data = p;
        state = sw_http_H;
        break;
  • 作用:检测到 HTTP/1.x 协议的开头
  • 逻辑
    • 记录协议字符串的起始位置 (http_protocol.data)
    • 转移到 sw_http_H 状态开始解析完整的 “HTTP/” 字符串
    • 对应格式:GET /index.html HTTP/1.1

    default:
        return NGX_HTTP_PARSE_INVALID_REQUEST;
    }
    break;
  • 作用:处理非法字符
  • 逻辑
    • 任何不符合上述情况的字符都会导致解析失败
    • 返回 NGX_HTTP_PARSE_INVALID_REQUEST 错误码
    • 确保请求行严格符合 HTTP 协议规范

状态转移示意图

[sw_http_09]
    │
    ├── ' ' → 保持状态
    ├── CR → 设置HTTP/0.9 → [sw_almost_done]
    ├── LF → 设置HTTP/0.9 → 完成解析
    ├── 'H' → 记录位置 → [sw_http_H]
    └── 其他 → 返回错误

// 解析"HTTP/"的后续字母
        case sw_http_H:
            switch (ch) {
            case 'T':
                state = sw_http_HT;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_http_HT:
            switch (ch) {
            case 'T':
                state = sw_http_HTT;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_http_HTT:
            switch (ch) {
            case 'P':
                state = sw_http_HTTP;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        case sw_http_HTTP:
            switch (ch) {
            case '/':
                state = sw_first_major_digit;
                break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

// 版本号解析
        /* first digit of major HTTP version */
        case sw_first_major_digit:
            if (ch < '1' || ch > '9') {
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }

            r->http_major = ch - '0';

            if (r->http_major > 1) {
                return NGX_HTTP_PARSE_INVALID_VERSION;
            }

            state = sw_major_digit;
            break;

        /* major HTTP version or dot */
        case sw_major_digit:
            if (ch == '.') {
                state = sw_first_minor_digit;
                break;
            }

            if (ch < '0' || ch > '9') {
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }

            r->http_major = r->http_major * 10 + (ch - '0');

            if (r->http_major > 1) {
                return NGX_HTTP_PARSE_INVALID_VERSION;
            }

            break;

        /* first digit of minor HTTP version */
        case sw_first_minor_digit:
            if (ch < '0' || ch > '9') {
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }

            r->http_minor = ch - '0';
            state = sw_minor_digit;
            break;

        /* minor HTTP version or end of request line */
        case sw_minor_digit:
            if (ch == CR) {
                state = sw_almost_done;
                break;
            }

            if (ch == LF) {
                goto done;
            }

            if (ch == ' ') {
                state = sw_spaces_after_digit;
                break;
            }

            if (ch < '0' || ch > '9') {
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }

            if (r->http_minor > 99) {
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }

            r->http_minor = r->http_minor * 10 + (ch - '0');
            break;

        case sw_spaces_after_digit:
            switch (ch) {
            case ' ':
                break;
            case CR:
                state = sw_almost_done;
                break;
            case LF:
                goto done;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;

        /* end of request line */
        case sw_almost_done:
            r->request_end = p - 1;
            switch (ch) {
            case LF:
                goto done;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
        }
    }
    b->pos = p;
    r->state = state;

    return NGX_AGAIN;

完成处理

done:
    b->pos = p + 1;  // 推进缓冲区位置
    
    if (r->request_end == NULL) {
        r->request_end = p;
    }
    
    // 组合版本号(如1.1→1001)
    r->http_version = r->http_major * 1000 + r->http_minor;
    r->state = sw_start;  // 重置状态
    
    // HTTP/0.9只允许GET方法
    if (r->http_version == 9 && r->method != NGX_HTTP_GET) {
        return NGX_HTTP_PARSE_INVALID_09_METHOD;
    }
    
    return NGX_OK;
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐