Mercurial > hg > nginx-site
changeset 1960:9550ea66abdd
HTTP response section of the development guide.
author | Roman Arutyunyan <arut@nginx.com> |
---|---|
date | Mon, 10 Apr 2017 15:09:06 +0300 |
parents | d0aebb2337ec |
children | dd4b6c564e10 |
files | xml/en/docs/dev/development_guide.xml |
diffstat | 1 files changed, 562 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/xml/en/docs/dev/development_guide.xml +++ b/xml/en/docs/dev/development_guide.xml @@ -4634,6 +4634,568 @@ expression and put result into <literal> </section> +<section name="Response" id="http_response"> + +<para> +An HTTP response in nginx is produced by sending the response header followed by +the optional response body. +Both header and body are passed through a chain of filters and eventually get +written to the client socket. +An nginx module can install its handler into the header or body filter chain +and process the output coming from the previous handler. +</para> + + +<section name="Response header" id="http_response_header"> + +<para> +Output header is sent by the function +<literal>ngx_http_send_header(r)</literal>. +Prior to calling this function, <literal>r->headers_out</literal> should contain +all the data required to produce the HTTP response header. +It's always required to set the <literal>status</literal> field of +<literal>r->headers_out</literal>. +If the response status suggests that a response body follows the header, +<literal>content_length_n</literal> can be set as well. +The default value for this field is -1, which means that the body size is +unknown. +In this case, chunked transfer encoding is used. +To output an arbitrary header, <literal>headers</literal> list should be +appended. +</para> + +<programlisting> +static ngx_int_t +ngx_http_foo_content_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_table_elt_t *h; + + /* send header */ + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = 3; + + /* X-Foo: foo */ + + h = ngx_list_push(&r->headers_out.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = 1; + ngx_str_set(&h->key, "X-Foo"); + ngx_str_set(&h->value, "foo"); + + rc = ngx_http_send_header(r); + + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + return rc; + } + + /* send body */ + + ... +} +</programlisting> + +</section> + + +<section name="Header filters" id="http_header_filters"> + +<para> +The <literal>ngx_http_send_header(r)</literal> function invokes the header +filter chain by calling the top header filter handler +<literal>ngx_http_top_header_filter</literal>. +It's assumed that every header handler calls the next handler in chain until +the final handler <literal>ngx_http_header_filter(r)</literal> is called. +The final header handler constructs the HTTP response based on +<literal>r->headers_out</literal> and passes it to the +<literal>ngx_http_writer_filter</literal> for output. +</para> + +<para> +To add a handler to the header filter chain, one should store its address in +<literal>ngx_http_top_header_filter</literal> global variable at configuration +time. +The previous handler address is normally stored in a module's static variable +and is called by the newly added handler before exiting. +</para> + +<para> +The following is an example header filter module, adding the HTTP header +"X-Foo: foo" to every output with the status 200. +</para> + +<programlisting> +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +static ngx_int_t ngx_http_foo_header_filter(ngx_http_request_t *r); +static ngx_int_t ngx_http_foo_header_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_foo_header_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_foo_header_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_foo_header_filter_module = { + NGX_MODULE_V1, + &ngx_http_foo_header_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; + + +static ngx_int_t +ngx_http_foo_header_filter(ngx_http_request_t *r) +{ + ngx_table_elt_t *h; + + /* + * The filter handler adds "X-Foo: foo" header + * to every HTTP 200 response + */ + + if (r->headers_out.status != NGX_HTTP_OK) { + return ngx_http_next_header_filter(r); + } + + h = ngx_list_push(&r->headers_out.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = 1; + ngx_str_set(&h->key, "X-Foo"); + ngx_str_set(&h->value, "foo"); + + return ngx_http_next_header_filter(r); +} + + +static ngx_int_t +ngx_http_foo_header_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_foo_header_filter; + + return NGX_OK; +} +</programlisting> + +</section> + +</section> + + +<section name="Response body" id="http_response_body"> + +<para> +Response body is sent by calling the function +<literal>ngx_http_output_filter(r, cl)</literal>. +The function can be called multiple times. +Each time it sends a part of the response body passed as a buffer chain. +The last body buffer should have the <literal>last_buf</literal> flag set. +</para> + +<para> +The following example produces a complete HTTP output with "foo" as its body. +In order for the example to work not only as a main request but as a subrequest +as well, the <literal>last_in_chain_flag</literal> is set in the last buffer +of the output. +The <literal>last_buf</literal> flag is set only for the main request since +a subrequest's last buffers does not end the entire output. +</para> + +<programlisting> +static ngx_int_t +ngx_http_bar_content_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t out; + + /* send header */ + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = 3; + + rc = ngx_http_send_header(r); + + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + return rc; + } + + /* send body */ + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NGX_ERROR; + } + + b->last_buf = (r == r->main) ? 1: 0; + b->last_in_chain = 1; + + b->memory = 1; + + b->pos = (u_char *) "foo"; + b->last = b->pos + 3; + + out.buf = b; + out.next = NULL; + + return ngx_http_output_filter(r, &out); +} +</programlisting> + +</section> + + +<section name="Body filters" id="http_body_filters"> + +<para> +The function <literal>ngx_http_output_filter(r, cl)</literal> invokes the +body filter chain by calling the top body filter handler +<literal>ngx_http_top_body_filter</literal>. +It's assumed that every body handler calls the next handler in chain until +the final handler <literal>ngx_http_write_filter(r, cl)</literal> is called. +</para> + +<para> +A body filter handler receives a chain of buffers. +The handler is supposed to process the buffers and pass a possibly new chain to +the next handler. +It's worth noting that the chain links <literal>ngx_chain_t</literal> of the +incoming chain belong to the caller. +They should never be reused or changed. +Right after the handler completes, the caller can use its output chain links +to keep track of the buffers it has sent. +To save the buffer chain or to substitute some buffers before sending further, +a handler should allocate its own chain links. +</para> + +<para> +Following is the example of a simple body filter counting the number of +body bytes. +The result is available as the <literal>$counter</literal> variable which can be +used in the access log. +</para> + +<programlisting> +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +typedef struct { + off_t count; +} ngx_http_counter_filter_ctx_t; + + +static ngx_int_t ngx_http_counter_body_filter(ngx_http_request_t *r, + ngx_chain_t *in); +static ngx_int_t ngx_http_counter_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_counter_add_variables(ngx_conf_t *cf); +static ngx_int_t ngx_http_counter_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_counter_filter_module_ctx = { + ngx_http_counter_add_variables, /* preconfiguration */ + ngx_http_counter_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_counter_filter_module = { + NGX_MODULE_V1, + &ngx_http_counter_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_body_filter_pt ngx_http_next_body_filter; + +static ngx_str_t ngx_http_counter_name = ngx_string("counter"); + + +static ngx_int_t +ngx_http_counter_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + ngx_chain_t *cl; + ngx_http_counter_filter_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_counter_filter_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_http_set_ctx(r, ctx, ngx_http_counter_filter_module); + } + + for (cl = in; cl; cl = cl->next) { + ctx->count += ngx_buf_size(cl->buf); + } + + return ngx_http_next_body_filter(r, in); +} + + +static ngx_int_t +ngx_http_counter_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, + uintptr_t data) +{ + u_char *p; + ngx_http_counter_filter_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module); + if (ctx == NULL) { + v->not_found = 1; + return NGX_OK; + } + + p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); + if (p == NULL) { + return NGX_ERROR; + } + + v->data = p; + v->len = ngx_sprintf(p, "%O", ctx->count) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_counter_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var; + + var = ngx_http_add_variable(cf, &ngx_http_counter_name, 0); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = ngx_http_counter_variable; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_counter_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_body_filter = ngx_http_top_body_filter; + ngx_http_top_body_filter = ngx_http_counter_body_filter; + + return NGX_OK; +} +</programlisting> + +</section> + + +<section name="Building filter modules" id="http_building_filter_modules"> + +<para> +When writing a body or header filter, a special care should be taken of the +filters order. +There's a number of header and body filters registered by nginx standard +modules. +It's important to register a filter module in the right place in respect to +other filters. +Normally, filters are registered by modules in their postconfiguration handlers. +The order in which filters are called is obviously the reverse of when they are +registered. +</para> + +<para> +A special slot <literal>HTTP_AUX_FILTER_MODULES</literal> for third-party filter +modules is provided by nginx. +To register a filter module in this slot, the <literal>ngx_module_type</literal> +variable should be set to the value of <literal>HTTP_AUX_FILTER</literal> in +module's configuration. +</para> + +<para> +The following example shows a filter module config file assuming it only has +one source file <literal>ngx_http_foo_filter_module.c</literal> +</para> + +<programlisting> +ngx_module_type=HTTP_AUX_FILTER +ngx_module_name=ngx_http_foo_filter_module +ngx_module_srcs="$ngx_addon_dir/ngx_http_foo_filter_module.c" + +. auto/module +</programlisting> + +</section> + + +<section name="Buffer reuse" id="http_body_buffers_reuse"> + +<para> +When issuing or altering a stream of buffers, it's often desirable to reuse the +allocated buffers. +A standard approach widely adopted in nginx code is to keep two buffer chains +for this purpose: <literal>free</literal> and <literal>busy</literal>. +The <literal>free</literal> chain keeps all free buffers. +These buffers can be reused. +The <literal>busy</literal> chain keeps all buffers sent by the current +module which are still in use by some other filter handler. +A buffer is considered in use if its size is greater than zero. +Normally, when a buffer is consumed by a filter, its <literal>pos</literal> +(or <literal>file_pos</literal> for a file buffer) is moved towards +<literal>last</literal> (<literal>file_last</literal> for a file buffer). +Once a buffer is completely consumed, it's ready to be reused. +To update the <literal>free</literal> chain with newly freed buffers, +it's enough to iterate over the <literal>busy</literal> chain and move the zero +size buffers at the head of it to <literal>free</literal>. +This operation is so common that there is a special function +<literal>ngx_chain_update_chains(free, busy, out, tag)</literal> which does +this. +The function appends the output chain <literal>out</literal> to +<literal>busy</literal> and moves free buffers from the top of +<literal>busy</literal> to <literal>free</literal>. +Only the buffers with the given <literal>tag</literal> are reused. +This lets a module reuse only the buffers allocated by itself. +</para> + +<para> +The following example is a body filter inserting the “foo” string before each +incoming buffer. +The new buffers allocated by the module are reused if possible. +Note that for this example to work properly, it's also required to set up a +header filter and reset <literal>content_length_n</literal> to -1, which is +beyond the scope of this section. +</para> + +<programlisting> +typedef struct { + ngx_chain_t *free; + ngx_chain_t *busy; +} ngx_http_foo_filter_ctx_t; + + +ngx_int_t +ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t *cl, *tl, *out, **ll; + ngx_http_foo_filter_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_foo_filter_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_http_set_ctx(r, ctx, ngx_http_foo_filter_module); + } + + /* create a new chain "out" from "in" with all the changes */ + + ll = &out; + + for (cl = in; cl; cl = cl->next) { + + /* append "foo" in a reused buffer if possible */ + + tl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (tl == NULL) { + return NGX_ERROR; + } + + b = tl->buf; + b->tag = (ngx_buf_tag_t) &ngx_http_foo_filter_module; + b->memory = 1; + b->pos = (u_char *) "foo"; + b->last = b->pos + 3; + + *ll = tl; + ll = &tl->next; + + /* append the next incoming buffer */ + + tl = ngx_alloc_chain_link(r->pool); + if (tl == NULL) { + return NGX_ERROR; + } + + tl->buf = cl->buf; + *ll = tl; + ll = &tl->next; + } + + *ll = NULL; + + /* send the new chain */ + + rc = ngx_http_next_body_filter(r, out); + + /* update "busy" and "free" chains for reuse */ + + ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, + (ngx_buf_tag_t) &ngx_http_foo_filter_module); + + return rc; +} +</programlisting> + +</section> + + <section name="Load balancing" id="http_load_balancing"> <para>