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), module — ngx_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.
Being new to Nginx, I would like to understand better the part ‘ngx_conf_t’ plays, because I see it used but still not able to really pick up on just how it is working.
I found what I was looking for. I decided to take on some modules. It is a bit much to pick up on at first.
The tutorials found here are great.