前言
本文介绍如何处理一个实际的HTTP请求。在之前的文章(定义自己的HTTP模块)中提到,在出现mytest配置项时,ngx_http_mytest方法会被调用,这是将ngx_http_core_loc_conf_t结构的handler成员指定为ngx_http_mytest_handler,另外,HTTP框架在接收完HTTP请求的头部后,会调用handler指向的方法。看一下handler成员的原型ngx_http_handler_pt:
typedef ngx_init_t (*ngx_http_handler_pt)(ngx_http_request_t *r);
从上面这行代码可以看出,实际处理请求的方法ngx_http_mytest_handler将接收一个ngx_http_request_t类型的参数,返回一个ngx_int_t类型的结果。下面先探讨下ngx_http_mytest_handler方法可以返回什么,再看一下参数r包含了哪些Nginx已经解析完的用户请求信息。
一、处理方法的返回值
这个返回值可以是HTTP中响应包的返回码,其中包括了HTTP框架已经在/src/http/ngx_http_request.h文件中定义好的宏,内容如下:
#define NGX_HTTP_OK 200
#define NGX_HTTP_CREATED 201
#define NGX_HTTP_ACCEPTED 202
#define NGX_HTTP_NO_CONTENT 204
#define NGX_HTTP_PARTIAL_CONTENT 206
#define NGX_HTTP_SPECIAL_RESPONSE 300
#define NGX_HTTP_MOVED_PERMANENTLY 301
#define NGX_HTTP_MOVED_TEMPORARILY 302
#define NGX_HTTP_SEE_OTHER 303
#define NGX_HTTP_NOT_MODIFIED 304
#define NGX_HTTP_TEMPORARY_REDIRECT 307
#define NGX_HTTP_PERMANENT_REDIRECT 308
#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_NOT_ALLOWED 405
#define NGX_HTTP_REQUEST_TIME_OUT 408
#define NGX_HTTP_CONFLICT 409
#define NGX_HTTP_LENGTH_REQUIRED 411
#define NGX_HTTP_PRECONDITION_FAILED 412
#define NGX_HTTP_REQUEST_ENTITY_TOO_LARGE 413
#define NGX_HTTP_REQUEST_URI_TOO_LARGE 414
#define NGX_HTTP_UNSUPPORTED_MEDIA_TYPE 415
#define NGX_HTTP_RANGE_NOT_SATISFIABLE 416
#define NGX_HTTP_MISDIRECTED_REQUEST 421
#define NGX_HTTP_TOO_MANY_REQUESTS 429
/* The special code to close connection without any response */
#define NGX_HTTP_CLOSE 444
#define NGX_HTTP_NGINX_CODES 494
#define NGX_HTTP_REQUEST_HEADER_TOO_LARGE 494
#define NGX_HTTPS_CERT_ERROR 495
#define NGX_HTTPS_NO_CERT 496
#define NGX_HTTP_TO_HTTPS 497
#define NGX_HTTP_CLIENT_CLOSED_REQUEST 499
#define NGX_HTTP_INTERNAL_SERVER_ERROR 500
#define NGX_HTTP_NOT_IMPLEMENTED 501
#define NGX_HTTP_BAD_GATEWAY 502
#define NGX_HTTP_SERVICE_UNAVAILABLE 503
#define NGX_HTTP_GATEWAY_TIME_OUT 504
#define NGX_HTTP_VERSION_NOT_SUPPORTED 505
#define NGX_HTTP_INSUFFICIENT_STORAGE 507
- 在ngx_http_mytest_handler的返回值中,如果是正常的HTTP返回码,Nginx就会按照规范构造合法的响应包发送给用户。
- 在处理方法中除了返回HTTP响应码外,还可以返回Nginx全局定义的几个错误码:
// 表示成功。Nginx将会继续执行该请求的后续动作。 #define NGX_OK 0 /* 表示错误。这时会调用ngx_http_terminate_request终止请求。 如果还有POST子请求,那么将会在执行完POST请求后再终止本次请求。 */ #define NGX_ERROR -1 #define NGX_AGAIN -2 #define NGX_BUSY -3 /* 表示到此为止,同时HTTP框架将暂时不再继续执行这个请求的后续部分。 这时会检查连接的类型,如果是keepalive类型的用户请其9u,就会保持住HTTP连接,然后把控制权交给Nginx */ #define NGX_DONE -4 // 继续在NGX_HTTP_CONTENT_PHASE阶段寻找下一个对于该请求感兴趣的HTTP模块来再次处理这个请求 #define NGX_DECLINED -5 #define NGX_ABORT -6
- 这些错误码对于Nginx自身提供的大部分方法来说都是通用的。所以,当我们最后调用ngx_http_output_fileter向用户发送响应包时,可以将ngx_http_output_filter的返回值作为ngx_http_mytest_handler方法的返回值使用。
二、获取URL和参数
请求的所有信息(如方法、URI、协议版本号和头部)都可以在传入的ngx_http_request_t类型参数r中取得。ngx_http_request_t结构体的内容很多,这里只介绍一下获取URI和参数的方法。下面先介绍相关成员的定义。
struct ngx_http_request_s {
...
ngx_uint_t method;
ngx_uint_t http_version;
ngx_str_t request_line;
ngx_str_t uri;
ngx_str_t args;
ngx_str_t exten;
ngx_str_t unparsed_uri;
ngx_str_t method_name;
ngx_str_t http_protocol;
u_char *uri_start;
u_char *uri_end;
u_char *uri_ext;
u_char *args_start;
u_char *request_start;
u_char *request_end;
u_char *method_end;
u_char *schema_start;
u_char *schema_end;
u_char *host_start;
ngx_buf_t *header_in;
ngx_http_headers_in_t headers_in;
...
};
1.方法名
method的类型是ngx_uint_t(无符号整型),它是Nginx忽略大小写等情形是解析完用户请求后得到的方法类型。其取值范围如下:
#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
#define NGX_HTTP_DELETE 0x0020
#define NGX_HTTP_MKCOL 0x0040
#define NGX_HTTP_COPY 0x0080
#define NGX_HTTP_MOVE 0x0100
#define NGX_HTTP_OPTIONS 0x0200
#define NGX_HTTP_PROPFIND 0x0400
#define NGX_HTTP_PROPPATCH 0x0800
#define NGX_HTTP_LOCK 0x1000
#define NGX_HTTP_UNLOCK 0x2000
#define NGX_HTTP_PATCH 0x4000
#define NGX_HTTP_TRACE 0x8000
- 当需要了解用户请求中的HTTP方法时,应该使用r->method这个整型成员与以上15个宏进行比较,这样速度是最快的
- 还可以用method_name取得用户请求中的方法名字符串
- 还可以联合request_start与method_end指针取得方法名
2.URI
- ngx_str_t类型的uri成员指向用户请求中的URI。u_char类型的uri_start和uri_end也与request_start、method_end的用法类似,唯一不同的是,method_end指向方法名的最后一个字符,而uri_end指向URI结束后的下一个地址,也就是最后一个字符的下一个字符地址(HTTP框架的行为),这是大部分u_char类型指针对“xxx_start”和“xxx_end”变量的用法。
- ngx_str_t类型的exten成员指向用户请求的文件扩展名。
- uri_ext指针指向的地址与exten.data相同。
- unparsed_uri表示没有进行URL解析的原始请求。
3.参数
- args指向用户请求中的URL参数
- args_start指向URL参数的其实地址,配合uri_end使用也可以获得URL参数。
4.协议版本
- http_protocol的data成员指向用户请求中HTTP协议版本字符串的起始地址,len成员为协议版本字符串长度。
- http_version是Nginx解析过的协议版本,取值范围:
#define NGX_HTTP_VERSION_9 9 #define NGX_HTTP_VERSION_10 1000 #define NGX_HTTP_VERSION_11 1001 #define NGX_HTTP_VERSION_20 2000
- 使用request_start和request_end可以获取原始的用户请求行
三、获取HTTP头部
在ngx_http_request_t* r中就可以取到请求中的HTTP头部。
- header_in指向Nginx收到的未经解析的HTTP头部(header_in就是接收HTTP头部的缓冲区)。
- ngx_http_headers_in_t类型的headers_in则存储已经解析过的HTTP头部。下面介绍ngx_http_headers_in_t结构体中的成员。
typedef struct { // 所有解析过的HTTP头部都在headers链表中,headers链表的每一个元素都是ngx_table_elt_t成员 ngx_list_t headers; /* 以下每个ngx_table_elt_t成员都是RFC2616规范中定义的HTTP头部,它们都指向headers链表中的相应成员。 当它们为NULL空指针时,表示没有解析到相应的HTTP头部 */ 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; ngx_table_elt_t *accept_encoding; ngx_table_elt_t *via; ngx_table_elt_t *authorization; ngx_table_elt_t *keep_alive; ngx_array_t x_forwarded_for; ngx_table_elt_t *x_real_ip; ngx_table_elt_t *accept; ngx_table_elt_t *accept_language; ngx_table_elt_t *depth; ngx_table_elt_t *destination; ngx_table_elt_t *overwrite; ngx_table_elt_t *date; // user和passwd只有ngx_http_auth_basic_module才会用到的成员,这里可以忽略 ngx_str_t user; ngx_str_t passwd; // cookies是以ngx_array_t数组存储的,这里暂不介绍 ngx_array_t cookies; // server名称 ngx_str_t server; // 根据ngx_table_elt_t *content_length计算出的HTTP包体大小 off_t content_length_n; time_t keep_alive_n; /* HTTP连接类型 取值范围是0,NGX_HTTP_CONNECTION_CLOSE或者NGX_http_CONNECTION_KEEP_ALIVE */ unsigned connection_type:2; /* 以下标志位是HTTP框架根据浏览器传来的“useragent”头部,用来判断浏览器的类型 值为1时表示是相应的浏览器发来的请求,值为0时则相反 */ unsigned chunked:1; 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;
获取HTTP头部时,直接使用r->headers_in的相应成员就可以了。这里举例说明下如何通过遍历headers链表获取非RFC2616标准的HTTP头部,我们尝试在一个用户请求中找到“Rpc-description”头部,首先判断其值是否为“uploadFile”,在决定后续的服务器行为,代码如下:
ngx_list_part_t *part = &r->headers_in.headers.part;
ngx_table_elt_t *header = part->elts;
// 开始遍历链表
for (i = 0; /* void */; i++) {
// 判断是否到达链表中当前数组的结尾处
if (i >= part->nelts) {
// 是否还有下一个链表数组元素
if (part->next == null) {
break;
}
/*
part设置为next来访问下一个链表数组;header也指向下一个链表数组的首地址;
i设置为0时,表示从头开始遍历新的链表数组
*/
part = part->next;
header = part->elts;
i = 0;
}
// hash为0时表示不是合法的头部
if (header[i].hash == 0) {
continue;
}
/*
判断当前的头部是否为“Rpc-Description”。
如果想要忽略大小写,则应该先用header[i].lowcase_key代替header[i].key.data,然后比较字符串。
*/
if (0 == ngx_strncmp(header[i].value.data,
"uploadFile",
header[i].value.len))
{
// 找到了正确的头部,继续向下执行
}
}
- 对于常见的HTTP头部,直接获取r->header_in中已经由HTTP框架解析过的成员即可,而对于不常见的HTTP头部,需要遍历r->headers_in.headers链表才能获得。
四、获取HTTP包体
HTTP包体的长度有可能非常大,一次性调用并读取完所有的包体可能会阻塞Nginx进程。HTTP框架提供了一种异步接收包体的方法:
ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_http_client_body_handler_pt post_handler);
- ngx_http_read_client_request_body是一个异步方法。
- 该方法只是说明要求Nginx开始接收请求的包体,并不表示已经接收完。
- 当接收完所有的包体内容后,post_handler指向的回调方法会被调用。
下面看一下包体接收完毕后的回调方法原型ngx_http_client_body_handler_pt如何定义:
typedef void (*ngx_http_client_body_handler_pt)(ngx_http_request_t *r);
- 其中有参数ngx_http_request_t *r,这个请求的信息都可以从r中获得。
- 可以定义一个方法 void func(ngx_http_request_t *r),在Nginx接收完包体时调用它,后续的流程也可以写在该方法中。
- ngx_http_client_body_handler的返回值类型为void,Nginx不会根据返回值做一些收尾工作,所以我们在该方法里处理完请求时必须要主动调用ngx_http_finalize_request方法来结束请求。
接收包体时代码可以这样写:
ngx_int_t rc = ngx_http_read_client_request_body(r, ngx_http_mytest_body_handler);
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
return rc;
}
return NGX_DONE:
如果不想处理请求中的包体,那么可以调用ngx_http_discard_request_body方法将接收自客户端的HTTP包体丢弃掉。
ngx_int_t rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
- 这里ngx_http_discard_request_body方法的意义在于,有些客户端可能会一直试图发送包体,如果HTTP模块不接收发来的TCP流,有可能造成客户端发送超时。
接收完请求的包体后,可以在r->request_body->temp_file->file中获取临时文件(如果将r->request_body_in_file_only标志位设置为1,那就一定可以在这个变量获取到包体)。还可以从r->request_body->temp_file->file.name中获取Nginx接收到的请求包体所在文件的内容(包括路径)。
总结
以上就是对于在HTTP框架中如何获取处理方法的返回值、URL和参数、HTTP头部和HTTP包体的方法,我们可以在自定义的模块里运用这些方法来处理业务中定制的业务需求。