`
simohayha
  • 浏览: 1400278 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

nginx中sub_request的处理

阅读更多
首先来看subrequest的处理。

什么是subrequest,顾名思义,那就是子请求,也就是在当前的一个请求中nginx再生成一个请求。比如在nginx的HttpAddition这个filter,就有用到subrequest。

这里要注意,一个subrequest是当父reuest执行完毕后才会被执行,并且它会将所有的需要进行的handler phase重新执行一遍(这个我们后面的代码会看到).

这里还涉及到一个很关键的filter,那就是postpone filter,这个filter就用来缓存住父request,这里的缓存就是将需要发送的数据保存到一个链表中。这个是因为会先执行subrequest,然后才会执行request,因此如果有subrequest的话,这个filter就会跳过后面的发送filter,直接返回ok。

sub request是通过post pone filter以及finalize_request,还有下面的三个域来配合实现的,接下来我们会一个个的分析。

因此这里有这样三个概念,一个是postpone_request,一个是post_request,一个是post_subrequest.其实这三个也就是request的三个域了:

//这个表示主的request,也就是当前的request链中最上面的那个request,通过这个域我们就能判断当前的request是不是subrequest。
ngx_http_request_t               *main;
//这个表示当前的request的父request。
    ngx_http_request_t               *parent;
//最关键就是下面三个域。
    ngx_http_postponed_request_t     *postponed;
    ngx_http_post_subrequest_t       *post_subrequest;
    ngx_http_posted_request_t        *posted_requests;


ok,我们一个个来看,先来看postponed,这个域用来缓存父request的数据(也就是将要发送数据的request),而缓存这个动作是在postpone filter中来做的,我们后面回来分析这个filter。下面就是它的结构:

struct ngx_http_postponed_request_s {
    ngx_http_request_t               *request;
    ngx_chain_t                      *out;
    ngx_http_postponed_request_t     *next;
};


可以看到它就是一个很简单的链表,三个域的意思分别为:
request 保存了subrequest
out保存了所需要发送的chain。
next保存了下一个postpone_request.

然后是post_subrequest,这个域保存了子请求的post request,它也就是保存了需要被发送的request. 来看它的结构:

typedef struct {
    ngx_http_post_subrequest_pt       handler;
    void                             *data;
} ngx_http_post_subrequest_t;


可以看到它的结构更加简单,一个handler,保存了到时需要执行的回掉函数,一个data,保存了传递的数据。

最后是posted_requests,这个保存了所有的需要处理的request链表,也就是说它即包含子请求也包含父请求。来看它的结构:

struct ngx_http_posted_request_s {
    ngx_http_request_t               *request;
    ngx_http_posted_request_t        *next;
};


request保存了需要处理的request,next保存了下一个需要处理的request。

然后我们来详细分析sub request的处理流程以及代码,这里代码的分析顺序是按照sub request的流程来的。

首先来看sub request的设置函数ngx_http_subrequest。

这个函数的主要功能就是新建一个request,然后设置对应的属性,其中大部分属性都是和父request相同的,还有一些特殊的sub request独有的属性我们会在下面的代码中分析到(主要是我上面介绍的4个域)。

这个函数的参数比较多,有6个参数,来看它的原型:
ngx_int_t
ngx_http_subrequest(ngx_http_request_t *r,
    ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr,
    ngx_http_post_subrequest_t *ps, ngx_uint_t flags)

r表示需要生成子请求的request,uri表示子请求的uri,args表示子请求的参数,psr表示最终生成的子请求,ps表示子请求的post_subrequest,flags主要控制sub request的内容是否要放到内存中。

代码比较长,我们分开来看,下面这段是相关的初始化:

 
ngx_connection_t              *c;
    ngx_http_request_t            *sr;
    ngx_http_core_srv_conf_t      *cscf;
    ngx_http_postponed_request_t  *pr, *p;

//subrequest表示了当前还可以处理的最大个数的sub request,这个值的默认值是50.表示nginx中能够处理的最多的嵌套子请求的个数是50.
    r->main->subrequests--;

//如果为0,则表示已达到最大的限制,因此返回error。
    if (r->main->subrequests == 0) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "subrequests cycle while processing \"%V\"", uri);
        r->main->subrequests = 1;
        return NGX_ERROR;
    }
//新建一个sub request。
    sr = ngx_pcalloc(r->pool, sizeof(ngx_http_request_t));
    if (sr == NULL) {
        return NGX_ERROR;
    }

    sr->signature = NGX_HTTP_MODULE;

//设置connection。
    c = r->connection;
    sr->connection = c;

//下面的初始化大部分都是和父请求一样的。
    sr->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
    if (sr->ctx == NULL) {
        return NGX_ERROR;
    }

    if (ngx_list_init(&sr->headers_out.headers, r->pool, 20,
                      sizeof(ngx_table_elt_t))
        != NGX_OK)
    {
        return NGX_ERROR;
    }
......................................................
    sr->request_body = r->request_body;
//可以看到子请求只会是Get方法。
    sr->method = NGX_HTTP_GET;
    sr->http_version = r->http_version;

    sr->request_line = r->request_line;


接下来这段也是初始化,只不过主要是初始化一些sub request特有的属性。这里最关键的就是两个事件处理函数的赋值,read_event_handler和write_event_handler 。其中读事件的handler被赋值为一个空的函数,也就是在sub request中,不会处理读事件。而写事件的handler被赋值为ngx_http_handler,这个函数我们知道,它就是整个nginx的handler处理的入口,因此也就是说sub request最终会把所有的phase再重新走一遍。

这里还要注意,那就是父请求可能会有多个儿子请求。
//子请求的uri
    sr->uri = *uri;

    if (args) {
//参数设置
        sr->args = *args;
    }

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http subrequest \"%V?%V\"", uri, &sr->args);
//子请求的内容是否需要放到内存中。
    sr->subrequest_in_memory = (flags & NGX_HTTP_SUBREQUEST_IN_MEMORY) != 0;
//这个貌似是ssi用到的。
    sr->waited = (flags & NGX_HTTP_SUBREQUEST_WAITED) != 0;

..............................................................................

    ngx_http_set_exten(sr);
//设置main,也就是最上层的那个request。
    sr->main = r->main;
//设置父request
    sr->parent = r;
//可以看到ost_subrequest 被设置为我们传递进来的值。
    sr->post_subrequest = ps;

//读写事件的处理函数的赋值
    sr->read_event_handler = ngx_http_request_empty_handler;
    sr->write_event_handler = ngx_http_handler;

//设置连接的request为子请求。这里的意思是如果父请求设置第二个子请求的话,这里就不需要设置连接的request了。
    if (c->data == r && r->postponed == NULL) {
        c->data = sr;
    }

    sr->variables = r->variables;

    sr->log_handler = r->log_handler;

//开始赋值postponed request.
    pr = ngx_palloc(r->pool, sizeof(ngx_http_postponed_request_t));
    if (pr == NULL) {
        return NGX_ERROR;
    }
//它的request设置为子请求,也就是每个子请求都会用一个postponed request包装起来。
    pr->request = sr;
    pr->out = NULL;
    pr->next = NULL;
//如果是第一次给父请求设置孩子,那么将pr放到postponed链表的结尾。
    if (r->postponed) {
        for (p = r->postponed; p->next; p = p->next) { /* void */ }
//找到尾部,然后插入。
        p->next = pr;

    } else {
//否则直接设置
        r->postponed = pr;
    }

//设置内部标记
    sr->internal = 1;

    sr->discard_body = r->discard_body;
    sr->expect_tested = 1;
    sr->main_filter_need_in_memory = r->main_filter_need_in_memory;

    sr->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
//subrequests加1.
    r->main->subrequests++;

//保存生成的sub request,以供外部使用。
    *psr = sr;
//设置post request。
    return ngx_http_post_request(sr);
}


上面的代码有个有疑问的地方,那就是subrequests,我查了下代码只有这个函数里面有对它进行操作,可是这里前面--,后面++,那不是基本没有可能这个值是0。

前面的代码我们可以看到最后会调用ngx_http_post_reques来处理,这个函数是用来讲subrequest放到post request中的。

而post request的调用我会在后面分析到。

ngx_int_t
ngx_http_post_request(ngx_http_request_t *r)
{
    ngx_http_posted_request_t  *pr, **p;
//新建一个post request。
    pr = ngx_palloc(r->pool, sizeof(ngx_http_posted_request_t));
    if (pr == NULL) {
        return NGX_ERROR;
    }

//设置request为sub request.
    pr->request = r;
    pr->next = NULL;
//找到post request的尾部。
    for (p = &r->main->posted_requests; *p; p = &(*p)->next) { /* void */ }
//然后赋值。
    *p = pr;

    return NGX_OK;
}


然后来看postpone 这个filter,这个filter就是用来缓存父request的chain, 并且控制sub request的发送。

代码分段来看,先来看第一部分,这部分主要是处理父请求进来的情况,也就是缓存父请求的chain。

ngx_connection_t              *c;
    ngx_http_postponed_request_t  *pr;
//取得当前的链接
    c = r->connection;

//如果r不等于c->data,前面的分析知道c->data保存的是最新的一个sub request(同级的话,是第一个),因此不等于则说明是需要保存数据的父request。
    if (r != c->data) {

        if (in) {
//保存数据(下面会分析这段代码)
            ngx_http_postpone_filter_add(r, in);
//这里注意不发送任何数据,直接返回OK。而最终会在finalize_request中处理。
            return NGX_OK;
        }

        return NGX_OK;
    }

//如果r->postponed为空,则说明是最后一个sub request,也就是最新的那个,因此需要将它先发送出去。
    if (r->postponed == NULL) {

//如果in存在,则发送出去
        if (in || c->buffered) {
            return ngx_http_next_filter(r->main, in);
        }

        return NGX_OK;
    }

然后来看ngx_http_postpone_filter_add这个方法,这个方法主要是拷贝当前需要发送的chain到postponed的out域中。

这里要注意一个的就是由于filter有可能会进入多次,因此如果相同的request的in chain会拷贝到相同的posrponed request中。

还有这里要注意就是这里添加的postponed request的request域是NULL,也就是说明这个postponed request就是自己,也就是r==r->postponed->request.

static ngx_int_t
ngx_http_postpone_filter_add(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_http_postponed_request_t  *pr, **ppr;

//如果postponed存在,则进入相关处理
    if (r->postponed) {
//找到postponed的尾部
        for (pr = r->postponed; pr->next; pr = pr->next) { /* void */ }
//如果为空,则直接添加到当前的chain
        if (pr->request == NULL) {
            goto found;
        }

        ppr = &pr->next;
    } else {
        ppr = &r->postponed;
    }

    pr = ngx_palloc(r->pool, sizeof(ngx_http_postponed_request_t));
    if (pr == NULL) {
        return NGX_ERROR;
    }

    *ppr = pr;
//可以看到request是空。
    pr->request = NULL;
    pr->out = NULL;
    pr->next = NULL;

found:
//最终复制in到pr->out,也就是保存request 需要发送的数据。
    if (ngx_chain_add_copy(r->pool, &pr->out, in) == NGX_OK) {
        return NGX_OK;
    }

    return NGX_ERROR;
}


然后再回到ngx_http_postpone_filter,剩下的这段代码主要就是用来发送前面保存的父请求的chain.


//到达这里说明需要发送父请求的数据了。
if (in) {
//如果有chain,则保存数据。
        ngx_http_postpone_filter_add(r, in);
    }

//开始遍历postponed request.
    do {
        pr = r->postponed;
//如果存在request,则说明这个postponed request是sub request,因此需要将它放到post_request中。
        if (pr->request) {

            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                           "http postpone filter wake \"%V?%V\"",
                           &pr->request->uri, &pr->request->args);

            r->postponed = pr->next;

            c->data = pr->request;
//放到post request中。
            return ngx_http_post_request(pr->request);
        }

        if (pr->out == NULL) {
            ngx_log_error(NGX_LOG_ALERT, c->log, 0,
                          "http postpone filter NULL output",
                          &r->uri, &r->args);

        } else {
//说明pr->out不为空,此时需要将保存的父request的数据发送。
            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                           "http postpone filter output \"%V?%V\"",
                           &r->uri, &r->args);
//发送
            if (ngx_http_next_filter(r->main, pr->out) == NGX_ERROR) {
                return NGX_ERROR;
            }
        }

        r->postponed = pr->next;

    } while (r->postponed);


然后我们来看ngx_http_finalize_request中sub request的处理部分,其实ngx_http_finalize_request中,一大部分都是sub request的处理,

这个处理其实主要就是修改一开始介绍的request的三个域。

这里要注意,由于如果有sub request的话,postponed filter会返回NGX_OK。

还有就是所有需要处理的request都必须放入到post request中,儿postponed request中保存的是暂时缓存的不需要发送的request。
  
//如果r不等于r->main的话,则说明当前的请求是是sub request,此时进入相关处理。
 if (r != r->main) {
//如果含有postponed的话,则说明这个request并不是最后一个sub request,因此设置write handler,并且返回。
        if (r->buffered || r->postponed) {

            if (ngx_http_set_write_handler(r) != NGX_OK) {
                ngx_http_close_request(r->main, 0);
            }

            return;
        }
//取得request的父request
        pr = r->parent;

//如果r等于c->data,则说明当前是最后一个sub request,此时需要修改c->data,以便于在postponed filter中发送保存的父request的数据。
        if (r == c->data) {
...................................................................

            r->done = 1;
//如果父request的postponed存在并且它的request为当前的r,则开始处理接下来的postponed。
            if (pr->postponed && pr->postponed->request == r) {
//取next
                pr->postponed = pr->postponed->next;
            }
//修改c->data,这个将会在run_post_request中使用,接下来就会分析这个函数。
            c->data = pr;

        } else {
//否则则设置write handler.
            r->write_event_handler = ngx_http_request_finalizer;

            if (r->waited) {
                r->done = 1;
            }
        }
//最终将pr也就是父request放入到post request中。
        if (ngx_http_post_request(pr) != NGX_OK) {
            ngx_http_close_request(r->main, 0);
            return;
        }
..............................................

        return;
    }


上面有一个函数那就是ngx_http_set_write_handler,这个用来设置write handler,这里是这是write handler为ngx_http_writer,而我们要知道sub request的处理是,不停的保存父request的chain(在postponed filter中),而每次保存完毕之后就返回ngx_ok,此时由于这个request已经经历完毕所有的handler phase,因此我们就需要修改它的write handler,以便于需要发送的时候跳过handler 阶段,因此就设置ngx_http_writer,这个函数主要就是调用out_put filter,而不经过handler phase。

最后我们来看post request的调用在那里。

在nginx中,request的执行是在ngx_http_process_request中的,来看这个函数的最后两句:

//处理request,每个sub request在处理之前的write handler 都是这个函数。
ngx_http_handler(r);

//开始run post request
    ngx_http_run_posted_requests(c);


我们来详细看ngx_http_run_posted_requests的实现。这个函数就是遍历post request,然后调用它的write handler对request进行处理。

void
ngx_http_run_posted_requests(ngx_connection_t *c)
{
    ngx_http_request_t         *r;
    ngx_http_log_ctx_t         *ctx;
    ngx_http_posted_request_t  *pr;

//开始遍历
    for ( ;; ) {

//如果连接已经被销毁,则直接返回。
        if (c->destroyed) {
            return;
        }
//取出c->data(可以看到在finalize request中会修改到这个域的,也就是这个域会始终保存最新的sub request(也就是将要被发送的request).
        r = c->data;
        pr = r->main->posted_requests;

        if (pr == NULL) {
            return;
        }
//赋值为下一个。
        r->main->posted_requests = pr->next;

        r = pr->request;

        ctx = c->log->data;
        ctx->current_request = r;
//调用write handler.

        r->write_event_handler(r);
    }
}






0
0
分享到:
评论

相关推荐

    Nginx记录分析响应慢的请求及替换网站响应内容的配置

    作者的初衷是写给自己用的,用来找出站点中处理时间较长的请求, 这些请求是造成服务器高负载的很大根源. 日志记录之后,在使用perl脚本分析日志,即可知道哪些请求需要修正. 1. 模块安装 nginx第三方模块安装方法...

    Openresty_For_Windows_1.7.10.zip

    Nginx Openresty For Windows (NOW) 是带有 Openresty 的 Windows 版本中的 Nginx。 它有一些特点: 高性能 并发两万多个连接 多进程 支持共享内存 支持udp代理 与 nginx 原始版本相比修复的各种错误 它已符合...

    Nginx1.22.0版本Linux已编译可直接使用

    在实际部署中,你可能还需要配置 Nginx 的主配置文件(通常是 `/etc/nginx/nginx.conf` 或 `/usr/local/nginx/conf/nginx.conf`),定义 server 块来处理特定域名的请求,设置虚拟主机,以及定义路由规则等。...

    nginx-1.22.0打包的二进制文件

    打包内容:compat、file-aio、threads、http_addition_module、http_auth_request_module、http_dav_module、http_flv_module、http_gunzip_module、http_gzip_static_module、http_mp4_module、...

    nginx在Linux下的安装方法.pdf

    module,提供基本的Nginx状态监控功能 --with-http_secure_link_module - 启用ngx_http_secure_link_module,用于处理预签名URL,增加安全性 --with-http_auth_request_module - 启用ngx_http_auth_request_module,...

    nginx-Win32.zip

    需要用到with-stream和with-stream_realip_module,自己编译了一个 -----------------------------版本信息----------------------------------------- nginx version: nginx/1.21.1 built by cl ......

    nginx配置教程

    --with-http_auth_request_module \ --with-mail \ --with-mail_ssl_module \ --with-file-aio \ --with-ipv6 \ --with-http_v2_module \ --with-threads \ --with-stream \ --with-stream_ssl_module ```...

    nginx-1.17.9.tar.gz

    在Nginx 1.17.9中,我们可以利用以下负载均衡策略:** 1. **轮询(Round Robin)**:这是默认的负载均衡算法,每个请求按顺序分配到不同的服务器,确保所有服务器平均分担负载。 2. **最少连接数(Least ...

    nginx脚本安装

    --with-http_auth_request_module \ --with-http_slice_module \ --with-mail \ --with-mail_ssl_module \ --with-stream \ --with-stream_ssl_module \ --with-openssl=../openssl \ --with-pcre=../pcre-...

    Nginx 37道面试题及答案.docx

    * 每进来一个request,会有一个worker进程去处理,但不是全程的处理,处理到可能发生阻塞的地方,然后这个处理的worker继续处理其他请求 * 由于web server的工作性质决定了每个request的大部份生命都是在网络传输中...

    Ubuntu RTMP视频服务器搭建教程

    --with-http_auth_request_module \ --with-threads \ --with-stream \ --with-stream_ssl_module \ --with-http_slice_module \ --with-mail \ --with-mail_ssl_module \ --with-file-aio \ --with-...

    nginx配置指南

    **Nginx配置指南** Nginx是一款高性能的HTTP和反向代理服务器,以其轻量级、高并发处理能力和稳定性而被广泛应用于Web服务器领域。本文将指导您如何在CentOS系统上安装并配置Nginx,同时涵盖相关依赖软件的安装与......

    nginx安装包以及配置环境

    同时,`pcre`库用于支持Nginx的正则表达式处理。 ```bash sudo apt-get update sudo apt-get install -y gcc g++ make sudo apt-get install -y libpcre3-dev zlib1g-dev ``` ### 2. 下载Nginx源码 这里我们使用...

    Linux下mysql+PHP+nginx的搭建(已测试)

    在Linux系统中搭建一个基于MySQL、PHP和Nginx的环境是常见的Web开发配置,这种组合通常被称为LAMP(Linux, Apache, MySQL, PHP)架构,但在本例中使用的是Nginx代替Apache,所以是LNMP(Linux, Nginx, MySQL, PHP)...

Global site tag (gtag.js) - Google Analytics