NAV

Introduction

Welcome to my Nginx module guide!

To follow this guide, you need to know a decent amount of C. You should know about structs, pointers, and functions. You also need to know how the nginx.conf file works.

If you find a mistake in the guide, please report it in an issue!

The Handler Guide

Let’s get started with a quick hello world module called ngx_http_hello_world_module.

This module will be a handler, meaning that it will take a request and generate output.

In the nginx source, create a folder called ngx_http_hello_world_module, and make two files in it: config and ngx_http_hello_world_module.c.

The Config File

config

ngx_addon_name=ngx_http_hello_world_module

if test -n "$ngx_module_link"; then
    # The New Way
    ngx_module_type=HTTP
    ngx_module_name=ngx_http_hello_world_module
    ngx_module_srcs="$ngx_addon_dir/ngx_http_hello_world_module.c"

    . auto/module
else
    # The Old Way
    HTTP_MODULES="$HTTP_MODULES ngx_http_hello_world_module"
    NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_world_module.c"
fi

The config file is just a simple shell script that will be used at compile time to show Nginx where your module source is. As you can see, the config file tests to see if your nginx version supports dynamic modules (the test -n line). If it supports dynamic modules, the module is added the new way. Otherwise, it is added the old way.

The C File

ngx_http_hello_world_module.c


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

static char *ngx_http_hello_world(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

static ngx_command_t  ngx_http_hello_world_commands[] = {
  {
    ngx_string("print_hello_world"),
    NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
    ngx_http_hello_world,
    0,
    0,
    NULL
  },
    ngx_null_command
};

static ngx_http_module_t  ngx_http_hello_world_module_ctx = {
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL
};

ngx_module_t ngx_http_hello_world_module = {
  NGX_MODULE_V1,
  &ngx_http_hello_world_module_ctx,
  ngx_http_hello_world_commands,
  NGX_HTTP_MODULE,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NGX_MODULE_V1_PADDING
};

static ngx_int_t ngx_http_hello_world_handler(ngx_http_request_t *r)
{
  u_char *ngx_hello_world = (u_char *) "Hello World!";
  size_t sz = strlen(ngx_hello_world);

  r->headers_out.content_type.len = strlen("text/html") - 1;
  r->headers_out.content_type.data = (u_char *) "text/html";
  r->headers_out.status = NGX_HTTP_OK;
  r->headers_out.content_length_n = sz;
  ngx_http_send_header(r);

  ngx_buf_t    *b;
  ngx_chain_t   *out;

  b = ngx_calloc_buf(r->pool);

  out = ngx_alloc_chain_link(r->pool);

  out->buf = b;
  out->next = NULL;

  b->pos = ngx_hello_world;
  b->last = ngx_hello_world + sz;
  b->memory = 1;
  b->last_buf = 1;

  return ngx_http_output_filter(r, out);
}

static char *ngx_http_hello_world(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_hello_world_handler;
  return NGX_CONF_OK;
}

This C file is huge! Let’s go through it line by line:

The first line is a prototype for the function ngx_http_hello_world. We’ll define the function at the end of the file.

ngx_http_hello_world_commands is a static array of directives. In our module, we have only one directive: print_hello_world. It will have no arguments, so we put in NGX_CONF_NOARGS.

ngx_http_hello_world_module_ctx is an array of function references. The functions will be executed for various purposes such as preconfiguration, postconfiguration, etc. We don’t need this array in our module, but we still have to define it and fill it with NULLs.

ngx_http_hello_world_module is an array of definitions for the module. It tells where the array of directives and functions are (ngx_http_hello_world_module and ngx_http_hello_world_module_ctx). We can also add init and exit callback functions. In our module, we don’t need them so we put NULLs instead.

Now for the interesting part. ngx_http_hello_world_handler is the heart of our module. We want to print Hello World! on the screen, so we have an unsigned char * with our message in it. Right after that, there is another variable with the size of the message.

Next, we have to send the headers. Notice that ngx_http_hello_world_handler had 1 argument that was of type ngx_http_request_t. This is a custom struct made by Nginx. It has a member called headers_out, which we use to send the headers. After we are done setting the headers, we can send them with ngx_http_send_header(r).

Now we have to send the body. ngx_buf_t is a buffer, and ngx_chain_t is a chain link. The chain links send responses buffer by buffer and point to the next link. In our module, there is no next link, so we set out->next to NULL. ngx_calloc_buf and ngx_alloc_chain_link are Nginx’s calloc wrappers that automatically take care of garbage collection. b->pos and b->last help us send our content. b->pos is the first position in the memory and b->last is the last position. b->memory is set to 1 because our content is read-only. b->last_buf tells that our buffer is the last buffer in the request.

Now that we’re done setting the body, we can send it with return ngx_http_output_filter(r, &out)

Now we define that function we prototyped in the beginning. We can show Nginx what our handler is called with clcf-> handler = ngx_http_hello_world_handler.

And we’re done with our C file! Time to build the module.

Building the Module

How to build the module:

$ ./configure \
> --prefix=/where/i/want/to/install/nginx \
> --add-dynamic-module=/path/to/ngx_http_hello_world_module

# Build module and Nginx

$ make
$ make install

# Build module

$ make modules
$ cp objs/ngx_http_hello_world_module.so <nginx_install_location>/modules

In the Nginx source, run configure, make, and make install. If you only want to build the modules and not the Nginx server itself, you can run make modules.

Using the Module

nginx.conf

load_module "modules/ngx_http_hello_world_module.so"

http {
  default_type  application/octet-stream;
  server {
    listen 8000;
    server_name localhost;
    location / {
      root  html;
      index index.html index.htm;
    }
    location /test {
      print_hello_world;
    }
  }
}

To use the module, edit your nginx.conf file found in the conf directory in the install location.

When you’re done, you can run nginx (<nginx_install_location>/sbin/nginx) and take a look at your work at localhost:8000/test. You should get a blank page saying Hello World!. If so, congratulations! You made your first Nginx module! This module is the base for making any handler.

Printing All the URL Arguments

Modified ngx_http_hello_world_handler:

static ngx_int_t ngx_http_hello_world_handler(ngx_http_request_t *r)
{
  u_char *ngx_hello_world = r->args.data;
  size_t sz = r->args.len;

  r->headers_out.content_type.len = strlen("text/html") - 1;
  r->headers_out.content_type.data = (u_char *) "text/html";
  r->headers_out.status = NGX_HTTP_OK;
  r->headers_out.content_length_n = sz;
  ngx_http_send_header(r);

  ngx_buf_t    *b;
  ngx_chain_t   *out;

  b = ngx_calloc_buf(r->pool);

  out = ngx_alloc_chain_link(r->pool);

  out.buf = b;
  out.next = NULL;

  b->pos = ngx_hello_world;
  b->last = ngx_hello_world + sz;
  b->memory = 1;
  b->last_buf = 1;

  return ngx_http_output_filter(r, &out);
}

Now, we’ll modify our module slightly to print all the URL arguments (everything after the ?). So if our request is localhost:8000/test?foo=bar&hello=world we should get foo=bar&hello=world printed in the body.

We need to modify the handler, ngx_http_hello_world_handler. Notice that the string Hello World! was changed to r->args.data, and strlen(ngx_hello_world) was changed to r->args.len. r->argsstores all the arguments and is of type ngx_str_t. ngx_str_ts have a data and a len element, for storing the string and its length.

When you’re done, you should stop nginx (<nginx_install_location>/sbin/nginx> -s stop) and build again. After that’s done, start Nginx again and go to localhost:8000/test?foo=hello&bar=world. You should see foo=hello&bar=world printed in the body.

Many Buffers

Modified ngx_http_hello_world_handler:

static ngx_int_t ngx_http_hello_world_handler(ngx_http_request_t *r)
{
  u_char *ngx_hello_world = (u_char *) "Hello World!";
  size_t sz = strlen(ngx_hello_world);

  r->headers_out.content_type.len = strlen("text/html") - 1;
  r->headers_out.content_type.data = (u_char *) "text/html";
  r->headers_out.status = NGX_HTTP_OK;
  r->headers_out.content_length_n = 2 * sz; 
  ngx_http_send_header(r);

  ngx_buf_t    *b, *b2;
  ngx_chain_t   *out, *out2;

  b = ngx_calloc_buf(r->pool);
  b2 = ngx_calloc_buf(r->pool);

  out = ngx_alloc_chain_link(r->pool);
  out2 = ngx_alloc_chain_link(r->pool);

  out->buf = b;
  out->next = out2;

  out2->buf = b2; 
  out2->next = NULL;

  b->pos = ngx_hello_world;
  b->last = ngx_hello_world + sz;
  b->memory = 1;
  b->last_buf = 0;

  b2->pos = ngx_hello_world;
  b2->last = ngx_hello_world + sz;
  b2->memory = 1;
  b2->last_buf = 1;

  return ngx_http_output_filter(r, out);
}

Get your hello world template again and add another buffer (ngx_buf_t *b2) and another chain link (ngx_chain_t out2;). Then allocate some memory with ngx_calloc_buf and ngx_alloc_chain_link. Everything is the same except that we are setting b->last_buf to 0 and out.next to out2. This is because our original buffer is no longer the last one, and the next buffer is out2.

Now, if we send the headers we only mention our original buffer because out links to the next buffer.

Rebuild, restart, and go to localhost:8000/test. You should see Hello World!Hello World!.

The Filter Guide

After a handler is loaded and run, all the filter modules are executed. Filters take the header and/or body, manipulate them, and then send them back.

Our module will add a music track to all web pages where the module is loaded.

The Config File

config

ngx_addon_name=ngx_http_hello_world_module

if test -n "$ngx_module_link"; then
    # The New Way
    ngx_module_type=HTTP_FILTER
    ngx_module_name=ngx_http_hello_world_module
    ngx_module_srcs="$ngx_addon_dir/ngx_http_hello_world_module.c"

    . auto/module
else
    # The Old Way
    HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_hello_world_module"
    NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_world_module.c"
fi

Nothing is very different here, except that HTTP is replaced with HTTP_FILTER.

The C File

ngx_http_hello_world_module.c

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

u_char *hello_world = (u_char *) "<audio controls loop autoplay src=\"https://upload.wikimedia.org/wikipedia/commons/8/85/Holst-_mars.ogg\"></audio>";

static char *
ngx_http_hello_world(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
  return NGX_OK;
}
static ngx_int_t ngx_http_hello_world_init(ngx_conf_t *cf);


static ngx_command_t  ngx_http_hello_world_commands[] = {

  { ngx_string("hello_world"),
  NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
  ngx_http_hello_world,
  0,
  0,
  NULL },

  ngx_null_command
};


static ngx_http_module_t  ngx_http_hello_world_module_ctx = {
  NULL,                                         /* proconfiguration */
  ngx_http_hello_world_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_hello_world_module = {
  NGX_MODULE_V1,
  &ngx_http_hello_world_module_ctx, /* module context */
  ngx_http_hello_world_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_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt   ngx_http_next_body_filter;


static ngx_int_t
ngx_http_hello_world_header_filter(ngx_http_request_t *r)
{

  r->headers_out.content_length_n += strlen((char *)hello_world);

  return ngx_http_next_header_filter(r);
}


static ngx_int_t
ngx_http_hello_world_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{

  ngx_buf_t             *buf;
  ngx_chain_t           *link;


  buf = ngx_calloc_buf(r->pool);

  buf->pos = hello_world;
  buf->last = buf->pos + strlen((char *)hello_world);
  buf->start = buf->pos;
  buf->end = buf->last;
  buf->last_buf = 0;
  buf->memory = 1;

  link = ngx_alloc_chain_link(r->pool);

  link->buf = buf;
  link->next = in;

  return ngx_http_next_body_filter(r, link);
}

static ngx_int_t
ngx_http_hello_world_init(ngx_conf_t *cf)
{
  ngx_http_next_body_filter = ngx_http_top_body_filter;
  ngx_http_top_body_filter = ngx_http_hello_world_body_filter;

  ngx_http_next_header_filter = ngx_http_top_header_filter;
  ngx_http_top_header_filter = ngx_http_hello_world_header_filter;

  return NGX_OK;
}

Let’s see how this is different from our handler. First we see our u_char: An HTML <audio> element with a song (from Wikipedia).

Next, instead of prototyping ngx_http_background_music, we just defined it. Normally, this function would be more interesting, but since this is just a demo module, we don’t need to do anything other than returning NGX_OK.

The filter is made of two parts: the header filter, and the body filter. For our header filter, we only have to add the content length of our background_music variable. After that, we pass on the baton to the next header filter with ngx_http_next_header_filter.

The body filter accepts 2 arguments: An ngx_http_request_t, and a chain link, ngx_chain_t. The chain link is from the handler and previous filters (if any). What we want to do is to prefix our audio element to the chain link in. It won’t be perfectly valid HTML, but it’s good enough for now.

Our buffer should have last_buf set to 0 because it isn’t the last buffer: The last buffer is in the in chain link. So we’ll just set link->next to in and call the next body filter.

The ngx_http_background_music_init just tells what our filter funtions are called.

And we’re done. Now build and reload nginx, and you should see… a 404 page? Yes, there will be a 404 page, but with an audio track above it.

Using the Module

nginx.conf

load_module "modules/ngx_http_hello_world_module.so"

http {
  default_type  application/octet-stream;
  server {
    listen 8000;
    server_name localhost;
    location / {
      root  html;
      index index.html index.htm;
    }
    location /test {
      root html;
      index index.html index.htm;
      print_hello_world;
    }
  }
}

There was a 404 page because there was no other handler given. If you just add an HTML file, that should be taken care of.

Data Types

ngx_http_request_t

Example usage of the server variable:

function hello_world_handler(ngx_http_request_t *r){
  ...
  ngx_str_t *test = r->server;
  u_char *testString = test.data;
  ...
}

This table lists some useful members of ngx_http_request_t

Member Type Description
args ngx_str_t All the arguments in one string
server ngx_str_t The server name
uri ngx_str_t The request path
pool ngx_pool_t* Used for allocating memory

ngx_str_t

ngx_str_t usage:

// Initialize string
ngx_str_t mystring = ngx_string("hello");

// Use string
...
r->pos = mystring.data;
r->last = mystring.data + mystring.len;
...

The ngx_str_t datatype has 2 members: data and len. They allow you to access the contents of the string and it’s length.

Member Type Description
data u_char* The contents of the string
len size_t The length of the string

Other Stuff

Troubleshooting

1. Only part of my text is showing up!

You have to correctly set the size in both the headers and in the buffer. If your string is a u_char*, use ngx_strlen, if it’s an ngx_str_t, use {variable_name}.len.

2. Some weird string is showing up after my text.

See #1.

3. Why is my filter not working (but compiling)?

Make sure you’ve configured it correctly. The config file should have HTTP_FILTER instead of HTTP. Also, check if you’ve sent the body correctly, and make sure your buffer is in the chain link.