subrequest简介
subrequest,即子请求,它是nginx中的一个概念。它是在当前请求中发起的一个新的请求。比较常见的用法是用利用subrequest来访问一个或者多个upstream的后端,然后以同步或者异步的方式处理返回结果。subrequest最擅长处理的是输出逻辑,在nginx内部已经内建有完整的机制来实现(见ssi模块)。但很多业务逻辑,并不是只有输出逻辑,还有筛选、计算、排序等等逻辑,如果需要以nginx模块的形式来开发的话,利用subrequst仍然是最简单的方法,但复杂程度会比较高。
利用subrequest进行模块开发
subrequest调用法
ngx_int_t ngx_str_t ngx_http_request_t ngx_http_post_subrequest_t loc.len = sizeof(“/test”) -1; loc.data = (u_char *)“/test”; args.len =sizeof("arg1=A&arg2=B”) - 1; args.data = (u_char *)"arg1=A&arg2=B”; psr =ngx_palloc(r->pool,sizeof(ngx_http_post_subrequest_t)); if (psr == NULL){ } psr->data =callback_data; psr->handler= callback_handler; flags =NGX_HTTP_SUBREQUEST_IN_MEMORY|NGX_HTTP_SUBREQUEST_WAITED; rc = ngx_http_subrequest(r,& loc, &args, &sr,psr, flags); |
调用subrequest是利用ngx_http_subrequest函数,这个函数将当前的请求r作为子请求的骨架,再利用传入的loc和args更新子请求的uri和输入参数,将psr作为子请求完成后的回调,最后用flags更新子请求的行为标志,生成的子请求的数据结构ngx_http_request_t会通过sr返回给调用者。
flags有两个标志:
l
l
回调数据ngx_http_post_subrequest_t是子请求处理完成后在ngx_http_finalize_request调用的,如果没有回调函数,调用ngx_http_subrequest时将psr设为NULL。回调函数的声明是ngx_int_t callback_handler(ngx_http_request_t *r,void *data, ngx_int_t rc)
这里的r是子请求,它的父请求是r->parent,它的祖先请求是r->main。data是定义callback_handler的psr中同时定义的data,可以为NULL,设置为NULL时回调函数的data参数就是NULL,rc是子请求处理的返回值,这个对于不同的子请求肯定是不一样的,但一般说来,子请求正常完成,rc一定是NGX_OK。如果子请求失败了,会返回NGX_ERROR等等错误代码(负值)或者子请求所用协议的状态码(比如子请求使用proxy模块作为Handler的话会返回403、501等等HTTP状态)。
subrequest运行时
subrequest是一种复杂机制,所以无法像之前那样给出简单的例子。这里需要简单分析subrequest的运行机制,只是很简单的分析。
调用ngx_http_subrequest |
处理主请求 |
继续处理主请求,返回 |
执行子请求 |
执行子请求完毕,调用回调,设置r->done |
继续处理主请求 |
可以看到,当我们调用ngx_http_subrequest,我们只是告诉nginx,我们有一个子请求需要执行,而这时子请求还没有执行。主请求根据自己的情况决定是否继续执行下去,还是先让子请求执行。子请求执行的时机一定是在主请求交出执行权力以后。子请求执行完毕后,父请求会继续执行。“继续”是宏观意义上的,具体的意思我们在《Nginx模块开发(五)》中详细讨论,大致的说法就是,nginx知道自己应该从哪个处理函数开始执行主请求,但是函数内部的执行状态,nginx不知道,需要函数自己维护。唯一的例外是输出逻辑,nginx非常清楚自己应该怎么执行下去。
subrequest与Filter
Filter中使用subrequest可以使用类似于下面的代码段。这个例子是需要处理有多个subrequest的情况(如果只有一个,可以不用subrequest数组):
typedef struct { }my_module_subrequest_conf_t; static ngx_int_t my_module_body_filter(ngx_http_request_t *r, ngx_chain_t*in) { } |
首先,现在的ngx_http_subrequest只会返回NGX_OK或者NGX_ERROR,可能古老的版本情况会多一些,现在已经遇不到了。其次,r != r->main的判断是必须的,因为filter的全局性。再次,只要创建一个subrequest失败,就应该立即返回。最后,filter函数是可能被多次执行的,其中有可能出现in是NULL的情况,这点要注意。
上面的例子负责发送所有的子请求,如果子请求的逻辑是直接输出内容,那么不需要添加其他代码,nginx会帮助我们自动完成后面的工作。如果需要等待子请求处理完成并一次性对子请求输出做过滤后再输出,那么情况要复杂一些,需要自己判断子请求是否处理完毕,同时还要自行处理子请求的返回。整个框架类似下面的代码:
static ngx_int_t my_module_body_filter(ngx_http_request_t *r, ngx_chain_t*in) { } |
这里首先要设置flag,使子请求不输出结果并在结束时设置sr->done属性。其次是判断所有请求是否处理完毕,使用的方法是判断所有的sr->done标记是否为1。最后我们需要自己去子请求的数据结构(sr)中取得子请求的结果,并自己处理这些结果。
利用这种方式时,需要注意NGX_HTTP_SUBREQUEST_IN_MEMORY标记是否被子请求所支持。如果不支持的话,建议仅使用nginx作为网关,将请求转发到后端php\python\java等等,然后在这些后端实现逻辑。
最后还有一种情况,某请求需要发送一个子请求,等待子请求完成,根据这个子请求结果发送下一个子请求。那么在filter中需要记录当前的处理状态,类似于下面这段代码:
static ngx_int_t my_module_body_filter(ngx_http_request_t *r, ngx_chain_t*in) { } |
我们这里使用ctx->i和subrequest->sr联合起来保存当前处理状态。ctx->i保存处理到第几个子请求的状态,subrequest->sr反映当前子请求是未发送还是已发送的状态。
subrequest与Handler
在Handler中使用subrequest需要多一些处理挂接nginx的事件的逻辑,这是与在Filter中使用subrequest最大的不同,因为对于后者,nginx有一块逻辑自动设置事件,形成一个可以运动的循环,是Filter能够一直正确的处理,而在Handler中使用subrequest,我们其实是破坏了nginx本身的循环,所以需要编程人员自己构建一个事件循环来完成自己的工作。
我们在这里只介绍一种最容易理解的循环。画个流程图来展现这个循环:
nginx使用ngx_http_core_run_phases处理主请求 |
nginx使用ngx_http_request_handler处理子请求 |
nginx使用ngx_http_core_run_phases继续处理主请求 |
但是nginx默认的循环不是它,实际上,大部分情况下,nginx使用下面循环:
nginx使用ngx_http_core_run_phases处理主请求 |
nginx使用ngx_http_request_handler处理子请求 |
nginx使用ngx_http_writer继续处理主请求 |
所以,我们的程序需要如下设计:
1.
2.
3.
4.
一个简单的轮回如下例:
typedef struct { } my_ctx_t; static ngx_int_t my_handler(ngx_http_request_t *r) { } } static ngx_int_t my_subrequest_post_handler(ngx_http_request_t *r, void *data, ngx_int_t rc) { if ( ) { } |
大家可以注意代码里面的几点:
1.
2.
r->parent->write_event_handler= ngx_http_core_run_phases
3.
r->main->count++;
return NGX_DONE;