How to return a simple page

Let’s see what we need to do in order to return a simple page to a client using Nginx module. We need to generate a header and a body of the response. To send a response header we use function ngx_http_send_header:

#include <ngx_http.h>
ngx_int_t ngx_http_send_header(ngx_http_request_t *r);

The only argument r is the request for which you want to generate and send a header. This function serializes headers from the list of response headers r->headers_out into a buffer and sends this buffer to a client or queues this buffer into output queue r->out if the client socket is not writeable. The HTTP version in the status line will be determined for you automatically. The HTTP status code is taken from r->headers_out.status and the status text will be filled according to the status code.

Lets see how we can add some custom header line to our response header.

The list r->headers_out.headers contains all the header lines that will be sent in the response header. It is possible to modify this list at any time. Let’s see how we can do that:

            h = ngx_list_push(&r->headers_out.headers);
            if (h == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }
            h->key.len = sizeof("X-Custom-Header") - 1;
            h->key.data = (u_char *) "X-Custom-Header";
            h->value.len = sizeof("test") - 1;
            h->value.data = (u_char *) "test";

This code adds a response header “X-Custom-Header” with value “test”.

The function ngx_http_send_header returns following statuses:

Status Meaning
NGX_OK The header has been successfully sent
NGX_AGAIN Data is being sent to the client
NGX_ERROR, NGX_HTTP_… An error has occurred while generating or sending the header

Now what would you do if ngx_http_send_header returns NGX_AGAIN? Basically treat it as NGX_OK. This is because implementing additional logic for NGX_AGAIN would be too complex and Nginx will handle it for you anyway. Remember that header data will be queued as well as body data. If the client socket never becomes writeable, the content of the queue will be simply discarded.

Before the header is serialized, Nginx calls header filters. In fact it happens within the call to ngx_http_send_header. Some filter might decide, that the body is not needed. Be prepared, that r->header_only might be changed when ngx_http_send_header returns. Here is an example of code that prepares and sends a response header, finishing the handler in case of an error or if the body is not needed:

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = ngx_http_sample_text.len;
    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

In order to sent the body of a response you need to allocate a buffer chain and put one or more buffers with the body content into it. After you have created the chain, call function ngx_http_output_filter:

ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in);

The arguments of this function are:

r — request, for which output filters will be called;

in — buffer chain that contains the response body.

The function returns following statuses:

Status Meaning
NGX_OK All buffers have been successfully sent or queued
NGX_AGAIN Data is being sent to the client
NGX_ERROR An error has occurred while sending the data or processing the buffer chain

What is convenient is that this function can accept chain links that are allocated on stack. The buffers that contain a response body might be allocated in the initialized constants segment, in the memory pool of a request or in the memory pool of the configuration.

Essentially it’s a bad idea to hold large quantities of data in the memory. To solve this problem, a buffer is allowed to point to a sequence of bytes in a file. In that case it’s in_file field is set to 1, file field points to the file, file_pos field points to the first byte of the sequence and file_last field points to the next byte after the last byte of the sequence. When such buffer is submitted to ngx_http_output_filter, Nginx takes care of loading data from the specified file into the memory in chunks or uses sendfile system call if possible.

By manipulating a buffer chain, one can easily concatenate large parts of the same file or multiple files and/or interleave them with portions of in-memory data.

Here is an example of code that creates and queues a buffer chain:

    ngx_int_t     rc;
    ngx_buf_t    *b;
    ngx_chain_t   out;
    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    out.buf = b;
    out.next = NULL;
    b->start = b->pos = ngx_http_sample_text.data;
    b->end = b->last = ngx_http_sample_text.data + ngx_http_sample_text.len;
    b->memory = 1;
    b->last_buf = 1;
    b->last_in_chain = 1;
    return ngx_http_output_filter(r, &out);

Now let’s put everything together and create a module that returns constant text in response:

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static char *ngx_http_sample_module_command(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_command_t  ngx_http_sample_commands[] = {
    { ngx_string("sample"),
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
      ngx_http_sample_module_command,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },
      ngx_null_command
};
ngx_http_module_t  ngx_http_sample_module_ctx = {
    NULL,                                  /* preconfiguration */
    NULL,                                  /* postconfiguration */
    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */
    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */
    NULL,                                  /* create location configration */
    NULL                                   /* merge location configration */
};
ngx_module_t  ngx_http_sample_module = {
    NGX_MODULE_V1,
    &ngx_http_sample_module_ctx,           /* module context */
    ngx_http_sample_commands,              /* 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_str_t  ngx_http_sample_text = ngx_string(
    "I'm so powerful on stage that I seem to have created a monster.\n" \
    "When I'm performing I'm an extrovert, yet inside I'm a completely different man."
);
static ngx_int_t
ngx_http_sample_handler(ngx_http_request_t *r)
{
    ngx_int_t     rc;
    ngx_buf_t    *b;
    ngx_chain_t   out;
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = ngx_http_sample_text.len;
    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }
    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    out.buf = b;
    out.next = NULL;
    b->start = b->pos = ngx_http_sample_text.data;
    b->end = b->last = ngx_http_sample_text.data + ngx_http_sample_text.len;
    b->memory = 1;
    b->last_buf = 1;
    b->last_in_chain = 1;
    return ngx_http_output_filter(r, &out);
}
static char *
ngx_http_sample_module_command(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_sample_handler;
    return NGX_CONF_OK;
}

Note that we fill the r->headers_out.content_length_n field. This is because we must return “Content-Length” header whenever we know the length of the body. In our example the length of the constant string is always known, so it is always possible to determine the length of the body. The function ngx_http_send_header conveniently converts integer value of this field into a header line.

To try out the module configure it in some location:

        location /test {
            sample;
        }

Below is the example output of the module in a browser:

Here you can download an archive that contains complete source code of the module and a configuration file for it.

This entry was posted in nginx. Bookmark the permalink.