Posted on and Updated on

An HTTP module basics and configuration

In the previous article I explained how modules of all types link into Nginx. Now let’s  look closer at the specifics of HTTP modules.

An HTTP module has the value NGX_HTTP_MODULE in its type field and the ctx field points to a global instance of a structure ngx_http_module_t:

typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);

    void       *(*create_main_conf)(ngx_conf_t *cf);
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);

    void       *(*create_srv_conf)(ngx_conf_t *cf);
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);

    void       *(*create_loc_conf)(ngx_conf_t *cf);
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

As you might have already noticed, this structure contains only pointers to handlers . Here is a description of these handlers:

  • preconfiguration — a handler that is called before parsing the HTTP configuration;
  • postconfiguration — a handler that is called after parsing the HTTP configuration;
  • create_main_conf — a handler that is called in order to create the main configuration structure for a module;
  • init_main_conf — a handler that is called in order to initialise the main configuration structure for a module;
  • create_srv_conf — a handler that is called in order to create a virtual server configuration structure for a module;
  • merge_srv_conf — a handler that is called in order to merge two virtual server configuration structures for a module;
  • create_loc_conf — a handler that is called in order to create a location configuration structure for a module;
  • merge_loc_conf — a handler that is called in order to merge two location configuration structures for a module.

Any of the fields can contain a NULL value. This would mean that there is no need to call the corresponding handler. However create_(srv|loc)_conf and merge_(srv|loc)_conf handlers usually come in pairs. All these handlers are called on the configuration stage, therefore there is no need to synchronise access to any resources within the master process that they are dealing with.

The preconfiguration handler is used to register resources that need to be accessibled during the processing of the HTTP server configuration. The variables are an example of such a resource.

The postconfiguration handler is used to configure resources that appear during the processing of an HTTP server configuration, for instance phase handlers.

Here is an example of an HTTP module context:

ngx_http_module_t  ngx_http_sample_module_ctx = {
    ngx_http_sample_module_pre,            /* preconfiguration */
    NULL,                                  /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_sample_module_create_loc_conf,  /* create location configuration */
    ngx_http_sample_module_merge_loc_conf    /* merge location configuration */
};

In order to understand how handlers create_(srv|loc)_conf, merge_(srv|loc)_conf work you need to understand the hierarchy of configurations in Nginx. Here is a picture that illustrates this:

An Nginx HTTP server has 3 levels of configurations: main, virtual server and location. A configuration is a set of configuration data structures for a certain level for all HTTP modules. There is only one main configuration for the entire HTTP server. Nginx asks each module to create a configuration data structure for itself for the main configuration by calling the create_main_conf handler. There are as many virtual server configurations as many server blocks are configured. Each module may contribute a data structure to each of these configurations. There is also a default server configuration. It stores parameters that are defined in the http block, but belong to the server configuration. The main configuration and each server configuration contains a default location configuration. It stores parameters that are defined in http and server blocks respectively, but belong to the location configuration.

The typical configuration data structure looks like:

typedef struct {
    ngx_array_t                *blocks;
    ngx_flag_t                  escalate;
    ngx_str_t                   override_content_type;
} ngx_http_eval_loc_conf_t;

In the configuration data structure a module can store parameters of whatever type it wants, including pointers to other data structures and pointers to shared memory. Handlers that create configuration data structures usually allocate them from the memory pool of configuration, for example:

static void *
ngx_http_source_cookie_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_source_cookie_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_source_cookie_loc_conf_t));

    if(conf == NULL) {
        return NGX_CONF_ERROR;
    }

    conf->expires_time = NGX_CONF_UNSET;
    conf->value = NGX_CONF_UNSET_PTR;

    return conf;
}

Calling ngx_pcalloc is sufficient to set the undefined value to pointers (ngx_pcalloc fills allocated memory with zeroes). For other data types it might be necessary to assign undefined values, so that Nginx can detect duplicate directives. Certain configuration directive handlers programmed to tell Nginx to stop and return an error message when they find a defined configuration parameter.

When Nginx finishes parsing the http configuration block, it merges the default server configuration into all server configurations that were defined in that block by calling the merge_srv_conf handler. Then it merges the default location configuration of the http block into all default location configurations of all server blocks by calling the merge_loc_conf handler. After that it merges the default location configurations of all server blocks into all location configurations that were defined in those server blocks.

This allows you to specify default_type text/plain in an http block and this parameter will be propagated to all locations in your configuration. Thus the function of merge handlers is to assign a default value to a configuration parameter or to inherit the value from configuration level above. There is a set of standard helper functions that you can use for merging configuration parameters:

Name Data type
Field type
ngx_conf_merge_ptr_value Pointer pointer
ngx_conf_merge_uint_value Unsigned integer ngx_uint_t
ngx_conf_merge_msec_value Time in milliseconds ngx_msec_t
ngx_conf_merge_sec_value Time in seconds time_t
ngx_conf_merge_size_value Length size_t
ngx_conf_merge_bufs_value Number and size of buffers ngx_bufs_t
ngx_conf_merge_bitmask_value Bitmap ngx_uint_t
ngx_conf_merge_path_value Path in the filesystem and number of characters in hashed directories ngx_path_t
ngx_conf_merge_off_value Flag ngx_flag_t
ngx_conf_merge_str_value String ngx_str_t

Here is an example of an implementation of the merge handler for a configuration data structure with a string field and an integer field:

typedef struct {
    ngx_str_t str_param;
    ngx_uint_t int_param;
} ngx_http_sample_module_loc_conf_t;

static char *
ngx_http_sample_module_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_sample_module_loc_conf_t  *prev = parent;
    ngx_http_sample_module_loc_conf_t  *conf = child;

    ngx_conf_merge_str_value(conf->str_param, prev->str_param, "default value");

    ngx_conf_merge_uint_value(conf->int_param, prev->int_param, 1);

    return NGX_CONF_OK;
}

Pointers to configuration data structures are passed to this handler as arguments. Nginx expects to get NGX_CONF_OK as return value if merging has gone without any problems.

Which configuration parameters should go to which configuration? Obviously, a parameter needs to go to the most specific configuration level in which it may appear.

For example, the directive listen is specific only for a virtual server and it can never appear in a location block, therefore it must go to the server configuration.

At runtime Nginx maintains pointers to configuration structures of every level for each HTTP-request. You can use the following macros to access configuration structures of all levels at runtime:

ngx_http_get_module_main_conf(r, module)
ngx_http_get_module_srv_conf(r, module)
ngx_http_get_module_loc_conf(r, module)

Here r — is a pointer to ngx_http_request_t (an HTTP-request), modulengx_module_t structure. Every macro evaluates to a pointer to a configuration structure of the corresponding level. Example:

    ngx_http_source_cookie_loc_conf_t  *sclc;

    sclc = ngx_http_get_module_loc_conf(r, ngx_http_source_cookie_module);

At the configuration stage, you can use another set of functions to access configuration data structures:

ngx_http_conf_get_module_main_conf(cf, module)
ngx_http_conf_get_module_srv_conf(cf, module)
ngx_http_conf_get_module_loc_conf(cf, module)

Here cf is a pointer to the structure ngx_conf_t (the Nginx configuration), module is the structure ngx_module_t (the description of the module). Every macro evaluates to a pointer to a configuration structure of the corresponding level.

Now we should proceed to the question of how to implement configuration directives. However, this question deserves a whole new article, so we’ll look at it next time.