# HG changeset patch # User Roman Arutyunyan # Date 1491826146 -10800 # Node ID 9550ea66abdddac1623d5adad1aaf8cf8516b5d7 # Parent d0aebb2337ec5ea1688ff0e4c49b81e754f398c2 HTTP response section of the development guide. diff -r d0aebb2337ec -r 9550ea66abdd xml/en/docs/dev/development_guide.xml --- a/xml/en/docs/dev/development_guide.xml Fri Apr 07 18:07:18 2017 +0300 +++ b/xml/en/docs/dev/development_guide.xml Mon Apr 10 15:09:06 2017 +0300 @@ -4634,6 +4634,568 @@ +
+ + +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. + + + +
+ + +Output header is sent by the function +ngx_http_send_header(r). +Prior to calling this function, r->headers_out should contain +all the data required to produce the HTTP response header. +It's always required to set the status field of +r->headers_out. +If the response status suggests that a response body follows the header, +content_length_n 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, headers list should be +appended. + + + +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 */ + + ... +} + + +
+ + +
+ + +The ngx_http_send_header(r) function invokes the header +filter chain by calling the top header filter handler +ngx_http_top_header_filter. +It's assumed that every header handler calls the next handler in chain until +the final handler ngx_http_header_filter(r) is called. +The final header handler constructs the HTTP response based on +r->headers_out and passes it to the +ngx_http_writer_filter for output. + + + +To add a handler to the header filter chain, one should store its address in +ngx_http_top_header_filter 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. + + + +The following is an example header filter module, adding the HTTP header +"X-Foo: foo" to every output with the status 200. + + + +#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; +} + + +
+ +
+ + +
+ + +Response body is sent by calling the function +ngx_http_output_filter(r, cl). +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 last_buf flag set. + + + +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 last_in_chain_flag is set in the last buffer +of the output. +The last_buf flag is set only for the main request since +a subrequest's last buffers does not end the entire output. + + + +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); +} + + +
+ + +
+ + +The function ngx_http_output_filter(r, cl) invokes the +body filter chain by calling the top body filter handler +ngx_http_top_body_filter. +It's assumed that every body handler calls the next handler in chain until +the final handler ngx_http_write_filter(r, cl) is called. + + + +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 ngx_chain_t 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. + + + +Following is the example of a simple body filter counting the number of +body bytes. +The result is available as the $counter variable which can be +used in the access log. + + + +#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; +} + + +
+ + +
+ + +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. + + + +A special slot HTTP_AUX_FILTER_MODULES for third-party filter +modules is provided by nginx. +To register a filter module in this slot, the ngx_module_type +variable should be set to the value of HTTP_AUX_FILTER in +module's configuration. + + + +The following example shows a filter module config file assuming it only has +one source file ngx_http_foo_filter_module.c + + + +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 + + +
+ + +
+ + +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: free and busy. +The free chain keeps all free buffers. +These buffers can be reused. +The busy 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 pos +(or file_pos for a file buffer) is moved towards +last (file_last for a file buffer). +Once a buffer is completely consumed, it's ready to be reused. +To update the free chain with newly freed buffers, +it's enough to iterate over the busy chain and move the zero +size buffers at the head of it to free. +This operation is so common that there is a special function +ngx_chain_update_chains(free, busy, out, tag) which does +this. +The function appends the output chain out to +busy and moves free buffers from the top of +busy to free. +Only the buffers with the given tag are reused. +This lets a module reuse only the buffers allocated by itself. + + + +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 content_length_n to -1, which is +beyond the scope of this section. + + + +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; +} + + +
+ +