Nginx sub-request (subrequest) Principle Analysis

Nginx sub-request (subrequest) Principle Analysis

Nginx sub-requests which relies on its own design stage access, the realization of the url bypass request initiated by function, usually used authentication mirroring function. Of course, there are other uses not enumerate here, usually the user interface like the built-in interface or lua auth_request the capture interface. The two external interfaces are used Nginx's ngx_http_subrequestfunction. The paper which is slightly sub-request carding processes.

BACKGROUND requirements: Nginx respective data structures used to describe the request lifecycle are recognized, for example, ngx_http_request_tas well ngx_connection_t.

Simple business

A configuration file, when the external request is a / login, the first bypass initiated the request, the request url is / auth, when the url returns 200, before continuing the service processing. Of course, in order to simplify / auth logic, where direct / auth return a 2xx below, usually, is the need to complex business logic in order to complete the authentication. (Auth_request detailed features please inquire on their own, not repeat them here).

    location /login {
        auth_request /auth/;
        
        #业务处理
        proxy_pass http://xxxx;
    }

    location  /auth {
        return 200 ok;
    #   proxy_pass http://ups;
    }

Nginx is how to achieve the bypass function of it? We'll explore next.

When an external request arrives, first subjected is the Nginx HTTP conventional process, i.e. resolution protocol, then matched LOCATION, These phase processing proceeds access stage, access stage is located in the content stages (i.e. proxy_pass) before, so access stage workflow process can be performed prior to proxy_pass.

For this case, the configuration file that is above enumerated auth_request performs ngx_http_auth_request_handlerthis function access stage.

Its call stack as follows

ngx_http_auth_request_handler
ngx_http_core_access_phase
ngx_http_core_run_phases
ngx_http_handler
ngx_http_process_request
xxxxx

If you are familiar with Nginx processing flow, these call stack is actually very familiar with, ngx_http_core_run_phasesto deal with the various stages of the cycle, ngx_http_core_access_phaseis a package, the actual call is registered specific ngx_http_auth_request_handlerfunction. ngx_http_auth_request_handlerFunction is doing?

static ngx_int_t
ngx_http_auth_request_handler(ngx_http_request_t *r)
{
    ngx_http_request_t            *sr;
    ngx_http_post_subrequest_t    *ps;
    ngx_http_auth_request_ctx_t   *ctx;
    
    ctx = ngx_http_get_module_ctx(r, ngx_http_auth_request_module);

    if (ctx != NULL) {
        #第二次函数进来时会到这里,用来判断认证的状态码,而第一次进来时ctx还是空的。这里我们暂不关心。
    }
    
    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_auth_request_ctx_t));
    if (ctx == NULL) {
        return NGX_ERROR;
    }

    ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
    ps->handler = ngx_http_auth_request_done;
    ps->data = ctx;
    
    ngx_http_subrequest(r, &arcf->uri, NULL, &sr, ps, NGX_HTTP_SUBREQUEST_WAITED);
   
   
   return NGX_AGAIN;

}

The above function has two critical points, the first point is a call key ngx_http_subrequest, the second key point is return NGX_AGAIN;, to say the latter, when the return NGX_AGAIN, outside ngx_http_core_run_phaseswill exit the loop, i.e., does not perform the later stages (content phase), this is intended to complete the certification, then perform a later stage, which is consistent with "access" logic.

void
ngx_http_core_run_phases(ngx_http_request_t *r)
{
    ngx_int_t                   rc;
    ngx_http_phase_handler_t   *ph;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    ph = cmcf->phase_engine.handlers;

    while (ph[r->phase_handler].checker) {

        # ngx_http_core_access_phase 当发现 ngx_http_auth_request_handler返回 NGX_AGAIN时,会返回 NGX_OK
        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);

        if (rc == NGX_OK) {
            return;
        }
    }
}

Let's look at the second key ngx_http_subrequestfunction:

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)
{

    ngx_http_request_t            *sr;
    
    #创建一个 ngx_http_request_t ,这个就是旁路请求的 数据结构,也叫字请求
    sr = ngx_pcalloc(r->pool, sizeof(ngx_http_request_t));
    
    #根据access阶段设置的url,查找对应的location块
    ngx_http_update_location_config(sr);

    #将这个sr插入进 r的posted_requests队列。即这个字请求插入到主请求的一个队列中
    return ngx_http_post_request(sr, NULL);

}

You can see, this function simply creates a sub request data structure, and then linked to the request of the parent list, it did not seem to handle it.

Then the word processing request where it? We look back at the call stack above, and finally the use of xxxxx, because this is part of the agreement related to the HTTP request is ngx_http_process_request_headersfor HTTP2 request is ngx_http_v2_run_request, these two functions have a ngx_http_run_posted_requestsfunction (see source code it yourself here, the number of lines too the more not listed). This function is the previously generated sub-request processing cycle.

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

    for ( ;; ) {

        if (c->destroyed) {
            return;
        }

        r = c->data;
        pr = r->main->posted_requests;

        if (pr == NULL) {
            return;
        }

        r->main->posted_requests = pr->next;

        #这个r就是ngx_http_subrequest函数生成的sr
        r = pr->request;

        ngx_http_set_log_request(c->log, r);

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                       "http posted request: \"%V?%V\"", &r->uri, &r->args);

        #write_event_handler 就是 ngx_http_handler,即和主请求一样,从头跑一边,所以也可以理解为子请求的逻辑可以使用Nginx所有的功能。
        r->write_event_handler(r);
    }
}

So here we see the sub request has been processed, the processing entrance is ngx_http_handler, namely sub-request will be processed from the beginning again. So, sub-request processing is complete, how to awaken their own parent request to continue processing the rest of the stage it? Here it relates to the HTTP request to release the stage, any HTTP request, after its processing is complete (i.e., in response to completion of transmission), it performs the ngx_http_finalize_requestfunction:

ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
{

    ......
    
    #这里是回调函数,对于auth_request模块,是在 `ngx_http_auth_request_handler`函数中注册的`ngx_http_auth_request_done`,这不是重点。
    if (r != r->main && r->post_subrequest) {
        rc = r->post_subrequest->handler(r, r->post_subrequest->data, rc);
    }

    ......
    
    #这是子请求的判断,原始请求 r和r->main是一样的,但是子请求r是自己,r->main是父请求
    if (r != r->main) {
        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

        if (r->background) {

        }

        #获取到父请求
        pr = r->parent;

        if (r == c->data) {
            #父请求的引用计数减去
            r->main->count--;

            if (!r->logged) {
                if (clcf->log_subrequest) {
                    ngx_http_log_request(r);
                }

                r->logged = 1;

            } else {
                ngx_log_error(NGX_LOG_ALERT, c->log, 0,
                              "subrequest: \"%V?%V\" logged again",
                              &r->uri, &r->args);
            }

            r->done = 1;

            if (pr->postponed && pr->postponed->request == r) {
                pr->postponed = pr->postponed->next;
            }

            c->data = pr;

        } else {

        }

        #这里pr是父请求,将自己放到自己的post队列里面,这个是唤醒父请求的关键
        if (ngx_http_post_request(pr, NULL) != NGX_OK) {
            r->main->count++;
            ngx_http_terminate_request(r, 0);
            return;
        }

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

        return;
    }

}

Note that, at this time ngx_http_finalize_requestthe function is still our ngx_http_run_posted_requestsmedium, so the above will request the Father himself calls ngx_http_post_requestit makes sense, so that when the outer loop, can execute the handler parent request.

In this way, the parent request is restored. From the access stage and then execute (because the return is first performed AGAIN), we'll look back ngx_http_auth_request_handler, when we enter the second is how.

static ngx_int_t
ngx_http_auth_request_handler(ngx_http_request_t *r)
{

    #这里是上文我们忽略的地方,而现在需要重点分析
    
    if (ctx != NULL) {
        if (!ctx->done) {
            return NGX_AGAIN;
        }

        /*
         * as soon as we are done - explicitly set variables to make
         * sure they will be available after internal redirects
         */

        if (ngx_http_auth_request_set_variables(r, arcf, ctx) != NGX_OK) {
            return NGX_ERROR;
        }

        #下面是错误码,非200的,就返回错误码,只有2xx的认证结果,本函数才返回NGX_OK,即外层的阶段循环,会执行后续的阶段
        /* return appropriate status */

        if (ctx->status == NGX_HTTP_FORBIDDEN) {
            return ctx->status;
        }

        if (ctx->status == NGX_HTTP_UNAUTHORIZED) {
            sr = ctx->subrequest;

            h = sr->headers_out.www_authenticate;

            if (!h && sr->upstream) {
                h = sr->upstream->headers_in.www_authenticate;
            }

            if (h) {
                ho = ngx_list_push(&r->headers_out.headers);
                if (ho == NULL) {
                    return NGX_ERROR;
                }

                *ho = *h;

                r->headers_out.www_authenticate = ho;
            }

            return ctx->status;
        }

        if (ctx->status >= NGX_HTTP_OK
            && ctx->status < NGX_HTTP_SPECIAL_RESPONSE)
        {
            return NGX_OK;
        }

        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "auth request unexpected status: %ui", ctx->status);

        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
}
发布了122 篇原创文章 · 获赞 117 · 访问量 50万+

Guess you like

Origin blog.csdn.net/mrpre/article/details/104634504