状态机解析请求行

  • 微信公众号:郑尔多斯
  • 关注「郑尔多斯」公众号 ,回复「领取资源」,获取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.mzph.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 输出…