Desarrollo#

Angie es un proyecto de código abierto que da la bienvenida a todos los colaboradores.

Código fuente#

Puedes clonar el código fuente de Angie desde nuestros repositorios públicos: Mercurial, Git.

Estilo de codificación#

Tus cambios deben ser consistentes con el resto del código de Angie; las convenciones de codificación son un buen punto de partida.

Truco

En caso de duda, examina el código cercano para seguir su ejemplo, o simplemente usa grep en la base de código para inspirarte.

Mensajes de commits#

Históricamente, el registro de commits se mantiene en inglés.

Comienza con un resumen de una sola línea de lo que se hizo. Puede llevar un prefijo que el registro de commits use para la parte de código afectada. El resumen puede tener hasta 67 caracteres y puede ir seguido de una línea en blanco y más detalles.

Un buen mensaje indica qué causó el cambio, qué se hizo al respecto y cuál es la situación ahora:

API: bad things removed, good things added.

As explained elsewhere[1], the original API was bad because stuff;
this change was introduced to improve that aspect locally.

Levels of goodness have been implemented to mitigate the badness;
this is now the preferred way to work.  Also, the badness is gone.

[1] https://example.com

Detalles que pueden pasar desapercibidos:

  • El resumen termina con un punto y empieza con una letra mayúscula.

  • Si se usa un prefijo, éste va seguido de una letra minúscula.

  • Dos espacios en blanco separan oraciones dentro de una misma línea.

Verificaciones finales#

  • Haz todo lo posible para verificar que los cambios funcionen en todas las plataformas de destino.

  • Para cada plataforma, ejecuta la suite de pruebas para asegurarte de que no haya regresiones:

    $ cd tests
    $ prove .
    

    Consulta el archivo tests/README para obtener detalles.

  • Asegúrate de estar cómodo con los términos legales.

Envío de contribuciones#

Para enviar un parche, crea una pull request en nuestro espejo de GitHub.

Para preguntas y sugerencias, ponte en contacto con los desarrolladores a través de Issues de GitHub.

Convenciones de codificación#

El código fuente sigue la estructura y convenciones siguientes.

Disposición del código#

  • auto — Scripts de compilación

  • src

    • core — Tipos y funciones básicas — cadena, arreglo, registro, pool, etc.

    • event — Núcleo de eventos

      • modules — Módulos de notificación de eventos: epoll, kqueue, select etc.

    • http — Módulo HTTP principal y código común

      • modules — Otros módulos HTTP

      • v2 — HTTP/2

    • mail — Módulos de correo

    • os — Código específico de la plataforma

      • unix

      • win32

    • stream — Módulos de flujo

Archivos de inclusión#

Las siguientes dos sentencias #include deben aparecer al principio de cada archivo Angie:

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

Además de eso, el código HTTP debe incluir

#include <ngx_http.h>

El código de Mail debe incluir

#include <ngx_mail.h>

El código de Stream debe incluir

#include <ngx_stream.h>

Enteros#

Para fines generales, el código de Angie utiliza dos tipos enteros: ngx_int_t y ngx_uint_t, que son typedefs para intptr_t y uintptr_t respectivamente.

Códigos de retorno comunes#

La mayoría de las funciones en Angie devuelven los siguientes códigos:

  • NGX_OK — Operación exitosa.

  • NGX_ERROR — La operación falló.

  • NGX_AGAIN — Operación incompleta; vuelva a llamar a la función.

  • NGX_DECLINED — Operación rechazada, por ejemplo, porque está deshabilitada en la configuración. Esto nunca es un error.

  • NGX_BUSY — El recurso no está disponible.

  • NGX_DONE — Operación completa o continuada en otro lugar. También se usa como código de éxito alternativo.

  • NGX_ABORT — La función fue abortada. También se usa como código de error alternativo.

Manejo de errores#

El macro ngx_errno devuelve el último código de error del sistema. Se asigna a errno en plataformas POSIX y a GetLastError() en Windows. El macro ngx_socket_errno devuelve el último número de error de socket. Al igual que el macro ngx_errno, se asigna a errno en plataformas POSIX. Se asigna a la llamada WSAGetLastError() en Windows. Acceder a los valores de ngx_errno o ngx_socket_errno más de una vez seguidas puede causar problemas de rendimiento. Si el valor de error podría usarse varias veces, cúrralo en una variable local de tipo ngx_err_t. Para establecer errores, use los macros ngx_set_errno(errno) y ngx_set_socket_errno(errno).

Los valores de ngx_errno y ngx_socket_errno pueden pasarse a las funciones de registro ngx_log_error() y ngx_log_debugX(), en cuyo caso se añade el texto de error del sistema al mensaje de registro.

Ejemplo usando ngx_errno:

ngx_int_t
ngx_my_kill(ngx_pid_t pid, ngx_log_t *log, int signo)
{
    ngx_err_t  err;

    if (kill(pid, signo) == -1) {
        err = ngx_errno;

        ngx_log_error(NGX_LOG_ALERT, log, err, "kill(%P, %d) failed", pid, signo);

        if (err == NGX_ESRCH) {
            return 2;
        }

        return 1;
    }

    return 0;
}

Cadenas#

Visión general#

Para cadenas en C, Angie utiliza un puntero de tipo carácter sin signo u_char *.

El tipo de cadena de Angie ngx_str_t se define como sigue:

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

El campo len almacena la longitud de la cadena y data contiene los datos de la cadena. La cadena, contenida en ngx_str_t, puede o no estar terminada en nulo tras los bytes de len. En la mayoría de los casos no lo está. Sin embargo, en ciertas partes del código (p. ej., al analizar la configuración), ngx_str_t objetos se sabe que están terminados en nulo, lo que simplifica la comparación de cadenas y facilita pasarlas a llamadas al sistema.

Las operaciones de cadena en Angie se declaran en src/core/ngx_string.h. Algunas de ellas son envoltorios alrededor de funciones estándar de C:

  • ngx_strcmp()

  • ngx_strncmp()

  • ngx_strstr()

  • ngx_strlen()

  • ngx_strchr()

  • ngx_memcmp()

  • ngx_memset()

  • ngx_memcpy()

  • ngx_memmove()

Otras funciones de cadena son específicas de Angie

  • ngx_memzero() — Llena la memoria de ceros.

  • ngx_explicit_memzero() — Hace lo mismo que ngx_memzero(), pero esta llamada nunca es eliminada por la optimización de eliminación de almacenamiento muerto del compilador. Esta función puede usarse para borrar datos sensibles como contraseñas y claves.

  • ngx_cpymem() — Hace lo mismo que ngx_memcpy(), pero devuelve la dirección final del destino. Esto resulta práctico para anexar varias cadenas en una fila.

  • ngx_movemem() — Hace lo mismo que ngx_memmove(), pero devuelve la dirección final del destino.

  • ngx_strlchr() — Busca un carácter en una cadena, delimitado por dos punteros.

Las siguientes funciones realizan conversión de mayúsculas/minúsculas y comparación:

  • ngx_tolower()

  • ngx_toupper()

  • ngx_strlow()

  • ngx_strcasecmp()

  • ngx_strncasecmp()

Las siguientes macros simplifican la inicialización de cadenas:

  • ngx_string(text) — inicializador estático para el tipo ngx_str_t a partir de la cadena literal C text

  • ngx_null_string — inicializador estático de cadena vacía para el tipo ngx_str_t

  • ngx_str_set(str, text) — inicializa la cadena str de tipo ngx_str_t * con la literal de cadena C text

  • ngx_str_null(str) — inicializa la cadena str de tipo ngx_str_t * con la cadena vacía

Formato#

Las siguientes funciones de formato admiten tipos específicos de Angie:

  • ngx_sprintf(buf, fmt, ...)

  • ngx_snprintf(buf, max, fmt, ...)

  • ngx_slprintf(buf, last, fmt, ...)

  • ngx_vslprintf(buf, last, fmt, args)

  • ngx_vsnprintf(buf, max, fmt, args)

La lista completa de opciones de formato, admitidas por estas funciones, se encuentra en src/core/ngx_string.c. Algunas de ellas son:

  • %Ooff_t

  • %Ttime_t

  • %zssize_t

  • %ingx_int_t

  • %pvoid *

  • %Vngx_str_t *

  • %su_char * (null-terminated)

  • %*ssize_t + u_char *

Puede anteponer u en la mayoría de tipos para hacerlos sin signo. Para convertir la salida a hexadecimal, usa X o x.

Conversión numérica#

Varias funciones para conversión numérica están implementadas en Angie. Las cuatro primeras convierten una cadena de longitud dada a un entero positivo del tipo indicado. Devuelven NGX_ERROR en caso de error.

  • ngx_atoi(line, n)ngx_int_t

  • ngx_atosz(line, n)ssize_t

  • ngx_atoof(line, n)off_t

  • ngx_atotm(line, n)time_t

Hay dos funciones adicionales de conversión numérica. Al igual que las cuatro primeras, devuelven NGX_ERROR en caso de error.

  • ngx_atofp(line, n, point) — Convierte un número flotante de punto fijo de longitud dada a un entero positivo del tipo ngx_int_t. El resultado se desplaza a la izquierda por point posiciones decimales. Se espera que la representación en cadena del número tenga no más de points dígitos fraccionarios. Por ejemplo, ngx_atofp("10.5", 4, 2) devuelve 1050.

  • ngx_hextoi(line, n) — Convierte una representación hexadecimal de un entero positivo a ngx_int_t.

Expresiones regulares#

La interfaz de expresiones regulares en Angie es un envoltorio alrededor de la biblioteca PCRE. El archivo de encabezado correspondiente es src/core/ngx_regex.h.

Para usar una expresión regular para la coincidencia de cadenas, primero debe compilarse, lo cual generalmente se realiza en la fase de configuración. Tenga en cuenta que dado que el soporte de PCRE es opcional, todo el código que use la interfaz debe estar protegido por la macro circundante NGX_PCRE:

#if (NGX_PCRE)
ngx_regex_t          *re;
ngx_regex_compile_t   rc;

u_char                errstr[NGX_MAX_CONF_ERRSTR];

ngx_str_t  value = ngx_string("message (\\d\\d\\d).*Codeword is '(?<cw>\\w+)'");

ngx_memzero(&rc, sizeof(ngx_regex_compile_t));

rc.pattern = value;
rc.pool = cf->pool;
rc.err.len = NGX_MAX_CONF_ERRSTR;
rc.err.data = errstr;
/* rc.options can be set to NGX_REGEX_CASELESS */

if (ngx_regex_compile(&rc) != NGX_OK) {
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
    return NGX_CONF_ERROR;
}

re = rc.regex;
#endif

Después de una compilación exitosa, los campos captures y named_captures en la ngx_regex_compile_t estructura contienen la cantidad de capturas y capturas con nombre, respectivamente, encontradas en la expresión regular.

La expresión regular compilada puede luego usarse para hacer coincidencias con cadenas:

ngx_int_t  n;
int        captures[(1 + rc.captures) * 3];

ngx_str_t input = ngx_string("This is message 123. Codeword is 'foobar'.");

n = ngx_regex_exec(re, &input, captures, (1 + rc.captures) * 3);
if (n >= 0) {
    /* string matches expression */

} else if (n == NGX_REGEX_NO_MATCHED) {
    /* no match was found */

} else {
    /* some error */
    ngx_log_error(NGX_LOG_ALERT, log, 0, ngx_regex_exec_n " failed: %i", n);
}

Los argumentos de ngx_regex_exec() son la expresión regular compilada re, la cadena a comparar input, un arreglo opcional de enteros para contener las captures que se encuentren, y el tamaño del arreglo. El tamaño del arreglo captures debe ser un múltiplo de tres, tal como lo exige la PCRE API. En el ejemplo, el tamaño se calcula a partir del total de capturas más uno para la cadena coincidente.

Si hay coincidencias, las capturas pueden accederse de la siguiente manera:

u_char     *p;
size_t      size;
ngx_str_t   name, value;

/* todas las capturas */
for (i = 0; i < n * 2; i += 2) {
    value.data = input.data + captures[i];
    value.len = captures[i + 1] - captures[i];
}

/* accediendo a capturas con nombre */

size = rc.name_size;
p = rc.names;

for (i = 0; i < rc.named_captures; i++, p += size) {

    /* nombre de la captura */
    name.data = &p[2];
    name.len = ngx_strlen(name.data);

    n = 2 * ((p[0] << 8) + p[1]);

    /* valor capturado */
    value.data = &input.data[captures[n]];
    value.len = captures[n + 1] - captures[n];
}

La función ngx_regex_exec_array() acepta el arreglo de elementos ngx_regex_elt_t (que son simplemente expresiones regulares compiladas con nombres asociados), una cadena a comparar y un registro. La función aplica expresiones del arreglo a la cadena hasta que se encuentre una coincidencia o no queden expresiones por aplicar. El valor de retorno es NGX_OK cuando hay coincidencia y NGX_DECLINED en caso contrario, o NGX_ERROR en caso de error.

Tiempo#

La estructura ngx_time_t representa el tiempo con tres tipos distintos para segundos, milisegundos y el desplazamiento GMT:

typedef struct {
    time_t      sec;
    ngx_uint_t  msec;
    ngx_int_t   gmtoff;
} ngx_time_t;

La estructura ngx_tm_t es un alias de struct tm en plataformas UNIX y de SYSTEMTIME en Windows.

Para obtener la hora actual, por lo general es suficiente acceder a una de las variables globales disponibles, que representan el valor del tiempo almacenado en caché en el formato deseado.

Las representaciones en cadena disponibles son:

  • ngx_cached_err_log_time — Utilizada en entradas de registro de errores: "1970/09/28 12:00:00"

  • ngx_cached_http_log_time — Utilizada en entradas de registro de acceso HTTP: "28/Sep/1970:12:00:00 +0600"

  • ngx_cached_syslog_time — Utilizada en entradas de syslog: "Sep 28 12:00:00"

  • ngx_cached_http_time — Utilizada en encabezados HTTP: "Mon, 28 Sep 1970 06:00:00 GMT"

  • ngx_cached_http_log_iso8601 — El formato ISO 8601 estándar: "1970-09-28T12:00:00+06:00"

Las macros ngx_time() y ngx_timeofday() devuelven el valor de tiempo actual en segundos y son la forma preferida de acceder al valor de tiempo en caché.

Para obtener el tiempo explícitamente, use ngx_gettimeofday(), que actualiza su argumento (puntero a struct timeval). El tiempo se actualiza siempre cuando Angie regresa al bucle de eventos desde llamadas al sistema. Para actualizar el tiempo de inmediato, llame a ngx_time_update(), o ngx_time_sigsafe_update() si está actualizando el tiempo en el contexto del manejador de señales.

Las siguientes funciones convierten time_t en la representación de tiempo desglosada indicada. La primera función de cada par convierte time_t a ngx_tm_t y la segunda (con el sufijo _libc_) a struct tm:

  • ngx_gmtime(), ngx_libc_gmtime() — Tiempo expresado como UTC

  • ngx_localtime(), ngx_libc_localtime() — Tiempo expresado relativo a la zona horaria local

La función ngx_http_time(buf, time) devuelve una representación en cadena adecuada para su uso en encabezados HTTP (por ejemplo, "Mon, 28 Sep 1970 06:00:00 GMT"). La ngx_http_cookie_time(buf, time) devuelve una representación en cadena adecuada para cookies HTTP ("Thu, 31-Dec-37 23:55:55 GMT").

Contenedores#

Arreglo#

El tipo de arreglo Angie ngx_array_t se define de la siguiente manera

typedef struct {
    void        *elts;
    ngx_uint_t   nelts;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *pool;
} ngx_array_t;

Los elementos del arreglo están disponibles en el campo elts. El campo nelts contiene el número de elementos. El campo size contiene el tamaño de un solo elemento y se establece cuando el arreglo se inicializa.

Utilice la llamada ngx_array_create(pool, n, size) para crear un arreglo en un pool, y la llamada ngx_array_init(array, pool, n, size) para inicializar un objeto de arreglo que ya ha sido asignado.

ngx_array_t  *a, b;

/* crear un arreglo de cadenas con memoria preasignada para 10 elementos */
a = ngx_array_create(pool, 10, sizeof(ngx_str_t));

/* inicializar arreglo de cadenas para 10 elementos */
ngx_array_init(&b, pool, 10, sizeof(ngx_str_t));

Utilice las siguientes funciones para añadir elementos a un arreglo:

  • ngx_array_push(a) añade un elemento al final y devuelve un puntero a él

  • ngx_array_push_n(a, n) añade n elementos al final y devuelve un puntero al primero

Si la cantidad de memoria actualmente asignada no es suficiente para alojar los nuevos elementos, se asigna un nuevo bloque de memoria y los elementos existentes se copian en él. El nuevo bloque de memoria suele ser el doble de grande que el existente.

s = ngx_array_push(a);
ss = ngx_array_push_n(&b, 3);

Lista#

En Angie, una lista es una secuencia de arreglos, optimizada para insertar una cantidad potencialmente grande de elementos. El tipo de lista ngx_list_t se define de la siguiente manera:

typedef struct {
    ngx_list_part_t  *last;
    ngx_list_part_t   part;
    size_t            size;
    ngx_uint_t        nalloc;
    ngx_pool_t       *pool;
} ngx_list_t;

Los elementos reales se almacenan en partes de la lista, que se definen como sigue:

typedef struct ngx_list_part_s  ngx_list_part_t;

struct ngx_list_part_s {
    void             *elts;
    ngx_uint_t        nelts;
    ngx_list_part_t  *next;
};

Antes de usarla, una lista debe ser inicializada llamando ngx_list_init(list, pool, n, size) o creada llamando ngx_list_create(pool, n, size). Ambas funciones toman como argumentos el tamaño de un solo elemento y una cantidad de elementos por parte de la lista. Para añadir un elemento a una lista, use la función ngx_list_push(list). Para iterar sobre los elementos, acceda directamente a los campos de la lista como se muestra en el ejemplo:

ngx_str_t        *v;
ngx_uint_t        i;
ngx_list_t       *list;
ngx_list_part_t  *part;

list = ngx_list_create(pool, 100, sizeof(ngx_str_t));
if (list == NULL) { /* error */ }

/* añadir elementos a la lista */

v = ngx_list_push(list);
if (v == NULL) { /* error */ }
ngx_str_set(v, "foo");

v = ngx_list_push(list);
if (v == NULL) { /* error */ }
ngx_str_set(v, "bar");

/* iterar sobre la lista */

part = &list->part;
v = part->elts;

for (i = 0; /* void */; i++) {

    if (i >= part->nelts) {
        if (part->next == NULL) {
            break;
        }

        part = part->next;
        v = part->elts;
        i = 0;
    }

    ngx_do_smth(&v[i]);
}

Las listas se utilizan principalmente para encabezados HTTP de entrada y salida.

Las listas no admiten la eliminación de elementos. Sin embargo, cuando sea necesario, los elementos pueden marcarse internamente como ausentes sin eliminarse realmente de la lista. Por ejemplo, para marcar como ausentes los encabezados de salida HTTP (que se almacenan como objetos ngx_table_elt_t) establezca el campo hash en ngx_table_elt_t a cero. Los elementos marcados de esta forma se omiten explícitamente cuando se itera sobre los encabezados.

Cola#

En Angie, una cola es una lista doblemente enlazada intrusiva, con cada nodo definido como sigue:

typedef struct ngx_queue_s  ngx_queue_t;

struct ngx_queue_s {
    ngx_queue_t  *prev;
    ngx_queue_t  *next;
};

El nodo cabeza de la cola no está enlazado con ningún dato. Utilice la llamada ngx_queue_init(q) para inicializar la cabeza de la lista antes de su uso. Las colas soportan las siguientes operaciones:

  • ngx_queue_insert_head(h, x), ngx_queue_insert_tail(h, x) — Inserta un nuevo nodo

  • ngx_queue_remove(x) — Elimina un nodo de la cola

  • ngx_queue_split(h, q, n) — Divide una cola en un nodo, devolviendo la cola tail en una cola separada

  • ngx_queue_add(h, n) — Añade una segunda cola a la primera

  • ngx_queue_head(h), ngx_queue_last(h) — Obtiene el primer o último nodo de la cola

  • ngx_queue_sentinel(h) - Obtiene un objeto centinela de cola para terminar la iteración en

  • ngx_queue_data(q, type, link) — Obtiene una referencia al inicio de la estructura de datos del nodo de la cola, considerando el desplazamiento del campo de la cola en ella

Un ejemplo:

typedef struct {
    ngx_str_t    value;
    ngx_queue_t  queue;
} ngx_foo_t;

ngx_foo_t    *f;
ngx_queue_t   values, *q;

ngx_queue_init(&values);

f = ngx_palloc(pool, sizeof(ngx_foo_t));
if (f == NULL) { /* error */ }
ngx_str_set(&f->value, "foo");

ngx_queue_insert_tail(&values, &f->queue);

/* insert more nodes here */

for (q = ngx_queue_head(&values);
     q != ngx_queue_sentinel(&values);
     q = ngx_queue_next(q))
{
    f = ngx_queue_data(q, ngx_foo_t, queue);

    ngx_do_smth(&f->value);
}

Árbol Rojo-Negro#

El archivo de cabecera src/core/ngx_rbtree.h ofrece acceso a la implementación eficiente de árboles rojo-negro.

typedef struct {
    ngx_rbtree_t       rbtree;
    ngx_rbtree_node_t  sentinel;

    /* custom per-tree data here */
} my_tree_t;

typedef struct {
    ngx_rbtree_node_t  rbnode;

    /* custom per-node data */
    foo_t              val;
} my_node_t;

Para tratar un árbol en su conjunto, necesitas dos nodos: la raíz y la sentinela. Generalmente, se añaden a una estructura personalizada, lo que te permite organizar tus datos en un árbol en el que las hojas contienen un enlace a tus datos o incrustan tus datos.

Para inicializar un árbol:

my_tree_t  root;

ngx_rbtree_init(&root.rbtree, &root.sentinel, insert_value_function);

Para recorrer un árbol e insertar nuevos valores, usa las funciones insert_value. Por ejemplo, la función ngx_str_rbtree_insert_value maneja el tipo ngx_str_t. Sus argumentos son punteros al nodo raíz de la inserción, al nodo recién creado para añadir y a la sentinela del árbol.

void ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp,
                                 ngx_rbtree_node_t *node,
                                 ngx_rbtree_node_t *sentinel)

La traversa es bastante directa y puede demostrarse con el siguiente patrón de función de búsqueda:

my_node_t *
my_rbtree_lookup(ngx_rbtree_t *rbtree, foo_t *val, uint32_t hash)
{
    ngx_int_t           rc;
    my_node_t          *n;
    ngx_rbtree_node_t  *node, *sentinel;

    node = rbtree->root;
    sentinel = rbtree->sentinel;

    while (node != sentinel) {

        n = (my_node_t *) node;

        if (hash != node->key) {
            node = (hash < node->key) ? node->left : node->right;
            continue;
        }

        rc = compare(val, node->val);

        if (rc < 0) {
            node = node->left;
            continue;
        }

        if (rc > 0) {
            node = node->right;
            continue;
        }

        return n;
    }

    return NULL;
}

La función compare() es una función de comparación clásica que devuelve un valor menor que cero, igual a cero o mayor que cero. Para acelerar las búsquedas y evitar comparar objetos de usuario que pueden ser grandes, se utiliza un campo hash entero.

Para añadir un nodo al árbol, aloca un nuevo nodo, inicialízalo y llama a ngx_rbtree_insert():

my_node_t          *my_node;
ngx_rbtree_node_t  *node;

my_node = ngx_palloc(...);
init_custom_data(&my_node->val);

node = &my_node->rbnode;
node->key = create_key(my_node->val);

ngx_rbtree_insert(&root->rbtree, node);

Para eliminar un nodo, llama a la función ngx_rbtree_delete():

ngx_rbtree_delete(&root->rbtree, node);

Tabla hash#

Las funciones de tablas hash se declaran en src/core/ngx_hash.h. Se admite coincidencia exacta y con comodines. La segunda requiere una configuración adicional y se describe en una sección separada a continuación.

Antes de inicializar una hash, necesitas conocer el número de elementos que contendrá para que Angie pueda construirla de forma óptima. Dos parámetros que deben configurarse son max_size y bucket_size, como se detalla en un documento separado documento. Generalmente, son configurables por el usuario. La configuración de inicialización de hash se almacena con el tipo ngx_hash_init_t, y el propio hash es ngx_hash_t:

ngx_hash_t       foo_hash;
ngx_hash_init_t  hash;

hash.hash = &foo_hash;
hash.key = ngx_hash_key;
hash.max_size = 512;
hash.bucket_size = ngx_align(64, ngx_cacheline_size);
hash.name = "foo_hash";
hash.pool = cf->pool;
hash.temp_pool = cf->temp_pool;

La clave es un puntero a una función que crea la clave entera del hash a partir de una cadena. Existen dos funciones genéricas de creación de claves: ngx_hash_key(data, len) y ngx_hash_key_lc(data, len). La segunda convierte una cadena a todos los caracteres en minúscula, por lo que la cadena pasada debe ser modificable. Si eso no es así, pasa la bandera NGX_HASH_READONLY_KEY a la función, inicializando el arreglo de claves (ver más abajo).

Las claves del hash se almacenan en ngx_hash_keys_arrays_t y se inicializan con ngx_hash_keys_array_init(arr, type): El segundo parámetro (type) controla la cantidad de recursos preasignados para el hash y puede ser NGX_HASH_SMALL o NGX_HASH_LARGE. Este último es adecuado si esperas que el hash contenga miles de elementos.

ngx_hash_keys_arrays_t  foo_keys;

foo_keys.pool = cf->pool;
foo_keys.temp_pool = cf->temp_pool;

ngx_hash_keys_array_init(&foo_keys, NGX_HASH_SMALL);

Para insertar claves en un arreglo de claves hash, utiliza la ngx_hash_add_key(keys_array, key, value, flags):

ngx_str_t k1 = ngx_string("key1");
ngx_str_t k2 = ngx_string("key2");

ngx_hash_add_key(&foo_keys, &k1, &my_data_ptr_1, NGX_HASH_READONLY_KEY);
ngx_hash_add_key(&foo_keys, &k2, &my_data_ptr_2, NGX_HASH_READONLY_KEY);

Para construir la tabla hash, llama a la ngx_hash_init(hinit, key_names, nelts):

ngx_hash_init(&hash, foo_keys.keys.elts, foo_keys.keys.nelts);

La función falla si max_size o bucket_size no son lo suficientemente grandes.

Cuando se construye el hash, usa la ngx_hash_find(hash, key, name, len) para buscar elementos:

my_data_t   *data;
ngx_uint_t   key;

key = ngx_hash_key(k1.data, k1.len);

data = ngx_hash_find(&foo_hash, key, k1.data, k1.len);
if (data == NULL) {
    /* key not found */
}

Coincidencia con comodines#

Para crear una hash que funcione con comodines, utiliza el tipo ngx_hash_combined_t. Incluye el tipo hash descrito arriba y tiene dos arreglos de claves adicionales: dns_wc_head y dns_wc_tail. La inicialización de las propiedades básicas es similar a la de un hash regular:

ngx_hash_init_t      hash
ngx_hash_combined_t  foo_hash;

hash.hash = &foo_hash.hash;
hash.key = ...;

Es posible añadir claves con comodines usando la bandera NGX_HASH_WILDCARD_KEY:

/* k1 = ".example.org"; */
/* k2 = "foo.*";        */
ngx_hash_add_key(&foo_keys, &k1, &data1, NGX_HASH_WILDCARD_KEY);
ngx_hash_add_key(&foo_keys, &k2, &data2, NGX_HASH_WILDCARD_KEY);

La función reconoce comodines y añade claves a los arreglos correspondientes. Por favor, consulta la documentación del módulo Map para la descripción de la sintaxis de comodines y del algoritmo de coincidencia.

Dependiendo del contenido de las claves añadidas, podría ser necesario inicializar hasta tres arreglos de claves: uno para la coincidencia exacta (descrita arriba) y dos más para habilitar la coincidencia desde la cabecera o desde la cola de una cadena:

if (foo_keys.dns_wc_head.nelts) {

    ngx_qsort(foo_keys.dns_wc_head.elts,
              (size_t) foo_keys.dns_wc_head.nelts,
              sizeof(ngx_hash_key_t),
              cmp_dns_wildcards);

    hash.hash = NULL;
    hash.temp_pool = pool;

    if (ngx_hash_wildcard_init(&hash, foo_keys.dns_wc_head.elts,
                               foo_keys.dns_wc_head.nelts)
        != NGX_OK)
    {
        return NGX_ERROR;
    }

    foo_hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
}

The keys array needs to be sorted, and initialization results must be added to the combined hash. The initialization of dns_wc_tail array is done similarly.

La búsqueda en un hash combinado se maneja mediante ngx_hash_find_combined(chash, key, name, len):

/* key = "bar.example.org"; - will match ".example.org" */
/* key = "foo.example.com"; - will match "foo.*"        */

hkey = ngx_hash_key(key.data, key.len);
res = ngx_hash_find_combined(&foo_hash, hkey, key.data, key.len);

Gestión de la memoria#

Heap#

Para asignar memoria desde el heap del sistema, use las siguientes funciones:

  • ngx_alloc(size, log) — Asignar memoria desde el heap del sistema. Este es un envoltorio alrededor de malloc() con soporte de registro (logging). Los errores de asignación y la información de depuración se registran en log.

  • ngx_calloc(size, log) — Asignar memoria desde el heap del sistema similar a ngx_alloc(), pero llena la memoria con ceros después de la asignación.

  • ngx_memalign(alignment, size, log) — Asignar memoria alineada desde el heap del sistema. Esto es un envoltorio alrededor de posix_memalign() en aquellas plataformas que proporcionan esa función. En caso contrario, la implementación recurre a ngx_alloc() que proporciona el alineamiento máximo.

  • ngx_free(p) — Liberar la memoria asignada. Este es un envoltorio alrededor de free()

Pool#

La mayoría de las asignaciones de Angie se realizan en pools. La memoria asignada en un pool de Angie se libera automáticamente cuando el pool se destruye. Esto proporciona un buen rendimiento de asignación y facilita el control de la memoria.

Un pool internamente asigna objetos en bloques continuos de memoria. Una vez que un bloque se llena, se asigna uno nuevo y se añade a la lista de bloques de memoria del pool. Cuando la asignación solicitada es demasiado grande para caber en un bloque, la solicitud se envía al asignador del sistema y el puntero devuelto se almacena en el pool para su liberación posterior.

El tipo para los pools de Angie es ngx_pool_t. Las siguientes operaciones son compatibles:

  • ngx_create_pool(size, log) — Crear un pool con el tamaño de bloque especificado. El objeto pool devuelto también se asigna dentro del pool. El size debe ser al menos NGX_MIN_POOL_SIZE y múltiplo de NGX_POOL_ALIGNMENT.

  • ngx_destroy_pool(pool) — Liberar toda la memoria del pool, incluyendo el propio objeto pool.

  • ngx_palloc(pool, size) — Asignar memoria alineada desde el pool especificado.

  • ngx_pcalloc(pool, size) — Asignar memoria alineada desde el pool especificado y llenarla de ceros.

  • ngx_pnalloc(pool, size) — Asignar memoria no alineada desde el pool especificado. Mayormente utilizada para asignar cadenas.

  • ngx_pfree(pool, p) — Liberar la memoria que fue previamente asignada en el pool especificado. Solo las asignaciones que resulten de solicitudes reenviadas al asignador del sistema pueden ser liberadas.

u_char      *p;
ngx_str_t   *s;
ngx_pool_t  *pool;

pool = ngx_create_pool(1024, log);
if (pool == NULL) { /* error */ }

s = ngx_palloc(pool, sizeof(ngx_str_t));
if (s == NULL) { /* error */ }
ngx_str_set(s, "foo");

p = ngx_pnalloc(pool, 3);
if (p == NULL) { /* error */ }
ngx_memcpy(p, "foo", 3);

Las cadenas de enlace (ngx_chain_t) se utilizan activamente en Angie, por lo que la implementación del pool de Angie proporciona una forma de reutilizarlas. El campo chain de ngx_pool_t mantiene una lista de enlaces asignados previamente listos para su reutilización. Para una asignación eficiente de un enlace de cadena en un pool, use la función ngx_alloc_chain_link(pool). Esta función busca un enlace de cadena libre en la lista del pool y asigna un nuevo enlace de cadena si la lista del pool está vacía. Para liberar un enlace, llame a la función ngx_free_chain(pool, cl).

Se pueden registrar manejadores de limpieza en un pool. Un manejador de limpieza es una devolución de llamada con un argumento que se llama cuando el pool se destruye. Un pool suele estar vinculado a un objeto específico de Angie (como una solicitud HTTP) y se destruye cuando el objeto llega al final de su vida. Registrar una limpieza de pool es una forma conveniente de liberar recursos, cerrar descriptores de archivos o hacer ajustes finales a los datos compartidos asociados con el objeto principal.

Para registrar una limpieza de pool, llame ngx_pool_cleanup_add(pool, size), que devuelve un ngx_pool_cleanup_t puntero a ser relleno por el emisor. Use el argumento size para asignar contexto para el manejador de limpieza.

ngx_pool_cleanup_t  *cln;

cln = ngx_pool_cleanup_add(pool, 0);
if (cln == NULL) { /* error */ }

cln->handler = ngx_my_cleanup;
cln->data = "foo";

...

static void
ngx_my_cleanup(void *data)
{
    u_char  *msg = data;

    ngx_do_smth(msg);
}

Memoria compartida#

La memoria compartida es utilizada por Angie para compartir datos comunes entre procesos. La función ngx_shared_memory_add(cf, name, size, tag) añade una nueva entrada de memoria compartida ngx_shm_zone_t a un ciclo. La función recibe el name y size de la zona. Cada zona compartida debe tener un nombre único. Si ya existe una entrada de zona compartida con el nombre proporcionado name y tag, la entrada de zona existente se reutiliza. La función falla con un error si existe una entrada con el mismo nombre pero con una etiqueta diferente. Por lo general, se pasa la dirección de la estructura del módulo como tag, haciendo posible reutilizar zonas compartidas por nombre dentro de un mismo módulo de Angie.

La estructura de entrada de memoria compartida ngx_shm_zone_t tiene los siguientes campos:

  • init — Callback de inicialización, llamado después de que la zona compartida se mapea a la memoria real

  • data — Contexto de datos, usado para pasar datos arbitrarios al callback init

  • noreuse — Bandera que desactiva la reutilización de una zona compartida desde el ciclo anterior

  • tag — Etiqueta de la zona compartida

  • shm — Objeto específico de la plataforma de tipo ngx_shm_t, que tiene al menos los siguientes campos:

    • addr — Dirección de memoria compartida mapeada, inicialmente NULL

    • size — Tamaño de la memoria compartida

    • name — Nombre de la memoria compartida

    • log — Registro de la memoria compartida

    • exists — Bandera que indica que la memoria compartida fue heredada desde el proceso maestro (Windows-específico)

Las entradas de zonas compartidas se mapean a la memoria real en ngx_init_cycle() después de analizar la configuración. En sistemas POSIX, se utiliza la syscall mmap() para crear el mapeo anónimo compartido. En Windows, se utiliza la pareja CreateFileMapping()/ MapViewOfFileEx().

Para asignar en memoria compartida, Angie proporciona el tipo de pool en slab ngx_slab_pool_t. Se crea automáticamente un pool en slab para asignar memoria en cada zona compartida de Angie. El pool se encuentra al inicio de la zona compartida y se puede acceder mediante la expresión (ngx_slab_pool_t *) shm_zone->shm.addr. Para asignar memoria en una zona compartida, llame a ngx_slab_alloc(pool, size) o ngx_slab_calloc(pool, size). Para liberar memoria, llame a ngx_slab_free(pool, p).

El pool en slab divide toda la zona compartida en páginas. Cada página se utiliza para asignar objetos del mismo tamaño. El tamaño especificado debe ser una potencia de 2 y mayor que el tamaño mínimo de 8 bytes. Los valores no conformes se redondean hacia arriba. Una máscara de bits para cada página rastrea qué bloques están en uso y cuáles están libres para la asignación. Para tamaños mayores que la mitad de una página (que suele ser 2048 bytes), la asignación se realiza una página completa a la vez.

Para proteger los datos en la memoria compartida del acceso concurrente, use el mutex disponible en el campo mutex de ngx_slab_pool_t. Un mutex se utiliza con mayor frecuencia por el slab pool mientras se asigna y libera memoria, pero se puede usar para proteger cualquier otra estructura de datos del usuario asignada en la zona compartida. Para bloquear o desbloquear un mutex, llame a ngx_shmtx_lock(&shpool->mutex) o ngx_shmtx_unlock(&shpool->mutex) respectivamente.

ngx_str_t        name;
ngx_foo_ctx_t   *ctx;
ngx_shm_zone_t  *shm_zone;

ngx_str_set(&name, "foo");

/* allocate shared zone context */
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_foo_ctx_t));
if (ctx == NULL) {
    /* error */
}

/* add an entry for 64k shared zone */
shm_zone = ngx_shared_memory_add(cf, &name, 65536, &ngx_foo_module);
if (shm_zone == NULL) {
    /* error */
}

/* register init callback and context */
shm_zone->init = ngx_foo_init_zone;
shm_zone->data = ctx;


...


static ngx_int_t
ngx_foo_init_zone(ngx_shm_zone_t *shm_zone, void *data)
{
    ngx_foo_ctx_t  *octx = data;

    size_t            len;
    ngx_foo_ctx_t    *ctx;
    ngx_slab_pool_t  *shpool;

    value = shm_zone->data;

    if (octx) {
        /* reusing a shared zone from old cycle */
        ctx->value = octx->value;
        return NGX_OK;
    }

    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;

    if (shm_zone->shm.exists) {
        /* initialize shared zone context in Windows Angie worker */
        ctx->value = shpool->data;
        return NGX_OK;
    }

    /* initialize shared zone */

    ctx->value = ngx_slab_alloc(shpool, sizeof(ngx_uint_t));
    if (ctx->value == NULL) {
        return NGX_ERROR;
    }

    shpool->data = ctx->value;

    return NGX_OK;
}

Registro#

Para el registro Angie utiliza objetos ngx_log_t. El registrador Angie admite varios tipos de salida:

  • stderr — Registro a la salida estándar de error (stderr)

  • file — Registro a un archivo

  • syslog — Registro a syslog

  • memory — Registro en el almacenamiento de memoria interna para fines de desarrollo; la memoria puede ser accedida más tarde con un depurador

Una instancia de registrador puede ser una cadena de registradores, vinculados entre sí mediante el campo next. En este caso, cada mensaje se escribe en todos los registradores de la cadena.

Para cada registrador, un nivel de severidad controla qué mensajes se escriben en el registro (solo se registran los eventos asignados a ese nivel o superior). Los siguientes niveles de severidad son compatibles:

  • NGX_LOG_EMERG

  • NGX_LOG_ALERT

  • NGX_LOG_CRIT

  • NGX_LOG_ERR

  • NGX_LOG_WARN

  • NGX_LOG_NOTICE

  • NGX_LOG_INFO

  • NGX_LOG_DEBUG

Para el registro de depuración, también se verifica la máscara de depuración. Las máscaras de depuración son:

  • NGX_LOG_DEBUG_CORE

  • NGX_LOG_DEBUG_ALLOC

  • NGX_LOG_DEBUG_MUTEX

  • NGX_LOG_DEBUG_EVENT

  • NGX_LOG_DEBUG_HTTP

  • NGX_LOG_DEBUG_MAIL

  • NGX_LOG_DEBUG_STREAM

Normalmente, los registradores se crean por el código existente de Angie a partir de las directivas error_log y están disponibles en casi todas las etapas del procesamiento en el ciclo, la configuración, la conexión del cliente y otros objetos.

Nginx proporciona las siguientes macros de registro:

  • ngx_log_error(level, log, err, fmt, ...) — Registro de errores

  • ngx_log_debug0(level, log, err, fmt), ngx_log_debug1(level, log, err, fmt, arg1) etc — Registro de depuración con hasta ocho argumentos de formato compatibles

Un mensaje de registro se formatea en un búfer de tamaño NGX_MAX_ERROR_STR (actualmente, 2048 bytes) en la pila. El mensaje se antepone con el nivel de severidad, el ID de proceso (PID), la ID de conexión (almacenada en log->connection), y el texto de error del sistema. Para los mensajes que no son de depuración: log->handler también se llama para anteponer información más específica al mensaje de registro. El módulo HTTP establece la función ngx_http_log_error() como manejador de registro para registrar direcciones de cliente y servidor, acción actual (almacenada en log->action), línea de la solicitud del cliente, nombre del servidor, etc.

/* specify what is currently done */
log->action = "sending mp4 to client";

/* error and debug log */
ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely
              closed connection");

ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
               "mp4 start:%ui, length:%ui", mp4->start, mp4->length);

La frase anterior da como resultado entradas de registro como estas:

2016/09/16 22:08:52 [info] 17445#0: *1 client prematurely closed connection while
sending mp4 to client, client: 127.0.0.1, server: , request: "GET /file.mp4 HTTP/1.1"
2016/09/16 23:28:33 [debug] 22140#0: *1 mp4 start:0, length:10000

Ciclo#

Un objeto ciclo almacena el contexto de ejecución de Angie creado a partir de una configuración específica. Su tipo es ngx_cycle_t. El ciclo actual es referenciado por la variable global ngx_cycle y es heredado por los trabajadores de Angie al iniciarse. Cada vez que se recarga la configuración de Angie, se crea un nuevo ciclo a partir de la nueva configuración de Angie; el ciclo antiguo suele eliminarse tras crearse con éxito el nuevo.

Un ciclo se crea mediante la función ngx_init_cycle(), que toma el ciclo anterior como argumento. La función localiza el archivo de configuración del ciclo anterior y hereda la mayor cantidad de recursos posible del ciclo anterior. Se crea un ciclo marcador de posición llamado "init cycle" al inicio de Angie, y luego es reemplazado por un ciclo real construido a partir de la configuración.

Los miembros del ciclo incluyen:

  • pool — Pool del ciclo. Creado para cada nuevo ciclo.

  • log — Registro del ciclo. Inicialmente heredado del ciclo antiguo, se establece para que apunte a new_log después de leer la configuración.

  • new_log — Registro del ciclo, creado por la configuración. Se ve afectado por la directiva a nivel raíz error_log.

  • connections, connection_n — Matriz de conexiones del tipo ngx_connection_t, creada por el módulo de eventos durante la inicialización de cada trabajador de Angie. La directiva worker_connections en la configuración de Angie establece el número de conexiones connection_n.

  • free_connections, free_connection_n — Lista y número de conexiones disponibles en la actualidad. Si no hay conexiones disponibles, un trabajador de Angie se niega a aceptar nuevos clientes o a conectarse a servidores upstream.

  • files, files_n — Matriz para mapear descriptores de archivos a conexiones de Angie. Este mapeo es utilizado por los módulos de eventos, que tienen la bandera NGX_USE_FD_EVENT (actualmente, es poll y devpoll).

  • conf_ctx — Matriz de configuraciones de módulos centrales. Las configuraciones se crean y llenan durante la lectura de los archivos de configuración de Angie.

  • modules, modules_n — Matriz de módulos del tipo ngx_module_t, tanto estáticos como dinámicos, cargados por la configuración actual.

  • listening — Matriz de objetos de escucha del tipo ngx_listening_t. Los objetos de escucha suelen añadirse mediante la directiva listen de varios módulos que llaman a la función ngx_create_listening(). Los sockets de escucha se crean en función de los objetos de escucha.

  • paths — Matriz de rutas del tipo ngx_path_t. Las rutas se agregan llamando a la función ngx_add_path() desde módulos que van a operar en ciertos directorios. Estos directorios son creados por Angie después de leer la configuración, si faltan. Además, se pueden añadir dos manejadores para cada ruta:

    • cargador de rutas — Se ejecuta solo una vez en 60 segundos después de iniciar o recargar Angie. Normalmente, el cargador lee el directorio y almacena datos en la memoria compartida de Angie. El manejador se llama desde el proceso dedicado de Angie "cache loader".

    • administrador de rutas — Se ejecuta periódicamente. Normalmente, el administrador elimina archivos antiguos del directorio y actualiza la memoria de Angie para reflejar los cambios. El manejador se llama desde el proceso dedicado "cache manager".

  • open_files — Lista de objetos de archivo abiertos del tipo ngx_open_file_t, que se crean al llamar a la función ngx_conf_open_file(). Actualmente, Angie usa este tipo de archivos abiertos para el registro. Después de leer la configuración, Angie abre todos los archivos de la lista open_files y almacena cada descriptor en el campo fd del objeto. Los archivos se abren en modo de anexar y se crean si faltan. Los archivos de la lista son reabiertos por los trabajadores de Angie al recibir la señal de reapertura (la más frecuente USR1). En este caso, el descriptor en el campo fd se cambia a un valor nuevo.

  • shared_memory — Lista de zonas de memoria compartida, cada una añadida por llamando a la función ngx_shared_memory_add(). Las zonas compartidas se asignan al mismo rango de direcciones en todos los procesos de Angie y se utilizan para compartir datos comunes, por ejemplo el árbol de caché HTTP en memoria.

Búfer#

Para operaciones de entrada/salida, Angie proporciona el tipo de búfer ngx_buf_t. Normalmente, se utiliza para contener datos que deben escribirse en un destino o leerse desde una fuente. Un búfer puede hacer referencia a datos en la memoria o en un archivo y, técnicamente, es posible que un búfer haga referencia a ambos al mismo tiempo. La memoria para el búfer se asigna por separado y no está relacionada con la estructura del búfer ngx_buf_t.

La estructura ngx_buf_t tiene los siguientes campos:

  • start, end — Los límites del bloque de memoria asignado para el búfer.

  • pos, last — Los límites del búfer de memoria; normalmente un subrango de start .. end.

  • file_pos, file_last — Los límites de un búfer de archivo, expresados como desplazamientos desde el inicio del archivo.

  • tag — Valor único usado para distinguir búferes; creado por diferentes módulos de Angie, usualmente para el fin de reutilización de búferes.

  • file — Objeto de archivo.

  • temporary — Indicador de que el búfer hace referencia a memoria escribible.

  • memory — Indicador de que el búfer hace referencia a memoria de solo lectura.

  • in_file — Indicador de que el búfer hace referencia a datos en un archivo.

  • flush — Indicador de que todos los datos anteriores al búfer deben ser vaciados.

  • recycled — Indicador de que el búfer puede reutilizarse y debe consumirse lo antes posible.

  • sync — Indicador de que el búfer no transporta datos ni contiene una señal especial como flush o last_buf. Por defecto, Angie considera tales búferes como una condición de error, pero esta bandera indica a Angie que omita la comprobación de errores.

  • last_buf — Indicador de que el búfer es el último en la salida.

  • last_in_chain — Indicador de que no quedan más búferes de datos en una solicitud o subsolicitud.

  • shadow — Referencia a otro búfer ("shadow") relacionado con el búfer actual, usualmente en el sentido de que el búfer utiliza datos del búfer sombra. Cuando el búfer se consume, normalmente el búfer sombra también se marca como consumido.

  • last_shadow — Indicador de que el búfer es el último que referencia a un búfer sombra particular.

  • temp_file — Indicador de que el búfer se encuentra en un archivo temporal.

Para operaciones de entrada y salida, los búferes se enlazan en cadenas. Una cadena es una secuencia de eslabones de cadena del tipo ngx_chain_t, definidos como sigue:

typedef struct ngx_chain_s  ngx_chain_t;

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

Cada eslabón de la cadena mantiene una referencia a su búfer y una referencia al siguiente eslabón de la cadena.

Un ejemplo de uso de búferes y cadenas:

typedef struct ngx_chain_s  ngx_chain_t;

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

Cada enlace de la cadena mantiene una referencia a su búfer y una referencia al siguiente enlace de la cadena.

Un ejemplo de uso de búferes y cadenas:

ngx_chain_t *
ngx_get_my_chain(ngx_pool_t *pool)
{
    ngx_buf_t    *b;
    ngx_chain_t  *out, *cl, **ll;

    /* first buf */
    cl = ngx_alloc_chain_link(pool);
    if (cl == NULL) { /* error */ }

    b = ngx_calloc_buf(pool);
    if (b == NULL) { /* error */ }

    b->start = (u_char *) "foo";
    b->pos = b->start;
    b->end = b->start + 3;
    b->last = b->end;
    b->memory = 1; /* read-only memory */

    cl->buf = b;
    out = cl;
    ll = &cl->next;

    /* second buf */
    cl = ngx_alloc_chain_link(pool);
    if (cl == NULL) { /* error */ }

    b = ngx_create_temp_buf(pool, 3);
    if (b == NULL) { /* error */ }

    b->last = ngx_cpymem(b->last, "foo", 3);

    cl->buf = b;
    cl->next = NULL;
    *ll = cl;

    return out;
}

Redes#

Conexión#

El tipo de conexión ngx_connection_t es un envoltorio alrededor de un descriptor de socket. Incluye los siguientes campos:

  • fd — Descriptor de socket

  • data — Contexto arbitrario de la conexión. Normalmente, es un puntero a un objeto de nivel superior construido sobre la conexión, como una solicitud HTTP o una sesión de Stream.

  • read, write — Eventos de lectura y escritura para la conexión.

  • recv, send, recv_chain, send_chain — Operaciones de E/S para la conexión.

  • pool — Pool de conexiones.

  • log — Registro de la conexión.

  • sockaddr, socklen, addr_text — Dirección de socket remota en formas binaria y textual.

  • local_sockaddr, local_socklen — Dirección de socket local en forma binaria. Inicialmente, estos campos están vacíos. Usa la función ngx_connection_local_sockaddr() para obtener la dirección del socket local.

  • proxy_protocol_addr, proxy_protocol_port - Dirección y puerto del cliente PROXY protocol, si el PROXY protocol está habilitado para la conexión.

  • ssl — Contexto SSL para la conexión.

  • reusable — Indicador de que la conexión está en un estado que la hace elegible para reutilización.

  • close — Indicador de que la conexión está siendo reutilizada y debe cerrarse.

Una conexión Angie puede encapsular de manera transparente la capa SSL. En este caso, el campo ssl de la conexión contiene un puntero a una estructura ngx_ssl_connection_t, que mantiene todos los datos relacionados con SSL para la conexión, incluyendo SSL_CTX y SSL. Los manejadores recv, send, recv_chain, y send_chain también se configuran como funciones habilitadas para SSL.

La directiva worker_connections en la configuración de Angie limita el número de conexiones por worker de Angie. Todas las estructuras de conexión se crean de antemano cuando un worker inicia y se almacenan en el campo connections del objeto ciclo. Para recuperar una estructura de conexión, usa la función ngx_get_connection(s, log). Toma como argumento s un descriptor de socket, que debe envolverse en una estructura de conexión.

Debido a que el número de conexiones por worker está limitado, Angie proporciona una forma de obtener conexiones que están actualmente en uso. Para habilitar o deshabilitar la reutilización de una conexión, llama a la función ngx_reusable_connection(c, reusable). Llamar ngx_reusable_connection(c, 1) establece la bandera reuse en la estructura de conexión e inserta la conexión en la cola reusable_connections_queue del ciclo. Cada vez que ngx_get_connection() detecta que no hay conexiones disponibles en la lista del ciclo free_connections, llama a ngx_drain_connections() para liberar un número específico de conexiones reutilizables. Para cada una de esas conexiones, se establece la bandera close y se llama a su manejador de lectura, que debe liberar la conexión llamando a ngx_close_connection(c) y dejarla disponible para su reutilización. Para salir del estado cuando una conexión puede reutilizarse, se llama a ngx_reusable_connection(c, 0).

Las conexiones de cliente HTTP son un ejemplo de conexiones reutilizables en Angie; se marcan como reutilizables hasta que se recibe el primer byte de solicitud del cliente.

Eventos#

Evento#

El objeto de evento ngx_event_t en Angie proporciona un mecanismo para notificar que ha ocurrido un evento específico.

Los campos en ngx_event_t incluyen:

  • data — Contexto de evento arbitrario utilizado en los manejadores de eventos, normalmente como puntero a una conexión relacionada con el evento.

  • handler — Función de devolución de llamada que se invocará cuando ocurra el evento.

  • write — Bandera que indica un evento de escritura. La ausencia de la bandera indica un evento de lectura.

  • active — Bandera que indica que el evento está registrado para recibir notificaciones de E/S, normalmente desde mecanismos de notificación como epoll, kqueue, poll.

  • ready — Bandera que indica que el evento ha recibido una notificación de E/S.

  • delayed — Bandera que indica que la E/S está retrasada debido a la limitación de velocidad.

  • timer — Nodo de árbol rojo-negro para insertar el evento en el árbol de temporizadores.

  • timer_set — Bandera que indica que el temporizador del evento está configurado y aún no ha expirado.

  • timedout — Bandera que indica que el temporizador del evento ha expirado.

  • eof — Bandera que indica que se produjo EOF al leer datos.

  • pending_eof — Bandera que indica que hay un EOF pendiente en el socket, aunque pueda haber algunos datos disponibles antes. La bandera se entrega a través del evento EPOLLRDHUP de epoll o la bandera EV_EOF de kqueue.

  • error — Bandera que indica que ocurrió un error durante la lectura (para un evento de lectura) o escritura (para un evento de escritura).

  • cancelable — Bandera de evento de temporizador que indica que el evento debe ignorarse durante el apagado del worker. El apagado suave del worker se retrasa hasta que no haya eventos de temporizador no cancelables programados.

  • posted — Bandera que indica que el evento está publicado en una cola.

  • queue — Nodo de cola para colocar el evento en una cola.

Eventos de E/S#

Cada conexión obtenida al llamar a la función ngx_get_connection() tiene dos eventos adjuntos, c->read y c->write, que se utilizan para recibir notificación de que el socket está listo para leer o escribir. Todos estos eventos operan en modo Edge-Triggered, lo que significa que solo disparan notificaciones cuando cambia el estado del socket. Por ejemplo, realizar una lectura parcial en un socket no hace que Angie entregue una notificación de lectura repetida hasta que lleguen más datos al socket. Incluso cuando el mecanismo subyacente de notificación de E/S es esencialmente Level-Triggered (poll, select etc), Angie convierte las notificaciones a Edge-Triggered. Para hacer que las notificaciones de eventos de Angie sean consistentes en todos los sistemas de notificación en diferentes plataformas, las funciones ngx_handle_read_event(rev, flags) y ngx_handle_write_event(wev, lowat) deben llamarse después de manejar una notificación de socket de E/S o de llamar a cualquier función de E/S en ese socket. Normalmente, las funciones se llaman una vez al final de cada manejador de evento de lectura o escritura.

Eventos de temporización#

Un evento puede configurarse para enviar una notificación cuando expire un tiempo de espera. El temporizador utilizado por los eventos cuenta milisegundos desde algún punto anterior no especificado truncados al tipo ngx_msec_t. Su valor actual se puede obtener de la variable ngx_current_msec.

La función ngx_add_timer(ev, timer) establece un tiempo de espera para un evento, ngx_del_timer(ev) elimina un tiempo de espera previamente configurado. El árbol rojo-negro global de temporizadores ngx_event_timer_rbtree almacena todos los tiempos de espera actualmente configurados. La clave en el árbol es de tipo ngx_msec_t y es el momento en que ocurre el evento. La estructura del árbol permite operaciones rápidas de inserción y eliminación, así como acceso a los tiempos de espera más cercanos, que Angie utiliza para determinar cuánto tiempo esperar para eventos de E/S y para expiración de temporizadores.

Eventos publicados#

Un evento puede ser publicado, lo que significa que su manejador se llamará en algún momento más tarde dentro de la iteración actual del bucle de eventos. Publicar eventos es una buena práctica para simplificar el código y evitar desbordamientos de pila. Los eventos publicados se mantienen en una cola de publicaciones. La macro ngx_post_event(ev, q) publica el evento ev en la cola de publicaciones q. La macro ngx_delete_posted_event(ev) elimina el evento ev de la cola en la que está publicado actualmente. Normalmente, los eventos se publican en la cola ngx_posted_events, que se procesa tarde en el bucle de eventos — después de que ya se hayan manejado todos los eventos de E/S y temporizadores. La función ngx_event_process_posted() se llama para procesar una cola de eventos. Llama a los manejadores de eventos hasta que la cola esté vacía. Esto significa que un manejador de eventos publicado puede publicar más eventos para ser procesados dentro de la iteración actual del bucle de eventos.

Un ejemplo:

void
ngx_my_connection_read(ngx_connection_t *c)
{
    ngx_event_t  *rev;

    rev = c->read;

    ngx_add_timer(rev, 1000);

    rev->handler = ngx_my_read_handler;

    ngx_my_read(rev);
}


void
ngx_my_read_handler(ngx_event_t *rev)
{
    ssize_t            n;
    ngx_connection_t  *c;
    u_char             buf[256];

    if (rev->timedout) { /* timeout expired */ }

    c = rev->data;

    while (rev->ready) {
        n = c->recv(c, buf, sizeof(buf));

        if (n == NGX_AGAIN) {
            break;
        }

        if (n == NGX_ERROR) { /* error */ }

        /* process buf */
    }

    if (ngx_handle_read_event(rev, 0) != NGX_OK) { /* error */ }
}

Bucle de eventos#

A excepción del proceso maestro de Angie, todos los procesos de Angie realizan E/S y, por lo tanto, tienen un bucle de eventos. (El proceso maestro de Angie, en cambio, pasa la mayor parte de su tiempo en la llamada sigsuspend() esperando a que lleguen señales.) El bucle de eventos de Angie se implementa en la función ngx_process_events_and_timers() , la cual se llama repetidamente hasta que el proceso salga.

El bucle de eventos tiene las siguientes etapas:

  • Encontrar el tiempo de espera que esté más cercano a expirar, llamando ngx_event_find_timer(). Esta función localiza el nodo más a la izquierda del árbol de temporizadores y devuelve el número de milisegundos hasta que expire el nodo.

  • Procesar eventos de E/S llamando a un manejador, específico del mecanismo de notificación de eventos, elegido por la configuración de Angie. Este manejador espera al menos un evento de E/S, pero solo hasta que expire el siguiente tiempo de espera. Cuando ocurre un evento de lectura o escritura, se establece la bandera ready y se llama al manejador del evento. Para Linux, normalmente se utiliza el manejador ngx_epoll_process_events(), que llama a epoll_wait() para esperar eventos de E/S.

  • Expirar temporizadores llamando a ngx_event_expire_timers(). El árbol de temporizadores se recorre desde el elemento más a la izquierda hacia la derecha hasta encontrar un temporizador no vencido. Para cada nodo vencido se establece la bandera de evento timedout, la bandera timer_set se restablece y se llama al manejador del evento.

  • Procesar eventos publicados llamando a ngx_event_process_posted(). La función elimina repetidamente el primer elemento de la cola de eventos publicados y llama al manejador del elemento, hasta que la cola esté vacía.

Todos los procesos de Angie manejan señales también. Los manejadores de señales solo establecen variables globales que se verifican después de la llamada a ngx_process_events_and_timers().

Procesos#

Hay varios tipos de procesos en Angie. El tipo de un proceso se guarda en la variable global ngx_process, y es uno de los siguientes:

  • NGX_PROCESS_MASTER — El proceso maestro, que lee la configuración de NGINX, crea ciclos y pone en marcha y controla procesos hijo. No realiza ninguna E/S y solo responde a señales. Su función de ciclo es ngx_master_process_cycle().

  • NGX_PROCESS_WORKER — El proceso trabajador, que maneja las conexiones de los clientes. Es iniciado por el proceso maestro y también responde a sus señales y comandos de canal. Su función de ciclo es ngx_worker_process_cycle(). Puede haber varios procesos trabajadores, tal como lo configure la directiva worker_processes.

  • NGX_PROCESS_SINGLE — El proceso único, que existe únicamente en el modo master_process off, y es el único proceso que se ejecuta en ese modo. Crea ciclos (como lo hace el proceso maestro) y maneja las conexiones de clientes (como lo hace el proceso trabajador). Su función de ciclo es ngx_single_process_cycle().

  • NGX_PROCESS_HELPER — El proceso auxiliar, del cual actualmente existen dos tipos: gestor de caché y cargador de caché. La función de ciclo para ambos es ngx_cache_manager_process_cycle().

Los procesos de Angie manejan las siguientes señales:

  • NGX_SHUTDOWN_SIGNAL (SIGQUIT en la mayoría de sistemas) — Apagado suave. Al recibir esta señal, el proceso maestro envía una señal de apagado a todos los procesos hijo. Cuando ya no quedan procesos hijo, el maestro destruye el grupo de ciclos y sale. Cuando un proceso trabajador recibe esta señal, cierra todos los sockets de escucha y espera hasta que no haya eventos programados no cancelables, luego destruye el grupo de ciclos y sale. Cuando el proceso de gestor de caché o el cargador de caché recibe esta señal, sale inmediatamente. La variable ngx_quit se establece en 1 cuando un proceso recibe esta señal, y se restablece inmediatamente después de ser procesada. La variable ngx_exiting se establece en 1 mientras un proceso trabajador está en el estado de apagado.

  • NGX_TERMINATE_SIGNAL (SIGTERM en la mayoría de sistemas) — Terminación. Al recibir esta señal, el proceso maestro envía una señal de terminación a todos los procesos hijo. Si un proceso hijo no sale en 1 segundo, el proceso maestro envía la señal SIGKILL para terminarlo. Cuando ya no quedan procesos hijo, el maestro destruye el grupo de ciclos y sale. Cuando un proceso trabajador, el gestor de caché o el cargador de caché recibe esta señal, destruye el grupo de ciclos y sale. La variable ngx_terminate se establece en 1 al recibir esta señal.

  • NGX_NOACCEPT_SIGNAL (SIGWINCH en la mayoría de sistemas) - Detener todos los procesos trabajador y auxiliar. Al recibir esta señal, el proceso maestro apaga sus procesos hijo. Si un binario Angie recién iniciado sale, los procesos hijo del antiguo maestro se inician de nuevo. Cuando un proceso trabajador recibe esta señal, se apaga en modo de depuración establecido por la directiva debug_points.

  • NGX_RECONFIGURE_SIGNAL (SIGHUP en la mayoría de sistemas) - Reconfigurar. Al recibir esta señal, el proceso maestro vuelve a leer la configuración y crea un nuevo ciclo basado en ella. Si el nuevo ciclo se crea con éxito, se elimina el antiguo ciclo y se inician los nuevos procesos hijo. Mientras tanto, los procesos hijo antiguos reciben la señal NGX_SHUTDOWN_SIGNAL. En modo de un solo proceso, Angie crea un nuevo ciclo, pero mantiene el antiguo hasta que ya no haya clientes con conexiones activas vinculadas a él. Los procesos trabajadores y auxiliares ignoran esta señal.

  • NGX_REOPEN_SIGNAL (SIGUSR1 en la mayoría de sistemas) — Reabrir archivos. El proceso maestro envía esta señal a los trabajadores, que vuelven a abrir todos open_files relacionados con el ciclo.

  • NGX_CHANGEBIN_SIGNAL (SIGUSR2 en la mayoría de sistemas) — Cambiar el binario de Angie. El proceso maestro inicia un nuevo binario de Angie y pasa una lista de todos los sockets de escucha. La lista en formato de texto, pasada en la variable de entorno "NGINX", consta de números de descriptor separados por punto y coma. El nuevo binario de Angie lee la variable "NGINX" y añade los sockets a su ciclo de inicio. Otros procesos ignoran esta señal.

Aunque todos los procesos trabajadores de Angie pueden recibir y manejar adecuadamente las señales POSIX, el proceso maestro no utiliza la syscall estándar kill() para pasar señales a trabajadores y ayudantes. En su lugar, Angie utiliza pares de sockets entre procesos que permiten enviar mensajes entre todos los procesos de Angie. Sin embargo, actualmente, los mensajes solo se envían desde el maestro a sus hijos. Los mensajes transportan las señales estándar.

Hilos#

Es posible desagregar a un hilo tareas que, de otro modo, bloquearían al proceso trabajador de Angie. Por ejemplo, Angie puede configurarse para utilizar hilos para realizar E/S de archivos. Otro caso de uso es una biblioteca que no tiene una interfaz asíncrona y, por lo tanto, no puede usarse normalmente con Angie. Tenga en cuenta que la interfaz de hilos es una ayuda para el enfoque asíncrono existente para procesar las conexiones de clientes, y de ninguna manera se pretende como un reemplazo.

Para gestionar la sincronización, están disponibles los siguientes wrappers sobre las primitivas de pthreads:

  • typedef pthread_mutex_t  ngx_thread_mutex_t;

    • ngx_int_t ngx_thread_mutex_create(ngx_thread_mutex_t *mtx, ngx_log_t *log);

    • ngx_int_t ngx_thread_mutex_destroy(ngx_thread_mutex_t *mtx, ngx_log_t *log);

    • ngx_int_t ngx_thread_mutex_lock(ngx_thread_mutex_t *mtx, ngx_log_t *log);

    • ngx_int_t ngx_thread_mutex_unlock(ngx_thread_mutex_t *mtx, ngx_log_t *log);

  • typedef pthread_cond_t  ngx_thread_cond_t;

    • ngx_int_t ngx_thread_cond_create(ngx_thread_cond_t *cond, ngx_log_t *log);

    • ngx_int_t ngx_thread_cond_destroy(ngx_thread_cond_t *cond, ngx_log_t *log);

    • ngx_int_t ngx_thread_cond_signal(ngx_thread_cond_t *cond, ngx_log_t *log);

    • ngx_int_t ngx_thread_cond_wait(ngx_thread_cond_t *cond, ngx_thread_mutex_t *mtx, ngx_log_t *log);

En lugar de crear un nuevo hilo para cada tarea, Angie implementa una estrategia de thread_pool. Se pueden configurar múltiples pools de hilos para distintos fines (por ejemplo, realizar E/S en diferentes conjuntos de discos). Cada pool de hilos se crea al inicio y contiene un número limitado de hilos que procesan una cola de tareas. Cuando se completa una tarea, se llama a un manejador de finalización predefinido.

El encabezado src/core/ngx_thread_pool.h contiene definiciones relevantes:

struct ngx_thread_task_s {
    ngx_thread_task_t   *next;
    ngx_uint_t           id;
    void                *ctx;
    void               (*handler)(void *data, ngx_log_t *log);
    ngx_event_t          event;
};

typedef struct ngx_thread_pool_s  ngx_thread_pool_t;

ngx_thread_pool_t *ngx_thread_pool_add(ngx_conf_t *cf, ngx_str_t *name);
ngx_thread_pool_t *ngx_thread_pool_get(ngx_cycle_t *cycle, ngx_str_t *name);

ngx_thread_task_t *ngx_thread_task_alloc(ngx_pool_t *pool, size_t size);
ngx_int_t ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task);

En el momento de la configuración, un módulo que desee usar hilos debe obtener una referencia a un grupo de hilos llamando a ngx_thread_pool_add(cf, name), que o bien crea un nuevo grupo de hilos con el nombre dado name o devuelve una referencia al grupo con ese nombre si ya existe.

Para añadir un task a una cola de un grupo de hilos especificado tp en tiempo de ejecución, usa la función ngx_thread_task_post(tp, task).

Para ejecutar una función en un hilo, pasa los parámetros y configura un manejador de finalización usando la estructura ngx_thread_task_t:

typedef struct {
    int    foo;
} my_thread_ctx_t;


static void
my_thread_func(void *data, ngx_log_t *log)
{
    my_thread_ctx_t *ctx = data;

    /* esta función se ejecuta en un hilo separado */
}


static void
my_thread_completion(ngx_event_t *ev)
{
    my_thread_ctx_t *ctx = ev->data;

    /* ejecutado en el bucle de eventos de Angie */
}


ngx_int_t
my_task_offload(my_conf_t *conf)
{
    my_thread_ctx_t    *ctx;
    ngx_thread_task_t  *task;

    task = ngx_thread_task_alloc(conf->pool, sizeof(my_thread_ctx_t));
    if (task == NULL) {
        return NGX_ERROR;
    }

    ctx = task->ctx;

    ctx->foo = 42;

    task->handler = my_thread_func;
    task->event.handler = my_thread_completion;
    task->event.data = ctx;

    if (ngx_thread_task_post(conf->thread_pool, task) != NGX_OK) {
        return NGX_ERROR;
    }

    return NGX_OK;
}

Módulos#

Agregar nuevos módulos#

Cada módulo independiente de Angie reside en un directorio separado que contiene al menos dos archivos: config y un archivo con el código fuente del módulo. El archivo config contiene toda la información necesaria para que Angie integre el módulo, por ejemplo:

ngx_module_type=CORE
ngx_module_name=ngx_foo_module
ngx_module_srcs="$ngx_addon_dir/ngx_foo_module.c"

. auto/module

ngx_addon_name=$ngx_module_name

El archivo config es un script de shell POSIX que puede definir y acceder a las siguientes variables:

  • ngx_module_type — Tipo de módulo a construir. Los valores posibles son CORE, HTTP, HTTP_FILTER, HTTP_INIT_FILTER, HTTP_AUX_FILTER, MAIL, STREAM, o MISC.

  • ngx_module_name — Nombres de módulo. Para construir varios módulos a partir de un conjunto de archivos de origen, especifica una lista de nombres separados por espacios. El primer nombre indica el nombre del binario de salida para el módulo dinámico. Los nombres de la lista deben coincidir con los nombres usados en el código fuente.

  • ngx_addon_name — Nombre del módulo tal como aparece en la salida en la consola desde el script de configuración.

  • ngx_module_srcs — Lista de archivos fuente separados por espacios utilizados para compilar el módulo. La variable $ngx_addon_dir se puede usar para representar la ruta al directorio del módulo.

  • ngx_module_incs — Rutas de include necesarias para compilar el módulo

  • ngx_module_deps — Lista de dependencias del módulo separadas por espacios. Por lo general, es la lista de archivos de cabecera.

  • ngx_module_libs — Lista de bibliotecas separadas por espacios con las que enlazar el módulo. Por ejemplo, use ngx_module_libs=-lpthread para enlazar a la biblioteca libpthread. Los siguientes macros se pueden usar para enlazar con las mismas bibliotecas que Angie: LIBXSLT, LIBGD, GEOIP, PCRE, OPENSSL, MD5, SHA1, ZLIB y PERL.

  • ngx_module_link — Variable establecida por el sistema de construcción para DYNAMIC en un módulo dinámico o ADDON para un módulo estático y se usa para determinar distintas acciones a realizar dependiendo del tipo de enlace.

  • ngx_module_order — Orden de carga del módulo; útil para los tipos de módulos HTTP_FILTER y HTTP_AUX_FILTER. El formato de esta opción es una lista de módulos separados por espacios. Todos los módulos de la lista que siguen al nombre del módulo actual terminan después de él en la lista global de módulos, lo que establece el orden para la inicialización de módulos. Para los módulos de filtro, una inicialización posterior significa una ejecución anterior.

    Los siguientes módulos se suelen usar como referencias. El ngx_http_copy_filter_module lee los datos para otros módulos de filtro y se ubica cerca de la parte inferior de la lista para que sea uno de los primeros en ejecutarse. El ngx_http_write_filter_module escribe los datos en el socket del cliente y se ubica cerca de la parte superior de la lista, y es el último en ejecutarse.

    Por defecto, los módulos de filtro se colocan antes del ngx_http_copy_filter en la lista de módulos para que el filtro controlador se ejecute después del controlador de la copia. Para otros tipos de módulos, el valor por defecto es la cadena vacía.

Para compilar un módulo en Angie de forma estática, usa el argumento --add-module=/path/to/module en el script de configuración. Para compilar un módulo para su carga dinámica posterior en Angie, usa el argumento --add-dynamic-module=/path/to/module.

Core Modules#

Los módulos son los bloques de construcción de Angie, y la mayor parte de su funcionalidad se implanta como módulos. El archivo fuente de un módulo debe contener una variable global de tipo ngx_module_t, que se define de la siguiente manera:

struct ngx_module_s {

    /* private part is omitted */

    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;

    ngx_int_t           (*init_master)(ngx_log_t *log);

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);

    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
    void                (*exit_thread)(ngx_cycle_t *cycle);
    void                (*exit_process)(ngx_cycle_t *cycle);

    void                (*exit_master)(ngx_cycle_t *cycle);

    /* stubs for future extensions are omitted */
};

La parte privada omitida incluye la versión del módulo y una firma y se llena usando la macro predefinida NGX_MODULE_V1.

Cada módulo mantiene sus datos privados en el campo ctx, reconoce las directivas de configuración, especificadas en el arreglo commands, y puede ser invocado en ciertas etapas del ciclo de Angie. El ciclo de vida del módulo consiste en los siguientes eventos:

  • Los controladores de directivas de configuración se llaman a medida que aparecen en los archivos de configuración, en el contexto del proceso maestro.

  • Después de que la configuración se analiza con éxito, el manejador init_module se llama en el contexto del proceso maestro. El manejador init_module se llama en el proceso maestro cada vez que se carga una configuración.

  • El proceso maestro crea uno o más procesos de trabajo y el manejador init_process se llama en cada uno de ellos.

  • Cuando un proceso de trabajo recibe el comando de apagado o terminación del maestro, invoca el manejador exit_process.

  • El proceso maestro llama al manejador exit_master antes de salir.

Debido a que los hilos se utilizan en Angie únicamente como una facilidad de E/S suplementaria con su propia API, los manejadores init_thread y exit_thread no se llaman actualmente. Tampoco hay un manejador init_master, porque sería un gasto innecesario.

El tipo de módulo type define exactamente qué se almacena en el campo ctx. Su valor es uno de los siguientes tipos:

  • NGX_CORE_MODULE

  • NGX_EVENT_MODULE

  • NGX_HTTP_MODULE

  • NGX_MAIL_MODULE

  • NGX_STREAM_MODULE

El NGX_CORE_MODULE es el más básico y, por tanto, el tipo de módulo más genérico y de nivel más bajo. Los otros tipos de módulo se implementan sobre él y proporcionan una forma más conveniente de tratar con dominios correspondientes, como manejar eventos o peticiones HTTP.

El conjunto de módulos núcleo incluye los módulos: ngx_core_module, ngx_errlog_module, ngx_regex_module, ngx_thread_pool_module y ngx_openssl_module módulos. El módulo HTTP, el módulo de flujo (stream), el módulo de correo y los módulos de eventos son módulos núcleo también. El contexto de un módulo núcleo se define como:

typedef struct {
    ngx_str_t             name;
    void               *(*create_conf)(ngx_cycle_t *cycle);
    char               *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;

donde el name es una cadena con el nombre del módulo, create_conf y init_conf son punteros a funciones que crean e inicializan la configuración del módulo, respectivamente. Para los módulos núcleo, Angie llama a create_conf antes de analizar una nueva configuración y init_conf después de que toda la configuración se ha analizado con éxito. La función típica create_conf reserva memoria para la configuración y establece valores por defecto.

Por ejemplo, un módulo simplista llamado ngx_foo_module podría verse así:

/*
 * Copyright (C) Author.
 */


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


typedef struct {
    ngx_flag_t  enable;
} ngx_foo_conf_t;


static void *ngx_foo_create_conf(ngx_cycle_t *cycle);
static char *ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf);

static char *ngx_foo_enable(ngx_conf_t *cf, void *post, void *data);
static ngx_conf_post_t  ngx_foo_enable_post = { ngx_foo_enable };


static ngx_command_t  ngx_foo_commands[] = {

    { ngx_string("foo_enabled"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      0,
      offsetof(ngx_foo_conf_t, enable),
      &ngx_foo_enable_post },

      ngx_null_command
};


static ngx_core_module_t  ngx_foo_module_ctx = {
    ngx_string("foo"),
    ngx_foo_create_conf,
    ngx_foo_init_conf
};


ngx_module_t  ngx_foo_module = {
    NGX_MODULE_V1,
    &ngx_foo_module_ctx,                   /* module context */
    ngx_foo_commands,                      /* module directives */
    NGX_CORE_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 void *
ngx_foo_create_conf(ngx_cycle_t *cycle)
{
    ngx_foo_conf_t  *fcf;

    fcf = ngx_pcalloc(cycle->pool, sizeof(ngx_foo_conf_t));
    if (fcf == NULL) {
        return NULL;
    }

    fcf->enable = NGX_CONF_UNSET;

    return fcf;
}


static char *
ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf)
{
    ngx_foo_conf_t *fcf = conf;

    ngx_conf_init_value(fcf->enable, 0);

    return NGX_CONF_OK;
}


static char *
ngx_foo_enable(ngx_conf_t *cf, void *post, void *data)
{
    ngx_flag_t  *fp = data;

    if (*fp == 0) {
        return NGX_CONF_OK;
    }

    ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "Foo Module is enabled");

    return NGX_CONF_OK;
}

Directivas de Configuración#

El tipo ngx_command_t define una única directiva de configuración. Cada módulo que admite configuración proporciona una matriz de tales estructuras que describen cómo procesar los argumentos y qué manejadores llamar:

typedef struct ngx_command_s  ngx_command_t;

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;
};

Termine el arreglo con el valor especial ngx_null_command. El name es el nombre de una directiva tal como aparece en el archivo de configuración, por ejemplo "worker_processes" o "listen". El type es un campo de bits de banderas que especifica el número de argumentos que toma la directiva, su tipo y el contexto en el que aparece. Las banderas son:

  • NGX_CONF_NOARGS — La directiva no toma argumentos.

  • NGX_CONF_1MORE — La directiva toma uno o más argumentos.

  • NGX_CONF_2MORE — La directiva toma dos o más argumentos.

  • NGX_CONF_TAKE1..:samp:NGX_CONF_TAKE7 — La directiva toma exactamente el número indicado de argumentos.

  • NGX_CONF_TAKE12, NGX_CONF_TAKE13, NGX_CONF_TAKE23, NGX_CONF_TAKE123, NGX_CONF_TAKE1234 — La directiva puede tomar un número diferente de argumentos. Las opciones están limitadas a los números indicados. Por ejemplo, NGX_CONF_TAKE12 significa que toma uno o dos argumentos.

Las banderas para los tipos de directiva son:

  • NGX_CONF_BLOCK — La directiva es un bloque, es decir, puede contener otras directivas dentro de sus llaves de apertura y cierre, o incluso implementar su propio analizador para manejar contenidos internos.

  • NGX_CONF_FLAG — La directiva toma un valor booleano, ya sea on o off.

El contexto de una directiva define dónde puede aparecer en la configuración:

  • NGX_MAIN_CONF — En el contexto de nivel superior.

  • NGX_HTTP_MAIN_CONF — En el bloque http.

  • NGX_HTTP_SRV_CONF — En un bloque server dentro del bloque http.

  • NGX_HTTP_LOC_CONF — En un bloque location dentro del bloque http.

  • NGX_HTTP_UPS_CONF — En un bloque upstream dentro del bloque http.

  • NGX_HTTP_SIF_CONF — En un bloque if dentro de un bloque server en el bloque http.

  • NGX_HTTP_LIF_CONF — En un bloque if dentro de un bloque location en el bloque http.

  • NGX_HTTP_LMT_CONF — En un bloque limit_except dentro del bloque http.

  • NGX_STREAM_MAIN_CONF — En el bloque stream bloque.

  • NGX_STREAM_SRV_CONF — En un bloque server dentro del bloque stream.

  • NGX_STREAM_UPS_CONF — En un bloque upstream dentro del bloque stream.

  • NGX_MAIL_MAIN_CONF — En el bloque mail.

  • NGX_MAIL_SRV_CONF — En un bloque server dentro del bloque mail.

  • NGX_EVENT_CONF — En el bloque event.

  • NGX_DIRECT_CONF — Usado por módulos que no crean una jerarquía de contextos y solo tienen una configuración global. Esta configuración se pasa al manejador como el argumento conf.

El analizador de configuración utiliza estas banderas para generar un error en caso de una directiva mal ubicada y llama a los manejadores de directivas proporcionados con un puntero de configuración adecuado, de modo que las mismas directivas en diferentes ubicaciones puedan almacenar sus valores en lugares distintos.

El campo set define un manejador que procesa una directiva y guarda los valores analizados en la configuración correspondiente. Existen varias funciones que realizan conversiones comunes:

  • ngx_conf_set_flag_slot — Convierte las cadenas literales on y off en un valor ngx_flag_t con valores 1 o 0, respectivamente.

  • ngx_conf_set_str_slot — Almacena una cadena como valor del tipo ngx_str_t.

  • ngx_conf_set_str_array_slot — Añade un valor a un arreglo ngx_array_t de cadenas ngx_str_t. El arreglo se crea si no existe ya.

  • ngx_conf_set_keyval_slot — Añade un par clave-valor a un arreglo ngx_array_t de pares clave-valor ngx_keyval_t. La primera cadena se convierte en la clave y la segunda en el valor. El arreglo se crea si no existe ya.

  • ngx_conf_set_num_slot — Convierte el argumento de una directiva a un valor ngx_int_t.

  • ngx_conf_set_size_slot — Convierte un size a un valor size_t expresado en bytes.

  • ngx_conf_set_off_slot — Convierte un offset a un valor off_t expresado en bytes.

  • ngx_conf_set_msec_slot — Convierte un time a un valor ngx_msec_t expresado en milisegundos.

  • ngx_conf_set_sec_slot — Convierte un time a un valor time_t expresado en segundos.

  • ngx_conf_set_bufs_slot — Convierte los dos argumentos suministrados en un objeto ngx_bufs_t que contiene el número y size de los búferes.

  • ngx_conf_set_enum_slot — Convierte el argumento suministrado en un valor ngx_uint_t. El arreglo null-terminated de ngx_conf_enum_t pasado en el campo post define las cadenas aceptables y sus valores enteros correspondientes.

  • ngx_conf_set_bitmask_slot — Convierte los argumentos suministrados en un valor ngx_uint_t. Los valores de máscara para cada argumento se ORean para producir el resultado. El arreglo null-terminated de ngx_conf_bitmask_t pasado en el campo post define las cadenas aceptables y los valores de máscara correspondientes.

  • set_path_slot — Convierte los argumentos suministrados a un valor ngx_path_t y realiza todas las inicializaciones necesarias. Para más detalles, consulte la documentación de la directiva proxy_temp_path.

  • set_access_slot — Convierte los argumentos suministrados a una máscara de permisos de archivo. Para más detalles, consulte la documentación de la directiva proxy_store_access.

El campo conf define qué estructura de configuración se pasa al manejador de directorios. Los módulos núcleo solo tienen la configuración global y establecen la bandera NGX_DIRECT_CONF para acceder a ella. Módulos como HTTP, Stream o Mail crean jerarquías de configuraciones. Por ejemplo, la configuración de un módulo se crea para server, location y if alcances.

  • NGX_HTTP_MAIN_CONF_OFFSET — Configuración para el bloque http.

  • NGX_HTTP_SRV_CONF_OFFSET — Configuración para un bloque server dentro del bloque http.

  • NGX_HTTP_LOC_CONF_OFFSET — Configuración para un bloque location dentro del http.

  • NGX_STREAM_MAIN_CONF_OFFSET — Configuración para el bloque stream.

  • NGX_STREAM_SRV_CONF_OFFSET — Configuración para un bloque server dentro del bloque stream.

  • NGX_MAIL_MAIN_CONF_OFFSET — Configuración para el bloque mail.

  • NGX_MAIL_SRV_CONF_OFFSET — Configuración para un bloque server dentro del bloque mail.

El offset define el desplazamiento de un campo en una estructura de configuración del módulo que contiene valores para esta directiva en particular. El uso típico es emplear la macro offsetof().

El post field tiene dos propósitos: puede usarse para definir un manejador que se llame después de que el manejador principal haya completado, o para pasar datos adicionales al manejador principal. En el primer caso, la estructura ngx_conf_post_t debe inicializarse con un puntero al manejador, por ejemplo:

static char *ngx_do_foo(ngx_conf_t *cf, void *post, void *data);
static ngx_conf_post_t  ngx_foo_post = { ngx_do_foo };

El argumento post es el propio objeto ngx_conf_post_t y data es un puntero al valor, convertido a partir de los argumentos por el manejador principal con el tipo adecuado.

HTTP#

Conexión#

Cada conexión de cliente HTTP pasa por las siguientes etapas:

  • ngx_event_accept() acepta una conexión TCP de cliente. Este manejador se llama en respuesta a una notificación de lectura en un socket de escucha. En esta etapa se crea un nuevo objeto ngx_connection_t para envolver el socket del cliente recién aceptado. Cada listener de Angie proporciona un manejador para pasar el nuevo objeto de conexión. Para conexiones HTTP es ngx_http_init_connection(c).

  • ngx_http_init_connection() realiza una inicialización temprana de la conexión HTTP. En esta etapa se crea un objeto ngx_http_connection_t para la conexión y su referencia se almacena en el campo data de la conexión. Más tarde será reemplazado por un objeto de solicitud HTTP. También se inicia un analizador del protocolo PROXY y el handshake SSL en esta etapa.

  • ngx_http_wait_request_handler() el manejador de eventos de lectura se llama cuando hay datos disponibles en el socket del cliente. En esta etapa se crea un objeto de solicitud HTTP ngx_http_request_t y se asigna al campo data de la conexión.

  • ngx_http_process_request_line() el manejador de eventos de lectura lee la línea de solicitud del cliente. El manejador es establecido por ngx_http_wait_request_handler(). Los datos se leen en el buffer de la conexión. El tamaño del búfer se establece inicialmente por la directiva client_header_buffer_size. Se espera que todo el encabezado del cliente quepa en el búfer. Si el tamaño inicial no es suficiente, se asigna un búfer más grande, con la capacidad definida por la directiva large_client_header_buffers.

  • ngx_http_process_request_headers() es el manejador de eventos de lectura, se configura después de ngx_http_process_request_line() para leer el encabezado de la solicitud del cliente.

  • ngx_http_core_run_phases() se llama cuando el encabezado de la solicitud ha sido completamente leído y analizado. Esta función ejecuta las fases de la solicitud desde NGX_HTTP_POST_READ_PHASE hasta NGX_HTTP_CONTENT_PHASE. La última fase tiene como objetivo generar una respuesta y pasarla a lo largo de la cadena de filtros. La respuesta no necesariamente se envía al cliente en esta fase. Puede permanecer en búfer y enviarse en la etapa de finalización.

  • ngx_http_finalize_request() suele llamarse cuando la solicitud ha generado toda la salida o ha producido un error. En este último caso se busca una página de error adecuada y se utiliza como respuesta. Si la respuesta no se ha enviado por completo al cliente en este punto, se activa un escritor HTTP ngx_http_writer() para terminar de enviar los datos pendientes.

  • ngx_http_finalize_connection() se llama cuando la respuesta completa ha sido enviada al cliente y la solicitud puede ser destruida. Si la función de mantener la conexión viva (keepalive) está habilitada, ngx_http_set_keepalive() se llama, lo que destruye la solicitud actual y espera la siguiente solicitud en la conexión. De lo contrario, ngx_http_close_request() destruye tanto la solicitud como la conexión.

Solicitud#

Para cada solicitud HTTP de cliente se crea el objeto ngx_http_request_t. Algunos de los campos de este objeto son:

  • connection — Puntero a un objeto de conexión de cliente ngx_connection_t. Varias solicitudes pueden hacer referencia al mismo objeto de conexión al mismo tiempo: una solicitud principal y sus subsolicitudes. Después de eliminarse una solicitud, puede crearse una nueva en la misma conexión.

    Cabe señalar que para las conexiones HTTP, el campo data de ngx_connection_t apunta de vuelta a la solicitud. Dichas solicitudes se conocen como activas, a diferencia de las demás solicitudes vinculadas a la conexión. Una solicitud activa se utiliza para manejar eventos de conexión del cliente y puede enviar su salida al cliente. Normalmente, cada solicitud llega a estar activa en algún momento para poder enviar su salida.

  • ctx — Array de contextos de módulos HTTP. Cada módulo de tipo NGX_HTTP_MODULE puede almacenar cualquier valor (normalmente, un puntero a una estructura) en la solicitud. El valor se almacena en el arreglo ctx en la posición ctx_index del módulo. Las siguientes macros proporcionan una forma conveniente de obtener y establecer los contextos de la solicitud:

    • ngx_http_get_module_ctx(r, module) — Devuelve el contexto del module

    • ngx_http_set_ctx(r, c, module) — Establece c como el contexto del module

  • main_conf, srv_conf, loc_conf — Arreglos de configuraciones actuales de la solicitud. Las configuraciones se almacenan en las posiciones ctx_index del módulo.

  • read_event_handler, write_event_handler — Manejadores de eventos de lectura y escritura para la solicitud. Normalmente, tanto el manejador de lectura como el de escritura para una conexión HTTP se establecen en ngx_http_request_handler(). Esta función llama a los manejadores read_event_handler y write_event_handler para la solicitud actualmente activa.

  • cache — Objeto de caché de la solicitud para almacenar en caché la respuesta upstream.

  • upstream — Objeto upstream de la solicitud para proxying.

  • pool — Pool de la solicitud. El objeto de solicitud se asigna en este pool, que se destruye cuando la solicitud se elimina. Para asignaciones que deben estar disponibles durante toda la vida de la conexión del cliente, use el pool de ngx_connection_t en su lugar.

  • header_in — Buffer en el que se lee el encabezado de la solicitud HTTP del cliente.

  • headers_in, headers_out — Objetos de encabezados HTTP de entrada y salida. Ambos objetos contienen el campo headers de tipo ngx_list_t para mantener la lista cruda de encabezados. Además de eso, hay encabezados específicos disponibles para obtenerlos y establecerlos como campos separados, por ejemplo content_length_n, status, etc.

  • request_body — Objeto del cuerpo de la solicitud del cliente.

  • start_sec, start_msec — Momento en el que se creó la solicitud, utilizado para rastrear la duración de la solicitud.

  • method, method_name — Representación numérica y textual del método de la solicitud HTTP del cliente. Los valores numéricos de los métodos están definidos en src/http/ngx_http_request.h con las macros NGX_HTTP_GET, NGX_HTTP_HEAD, NGX_HTTP_POST, etc.

  • http_protocol — Versión del protocolo HTTP del cliente en su forma de texto original ("HTTP/1.0", "HTTP/1.1" etc).

  • http_version — Versión del protocolo HTTP del cliente en forma numérica (NGX_HTTP_VERSION_10, NGX_HTTP_VERSION_11, etc.).

  • http_major, http_minor — Versión numérica del protocolo HTTP del cliente, desglosada en partes mayor y menor.

  • request_line, unparsed_uri — Línea de solicitud y URI en la solicitud original del cliente.

  • uri, args, exten — URI, argumentos y extensión de archivo para la solicitud actual. El valor de URI aquí podría diferir del URI original enviado por el cliente debido a la normalización. A lo largo del procesamiento de la solicitud, estos valores pueden cambiar a medida que se realizan redirecciones internas.

  • main — Puntero a un objeto de solicitud principal. Este objeto se crea para procesar una solicitud HTTP del cliente, a diferencia de las subsolicitudes, que se crean para realizar una tarea específica dentro de la solicitud principal.

  • parent — Puntero a la solicitud principal de una subsolicitud.

  • postponed — Lista de búferes de salida y subsolicitudes, en el orden en que se envían y crean. La lista se utiliza por el filtro de posposición para proporcionar una salida de la solicitud coherente cuando partes de ella son creadas por subsolicitudes.

  • post_subrequest — Puntero a un manejador con el contexto que debe llamarse cuando una subsolicitud se finaliza. No se utiliza para las solicitudes principales.

  • posted_requests — Lista de solicitudes que deben iniciarse o reanudarse, lo que se realiza llamando al manejador de escritura de la solicitud. Normalmente, este manejador contiene la función principal de la solicitud, que al principio ejecuta las fases de la solicitud y luego genera la salida.

    Normalmente, una solicitud se pubica mediante la llamada ngx_http_post_request(r, NULL). Siempre se publica en la lista principal de solicitudes posted_requests. La función ngx_http_run_posted_requests(c) ejecuta todas las solicitudes que están publicadas en la solicitud principal de la solicitud activa de la conexión pasada. Todos los manejadores de eventos llaman a ngx_http_run_posted_requests, lo que puede dar lugar a nuevas solicitudes publicadas. Normalmente, se llama después de invocar el manejador de lectura o escritura de una solicitud.

  • phase_handler — Índice de la fase actual de la solicitud.

  • ncaptures, captures, captures_data — Capturas de expresiones regulares producidas por la última coincidencia de regex de la solicitud. Una coincidencia de regex puede ocurrir en varios lugares durante el procesamiento de la solicitud: búsqueda en mapas, búsqueda del servidor por SNI o HTTP Host, reescritura, proxy_redirect, etc. Las capturas producidas por una búsqueda se almacenan en los campos mencionados arriba. El campo ncaptures contiene el número de capturas, captures contiene los límites de las capturas y captures_data contiene la cadena con la que se emparejó la expresión regular y que se utiliza para extraer las capturas. Después de cada nueva coincidencia de regex, las capturas de la solicitud se restablecen para contener nuevos valores.

  • count — Contador de referencias de la solicitud. Este campo tiene sentido solo para la solicitud principal. Aumentar el contador se hace con r->main->count++. Para disminuirlo, llame a ngx_http_finalize_request(r, rc). La creación de una subsolicitud y la ejecución del proceso de lectura del cuerpo de la solicitud también incrementan el contador.

  • subrequests — Nivel actual de anidamiento de subsolicitudes. Cada subsolicitud hereda el nivel de anidamiento de su padre, reducido en uno. Se genera un error si el valor llega a cero. El valor para la solicitud principal está definido por la NGX_HTTP_MAX_SUBREQUESTS constante.

  • uri_changes — Número de cambios de URI que quedan para la solicitud. El número total de veces que una solicitud puede cambiar su URI está limitado por la constante NGX_HTTP_MAX_URI_CHANGES. Con cada cambio, el valor se decrementa hasta que llega a cero, momento en el cual se genera un error. Las reescrituras y las redirecciones internas a ubicaciones normales o con nombre se consideran cambios de URI.

  • blocked — Contador de bloqueos mantenidos sobre la solicitud. Mientras este valor sea distinto de cero, la solicitud no puede terminarse. Actualmente, este valor aumenta por operaciones AIO pendientes (AIO POSIX y operaciones con hilos) y por un bloqueo de caché activo.

  • buffered — Máscara de bits que indica qué módulos han bufferizado la salida producida por la solicitud. Varios filtros pueden bufferizar la salida; por ejemplo, sub_filter puede bufferizar datos debido a una coincidencia parcial de cadena, el filtro copy puede bufferizar datos debido a la falta de búferes de salida disponibles, etc. Mientras este valor sea distinto de cero, la solicitud no se finaliza hasta que se vacíen los búferes de salida.

  • header_only — Indicador de que la salida no requiere un cuerpo. Por ejemplo, este indicador se utiliza en las solicitudes HTTP HEAD.

  • keepalive — Indicador de si se admite keepalive de la conexión del cliente. El valor se infiere a partir de la versión HTTP y del valor del encabezado "Connection".

  • header_sent — Indicador de que el encabezado de salida ya ha sido enviado por la solicitud.

  • internal — Indicador de que la solicitud actual es interna. Para entrar en el estado interno, una solicitud debe pasar por una redirección interna o ser una subsolicitud. Las solicitudes internas pueden entrar en ubicaciones internas.

  • allow_ranges — Indicador de que se puede enviar una respuesta parcial al cliente, tal como lo solicita el encabezado HTTP Range.

  • subrequest_ranges — Indicador de que se puede enviar una respuesta parcial mientras se procesa una subsolicitud.

  • single_range — Indicador de que solo se puede enviar al cliente un único rango continuo de datos de salida. Este indicador suele activarse al enviar un flujo de datos, por ejemplo desde un servidor proxy, y toda la respuesta no está disponible en un solo búfer.

  • main_filter_need_in_memory, filter_need_in_memory — Indicadores que solicitan que la salida producida esté en búferes de memoria en lugar de archivos. Esto es una señal para que el filtro de copia lea datos desde búferes de archivos incluso si sendfile está habilitado. La diferencia entre las dos señales es la ubicación de los módulos de filtro que las establecen. Los filtros llamados antes del filtro postpone en la cadena de filtros establecen filter_need_in_memory, solicitando que únicamente la salida de la solicitud actual se almacene en búferes de memoria. Los filtros llamados después en la cadena de filtros establecen main_filter_need_in_memory, solicitando que tanto la solicitud principal como todas las subsolicitudes lean archivos en memoria mientras se envía la salida.

  • filter_need_temporary — Indicador que solicita que la salida de la solicitud se genere en búferes temporales, pero no en búferes de memoria de solo lectura ni en búferes de archivos. Esto es utilizado por filtros que pueden modificar la salida directamente en los búferes donde se envía.

Configuración#

Cada módulo HTTP puede tener tres tipos de configuración:

  • Configuración principal — Se aplica a todo el bloque http. Funciona como configuración global para un módulo.

  • Configuración del servidor — Se aplica a un único bloque server. Funciona como configuración específica del servidor para un módulo.

  • Configuración de ubicación — Se aplica a un único bloque location, if o limit_except. Funciona como configuración específica de ubicación para un módulo.

Las estructuras de configuración se crean en la etapa de configuración de Angie invocando funciones, que asignan las estructuras, las inicializan y las fusionan. El siguiente ejemplo muestra cómo crear una configuración de ubicación simple para un módulo. La configuración tiene una única opción, foo, de tipo entero sin signo.

typedef struct {
    ngx_uint_t  foo;
} ngx_http_foo_loc_conf_t;


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

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

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

    ngx_http_foo_create_loc_conf,          /* create location configuration */
    ngx_http_foo_merge_loc_conf            /* merge location configuration */
};


static void *
ngx_http_foo_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_foo_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_foo_loc_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    conf->foo = NGX_CONF_UNSET_UINT;

    return conf;
}


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

    ngx_conf_merge_uint_value(conf->foo, prev->foo, 1);
}

Como se ve en el ejemplo, la función ngx_http_foo_create_loc_conf() crea una nueva estructura de configuración, y ngx_http_foo_merge_loc_conf() fusiona una configuración con la configuración de un nivel superior. De hecho, la configuración del servidor y de la ubicación no existe solo a nivel de servidor y de ubicación, sino que también se crea para todos los niveles superiores. Específicamente, una configuración del servidor también se crea a nivel principal y las configuraciones de ubicación se crean a los niveles principal, servidor y ubicación. Estas configuraciones permiten especificar configuraciones específicas del servidor y de la ubicación en cualquier nivel de un archivo de configuración Angie. Finalmente, las configuraciones se fusionan hacia abajo. Se proporcionan varios macros como NGX_CONF_UNSET y NGX_CONF_UNSET_UINT para indicar una configuración ausente e ignorarla durante la fusión. Los macros de fusión estándar de Angie, como ngx_conf_merge_value() y ngx_conf_merge_uint_value() ofrecen una forma conveniente de fusionar una configuración y establecer el valor por defecto si ninguna de las configuraciones proporcionó un valor explícito. Para la lista completa de macros para diferentes tipos, consulta src/core/ngx_conf_file.h.

Las siguientes macros están disponibles para acceder a la configuración de los módulos HTTP en tiempo de configuración. Todas ellas toman una referencia ngx_conf_t como primer argumento.

  • 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)

El siguiente ejemplo obtiene un puntero a una configuración de ubicación del módulo central estándar de Angie core module y reemplaza el manejador de contenido de la ubicación guardado en el campo handler de la estructura.

static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r);


static ngx_command_t  ngx_http_foo_commands[] = {

    { ngx_string("foo"),
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
      ngx_http_foo,
      0,
      0,
      NULL },

      ngx_null_command
};


static char *
ngx_http_foo(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_bar_handler;

    return NGX_CONF_OK;
}

Las siguientes macros están disponibles para acceder a la configuración de los módulos HTTP en tiempo de ejecución.

  • ngx_http_get_module_main_conf(r, module)

  • ngx_http_get_module_srv_conf(r, module)

  • ngx_http_get_module_loc_conf(r, module)

Estas macros reciben una referencia a una solicitud HTTP ngx_http_request_t. La configuración principal de una solicitud nunca cambia. La configuración del servidor puede cambiar con respecto al valor por defecto después de que se elige el servidor virtual para la solicitud. La configuración de ubicación seleccionada para procesar una solicitud puede cambiar varias veces como resultado de una operación de reescritura o de una redirección interna. El siguiente ejemplo muestra cómo acceder a la configuración HTTP de un módulo en tiempo de ejecución.

static ngx_int_t
ngx_http_foo_handler(ngx_http_request_t *r)
{
    ngx_http_foo_loc_conf_t  *flcf;

    flcf = ngx_http_get_module_loc_conf(r, ngx_http_foo_module);

    ...
}

Phases#

Cada solicitud HTTP pasa por una secuencia de fases. En cada fase se realiza un tipo distinto de procesamiento de la solicitud. Los manejadores específicos de los módulos pueden registrarse en la mayoría de las fases, y muchos módulos estándar de Angie registran sus manejadores de fase como una forma de ser llamados en una etapa específica del procesamiento de la solicitud. Las fases se procesan de forma sucesiva y los manejadores de fase se llaman una vez que la solicitud llega a la fase. A continuación se muestra la lista de fases HTTP de Angie.

  • NGX_HTTP_POST_READ_PHASE — Primera fase. El módulo RealIP registra su manejador en esta fase para permitir la sustitución de direcciones de cliente antes de que se invoque cualquier otro módulo.

  • NGX_HTTP_SERVER_REWRITE_PHASE — Fase en la que se procesan las directivas de reescritura definidas en un bloque server (pero fuera de un bloque location). El módulo Rewrite instala su manejador en esta fase.

  • NGX_HTTP_FIND_CONFIG_PHASE — Fase especial donde se elige una ubicación basada en la URI de la solicitud. Antes de esta fase, la ubicación por defecto para el servidor virtual relevante se asigna a la solicitud, y cualquier módulo que solicite una configuración de ubicación recibe la configuración de la ubicación del servidor por defecto. Esta fase asigna una nueva ubicación a la solicitud. No se pueden registrar manejadores adicionales en esta fase.

  • NGX_HTTP_REWRITE_PHASE — Igual que NGX_HTTP_SERVER_REWRITE_PHASE, pero para las reglas de reescritura definidas en la ubicación, elegida en la fase anterior.

  • NGX_HTTP_POST_REWRITE_PHASE — Fase especial donde la solicitud se redirige a una nueva ubicación si su URI cambió durante una reescritura. Esto se implementa haciendo que la solicitud pase de nuevo por NGX_HTTP_FIND_CONFIG_PHASE nuevamente. No se pueden registrar manejadores adicionales en esta fase.

  • NGX_HTTP_PREACCESS_PHASE — Fase común para distintos tipos de manejadores, no asociada con el control de acceso. Los módulos estándar de Angie Limit Conn y Limit Req registran sus manejadores en esta fase.

  • NGX_HTTP_ACCESS_PHASE — Fase en la que se verifica que el cliente está autorizado para realizar la solicitud. Módulos estándar de Angie como Acceso y Auth Basic registran sus manejadores en esta fase. Por defecto, el cliente debe superar la verificación de autorización de todos los manejadores registrados en esta fase para que la solicitud continúe a la siguiente fase. La directiva satisfy puede usarse para permitir que el procesamiento continúe si alguno de los manejadores de la fase autoriza al cliente.

  • NGX_HTTP_POST_ACCESS_PHASE — Fase especial en la que se procesa la directiva satisfy any. Si algunos manejadores de la fase de acceso negaron el acceso y ninguno lo permitió explícitamente, la solicitud se finaliza. No se pueden registrar manejadores adicionales en esta fase.

  • NGX_HTTP_PRECONTENT_PHASE — Fase para que se llamen los manejadores antes de generar el contenido. Módulos estándar como try_files y Mirror registran sus manejadores en esta fase.

  • NGX_HTTP_CONTENT_PHASE — Fase en la que normalmente se genera la respuesta. Varios módulos estándar de Angie registran sus manejadores en esta fase, incluido Índice. Se llaman de forma secuencial hasta que alguno de ellos genere la salida. También es posible establecer manejadores de contenido por ubicación. Si la configuración de ubicación de Módulo HTTP tiene establecida handler, se llama como manejador de contenido y se ignoran los manejadores instalados en esta fase.

  • NGX_HTTP_LOG_PHASE — Fase en la que se realiza el registro de la solicitud. Actualmente, solo el módulo Log registra su manejador en esta etapa para el registro de acceso. Los manejadores de la fase de registro se llaman al final del procesamiento de la solicitud, justo antes de liberar la solicitud.

A continuación se muestra un ejemplo de un manejador de la fase preacceso.

static ngx_http_module_t  ngx_http_foo_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_foo_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 */
};


static ngx_int_t
ngx_http_foo_handler(ngx_http_request_t *r)
{
    ngx_table_elt_t  *ua;

    ua = r->headers_in.user_agent;

    if (ua == NULL) {
        return NGX_DECLINED;
    }

    /* reject requests with "User-Agent: foo" */
    if (ua->value.len == 3 && ngx_strncmp(ua->value.data, "foo", 3) == 0) {
        return NGX_HTTP_FORBIDDEN;
    }

    return NGX_DECLINED;
}


static ngx_int_t
ngx_http_foo_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_foo_handler;

    return NGX_OK;
}

Phase handlers are expected to return specific codes:

  • NGX_OK — Proceda a la siguiente fase.

  • NGX_DECLINED — Proceda al siguiente manejador de la fase actual. Si el manejador actual es el último en la fase actual, pase a la siguiente fase.

  • NGX_AGAIN, NGX_DONE — Suspendir la gestión de la fase hasta que ocurra algún evento futuro que pueda ser una operación de E/S asíncrona o simplemente un retardo, por ejemplo. Se asume que la gestión de la fase se reanudará más tarde llamando ngx_http_core_run_phases().

  • Cualquier otro valor devuelto por el manejador de fase se trata como un código de finalización de la solicitud, en particular, un código de respuesta HTTP. La solicitud se finaliza con el código proporcionado.

Para algunas fases, los códigos de retorno se tratan de una manera ligeramente diferente. En la fase de contenido, cualquier código de retorno distinto de NGX_DECLINED se considera un código de finalización. Cualquier código de retorno de los manejadores de contenido de la ubicación se considera un código de finalización. En la fase de acceso, en el modo satisfy any cualquier código de retorno distinto de NGX_OK, NGX_DECLINED, NGX_AGAIN, NGX_DONE se considera una denegación. Si no hay manejadores de acceso subsiguientes que permitan o deneguen el acceso con un código diferente, el código de denegación se convertirá en el código de finalización.

Ejemplos#

El nginx-dev-examples repositorio proporciona ejemplos de módulos Angie.

Code style#

Reglas generales#

  • El ancho máximo de texto es de 80 caracteres

  • La sangría es de 4 espacios

  • No se permiten tabulaciones, ni espacios al final

  • Los elementos de la lista en la misma línea se separan con espacios

  • Los literales hexadecimales están en minúsculas

  • Los nombres de archivos, nombres de funciones y tipos, y las variables globales tienen el prefijo ngx_ o un prefijo más específico como ngx_http_ y ngx_mail_

size_t
ngx_utf8_length(u_char *p, size_t n)
{
    u_char  c, *last;
    size_t  len;

    last = p + n;

    for (len = 0; p < last; len++) {

        c = *p;

        if (c < 0x80) {
            p++;
            continue;
        }

        if (ngx_utf8_decode(&p, last - p) > 0x10ffff) {
            /* invalid UTF-8 */
            return n;
        }
    }

    return len;
}

Archivos#

Un archivo fuente típico puede contener las siguientes secciones separadas por dos líneas en blanco:

  • declaraciones de derechos de autor

  • inclusiones

  • definiciones de preprocesador

  • definiciones de tipos

  • prototipos de funciones

  • definiciones de variables

  • definiciones de funciones

Las declaraciones de derechos de autor se ven así:

/*
 * Copyright (C) Author Name
 * Copyright (C) Organization, Inc.
 */

Si el archivo se modifica significativamente, la lista de autores debe actualizarse, el nuevo autor se añade en la parte superior.

Los archivos ngx_config.h y ngx_core.h se incluyen siempre primero, seguidos de uno de ngx_http.h, ngx_stream.h, o ngx_mail.h. A continuación siguen archivos de cabecera externos opcionales:

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

#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxslt/xslt.h>

#if (NGX_HAVE_EXSLT)
#include <libexslt/exslt.h>
#endif

Los archivos de cabecera deben incluir la denominada "protección de cabecera":

#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_
#define _NGX_PROCESS_CYCLE_H_INCLUDED_
...
#endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */

Comentarios#

  • Los comentarios con // no se utilizan

  • el texto se escribe en inglés, se prefiere la ortografía estadounidense

  • los comentarios multilínea se formatean así:

    /*
     * The red-black tree code is based on the algorithm described in
     * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
     */
    
    /* find the server configuration for the address:port */
    

Preprocesador#

Los nombres de macros comienzan con ngx_ o NGX_ (o prefijo más específico). Los nombres de macros para constantes están en mayúsculas. Las macros parametrizadas y las macros para inicializadores están en minúsculas. El nombre de la macro y su valor se separan por al menos dos espacios:

#define NGX_CONF_BUFFER  4096

#define ngx_buf_in_memory(b)  (b->temporary || b->memory || b->mmap)

#define ngx_buf_size(b)                                                      \
    (ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos):                      \
                            (b->file_last - b->file_pos))

#define ngx_null_string  { 0, NULL }

Las condiciones están dentro de paréntesis, la negación está fuera:

#if (NGX_HAVE_KQUEUE)
...
#elif ((NGX_HAVE_DEVPOLL && !(NGX_TEST_BUILD_DEVPOLL)) \
       || (NGX_HAVE_EVENTPORT && !(NGX_TEST_BUILD_EVENTPORT)))
...
#elif (NGX_HAVE_EPOLL && !(NGX_TEST_BUILD_EPOLL))
...
#elif (NGX_HAVE_POLL)
...
#else /* select */
...
#endif /* NGX_HAVE_KQUEUE */

Tipos#

Los nombres de tipos terminan con el sufijo _t. Un nombre de tipo definido está separado por al menos dos espacios:

typedef ngx_uint_t  ngx_rbtree_key_t;

Los tipos de estructuras se definen usando typedef. Dentro de las estructuras, los tipos y nombres de los miembros están alineados:

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

Mantenga la alineación idéntica entre diferentes estructuras en el archivo. Una estructura que apunta a sí misma tiene el nombre que termina con "_s". Las definiciones de estructuras adyacentes están separadas por dos líneas en blanco:

typedef struct ngx_list_part_s  ngx_list_part_t;

struct ngx_list_part_s {
    void             *elts;
    ngx_uint_t        nelts;
    ngx_list_part_t  *next;
};


typedef struct {
    ngx_list_part_t  *last;
    ngx_list_part_t   part;
    size_t            size;
    ngx_uint_t        nalloc;
    ngx_pool_t       *pool;
} ngx_list_t;

Cada miembro de una estructura se declara en su propia línea:

typedef struct {
    ngx_uint_t        hash;
    ngx_str_t         key;
    ngx_str_t         value;
    u_char           *lowcase_key;
} ngx_table_elt_t;

Los punteros de funciones dentro de las estructuras tienen tipos definidos que terminan con "_pt":

typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size);
typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
    off_t limit);
typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size);
typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
    off_t limit);

typedef struct {
    ngx_recv_pt        recv;
    ngx_recv_chain_pt  recv_chain;
    ngx_recv_pt        udp_recv;
    ngx_send_pt        send;
    ngx_send_pt        udp_send;
    ngx_send_chain_pt  udp_send_chain;
    ngx_send_chain_pt  send_chain;
    ngx_uint_t         flags;
} ngx_os_io_t;

Las enumeraciones tienen tipos que terminan con "_e":

typedef enum {
    ngx_http_fastcgi_st_version = 0,
    ngx_http_fastcgi_st_type,
    ...
    ngx_http_fastcgi_st_padding
} ngx_http_fastcgi_state_e;

Variables#

Las variables se declaran ordenadas por la longitud del tipo base, luego alfabéticamente. Los nombres de tipos y nombres de variables están alineados. Las "columnas" de tipo y nombre se separan con dos espacios. Los arreglos grandes se colocan al final de un bloque de declaraciones:

u_char                      *rv, *p;
ngx_conf_t                  *cf;
ngx_uint_t                   i, j, k;
unsigned int                 len;
struct sockaddr             *sa;
const unsigned char         *data;
ngx_peer_connection_t       *pc;
ngx_http_core_srv_conf_t   **cscfp;
ngx_http_upstream_srv_conf_t *us, *uscf;
u_char                       text[NGX_SOCKADDR_STRLEN];

Las variables estáticas y globales pueden ser inicializadas en la declaración:

static ngx_str_t  ngx_http_memcached_key = ngx_string("memcached_key");
static ngx_uint_t  mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static ngx_str_t  ngx_http_memcached_key = ngx_string("memcached_key");
static ngx_uint_t  mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static uint32_t  ngx_crc32_table16[] = {
    0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
    ...
    0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};

Existen numerosas combinaciones comunes de tipos/nombres:

u_char                        *rv;
ngx_int_t                      rc;
ngx_conf_t                    *cf;
ngx_connection_t              *c;
ngx_http_request_t            *r;
ngx_peer_connection_t         *pc;
ngx_http_upstream_srv_conf_t  *us, *uscf;

Funciones#

Todas las funciones (incluso las estáticas) deben tener prototipos. Los prototipos incluyen los nombres de los argumentos. Los prototipos largos se envuelven con una sangría única en las líneas de continuación:

static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf,
    ngx_http_core_main_conf_t *cmcf);

static char *ngx_http_merge_servers(ngx_conf_t *cf,
    ngx_http_core_main_conf_t *cmcf, ngx_http_module_t *module,
    ngx_uint_t ctx_index);

El nombre de la función en una definición empieza en una nueva línea. Las llaves de apertura y cierre del cuerpo de la función están en líneas separadas. El cuerpo de una función está indentado. Hay dos líneas en blanco entre funciones:

static ngx_int_t
ngx_http_find_virtual_server(ngx_http_request_t *r, u_char *host, size_t len)
{
    ...
}


static ngx_int_t
ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
    ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
{
    ...
}

No hay espacio entre el nombre de la función y el paréntesis de apertura. Las llamadas largas a funciones se envuelven de modo que las líneas de continuación comienzan desde la posición del primer argumento de la función. Si esto es imposible, formatee la primera línea de continuación para que termine en la posición 79:

ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
               "http header: \"%V: %V\"",
               &h->key, &h->value);

hc->busy = ngx_palloc(r->connection->pool,
                  cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));

La macro ngx_inline debe utilizarse en lugar de inline:

static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf);

Expresiones#

Los operadores binarios, excepto . y -> deben ir separados de sus operandos por un espacio. Los operadores unarios y los subíndices no se separan de sus operandos por espacios:

width = width * 10 + (*fmt++ - '0');
ch = (u_char) ((decoded << 4) + (ch - '0'));
r->exten.data = &r->uri.data[i + 1];

Los casteos están separados por un espacio de las expresiones a las que se aplican las conversiones. Un asterisco dentro del casteo está separado por un espacio del nombre del tipo:

len = ngx_sock_ntop((struct sockaddr *) sin6, p, len, 1);

Si una expresión no cabe en una sola línea, se envuelve. El punto preferido para romper una línea es un operador binario. La línea de continuación se alinea con el inicio de la expresión:

if (status == NGX_HTTP_MOVED_PERMANENTLY
    || status == NGX_HTTP_MOVED_TEMPORARILY
    || status == NGX_HTTP_SEE_OTHER
    || status == NGX_HTTP_TEMPORARY_REDIRECT
    || status == NGX_HTTP_PERMANENT_REDIRECT)
{
    ...
}
p->temp_file->warn = "an upstream response is buffered "
                     "to a temporary file";

Como último recurso, es posible envolver una expresión para que la línea de continuación termine en la posición 79:

hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)
                                     + size * sizeof(ngx_hash_elt_t *));

Las reglas anteriores también se aplican a subexpresiones, donde cada subexpresión tiene su propio nivel de sangría:

if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING)
     || c->stale_updating) && !r->background
    && u->conf->cache_background_update)
{
    ...
}

A veces, es conveniente envolver una expresión después de un cast. En este caso, la línea de continuación está indentada:

node = (ngx_rbtree_node_t *)
           ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));

Los punteros se comparan explícitamente con NULL (no 0):

if (ptr != NULL) {
    ...
}

Condicionales y Bucles#

La palabra clave if está separada de la condición por un solo espacio. La llave de apertura se ubica en la misma línea, o en una línea dedicada si la condición ocupa varias líneas. La llave de cierre se ubica en una línea dedicada, opcionalmente seguida de else if / else. Por lo general, hay una línea en blanco antes de la parte else if / else:

if (node->left == sentinel) {
    temp = node->right;
    subst = node;

} else if (node->right == sentinel) {
    temp = node->left;
    subst = node;

} else {
    subst = ngx_rbtree_min(node->right, sentinel);

    if (subst->left != sentinel) {
        temp = subst->left;

    } else {
        temp = subst->right;
    }
}

Se aplican reglas de formato similares a do y while:

while (p < last && *p == ' ') {
    p++;
}
do {
    ctx->node = rn;
    ctx = ctx->next;
} while (ctx);

La palabra clave switch está separada de la condición por un espacio. La llave de apertura está ubicada en la misma línea. La llave de cierre está ubicada en una línea dedicada. Las palabras clave case se alinean con switch:

switch (ch) {
case '!':
    looked = 2;
    state = ssi_comment0_state;
    break;

case '<':
    copy_end = p;
    break;

default:
    copy_end = p;
    looked = 0;
    state = ssi_start_state;
    break;
}

La mayoría de los bucles for se formatean así:

for (i = 0; i < ccf->env.nelts; i++) {
    ...
}
for (q = ngx_queue_head(locations);
     q != ngx_queue_sentinel(locations);
     q = ngx_queue_next(q))
{
    ...
}

Si se omite alguna parte de la instrucción for, esto se indica con el comentario /* void */:

for (i = 0; /* void */ ; i++) {
    ...
}

Una construcción con el cuerpo vacío también se indica con el comentario /* void */ que puede ir en la misma línea:

for (cl = *busy; cl->next; cl = cl->next) { /* void */ }

Un bucle sin fin se ve así:

for ( ;; ) {
    ...
}

Etiquetas#

Las etiquetas están rodeadas de líneas en blanco y se indentan al nivel anterior:

    if (i == 0) {
        u->err = "host not found";
        goto failed;
    }

    u->addrs = ngx_pcalloc(pool, i * sizeof(ngx_addr_t));
    if (u->addrs == NULL) {
        goto failed;
    }

    u->naddrs = i;

    ...

    return NGX_OK;

failed:

    freeaddrinfo(res);
    return NGX_ERROR;

Depuración de problemas de memoria#

Para depurar problemas de memoria como desbordamientos de búfer o errores de uso después de liberar, puedes usar AddressSanitizer <https://en.wikipedia.org/wiki/AddressSanitizer>_ (ASan) soportado por algunos compiladores. Para habilitar ASan con gcc y clang, utiliza la opción de compilador y enlazador -fsanitize=address. Al compilar Angie, esto se puede hacer añadiendo la opción a --with-cc-opt y --with-ld-opt parámetros del script configure.

Ya que la mayoría de las asignaciones en Angie se realizan desde el pool interno de Angie, habilitar ASan puede no ser suficiente para depurar problemas de memoria. El pool interno asigna un gran bloque de memoria del sistema y extrae asignaciones más pequeñas de él. Sin embargo, este mecanismo puede desactivarse estableciendo la macro NGX_DEBUG_PALLOC a 1. En este caso, las asignaciones se pasan directamente al asignador del sistema, dándole control total sobre los límites de los búferes.

La siguiente línea de configuración resume la información proporcionada anteriormente. Se recomienda durante el desarrollo de módulos de terceros y al probar Angie en diferentes plataformas.

auto/configure --with-cc-opt='-fsanitize=address -DNGX_DEBUG_PALLOC=1'
               --with-ld-opt=-fsanitize=address

Errores comunes#

Escribir un módulo en C#

El error más común es intentar escribir un módulo en C completo cuando se puede evitar. En la mayoría de los casos tu tarea se puede lograr creando una configuración adecuada. Si escribir un módulo es inevitable, intenta hacerlo tan pequeño y simple como sea posible. Por ejemplo, un módulo puede exportar solo algunas variables.

Antes de empezar un módulo, considera las siguientes preguntas:

Cadenas en C#

El tipo de cadena más utilizado en Angie, ngx_str_t no es una cadena de estilo C terminada en cero. No puedes pasar los datos a funciones de la biblioteca C estándar como strlen() o strstr(). En su lugar, se deben usar las contrapartes de Angie que acepten ya sea ngx_str_t o un puntero a datos y una longitud. Sin embargo, hay un caso en el que ngx_str_t contiene un puntero a una cadena terminada en cero: las cadenas que provienen del análisis del archivo de configuración son terminadas en cero.

Variables Globales#

Evita usar variables globales en tus módulos. Lo más probable es que sea un error tener una variable global. Cualquier dato global debe estar ligado a un ciclo de configuración y ser asignado desde el correspondiente pool de memoria. Esto permite a Angie realizar recargas de configuración de forma suave. Un intento de usar variables globales probablemente romperá esta característica, porque será imposible tener dos configuraciones al mismo tiempo y deshacerse de ellas. A veces se requieren variables globales. En este caso, se necesita una atención especial para gestionar la reconfiguración correctamente. Además, verifica si las bibliotecas utilizadas por tu código tienen estado global implícito que podría romperse al recargar.

Gestión manual de memoria#

En lugar de lidiar con enfoques malloc/free que son propensos a errores, aprende a usar los pools de Angie. Un pool se crea y se vincula a un objeto - configuración, ciclo, conexión, o solicitud HTTP. Cuando el objeto se destruye, el pool asociado también se destruye. Así, al trabajar con un objeto, es posible asignar la cantidad necesaria desde el pool correspondiente y no preocuparse por liberar la memoria incluso en caso de errores.

Hilos#

Se recomienda evitar el uso de hilos en Angie porque definitivamente romperá las cosas: la mayoría de las funciones de Angie no son seguras para hilos. Se espera que un hilo ejecute solo llamadas al sistema y funciones de bibliotecas seguras para hilos. Si necesitas ejecutar código que no está relacionado con el procesamiento de solicitudes del cliente, la forma adecuada es programar un temporizador en el manejador del módulo init_process y realizar las acciones requeridas en el manejador del temporizador. Internamente Angie hace uso de threads para acelerar las operaciones relacionadas con IO, pero esto es un caso especial con muchas limitaciones.

Bibliotecas Bloqueantes#

Un error común es usar bibliotecas que bloquean internamente. La mayoría de las bibliotecas disponibles son síncronas y bloqueantes por naturaleza. En otras palabras, realizan una operación a la vez y pierden tiempo esperando la respuesta de otro par. Como resultado, cuando se procesa una solicitud con dicha biblioteca, todo el trabajador de Angie queda bloqueado, destruyendo así el rendimiento. Utiliza solo bibliotecas que proporcionen una interfaz asíncrona y no bloqueen todo el proceso.

Solicitudes HTTP a Servicios Externos#

Frecuentemente los módulos necesitan realizar una llamada HTTP a algún servicio externo. Un error común es usar alguna biblioteca externa, como libcurl, para realizar la solicitud HTTP. Es absolutamente innecesario traer una gran cantidad de código externo (probablemente bloqueante <#using_libraries>!) para la tarea que puede ser realizada por Angie por sí misma.

Hay dos escenarios básicos de uso cuando se necesita una solicitud externa:

  • en el contexto del procesamiento de una solicitud de cliente (por ejemplo, en el manejador de contenido)

  • en el contexto de un proceso de trabajo (por ejemplo, el manejador de temporizador)

En el primer caso, lo mejor es usar API de subsolicitudes. En lugar de acceder directamente al servicio externo, declaras una ubicación en la configuración de Angie y diriges tu subsolicitud a esa ubicación. Esta ubicación no se limita a solicitudes de proxying sino que puede contener otras directivas de Angie. Un ejemplo de ese enfoque es la directiva auth_request implementada en Auth Request.

Para el segundo caso, es posible usar la funcionalidad básica de cliente HTTP disponible en Angie. Por ejemplo, OCSP module implementa un cliente HTTP simple.