[Capa de puerta de enlace] Introducción al desarrollo del módulo Nginx C

Comenzando con el desarrollo del módulo Nginx C

I. Introducción

El propio Nginx admite varios módulos, como el módulo HTTP, el módulo EVENT y el módulo MAIL. Aquí solo se analizará el módulo HTTP.

El propio Nginx en realidad tiene menos trabajo. Cuando recibe una solicitud HTTP, solo asigna la solicitud a un bloque de ubicación buscando el archivo de configuración, y las instrucciones configuradas en esta ubicación iniciarán diferentes módulos para completar el trabajo. Por lo tanto, el módulo puede considerarse como el verdadero trabajador laboral de Nginx. Por lo general, las instrucciones en una ubicación incluirán un módulo controlador y varios módulos de filtro (varias ubicaciones también pueden reutilizar un módulo). El módulo controlador es responsable de procesar las solicitudes y completar la generación de contenido de respuesta, mientras que el módulo de filtro procesa el contenido de respuesta. Por lo tanto, el desarrollo del módulo Nginx se divide en desarrollo de controladores y desarrollo de filtros (el módulo de equilibrador no se considera por el momento)
Inserte la descripción de la imagen aquí

Como se muestra en la figura anterior, la solicitud pasa a través del núcleo de Nginx, se distribuye a un módulo controlador y luego a la respuesta de contenido normal, y luego la respuesta finalmente se devuelve a través de capas de filtrado.

2. Ejemplo práctico de módulo controlador

A continuación, mostraremos un proceso de desarrollo de un módulo Nginx simple. Desarrollaremos un módulo controlador de eco. La función de este módulo es recibir el comando "echo", especificar un parámetro de cadena y el módulo generará esta cadena como una respuesta HTTP .
Como configurado

location /echo {
    echo "hello nginx";
}

Visite http: // nombre de host: xxxx / echo hola nginx generará
un punto de vista intuitivo, la necesidad de implementar esta función requiere tres pasos:
①, lee el comando echo del archivo de configuración y sus parámetros
②, empaquetado para HTTP (HTTP agregó trabajo de clase )
③, Devolver el resultado al cliente

1. Defina la estructura de configuración del módulo

En primer lugar, necesitamos una estructura para almacenar los parámetros de instrucción relevantes leídos del archivo de configuración, es decir, la estructura de información de configuración del módulo. De acuerdo con las reglas de desarrollo del módulo Nginx, la regla de nomenclatura de esta estructura es

ngx_http_[module-name]_[main|srv|loc]_conf_t

Entre ellos, main, srv y loc se utilizan respectivamente para identificar la información de configuración en el bloque de tercer nivel del mismo módulo. Debido a que nuestro módulo de eco solo necesita ejecutarse por debajo del nivel Loc y necesita almacenar un parámetro de cadena, definimos la siguiente configuración del módulo

typedef struct {
    ngx_str_t ed;
} ngx_http_echo_loc_conf_t;

Entre ellos, el campo ed se usa para almacenar la cadena que debe ser especificada por el comando echo (si no conoce la definición de las variables de Nginx, puede consultar la información y aprender por sí mismo)

2. Definir instrucciones

Un módulo Nginx a menudo acepta una o más instrucciones, y el módulo echo recibe una instrucción "echo". El módulo Nginx usa uno

ngx_command_t

La matriz representa todos los módulos que el módulo puede recibir y cada elemento representa una instrucción.
ngx_command_t es un alias para ngx_command_s (Nginx se usa para usar el sufijo "s" para nombrar la estructura, y luego usa typedef con el mismo nombre sufijo "_t" que el nombre del tipo)
Se define en core / ngx_config_file.h:

struct ngx_command_s {
    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;
};

①, name
name es el nombre de la instrucción de entrada
②, type
type es el uso de indicadores de máscara para configurar parámetros de instrucción, y los tipos disponibles relevantes se definen en core / ngx_config_file.h

#define NGX_CONF_NOARGS      0x00000001
#define NGX_CONF_TAKE1       0x00000002
#define NGX_CONF_TAKE2       0x00000004
#define NGX_CONF_TAKE3       0x00000008
#define NGX_CONF_TAKE4       0x00000010
#define NGX_CONF_TAKE5       0x00000020
#define NGX_CONF_TAKE6       0x00000040
#define NGX_CONF_TAKE7       0x00000080
#define NGX_CONF_MAX_ARGS    8
#define NGX_CONF_TAKE12      (NGX_CONF_TAKE1|NGX_CONF_TAKE2)
#define NGX_CONF_TAKE13      (NGX_CONF_TAKE1|NGX_CONF_TAKE3)
#define NGX_CONF_TAKE23      (NGX_CONF_TAKE2|NGX_CONF_TAKE3)
#define NGX_CONF_TAKE123     (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3)
#define NGX_CONF_TAKE1234    (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3   \
        |NGX_CONF_TAKE4)
#define NGX_CONF_ARGS_NUMBER 0x000000ff
#define NGX_CONF_BLOCK       0x00000100
#define NGX_CONF_FLAG        0x00000200
#define NGX_CONF_ANY         0x00000400
#define NGX_CONF_1MORE       0x00000800
#define NGX_CONF_2MORE       0x00001000
#define NGX_CONF_MULTI       0x00002000

Entre ellos, el más utilizado es
NGX_CONF_NOARGS, lo que significa que este comando no acepta parámetros
NGX_CON F_TAKE1-7 significa que 1-7 piezas de
NGX_CONF_TAKE se reciben con precisión 12 significa que se
aceptan 1 o 2 parámetros NGX_CONF_1MORE significa que al menos un parámetro
NGX_CONF_FLAG significa que se acepta "on | off"

③, set
set es un puntero de función, utilizado para especificar una función de conversión de parámetros, esta función es generalmente para convertir los parámetros de las instrucciones relevantes en el archivo de configuración al formato requerido y almacenarlo en la estructura de configuración.
Nginx predefine algunas funciones de conversión para nuestra conveniencia. Estas funciones se definen en core / ngx.conf.file.hy generalmente terminan con _slot. Por ejemplo, ngx_conf_set_slot convierte "on o off" en 1 o 0. Otro ejemplo es ngx_conf_set_str_slot, que convierte una cadena desnuda en ngx_str_t.

④, conf
conf se usa para especificar la dirección real de la memoria del archivo de configuración correspondiente de Nginx, generalmente se puede especificar mediante constantes integradas, como NGX_HTTP_LOC_CONF_OFFSET

⑤, offset
offset especifica el offset del parámetro de esta instrucción

La siguiente es la definición de instrucción del módulo de eco de este ejemplo:

static ngx_command_t  ngx_http_echo_commands[] = {
    { ngx_string("echo"),
        NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_http_echo,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_echo_loc_conf_t, ed),
        NULL },
        ngx_null_command
};

La regla de nomenclatura de la matriz de instrucciones es ngx_http_ [nombre-módulo] _commands, tenga en cuenta que el último elemento de la matriz debe terminar con ngx_null_command

3. Escribe una función de conversión

El código de la función de conversión de parámetros es:

static char *
ngx_http_echo(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_echo_handler;
    ngx_conf_set_str_slot(cf,cmd,conf);
    return NGX_CONF_OK;
}

Esta función no solo llama a ngx_conf_set_str_slot para transformar los parámetros de la instrucción echo, sino que también modifica la configuración del módulo central (es decir, la configuración de la ubicación), reemplazando el manejador con el manejador que escribimos: ngx_http_echo_handler. De esta manera, se bloquea el controlador predeterminado de esta ubicación y se usa ngx_http_echo_handler para generar una respuesta HTTP.

4. Cree información de configuración de combinación

El siguiente paso es definir el contexto del módulo.
Aquí primero debe definir una variable de estructura de tipo ngx_http_module_t. La regla de nomenclatura es

ngx_http_[module-name]_module_ctx

Esta estructura se utiliza principalmente para definir cada función de Hook. La siguiente es la estructura de contexto del módulo de eco:

static ngx_http_module_t  ngx_http_echo_module_ctx = {
    NULL,                                  /* preconfiguration */
    NULL,                                  /* postconfiguration */
    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */
    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */
    ngx_http_echo_create_loc_conf,         /* create location configration */
    ngx_http_echo_merge_loc_conf           /* merge location configration */
};

Se puede ver que hay un total de 8 puntos de inyección de Hook, que serán llamados por Nginx en diferentes momentos. Dado que nuestro módulo solo se usa en el dominio de ubicación, no es necesario establecer el punto de inyección en NULL aquí.
Entre ellos, create_loc_conf se usa para inicializar una estructura de configuración, como la asignación de memoria para la estructura de configuración, etc .;
merge_loc_conf se usa para fusionar la información de configuración de su bloque padre en esta estructura, que es para lograr la herencia de la configuración.
Estas dos funciones serán llamadas automáticamente por Nginx. Preste atención a las reglas de nomenclatura aquí:

ngx_http_[module-name]_[create|merge]_[main|srv|loc]_conf

A continuación se muestra el código de estas dos funciones del módulo de eco

ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_echo_loc_conf_t  *conf;
    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
    if (conf == NULL) {
        return NGX_CONF_ERROR;
    }
    conf->ed.len = 0;
    conf->ed.data = NULL;
    return conf;
}
static char *
ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_echo_loc_conf_t *prev = parent;
    ngx_http_echo_loc_conf_t *conf = child;
    ngx_conf_merge_str_value(conf->ed, prev->ed, "");
    return NGX_CONF_OK;
}

Entre ellos, ngx_pcalloc se usa para asignar un espacio en el grupo de memoria de Nginx y es un contenedor de pcalloc. El espacio de memoria asignado mediante ngx_pcalloc no necesita liberarse manualmente, Nginx lo administrará por sí mismo y lo liberará cuando sea apropiado.

create_loc_conf crea un nuevo ngx_http_echo_loc_conf_t, asigna memoria, inicializa los datos que contiene y luego devuelve un puntero a esta estructura; merge_loc_conf fusiona la información de configuración del dominio del bloque principal en la nueva estructura de configuración creada por create_loc_conf.

5. Escribir controlador

A continuación está la preparación del manejador, que se puede decir que es el código que realmente implementa la lógica en el módulo. Tiene cuatro responsabilidades principales
①, leer en la configuración del módulo
②, procesar la función empresarial
③, generar el encabezado HTTP
④, generar Cuerpo HTTP

Primero publique el código del controlador del módulo de eco a continuación

static ngx_int_t
ngx_http_echo_handler(ngx_http_request_t *r)
{
    ngx_int_t rc;
    ngx_buf_t *b;
    ngx_chain_t out;
    ngx_http_echo_loc_conf_t *elcf;
    elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
    if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST)))
    {
        return NGX_HTTP_NOT_ALLOWED;
    }
    r->headers_out.content_type.len = sizeof("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 = elcf->ed.len;
    if(r->method == NGX_HTTP_HEAD)
    {
        rc = ngx_http_send_header(r);
        if(rc != NGX_OK)
        {
            return rc;
        }
    }
    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if(b == NULL)
    {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    out.buf = b;
    out.next = NULL;
    b->pos = elcf->ed.data;
    b->last = elcf->ed.data + (elcf->ed.len);
    b->memory = 1;
    b->last_buf = 1;
    rc = ngx_http_send_header(r);
    if(rc != NGX_OK)
    {
        return rc;
    }
    return ngx_http_output_filter(r, &out);
}

El parámetro de este método es un parámetro de tipo de puntero ngx_http_request_t, que apunta a una estructura que almacena esta solicitud HTTP

struct ngx_http_request_s {
    uint32_t                          signature;         /* "HTTP" */
    ngx_connection_t                 *connection;
    void                            **ctx;
    void                            **main_conf;
    void                            **srv_conf;
    void                            **loc_conf;
    ngx_http_event_handler_pt         read_event_handler;
    ngx_http_event_handler_pt         write_event_handler;
#if (NGX_HTTP_CACHE)
    ngx_http_cache_t                 *cache;
#endif
    ngx_http_upstream_t              *upstream;
    ngx_array_t                      *upstream_states;
    /* of ngx_http_upstream_state_t */
    ngx_pool_t                       *pool;
    ngx_buf_t                        *header_in;
    ngx_http_headers_in_t             headers_in;
    ngx_http_headers_out_t            headers_out;
    ngx_http_request_body_t          *request_body;
    time_t                            lingering_time;
    time_t                            start_sec;
    ngx_msec_t                        start_msec;
    ngx_uint_t                        method;
    ngx_uint_t                        http_version;
    ngx_str_t                         request_line;
    ngx_str_t                         uri;
    ngx_str_t                         args;
    ngx_str_t                         exten;
    ngx_str_t                         unparsed_uri;
    ngx_str_t                         method_name;
    ngx_str_t                         http_protocol;
    ngx_chain_t                      *out;
    ngx_http_request_t               *main;
    ngx_http_request_t               *parent;
    ngx_http_postponed_request_t     *postponed;
    ngx_http_post_subrequest_t       *post_subrequest;
    ngx_http_posted_request_t        *posted_requests;
    ngx_http_virtual_names_t         *virtual_names;
    ngx_int_t                         phase_handler;
    ngx_http_handler_pt               content_handler;
    ngx_uint_t                        access_code;
    ngx_http_variable_value_t        *variables;
    /* ... */
}

Pieza interceptada. Puede ver que hay información HTTP común, como uri, args y request_body. Los campos que necesitan atención especial aquí son headers_in, headers_out y chain, que representan el encabezado de solicitud, el encabezado de respuesta y la lista enlazada del búfer de datos de salida, respectivamente

El primer paso es obtener la información de configuración del módulo, esto es solo un uso simple de ngx_http_get_module_loc_conf.

El segundo paso es la lógica funcional. Debido a que el módulo de eco es muy simple, simplemente genera una cadena, por lo que no hay un código lógico funcional aquí.

El tercer paso es configurar el encabezado de respuesta. El contenido del encabezado se puede realizar completando headers_out, aquí solo establecemos contenido básico como el tipo de contenido y la longitud del contenido

Después de configurar la información del encabezado, use ngx_http_send_header para generar la información del encabezado. Ngx_http_send_header acepta un parámetro de tipo ngx_http_request_t.

El cuarto y más importante paso es generar el cuerpo de respuesta. Aquí primero debemos entender el mecanismo de E / S de Nginx. Nginx permite que el controlador genere un conjunto de salidas a la vez, que se pueden generar varias veces. Nginx organiza la salida en una estructura de lista de un solo enlace. Cada nodo en el La lista vinculada es un chain_t, definido en core / ngx_buf .h:

struct ngx_chain_s {
    ngx_buf_t    *buf;
    ngx_chain_t  *next;
};

Entre ellos, ngx_chain_t es un alias de ngx_chain_s, buf es un puntero a un cierto búfer de datos, y luego apunta al siguiente nodo de la lista enlazada.Puede ver que esta es una lista enlazada muy simple. La definición de ngx_buf_t es relativamente larga y complicada, por lo que no la publicaré aquí, consulte core / ngx_buf.h usted mismo. Los más importantes en ngx_but_t son pos y last, que representan respectivamente la dirección inicial y la dirección final de los datos del búfer en la memoria.Aquí pasamos la cadena en la configuración, last_buf es un campo de bits, establecido en 1 para indicar esto buffer Es el último elemento de la lista enlazada y 0 significa que hay más elementos detrás. Debido a que solo tenemos un conjunto de datos, solo hay un nodo en la lista enlazada de búfer. Si necesita ingresar varios conjuntos de datos, puede colocar cada conjunto de datos en diferentes búferes e insertarlo en la lista enlazada. La siguiente figura muestra la estructura de la lista de búfer de Nginx:
[Error en la transferencia de imagen de enlace externo, el sitio de origen puede tener un mecanismo de cadena anti-sanguijuelas, se recomienda guardar la imagen y cargarla directamente (img-J8rJHrdG-1610623194355) ( en-resource: // database / 645): 1)]

Una vez que los datos almacenados en búfer están listos, se pueden generar con ngx_http_output_filter (se enviarán a filtrar para varios procesos de filtrado). El primer parámetro de ngx_http_output_filter es la estructura ngx_http_request_t, y el segundo es la dirección de inicio y fuera de la lista de enlaces de salida. ngx_http_out_put_filter recorrerá la lista vinculada y mostrará todos los datos.

6. Combine el módulo Nginx para
completar el desarrollo de varios componentes del módulo Nginx. Lo siguiente es ponerlos juntos.
Un módulo Nginx se define como una estructura ngx_module_t. Esta estructura tiene muchos campos, pero los campos inicial y final generalmente se pueden completar con macros integradas de Nginx. La siguiente es la definición del cuerpo del módulo de nuestro módulo echo:

ngx_module_t  ngx_http_echo_module = {
    NGX_MODULE_V1,
    &ngx_http_echo_module_ctx,             /* module context */
    ngx_http_echo_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
};

El principio y el final están llenos de varios campos con NGX_MODULE_V1 y NGX_MODULE_V1_PADDING respectivamente, por lo que no profundizaré más. La información principal que debe completarse aquí es de arriba a abajo en orden de contexto, matriz de instrucciones, tipo de módulo y funciones de procesamiento de devolución de llamada para ciertos eventos específicos (no es necesario establecerlo en NULL). El contenido es relativamente fácil de Entiendo. Preste atención a nuestro eco Es un módulo HTTP, por lo que el tipo aquí es NGX_HTTP_MODULE. Otros tipos disponibles incluyen NGX_EVENT_MODULE (módulo de procesamiento de eventos) y NGX_MAIL_MODULE (módulo de correo).

A continuación se muestra el código completo de todo el módulo.

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
/* Module config */
typedef struct {
    ngx_str_t  ed;
} ngx_http_echo_loc_conf_t;
static char *ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
/* Directives */
static ngx_command_t  ngx_http_echo_commands[] = {
    { ngx_string("echo"),
        NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_http_echo,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_echo_loc_conf_t, ed),
        NULL },
        ngx_null_command
};
/* Http context of the module */
static ngx_http_module_t  ngx_http_echo_module_ctx = {
    NULL,                                  /* preconfiguration */
    NULL,                                  /* postconfiguration */
    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */
    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */
    ngx_http_echo_create_loc_conf,         /* create location configration */
    ngx_http_echo_merge_loc_conf           /* merge location configration */
};
/* Module */
ngx_module_t  ngx_http_echo_module = {
    NGX_MODULE_V1,
    &ngx_http_echo_module_ctx,             /* module context */
    ngx_http_echo_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
};
/* Handler function */
static ngx_int_t
ngx_http_echo_handler(ngx_http_request_t *r)
{
    ngx_int_t rc;
    ngx_buf_t *b;
    ngx_chain_t out;
    ngx_http_echo_loc_conf_t *elcf;
    elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
    if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST)))
    {
        return NGX_HTTP_NOT_ALLOWED;
    }
    r->headers_out.content_type.len = sizeof("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 = elcf->ed.len;
    if(r->method == NGX_HTTP_HEAD)
    {
        rc = ngx_http_send_header(r);
        if(rc != NGX_OK)
        {
            return rc;
        }
    }
    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if(b == NULL)
    {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    out.buf = b;
    out.next = NULL;
    b->pos = elcf->ed.data;
    b->last = elcf->ed.data + (elcf->ed.len);
    b->memory = 1;
    b->last_buf = 1;
    rc = ngx_http_send_header(r);
    if(rc != NGX_OK)
    {
        return rc;
    }
    return ngx_http_output_filter(r, &out);
}
static char *
ngx_http_echo(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_echo_handler;
    ngx_conf_set_str_slot(cf,cmd,conf);
    return NGX_CONF_OK;
}
static void *
ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_echo_loc_conf_t  *conf;
    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
    if (conf == NULL) {
        return NGX_CONF_ERROR;
    }
    conf->ed.len = 0;
    conf->ed.data = NULL;
    return conf;
}
static char *
ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_echo_loc_conf_t *prev = parent;
    ngx_http_echo_loc_conf_t *conf = child;
    ngx_conf_merge_str_value(conf->ed, prev->ed, "");
    return NGX_CONF_OK;
}

Supongo que te gusta

Origin blog.csdn.net/thesprit/article/details/112630377
Recomendado
Clasificación