Nginx开发HTTP模块(五):处理用户请求



前言

本文介绍如何处理一个实际的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包体的方法,我们可以在自定义的模块里运用这些方法来处理业务中定制的业务需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值