NGINX - performance HTTP server Support Forums

Please login or register.

Login with username, password and session length
Advanced search  

News:

Welcome to the Unofficial (YET) support forums for NGINX, a performance HTTP server.

Pages: [1]   Go Down

Author Topic: Emiller's Balls-Out Guide (IV - Components of an Nginx Module)  (Read 563 times)

0 Members and 1 Guest are viewing this topic.

katmai

  • Administrator
  • Jr. Member
  • *****
  • Karma: 0
  • Offline Offline
  • Posts: 57
    • View Profile
Emiller's Balls-Out Guide (IV - Components of an Nginx Module)
« on: November 30, 2007, 01:46:43 PM »
Components of an Nginx Module

As I said, you have a lot of flexibility when it comes to making an Nginx module. This section will describe the parts that are almost always present. It's intended as a guide for understanding a module, and a reference for when you think you're ready to start writing a module.
1. Module Configuration Struct(s)

Modules can define up to three configuration structs, one for the main, server, and location contexts. Most modules just need a location configuration. The naming convention for these is ngx_http_<module name>_(main|srv|loc)_conf_t. Here's an example, taken from the dav module:

typedef struct {
    ngx_uint_t  methods;
    ngx_flag_t  create_full_put_path;
    ngx_uint_t  access;
} ngx_http_dav_loc_conf_t;

Notice that Nginx has special data types (ngx_uint_t and ngx_flag_t); these are just aliases for the primitive data types you know and love (cf. core/ngx_config.h if you're curious).

The elements in the configuration structs are populated by module directives.
2. Module Directives

A module's directives appear in a static array of ngx_command_ts. Here's an example of how they're declared, taken from a small module I wrote:

static ngx_command_t  ngx_http_circle_gif_commands[] = {
    { ngx_string("circle_gif"),
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
      ngx_http_circle_gif,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("circle_gif_min_radius"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_circle_gif_loc_conf_t, min_radius),
      NULL },
      ...
      ngx_null_command
};

And here is the declaration of ngx_command_t (the struct we're declaring), found in core/ngx_conf_file.h:

struct ngx_command_t {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};

It seems like a bit much, but each element has a purpose.

The name is the directive string, no spaces. The data type is an ngx_str_t, which is usually instantiated with just (e.g.) ngx_str("proxy_pass"). Note: an ngx_str_t is a struct with a data element, which is a string, and a len element, which is the length of that string. Nginx uses this data structure most places you'd expect a string.

type is a set of flags that indicate where the directive is legal and how many arguments the directive takes. Applicable flags, which are bitwise-OR'd, are:

    * NGX_HTTP_MAIN_CONF: directive is valid in the main config
    * NGX_HTTP_SRV_CONF: directive is valid in the server (host) config
    * NGX_HTTP_LOC_CONF: directive is valid in a location config
    * NGX_HTTP_UPS_CONF: directive is valid in an upstream config

    * NGX_CONF_NOARGS: directive can take 0 arguments
    * NGX_CONF_TAKE1: directive can take exactly 1 argument
    * NGX_CONF_TAKE2: directive can take exactly 2 arguments
    * ...
    * NGX_CONF_TAKE7: directive can take exactly 7 arguments

    * NGX_CONF_FLAG: directive takes a boolean ("on" or "off")
    * NGX_CONF_1MORE: directive must be passed at least one argument
    * NGX_CONF_2MORE: directive must be passed at least two arguments

There are a few other options, too, see core/ngx_conf_file.h.

The set struct element is a pointer to a function for setting up part of the module's configuration; typically this function will translate the arguments passed to this directive and save an appropriate value in its configuration struct. This setup function will take three arguments:

   1. a pointer to an ngx_conf_t struct, which contains the arguments passed to the directive
   2. a pointer to the current ngx_command_t struct
   3. a pointer to the module's custom configuration struct

This setup function will be called when the directive is encountered. Nginx provides a number of functions for setting particular types of values in the custom configuration struct. These functions include:

    * ngx_conf_set_flag_slot: translates "on" or "off" to 1 or 0
    * ngx_conf_set_str_slot: saves a string as an ngx_str_t
    * ngx_conf_set_num_slot: parses a number and saves it to an int
    * ngx_conf_set_size_slot: parses a data size ("8k", "1m", etc.) and saves it to a size_t

There are several others, and they're quite handy (see core/ngx_conf_file.h). Modules can also put a reference to their own function here, if the built-ins aren't quite good enough.

How do these built-in functions know where to save the data? That's where the next two elements of ngx_command_t come in, conf and offset. conf tells Nginx whether this value will get saved to the module's main configuration, server configuration, or location configuration (with NGX_HTTP_MAIN_CONF_OFFSET, NGX_HTTP_SRV_CONF_OFFSET, or NGX_HTTP_LOC_CONF_OFFSET). offset then specifies which part of this configuration struct to write to.

Finally, post is just a pointer to other crap the module might need while it's reading the configuration. It's often NULL.

The commands array is terminated with ngx_null_command as the last element.
3. The Module Context

This is a static ngx_http_module_t struct, which just has a bunch of function references for creating the three configurations and merging them together. Its name is ngx_http_<module name>_module_ctx. In order, the function references are:

    * preconfiguration
    * postconfiguration
    * creating the main conf (i.e., do a malloc and set defaults)
    * initializing the main conf (i.e., override the defaults with what's in nginx.conf)
    * creating the server conf
    * merging it with the main conf
    * creating the location conf
    * merging it with the server conf

These take different arguments depending on what they're doing. Here's the struct definition, taken from http/ngx_http_config.h, so you can see the different function signatures of the callbacks:

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;

You can set functions you don't need to NULL, and Nginx will figure it out.

Most handlers just use the last two: a function to allocate memory for location-specific configuration (called ngx_http_<module name>_create_loc_conf), and a function to set defaults and merge this configuration with any inherited configuration (called ngx_http_<module name >_merge_loc_conf). The merge function is also responsible for producing an error if the configuration is invalid; these errors halt server startup.

Here's an example module context struct:

static ngx_http_module_t  ngx_http_circle_gif_module_ctx = {
    NULL,                          /* preconfiguration */
    NULL,                          /* postconfiguration */

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

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

    ngx_http_circle_gif_create_loc_conf,  /* create location configuration */
    ngx_http_circle_gif_merge_loc_conf /* merge location configuration */
};

Time to dig in deep a little bit. These configuration callbacks look quite similar across all modules and use the same parts of the Nginx API, so they're worth knowing about.
3A. create_loc_conf

Here's what a bare-bones create_loc_conf function looks like, taken from the circle_gif module I wrote (see the the source). It takes a directive struct (ngx_conf_t) and returns a newly created module configuration struct (in this case ngx_http_circle_gif_loc_conf_t).

static void *
ngx_http_circle_gif_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_circle_gif_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_circle_gif_loc_conf_t));
    if (conf == NULL) {
        return NGX_CONF_ERROR;
    }
    conf->min_radius = NGX_CONF_UNSET_UINT;
    conf->max_radius = NGX_CONF_UNSET_UINT;
    return conf;
}

First thing to notice is Nginx's memory allocation; it takes care of the free'ing as long as the module uses ngx_palloc (a malloc wrapper) or ngx_pcalloc (a calloc wrapper).

The possible UNSET constants are NGX_CONF_UNSET_UINT, NGX_CONF_UNSET_PTR, NGX_CONF_UNSET_SIZE, NGX_CONF_UNSET_MSEC, and the catch-all NGX_CONF_UNSET. UNSET tell the merging function that the value should be overridden.
3B. merge_loc_conf

Here's the merging function used in the circle_gif module:

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

    ngx_conf_merge_uint_value(conf->min_radius, prev->min_radius, 10);
    ngx_conf_merge_uint_value(conf->max_radius, prev->max_radius, 20);

    if (conf->min_radius < 1) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
            "min_radius must be equal or more than 1");
        return NGX_CONF_ERROR;
    }
    if (conf->max_radius < conf->min_radius) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
            "max_radius must be equal or more than min_radius");
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;
}

Notice first that Nginx provides nice merging functions for different data types (ngx_conf_merge_<data type>_value); the arguments are

   1. this location's value
   2. the value to inherit if #1 is not set
   3. the default if neither #1 nor #2 is set

The result is then stored in the first argument. Available merge functions include ngx_conf_merge_size_value, ngx_conf_merge_msec_value, and others. See core/ngx_conf_file.h for a full list.

Trivia question: How do these functions write to the first argument, since the first argument is passed in by value?

Answer: these functions are defined by the preprocessor (so they expand to a few "if" statements and assignments before reaching the compiler).

Notice also how errors are produced; the function writes something to the log file, and returns NGX_CONF_ERROR. That return code halts server startup. (Since the message is logged at level NGX_LOG_EMERG, the message will also go to standard out; FYI, core/ngx_log.h has a list of log levels.)
« Last Edit: November 30, 2007, 01:58:48 PM by katmai »
Logged
Pages: [1]   Go Up
« previous next »
 

Page created in 0.072 seconds with 21 queries.