nginx http模块开发(9) - http请求处理

2018-07-22 11:26:04

nginx 用宏定义了 http 状态码以及一些 nginx 特有的状态码,下面列出部分:

// http/nginx_http_request.h

#define NGX_HTTP_OK                        200
#define NGX_HTTP_CREATED                   201
#define NGX_HTTP_ACCEPTED                  202

#define NGX_HTTP_SPECIAL_RESPONSE          300
#define NGX_HTTP_MOVED_PERMANENTLY         301
#define NGX_HTTP_MOVED_TEMPORARILY         302

#define NGX_HTTP_BAD_REQUEST               400
#define NGX_HTTP_UNAUTHORIZED              401
#define NGX_HTTP_FORBIDDEN                 403
#define NGX_HTTP_NOT_FOUND                 404


#define NGX_HTTP_INTERNAL_SERVER_ERROR     500
#define NGX_HTTP_NOT_IMPLEMENTED           501
#define NGX_HTTP_BAD_GATEWAY               502

下面我们结合 ngx_http_request_t 结构体定义来阐述:

请求行

// 请求行字符串
ngx_str_t                         request_line;

包含请求方法,请求uri,http版本信息,类似下面

GET /index.html HTTP/1.1

请求方法

ngx_uint_t    method;  //数字形式
ngx_str_t     method_name; //字符串形式

method_name 是请求方法的字符串表示形式,例如 GET/HEAD/POST/PUT/COPY等。

method 则是以数字形式,nginx 定义了几个宏来表示:

// r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD)
#define NGX_HTTP_UNKNOWN                   0x0001
#define NGX_HTTP_GET                       0x0002
#define NGX_HTTP_HEAD                      0x0004
#define NGX_HTTP_POST                      0x0008
#define NGX_HTTP_PUT                       0x0010
...

比较的时候应尽量使用数字形式,速度更快。

协议版本

ngx_uint_t    http_version;

宏定义

#define NGX_HTTP_VERSION_9       9
#define NGX_HTTP_VERSION_10      1000
#define NGX_HTTP_VERSION_11      1001

资源标识符

// uri地址,不含参数,即$uri
ngx_str_t    uri;

// uri后的参数,不含问号,即$args
ngx_str_t    args;

// uri里文件的扩展名,没有就返回空字符串
ngx_str_t    exten;

// 原始请求uri,未解码,即$request_uri
ngx_str_t    unparsed_uri;

比如有如下请求

curl http://www.freecls.com/test/%40index.html?age=22

相应的

uri = /test/@index.html

args = age=22

exten = html

unparsed_uri = /test/%40index.html?age=22

在 http 请求的任意阶段都可以使用如下函数进行内部跳转:

ngx_int_t ngx_http_internal_redirect(ngx_http_request_t *r,
    ngx_str_t *uri, ngx_str_t *args);

ngx_int_t ngx_http_named_location(ngx_http_request_t *r, ngx_str_t *name);

第一个比较灵活,可以跳转到当前 server 的任意 location。

第二个只能跳转到命名 location,即 @ 标识的 location。

nginx 还提供了一个函数 ngx_http_parse_unsafe_uri 来检查 uri是否不安全,例如是否包含 ../。

ngx_int_t ngx_http_parse_unsafe_uri(ngx_http_request_t *r, ngx_str_t *uri,
    ngx_str_t *args, ngx_uint_t *flags);

返回 NGX_ERROR 代表不安全。

请求头

nginx_http_request_t 用两个成员存储收到的 http 头部信息:

// 缓冲区,用于读取请求头
// 如果有请求体数据,也会都读到这里
ngx_buf_t    *header_in;

// 请求头结构体
// 里面用链表存储了所有的头,也可以用指针快速访问常用头
ngx_http_headers_in_t    headers_in;

我们通常只需要关心解析好的 headers_in 结构体,所有的请求头都以链表的形式存储在 headers 里,但 nginx 已经把很多常用的请求头单独拎出来让我们可以直接访问而无需遍历 headers 。

对于 content_length 和 keep_alive 这两个头,nginx 已经帮我们解析出了里面的值分别是成员 content_length_n 和 keep_alive_n。

// 自定义或非常用头需要遍历链表查找
// content_length_n直接把头里的长度字符串转换为数字
typedef struct {
    // 所有头都存储在headers列表里,类型是ngx_table_elt_t
    ngx_list_t                        headers;

    // host、range等常用头,可以直接获取
    // 如果不存在那么指针就是nullptr
    ngx_table_elt_t                  *host;
    ngx_table_elt_t                  *connection;
    ngx_table_elt_t                  *if_modified_since;
    ngx_table_elt_t                  *if_unmodified_since;
    ngx_table_elt_t                  *if_match;
    ngx_table_elt_t                  *if_none_match;
    ngx_table_elt_t                  *user_agent;
    ngx_table_elt_t                  *referer;
    ngx_table_elt_t                  *content_length;
    ngx_table_elt_t                  *content_range;
    ngx_table_elt_t                  *content_type;

    ngx_table_elt_t                  *range;
    ngx_table_elt_t                  *if_range;

    ngx_table_elt_t                  *transfer_encoding;
    ngx_table_elt_t                  *te;
    ngx_table_elt_t                  *expect;
    ngx_table_elt_t                  *upgrade;

#if (NGX_HTTP_GZIP || NGX_HTTP_HEADERS)
    ngx_table_elt_t                  *accept_encoding;
    ngx_table_elt_t                  *via;
#endif

    ngx_table_elt_t                  *authorization;

    ngx_table_elt_t                  *keep_alive;

#if (NGX_HTTP_X_FORWARDED_FOR)
    ngx_array_t                       x_forwarded_for;
#endif

#if (NGX_HTTP_REALIP)
    ngx_table_elt_t                  *x_real_ip;
#endif

#if (NGX_HTTP_HEADERS)
    ngx_table_elt_t                  *accept;
    ngx_table_elt_t                  *accept_language;
#endif

#if (NGX_HTTP_DAV)
    ngx_table_elt_t                  *depth;
    ngx_table_elt_t                  *destination;
    ngx_table_elt_t                  *overwrite;
    ngx_table_elt_t                  *date;
#endif

    ngx_str_t                         user;
    ngx_str_t                         passwd;

    ngx_array_t                       cookies;

    ngx_str_t                         server;

    // content_length_n直接把头里的长度字符串转换为数字
    // content_length_n置0,表示无数据,丢弃成功
    off_t                             content_length_n;

    // keep_alive_n也直接转换为数字
    time_t                            keep_alive_n;

    unsigned                          connection_type:2;
    unsigned                          chunked:1;

    // user agent标志位
    unsigned                          msie:1;
    unsigned                          msie6:1;
    unsigned                          opera:1;
    unsigned                          gecko:1;
    unsigned                          chrome:1;
    unsigned                          safari:1;
    unsigned                          konqueror:1;
} ngx_http_headers_in_t;

响应头

结构体 ngx_http_headers_out_t 表示响应头,它包含了 http 响应里的状态行和头部信息。headers列表用于存储响应头信息,也可以直接用指针指定常用头,不必用列表。通常需要指定content_length_n,表示body的长度即 Content-Length。status是响应码,status_line可以定制响应状态行。

// 响应头数据结构
typedef struct {
    ngx_list_t                        headers;
    ngx_list_t                        trailers;

    // status是响应码,status_line可以定制响应状态行
    ngx_uint_t                        status;
    ngx_str_t                         status_line;

    // 常用头
    ngx_table_elt_t                  *server;
    ngx_table_elt_t                  *date;
    ngx_table_elt_t                  *content_length;
    ngx_table_elt_t                  *content_encoding;
    ngx_table_elt_t                  *location;
    ngx_table_elt_t                  *refresh;
    ngx_table_elt_t                  *last_modified;
    ngx_table_elt_t                  *content_range;
    ngx_table_elt_t                  *accept_ranges;
    ngx_table_elt_t                  *www_authenticate;
    ngx_table_elt_t                  *expires;
    ngx_table_elt_t                  *etag;

    ngx_str_t                        *override_charset;

    size_t                            content_type_len;
    ngx_str_t                         content_type;
    ngx_str_t                         charset;
    u_char                           *content_type_lowcase;
    ngx_uint_t                        content_type_hash;

    ngx_array_t                       cache_control;
    ngx_array_t                       link;

    // 通常需要指定content_length_n,表示body的长度
    off_t                             content_length_n;

    off_t                             content_offset;
    time_t                            date_time;
    time_t                            last_modified_time;
} ngx_http_headers_out_t;

响应头均是以 ngx_table_elt_t 的形式存储,如果想要删除某个头信息,只需要把响应的 hash 成员置为0即可,nginx 定义了几个宏用来清除常见响应头。

// 简化操作宏,清除响应头里的长度信息
#define ngx_http_clear_content_length(r)             \
                                                     \
    r->headers_out.content_length_n = -1;            \
    if (r->headers_out.content_length) {             \
        r->headers_out.content_length->hash = 0;     \
        r->headers_out.content_length = NULL;        \
    }

#define ngx_http_clear_accept_ranges(r)               \
                                                      \
    r->allow_ranges = 0;                              \
    if (r->headers_out.accept_ranges) {               \
        r->headers_out.accept_ranges->hash = 0;       \
        r->headers_out.accept_ranges = NULL;          \
    }

#define ngx_http_clear_last_modified(r)                 \
                                                        \
    r->headers_out.last_modified_time = -1;             \
    if (r->headers_out.last_modified) {                 \
        r->headers_out.last_modified->hash = 0;         \
        r->headers_out.last_modified = NULL;            \
    }

#define ngx_http_clear_location(r)                       \
                                                         \
    if (r->headers_out.location) {                       \
        r->headers_out.location->hash = 0;               \
        r->headers_out.location = NULL;                  \
    }

#define ngx_http_clear_etag(r)                            \
                                                          \
    if (r->headers_out.etag) {                            \
        r->headers_out.etag->hash = 0;                    \
        r->headers_out.etag = NULL;                       \
    }

nginx 还定义了两个函数用来发送和清除响应头

// 发送http头,调用过滤链表
// 走过整个header过滤链表
ngx_int_t ngx_http_send_header(ngx_http_request_t *r);

void ngx_http_clean_header(ngx_http_request_t *r);

响应体

调用函数 ngx_http_output_filter 可以把响应体数据经过滤链表后发送给客户端:

ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *chain);

响应体就是一些数据,可以用 ngx_buf_t 和 ngx_chain_t 表示。

ngx_http_send_special 可以控制响应数据

// ngx_http_send_special支持的参数
#define NGX_HTTP_LAST   1
#define NGX_HTTP_FLUSH  2

// 发送特殊的http响应数据,即flush和eof
ngx_int_t ngx_http_send_special(ngx_http_request_t *r, ngx_uint_t flags);


备注

1.测试环境centos7 64位,nginx版本为 1.14.0。
2..原文地址http://www.freecls.com/a/2712/d2


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。