Guía del desarrollador#
Convenciones de codificación#
El código fuente sigue la siguiente estructura y convenciones. Las siguientes dos sentencias Además de eso, el código HTTP debe incluir El código de correo debe incluir El código de flujo debe incluir Para fines generales, el código de Angie utiliza dos tipos enteros:
La mayoría de las funciones en Angie devuelven los siguientes códigos: La macro Los valores de Ejemplo usando Para cadenas en C, Angie utiliza un puntero de tipo carácter sin signo
El tipo de cadena de Angie El campo Las operaciones de cadena en Angie se declaran en
Otras funciones de cadena son específicas de Angie: Las siguientes funciones realizan conversión de mayúsculas/minúsculas y comparación: Las siguientes macros simplifican la inicialización de cadenas: Las siguientes funciones de formato admiten tipos específicos de Angie: La lista completa de opciones de formato admitidas por estas funciones se
encuentra en Puede anteponer 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 Hay dos funciones adicionales de conversión numérica.
Al igual que las cuatro primeras, devuelven La interfaz de expresiones regulares en Angie es un envoltorio alrededor
de la biblioteca PCRE.
El archivo de encabezado correspondiente es 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 Después de una compilación exitosa, los campos La expresión regular compilada puede luego usarse para hacer coincidencias con cadenas: Los argumentos de Si hay coincidencias, las capturas pueden accederse de la siguiente manera: La función La estructura La estructura 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: Las macros Para obtener el tiempo explícitamente, use Las siguientes funciones convierten La función Disposición del código#
auto — Scripts de compilaciónsrccore — Tipos y funciones básicas — cadena, arreglo, registro,
pool, etc.event — Núcleo de eventosmodules — Módulos de notificación de eventos:
epoll, kqueue, select
etc.http — Módulo HTTP principal y código comúnmodules — Otros módulos HTTPv2 — HTTP/2mail — Módulos de correoos — Código específico de la plataformaunixwin32stream — Módulos de flujoArchivos de inclusión#
#include deben aparecer al
principio de cada archivo Angie:#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_mail.h>
#include <ngx_stream.h>
Enteros#
ngx_int_t y ngx_uint_t, que son
typedefs para intptr_t y uintptr_t
respectivamente.Códigos de retorno comunes#
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#
ngx_errno devuelve el último código de error del sistema.
Se asigna a errno en plataformas POSIX y a la llamada
GetLastError() en Windows.
La macro ngx_socket_errno devuelve el último número de error de socket.
Al igual que la 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, guárdelo en una variable local
de tipo ngx_err_t.
Para establecer errores, use las macros ngx_set_errno(errno) y
ngx_set_socket_errno(errno).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.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#
u_char *.ngx_str_t se define como sigue:typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
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 (por ejemplo, al analizar la configuración),
se sabe que los objetos ngx_str_t están terminados en nulo, lo que
simplifica la comparación de cadenas y facilita pasarlas a
llamadas al sistema.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()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,
delimitada por dos punteros.ngx_tolower()ngx_toupper()ngx_strlow()ngx_strcasecmp()ngx_strncasecmp()ngx_string(text) — inicializador estático para el
tipo ngx_str_t a partir de la cadena literal C
textngx_null_string — inicializador estático de cadena vacía para el
tipo ngx_str_tngx_str_set(str, text) — inicializa la cadena
str de tipo ngx_str_t * con la literal de cadena C
textngx_str_null(str) — inicializa la cadena str
de tipo ngx_str_t * con la cadena vacíaFormato#
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)src/core/ngx_string.c. Algunas de ellas son:%O — off_t%T — time_t%z — ssize_t%i — ngx_int_t%p — void *%V — ngx_str_t *%s — u_char * (terminada en nulo)%*s — size_t + u_char *u en la mayoría de tipos para hacerlos sin signo.
Para convertir la salida a hexadecimal, use X o x.Conversión numérica#
NGX_ERROR en caso de error.ngx_atoi(line, n) — ngx_int_tngx_atosz(line, n) — ssize_tngx_atoof(line, n) — off_tngx_atotm(line, n) — time_tNGX_ERROR en caso de error.ngx_atofp(line, n, point) — Convierte un número 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 point 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#
src/core/ngx_regex.h.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
captures y
named_captures en la estructura
ngx_regex_compile_t contienen la cantidad de todas las
capturas y capturas con nombre, respectivamente, encontradas en la expresión regular.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);
}
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 size del arreglo.
El tamaño del arreglo captures debe ser un múltiplo de tres, tal como lo exige la
API de PCRE.
En el ejemplo, el tamaño se calcula a partir del número total de capturas más
uno para la propia cadena coincidente.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];
}
ngx_regex_exec_array() acepta un arreglo de elementos
ngx_regex_elt_t (que son simplemente expresiones regulares compiladas con nombres asociados),
una cadena a comparar y un log. La función aplica expresiones del arreglo a la cadena hasta que se encuentre una coincidencia o no queden más expresiones.
El valor de retorno es NGX_OK cuando hay una coincidencia y
NGX_DECLINED en caso contrario, o NGX_ERROR
en caso de error.Tiempo#
ngx_time_t representa el tiempo con tres tipos separados para segundos, milisegundos y el desplazamiento GMT:typedef struct {
time_t sec;
ngx_uint_t msec;
ngx_int_t gmtoff;
} ngx_time_t;
ngx_tm_t es un alias de
struct tm en plataformas UNIX y de SYSTEMTIME
en Windows.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 estándar ISO 8601:
"1970-09-28T12:00:00+06:00"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é.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.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 infijo _libc_)
a struct tm:ngx_gmtime(), ngx_libc_gmtime() — Tiempo expresado como UTCngx_localtime(), ngx_libc_localtime() — Tiempo expresado
relativo a la zona horaria localngx_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 función 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 élngx_array_push_n(a, n)añadenelementos 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)— Insertar un nuevo nodongx_queue_remove(x)— Eliminar un nodo de la colangx_queue_split(h, q, n)— Dividir una cola en un nodo, devolviendo la cola final en una cola separadangx_queue_add(h, n)— Añadir una segunda cola a la primerangx_queue_head(h),ngx_queue_last(h)— Obtener el primer o último nodo de la colangx_queue_sentinel(h)— Obtener un objeto centinela de cola para terminar la iteración en élngx_queue_data(q, type, link)— Obtener 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 proporciona 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 el centinela. 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 al centinela del árbol.
void ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node,
ngx_rbtree_node_t *sentinel)
El recorrido es bastante directo 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, reserva 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);
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 un hash, necesitas conocer el número de elementos que contendrá para que Angie pueda construirlo de forma óptima.
Dos parámetros que deben configurarse son max_size y bucket_size, como se detalla en un documento separado.
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;
key 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 array 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 array de claves hash, utiliza la función
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 función
ngx_hash_init(hinit, key_names, nelts):
ngx_hash_init(&hash, foo_keys.keys.elts, foo_keys.keys.nelts);
La función falla si los parámetros max_size o
bucket_size no son lo suficientemente grandes.
Cuando se construye el hash, usa la función
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 un hash que funcione con comodines, utiliza el tipo
ngx_hash_combined_t.
Incluye el tipo hash descrito arriba y tiene dos arrays 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 normal:
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 arrays 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 arrays de claves: uno para la coincidencia exacta (descrita arriba) y dos más para habilitar la coincidencia desde el inicio o desde el final 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;
}
El array de claves necesita ser ordenado, y los resultados de inicialización deben añadirse
al hash combinado.
La inicialización del array dns_wc_tail se hace de forma similar.
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 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 demalloc()con soporte de registro. Los errores de asignación y la información de depuración se registran enlog.ngx_calloc(size, log)— Asignar memoria desde el heap del sistema comongx_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. Este es un envoltorio alrededor deposix_memalign()en aquellas plataformas que proporcionan esa función. En caso contrario, la implementación recurre angx_alloc()que proporciona el alineamiento máximo.ngx_free(p)— Liberar la memoria asignada. Este es un envoltorio alrededor defree().
Pooling#
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 reenví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 en el pool. Elsizedebe ser al menosNGX_MIN_POOL_SIZEy múltiplo deNGX_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 con 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);
Los enlaces de cadena (ngx_chain_t) se utilizan activamente en Angie,
por lo que la implementación del pool de Angie proporciona una forma de reutilizarlos.
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 función de callback 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 útil. 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 a
ngx_pool_cleanup_add(pool, size), que devuelve un
puntero ngx_pool_cleanup_t para
ser rellenado por el llamador.
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#
Angie utiliza memoria compartida 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 el size
de la zona.
Cada zona compartida debe tener un nombre único.
Si ya existe una entrada de zona compartida con el name y
tag proporcionados, se reutiliza la entrada de zona existente.
La función falla con un error si una entrada existente con el mismo nombre tiene una
etiqueta diferente.
Normalmente, la dirección de la estructura del módulo se pasa como
tag, lo que permite reutilizar zonas compartidas por nombre dentro
de un 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 realdata— Contexto de datos, utilizado para pasar datos arbitrarios al callbackinitnoreuse— Bandera que deshabilita la reutilización de una zona compartida del ciclo antiguotag— Etiqueta de la zona compartidashm— Objeto específico de la plataforma de tipongx_shm_t, que tiene al menos los siguientes campos:addr— Dirección de memoria compartida mapeada, inicialmente NULLsize— Tamaño de la memoria compartidaname— Nombre de la memoria compartidalog— Registro de la memoria compartidaexists— Bandera que indica que la memoria compartida fue heredada del proceso maestro (específico de Windows)
Las entradas de zona compartida se mapean a la memoria real en
ngx_init_cycle() después de que se analiza la configuración.
En sistemas POSIX, se utiliza la llamada al sistema mmap() para crear el
mapeo anónimo compartido.
En Windows, se utiliza el par CreateFileMapping()/
MapViewOfFileEx().
Para asignar memoria compartida, Angie proporciona el slab pool
de tipo ngx_slab_pool_t.
Se crea automáticamente un slab pool para asignar memoria en cada zona compartida de Angie.
El pool se encuentra al principio 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 slab pool 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 asignación. Para tamaños mayores que media página (que normalmente es 2048 bytes), la asignación se realiza de una página completa a la vez.
Para proteger los datos en la memoria compartida del acceso concurrente, utilice el mutex
disponible en el campo mutex de
ngx_slab_pool_t.
Un mutex se utiliza más comúnmente por el slab pool al asignar y liberar
memoria, pero se puede usar para proteger cualquier otra estructura de datos de 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 de 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_EMERGNGX_LOG_ALERTNGX_LOG_CRITNGX_LOG_ERRNGX_LOG_WARNNGX_LOG_NOTICENGX_LOG_INFONGX_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_CORENGX_LOG_DEBUG_ALLOCNGX_LOG_DEBUG_MUTEXNGX_LOG_DEBUG_EVENTNGX_LOG_DEBUG_HTTPNGX_LOG_DEBUG_MAILNGX_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.
Angie proporciona las siguientes macros de registro:
ngx_log_error(level, log, err, fmt, ...)— registro de erroresngx_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), el
ID de conexión (almacenado 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);
El ejemplo 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
Ciclos#
Un objeto de 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 heredado por los workers de Angie cuando se inician.
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 normalmente se elimina después de que el nuevo se crea exitosamente.
Un ciclo es creado por 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 tantos recursos como sea posible del ciclo anterior.
Un ciclo de marcador de posición llamado "init cycle" se crea 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 configura para apuntar anew_logdespués de que se lee la configuración.new_log— Registro del ciclo, creado por la configuración. Se ve afectado por la directivaerror_logde ámbito raíz.connections,connection_n— Array de conexiones de tipongx_connection_t, creado por el módulo de eventos al inicializar cada worker de Angie. La directivaworker_connectionsen la configuración de Angie establece el número de conexionesconnection_n.free_connections,free_connection_n— Lista y número de conexiones actualmente disponibles. Si no hay conexiones disponibles, un worker de Angie rechaza aceptar nuevos clientes o conectarse a servidores upstream.files,files_n— Array para mapear descriptores de archivo a conexiones de Angie. Este mapeo es utilizado por los módulos de eventos que tienen la banderaNGX_USE_FD_EVENT(actualmente, sonpollydevpoll).conf_ctx— Array de configuraciones de módulos principales. Las configuraciones se crean y llenan al leer los archivos de configuración de Angie.modules,modules_n— Array de módulos de tipongx_module_t, tanto estáticos como dinámicos, cargados por la configuración actual.listening— Array de objetos de escucha de tipongx_listening_t. Los objetos de escucha normalmente se agregan mediante la directivalistende diferentes módulos que llaman a la funciónngx_create_listening(). Los sockets de escucha se crean basándose en los objetos de escucha.paths— Array de rutas de tipongx_path_t. Las rutas se agregan llamando a la funciónngx_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 agregar dos manejadores para cada ruta:path loader — Se ejecuta solo una vez en 60 segundos después de iniciar o recargar Angie. Normalmente, el loader lee el directorio y almacena datos en la memoria compartida de Angie. El manejador es llamado desde el proceso dedicado de Angie "cache loader".
path manager — Se ejecuta periódicamente. Normalmente, el manager elimina archivos antiguos del directorio y actualiza la memoria de Angie para reflejar los cambios. El manejador es llamado desde el proceso dedicado "cache manager".
open_files— Lista de objetos de archivo abierto de tipongx_open_file_t, que se crean llamando a la funciónngx_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 en la listaopen_filesy almacena cada descriptor de archivo en el campofddel objeto. Los archivos se abren en modo de anexado y se crean si faltan. Los archivos en la lista son reabiertos por los workers de Angie al recibir la señal de reapertura (más comúnmenteUSR1). En este caso, el descriptor en el campofdcambia a un nuevo valor.shared_memory— Lista de zonas de memoria compartida, cada una agregada llamando a la funciónngx_shared_memory_add(). Las zonas compartidas se mapean al mismo rango de direcciones en todos los procesos de Angie y se utilizan para compartir datos comunes, por ejemplo el árbol en memoria de la caché HTTP.
Buffer#
Para operaciones de entrada/salida, Angie proporciona el tipo de buffer ngx_buf_t.
Normalmente, se utiliza para contener datos que deben escribirse en un destino o leerse desde una fuente.
Un buffer puede hacer referencia a datos en la memoria o en un archivo y, técnicamente, es posible que un buffer haga referencia a ambos al mismo tiempo.
La memoria para el buffer se asigna por separado y no está relacionada con la estructura del buffer ngx_buf_t.
La estructura ngx_buf_t tiene los siguientes campos:
start,end— Los límites del bloque de memoria asignado para el buffer.pos,last— Los límites del buffer de memoria; normalmente un subrango destart..end.file_pos,file_last— Los límites de un buffer de archivo, expresados como desplazamientos desde el inicio del archivo.tag— Valor único usado para distinguir buffers; creado por diferentes módulos de Angie, usualmente con el propósito de reutilización de buffers.file— Objeto de archivo.temporary— Bandera que indica que el buffer hace referencia a memoria escribible.memory— Bandera que indica que el buffer hace referencia a memoria de solo lectura.in_file— Bandera que indica que el buffer hace referencia a datos en un archivo.flush— Bandera que indica que todos los datos anteriores al buffer deben ser vaciados.recycled— Bandera que indica que el buffer puede reutilizarse y debe consumirse lo antes posible.sync— Bandera que indica que el buffer no transporta datos ni señal especial comoflusholast_buf. Por defecto, Angie considera tales buffers como una condición de error, pero esta bandera indica a Angie que omita la comprobación de errores.last_buf— Bandera que indica que el buffer es el último en la salida.last_in_chain— Bandera que indica que no hay más buffers de datos en una solicitud o subsolicitud.shadow— Referencia a otro buffer ("shadow") relacionado con el buffer actual, usualmente en el sentido de que el buffer utiliza datos del shadow. Cuando el buffer se consume, normalmente el buffer shadow también se marca como consumido.last_shadow— Bandera que indica que el buffer es el último que hace referencia a un buffer shadow particular.temp_file— Bandera que indica que el buffer se encuentra en un archivo temporal.
Para operaciones de entrada y salida, los buffers 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 buffer y una referencia al siguiente eslabón de la cadena.
Un ejemplo de uso de buffers 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 socketdata— 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ónngx_connection_local_sockaddr()para obtener la dirección del socket local.proxy_protocol_addr,proxy_protocol_port— Dirección y puerto del cliente del protocolo PROXY, si el protocolo PROXY está habilitado para la conexión.ssl— Contexto SSL para la conexión.reusable— Bandera que indica que la conexión está en un estado que la hace elegible para reutilización.close— Bandera que indica 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 free_connections del ciclo, 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 comoepoll,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 eventoEPOLLRDHUPdeepollo la banderaEV_EOFdekqueue.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 la expiración de eventos de temporización.
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 a
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 a que ocurra 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
readyy se llama al manejador del evento. Para Linux, normalmente se utiliza el manejadorngx_epoll_process_events(), que llama aepoll_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 eventotimedout, la banderatimer_setse 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 Angie, crea ciclos e inicia y controla procesos hijo. No realiza ninguna E/S y solo responde a señales. Su función de ciclo esngx_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 esngx_worker_process_cycle(). Puede haber varios procesos trabajadores, tal como lo configure la directivaworker_processes.NGX_PROCESS_SINGLE— El proceso único, que existe únicamente en el modomaster_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 esngx_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 esngx_cache_manager_process_cycle().
Los procesos de Angie manejan las siguientes señales:
NGX_SHUTDOWN_SIGNAL(SIGQUITen 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 variablengx_quitse establece en1cuando un proceso recibe esta señal, y se restablece inmediatamente después de ser procesada. La variablengx_exitingse establece en1mientras un proceso trabajador está en el estado de apagado.NGX_TERMINATE_SIGNAL(SIGTERMen 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ñalSIGKILLpara 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 variablengx_terminatese establece en1al recibir esta señal.NGX_NOACCEPT_SIGNAL(SIGWINCHen 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 directivadebug_points.NGX_RECONFIGURE_SIGNAL(SIGHUPen 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ñalNGX_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(SIGUSR1en la mayoría de sistemas) — Reabrir archivos. El proceso maestro envía esta señal a los trabajadores, que vuelven a abrir todos losopen_filesrelacionados con el ciclo.NGX_CHANGEBIN_SIGNAL(SIGUSR2en 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 auxiliares.
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.
Threading#
Es posible delegar a un hilo separado tareas que de otro modo bloquearían el proceso worker de Angie. Por ejemplo, Angie puede configurarse para usar hilos para realizar E/S de archivos. Otro caso de uso es una biblioteca que no tiene interfaz asíncrona y por tanto no puede usarse normalmente con Angie. Tenga en cuenta que la interfaz de hilos es una ayuda para el enfoque asíncrono existente de procesamiento de conexiones de cliente, y de ninguna manera está destinada como reemplazo.
Para tratar con la sincronización, están disponibles los siguientes envoltorios sobre
primitivas 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. Pueden configurarse múltiples pools de hilos para diferentes propósitos (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 archivo de cabecera src/core/ngx_thread_pool.h contiene
las 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 tiempo de configuración, un módulo que desee usar hilos debe obtener una
referencia a un pool de hilos llamando a
ngx_thread_pool_add(cf, name), que crea un
nuevo pool de hilos con el name dado o devuelve una referencia
al pool con ese nombre si ya existe.
Para añadir una task a una cola de un pool de hilos especificado
tp en tiempo de ejecución, use la
función ngx_thread_task_post(tp, task).
Para ejecutar una función en un hilo, pase parámetros y configure 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;
/* this function is executed in a separate thread */
}
static void
my_thread_completion(ngx_event_t *ev)
{
my_thread_ctx_t *ctx = ev->data;
/* executed in Angie event loop */
}
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#
Añadir 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 establecer
y acceder a las siguientes variables:
ngx_module_type— Tipo de módulo a construir. Los valores posibles sonCORE,HTTP,HTTP_FILTER,HTTP_INIT_FILTER,HTTP_AUX_FILTER,MAIL,STREAM, oMISC.ngx_module_name— Nombres de módulo. Para construir múltiples módulos desde un conjunto de archivos fuente, especifique 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 en 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 del script configure.ngx_module_srcs— Lista separada por espacios de archivos fuente usados para compilar el módulo. La variable$ngx_addon_dirpuede usarse para representar la ruta al directorio del módulo.ngx_module_incs— Rutas de inclusión requeridas para construir el módulongx_module_deps— Lista separada por espacios de las dependencias del módulo. Normalmente, es la lista de archivos de cabecera.ngx_module_libs— Lista separada por espacios de bibliotecas a enlazar con el módulo. Por ejemplo, usengx_module_libs=-lpthreadpara enlazar la bibliotecalibpthread. Las siguientes macros pueden usarse para enlazar con las mismas bibliotecas que Angie:LIBXSLT,LIBGD,GEOIP,PCRE,OPENSSL,MD5,SHA1,ZLIB, yPERL.ngx_module_link— Variable establecida por el sistema de construcción aDYNAMICpara un módulo dinámico oADDONpara un módulo estático y usada para determinar diferentes acciones a realizar dependiendo del tipo de enlace.ngx_module_order— Orden de carga para el módulo; útil para los tipos de móduloHTTP_FILTERyHTTP_AUX_FILTER. El formato para esta opción es una lista separada por espacios de módulos. Todos los módulos en 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 módulos de filtro, una inicialización posterior significa una ejecución anterior.Los siguientes módulos se usan típicamente como referencias. El
ngx_http_copy_filter_modulelee los datos para otros módulos de filtro y se coloca cerca del final de la lista para que sea uno de los primeros en ejecutarse. Elngx_http_write_filter_moduleescribe los datos al socket del cliente y se coloca cerca del principio de la lista, y es el último en ejecutarse.Por defecto, los módulos de filtro se colocan antes del
ngx_http_copy_filteren la lista de módulos para que el manejador de filtro se ejecute después del manejador del filtro de copia. Para otros tipos de módulo el valor por defecto es la cadena vacía.
Para compilar un módulo en Angie estáticamente, use el
argumento --add-module=/path/to/module del script configure.
Para compilar un módulo para carga dinámica posterior en Angie, use el
argumento --add-dynamic-module=/path/to/module.
Módulos núcleo#
Los módulos son los bloques de construcción de Angie, y la mayor parte de su funcionalidad se
implementa como módulos.
El archivo fuente del módulo debe contener una variable global de tipo
ngx_module_t, que se define de la siguiente manera:
struct ngx_module_s {
/* la parte privada se omite */
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);
/* los stubs para extensiones futuras se omiten */
};
La parte privada omitida incluye la versión del módulo y una firma y se
completa 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
array commands, y puede invocarse en ciertas etapas del
ciclo de vida de Angie.
El ciclo de vida del módulo consiste en los siguientes eventos:
Los manejadores de directivas de configuración se llaman tal como aparecen en los archivos de configuración en el contexto del proceso maestro.
Después de que la configuración se analiza correctamente, el manejador
init_modulese llama en el contexto del proceso maestro. El manejadorinit_modulese llama en el proceso maestro cada vez que se carga una configuración.El proceso maestro crea uno o más procesos worker y el manejador
init_processse llama en cada uno de ellos.Cuando un proceso worker recibe el comando de apagado o terminación del maestro, invoca el manejador
exit_process.El proceso maestro llama al manejador
exit_masterantes de salir.
Debido a que los hilos se usan en Angie solo como una facilidad de E/S suplementaria con su
propia API, los manejadores init_thread y exit_thread
no se llaman actualmente.
Tampoco existe un manejador init_master, porque sería
una sobrecarga innecesaria.
El type del módulo define exactamente qué se almacena en el
campo ctx.
Su valor es uno de los siguientes tipos:
NGX_CORE_MODULENGX_EVENT_MODULENGX_HTTP_MODULENGX_MAIL_MODULENGX_STREAM_MODULE
El NGX_CORE_MODULE es el tipo de módulo más básico y, por lo tanto, el más
genérico y de nivel más bajo.
Los otros tipos de módulos se implementan sobre él y proporcionan una
forma más conveniente de tratar con dominios correspondientes, como el manejo de eventos o solicitudes 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.
El módulo HTTP, el módulo stream, el módulo mail y los módulos de eventos también son módulos núcleo.
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 name es una cadena con el nombre del módulo,
create_conf e 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 e init_conf después de que toda la configuración
se analiza correctamente.
La función típica create_conf asigna memoria para la
configuración y establece valores predeterminados.
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 un array de dichas estructuras
que describe 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 array con el valor especial ngx_null_command.
El name es el nombre de la directiva tal como aparece
en el archivo de configuración, por ejemplo, "worker_processes" o "listen".
El type es un campo de bits de flags que especifican el número de
argumentos que acepta la directiva, su tipo y el contexto en el que aparece.
Los flags son:
NGX_CONF_NOARGS— La directiva no acepta argumentos.NGX_CONF_1MORE— La directiva acepta uno o más argumentos.NGX_CONF_2MORE— La directiva acepta dos o más argumentos.NGX_CONF_TAKE1..NGX_CONF_TAKE7— La directiva acepta 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 aceptar un número diferente de argumentos. Las opciones están limitadas a los números especificados. Por ejemplo,NGX_CONF_TAKE12significa que acepta uno o dos argumentos.
Flags para tipos de directiva:
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 el contenido interno.NGX_CONF_FLAG— La directiva acepta un valor booleano, ya seaonooff.
El contexto de la 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 bloquehttp.NGX_HTTP_SRV_CONF— En un bloqueserverdentro del bloquehttp.NGX_HTTP_LOC_CONF— En un bloquelocationdentro del bloquehttp.NGX_HTTP_UPS_CONF— En un bloqueupstreamdentro del bloquehttp.NGX_HTTP_SIF_CONF— En un bloqueifdentro de un bloqueserveren el bloquehttp.NGX_HTTP_LIF_CONF— En un bloqueifdentro de un bloquelocationen el bloquehttp.NGX_HTTP_LMT_CONF— En un bloquelimit_exceptdentro del bloquehttp.NGX_STREAM_MAIN_CONF— En el bloquestream.NGX_STREAM_SRV_CONF— En un bloqueserverdentro del bloquestream.NGX_STREAM_UPS_CONF— En un bloqueupstreamdentro del bloquestream.NGX_MAIL_MAIN_CONF— En el bloquemail.NGX_MAIL_SRV_CONF— En un bloqueserverdentro del bloquemail.NGX_EVENT_CONF— En el bloqueevents.NGX_DIRECT_CONF— Utilizado por módulos que no crean una jerarquía de contextos y tienen solo una configuración global única. Esta configuración se pasa al manejador como el argumentoconf.
El analizador de configuración utiliza estos flags para generar un error ante una directiva mal ubicada y llama a los manejadores de directivas suministrados con el puntero de configuración apropiado, de modo que las mismas directivas en diferentes ubicaciones puedan almacenar sus valores en ubicaciones distintas.
El campo set define un manejador que procesa la directiva
y almacena los valores analizados en la configuración correspondiente.
Existen varias funciones que realizan conversiones comunes:
ngx_conf_set_flag_slot— Convierte las cadenas literalesonyoffen un valorngx_flag_tcon valores 1 o 0, respectivamente.ngx_conf_set_str_slot— Almacena una cadena como un valor del tipongx_str_t.ngx_conf_set_str_array_slot— Añade un valor a un arrayngx_array_tde cadenasngx_str_t. El array se crea si aún no existe.ngx_conf_set_keyval_slot— Añade un par clave-valor a un arrayngx_array_tde pares clave-valorngx_keyval_t. La primera cadena se convierte en la clave y la segunda en el valor. El array se crea si aún no existe.ngx_conf_set_num_slot— Convierte un argumento de la directiva a un valorngx_int_t.ngx_conf_set_size_slot— Convierte un tamaño a un valorsize_texpresado en bytes.ngx_conf_set_off_slot— Convierte un desplazamiento a un valoroff_texpresado en bytes.ngx_conf_set_msec_slot— Convierte un tiempo a un valorngx_msec_texpresado en milisegundos.ngx_conf_set_sec_slot— Convierte un tiempo a un valortime_texpresado en segundos.ngx_conf_set_bufs_slot— Convierte los dos argumentos suministrados en un objetongx_bufs_tque contiene el número y tamaño de búferes.ngx_conf_set_enum_slot— Convierte el argumento suministrado a un valorngx_uint_t. El array terminado en null dengx_conf_enum_tpasado en el campopostdefine las cadenas aceptables y los correspondientes valores enteros.ngx_conf_set_bitmask_slot— Convierte los argumentos suministrados a un valorngx_uint_t. Los valores de máscara para cada argumento se combinan con OR produciendo el resultado. El array terminado en null dengx_conf_bitmask_tpasado en el campopostdefine las cadenas aceptables y los correspondientes valores de máscara.ngx_conf_set_path_slot— Convierte los argumentos suministrados a un valorngx_path_ty realiza todas las inicializaciones necesarias. Para más detalles, consulte la documentación de la directiva proxy_temp_path.ngx_conf_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 la directiva.
Los módulos principales solo tienen la configuración global y establecen el
flag 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 los ámbitos server,
location e if.
NGX_HTTP_MAIN_CONF_OFFSET— Configuración para el bloquehttp.NGX_HTTP_SRV_CONF_OFFSET— Configuración para un bloqueserverdentro del bloquehttp.NGX_HTTP_LOC_CONF_OFFSET— Configuración para un bloquelocationdentro del bloquehttp.NGX_STREAM_MAIN_CONF_OFFSET— Configuración para el bloquestream.NGX_STREAM_SRV_CONF_OFFSET— Configuración para un bloqueserverdentro del bloquestream.NGX_MAIL_MAIN_CONF_OFFSET— Configuración para el bloquemail.NGX_MAIL_SRV_CONF_OFFSET— Configuración para un bloqueserverdentro del bloquemail.
El campo offset define el desplazamiento de un campo en la estructura de configuración de un módulo
que contiene los valores para esta directiva en particular.
El uso típico es emplear la macro offsetof().
El campo post tiene dos propósitos: puede utilizarse para definir
un manejador que se llamará 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 necesita
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 objeto ngx_conf_post_t
en sí mismo, y data es un puntero al valor,
convertido desde los argumentos por el manejador principal con el tipo apropiado.
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 objetongx_connection_tpara envolver el socket de cliente recién aceptado. Cada listener de Angie proporciona un manejador para pasar el nuevo objeto de conexión. Para conexiones HTTP esngx_http_init_connection(c).ngx_http_init_connection()realiza la inicialización temprana de la conexión HTTP. En esta etapa se crea un objetongx_http_connection_tpara la conexión y su referencia se almacena en el campodatade la conexión. Posteriormente será reemplazado por un objeto de solicitud HTTP. En esta etapa también se inician el analizador del protocolo PROXY y el handshake SSL.El manejador de eventos de lectura
ngx_http_wait_request_handler()se llama cuando hay datos disponibles en el socket del cliente. En esta etapa se crea un objeto de solicitud HTTPngx_http_request_ty se establece en el campodatade la conexión.El manejador de eventos de lectura
ngx_http_process_request_line()lee la línea de solicitud del cliente. El manejador es establecido porngx_http_wait_request_handler(). Los datos se leen en elbufferde la conexión. El tamaño del búfer se establece inicialmente mediante la directiva client_header_buffer_size. Se supone que todo el encabezado del cliente debe caber en el búfer. Si el tamaño inicial no es suficiente, se asigna un búfer más grande, con la capacidad establecida por la directiva large_client_header_buffers.El manejador de eventos de lectura
ngx_http_process_request_headers(), se establece después dengx_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 se ha leído y analizado completamente. Esta función ejecuta las fases de la solicitud desdeNGX_HTTP_POST_READ_PHASEhastaNGX_HTTP_CONTENT_PHASE. La última fase está destinada a generar una respuesta y pasarla a lo largo de la cadena de filtros. La respuesta no se envía necesariamente al cliente en esta fase. Puede permanecer en búfer y enviarse en la etapa de finalización.ngx_http_finalize_request()normalmente se llama cuando la solicitud ha generado toda la salida o ha producido un error. En este último caso se busca una página de error apropiada y se usa como respuesta. Si la respuesta no se ha enviado completamente al cliente en este punto, se activa un escritor HTTPngx_http_writer()para terminar de enviar los datos pendientes.ngx_http_finalize_connection()se llama cuando la respuesta completa se ha enviado al cliente y la solicitud puede destruirse. Si la función keepalive de la conexión del cliente está habilitada, se llama angx_http_set_keepalive(), 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 del 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 clientengx_connection_t. Varias solicitudes pueden referenciar el mismo objeto de conexión al mismo tiempo: una solicitud principal y sus subsolicitudes. Después de que se elimina una solicitud, se puede crear una nueva solicitud en la misma conexión.Tenga en cuenta que para las conexiones HTTP el campo
datadengx_connection_tapunta de vuelta a la solicitud. Tales solicitudes se denominan activas, en contraposición a las otras solicitudes vinculadas a la conexión. Una solicitud activa se utiliza para manejar eventos de conexión del cliente y se le permite enviar su respuesta al cliente. Normalmente, cada solicitud se vuelve activa en algún momento para poder enviar su salida.ctx— Array de contextos de módulos HTTP. Cada módulo de tipoNGX_HTTP_MODULEpuede almacenar cualquier valor (normalmente, un puntero a una estructura) en la solicitud. El valor se almacena en el arrayctxen la posiciónctx_indexdel módulo. Las siguientes macros proporcionan una forma conveniente de obtener y establecer contextos de solicitud:ngx_http_get_module_ctx(r, module)— Devuelve el contexto delmodulengx_http_set_ctx(r, c, module)— Establececcomo el contexto delmodule
main_conf,srv_conf,loc_conf— Arrays de configuraciones de la solicitud actual. Las configuraciones se almacenan en las posicionesctx_indexdel módulo.read_event_handler,write_event_handler- Manejadores de eventos de lectura y escritura para la solicitud. Normalmente, tanto el manejador de eventos de lectura como el de escritura para una conexión HTTP se establecen enngx_http_request_handler(). Esta función llama a los manejadoresread_event_handlerywrite_event_handlerpara la solicitud activa actual.cache— Objeto de caché de solicitud para almacenar en caché la respuesta upstream.upstream— Objeto upstream de solicitud para proxy.pool— Pool de solicitud. El objeto de solicitud en sí se asigna en este pool, que se destruye cuando se elimina la solicitud. Para asignaciones que necesitan estar disponibles durante toda la vida de la conexión del cliente, use el pool dengx_connection_ten 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 campoheadersde tipongx_list_tpara mantener la lista sin procesar de encabezados. Además de eso, los encabezados específicos están disponibles para obtener y establecer como campos separados, por ejemplocontent_length_n,status, etc.request_body— Objeto de cuerpo de solicitud del cliente.start_sec,start_msec— Punto temporal en el que se creó la solicitud, utilizado para rastrear la duración de la solicitud.method,method_name— Representación numérica y de texto del método de solicitud HTTP del cliente. Los valores numéricos para los métodos se definen ensrc/http/ngx_http_request.hcon las macrosNGX_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 del protocolo HTTP del cliente en forma numérica dividida 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í puede diferir del URI original enviado por el cliente debido a la normalización. Durante el 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, en contraposición a las subsolicitudes, que se crean para realizar una subtarea específica dentro de la solicitud principal.parent— Puntero a la solicitud padre de una subsolicitud.postponed— Lista de buffers de salida y subsolicitudes, en el orden en que se envían y crean. La lista es utilizada por el filtro postpone para proporcionar una salida de solicitud consistente cuando partes de ella son creadas por subsolicitudes.post_subrequest— Puntero a un manejador con el contexto que se llamará cuando una subsolicitud se finalice. No se utiliza para solicitudes principales.posted_requests— Lista de solicitudes que se iniciarán o reanudarán, lo cual se hace llamando alwrite_event_handlerde la solicitud. Normalmente, este manejador contiene la función principal de la solicitud, que al principio ejecuta las fases de la solicitud y luego produce la salida.Una solicitud normalmente se publica mediante la llamada
ngx_http_post_request(r, NULL). Siempre se publica en la listaposted_requestsde la solicitud principal. La funciónngx_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 angx_http_run_posted_requests, lo que puede conducir 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 regex 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 de map, búsqueda de servidor por SNI o Host HTTP, rewrite, proxy_redirect, etc. Las capturas producidas por una búsqueda se almacenan en los campos mencionados anteriormente. El camponcapturescontiene el número de capturas,capturescontiene los límites de las capturas ycaptures_datacontiene la cadena contra la cual se comparó la regex 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. El campo solo tiene sentido para la solicitud principal. Aumentar el contador se hace mediante un simpler->main->count++. Para disminuir el contador, llame angx_http_finalize_request(r, rc). La creación de una subsolicitud y la ejecución del proceso de lectura del cuerpo de la solicitud incrementan el contador.subrequests— Nivel de anidamiento de subsolicitud actual. Cada subsolicitud hereda el nivel de anidamiento de su padre, disminuido en uno. Se genera un error si el valor llega a cero. El valor para la solicitud principal se define mediante la constanteNGX_HTTP_MAX_SUBREQUESTS.uri_changes— Número de cambios de URI restantes para la solicitud. El número total de veces que una solicitud puede cambiar su URI está limitado por la constanteNGX_HTTP_MAX_URI_CHANGES. Con cada cambio, el valor se disminuye 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 en la solicitud. Mientras este valor sea distinto de cero, la solicitud no puede finalizarse. Actualmente, este valor se incrementa por operaciones AIO pendientes (POSIX AIO y operaciones de hilos) y bloqueos de caché activos.buffered— Máscara de bits que muestra qué módulos tienen salida almacenada en buffer producida por la solicitud. Varios filtros pueden almacenar en buffer la salida; por ejemplo, sub_filter puede almacenar datos en buffer debido a una coincidencia parcial de cadena, el filtro copy puede almacenar datos en buffer debido a la falta de buffers de salida libres, etc. Mientras este valor sea distinto de cero, la solicitud no se finaliza, esperando un vaciado.header_only— Bandera que indica que la salida no requiere un cuerpo. Por ejemplo, esta bandera se utiliza en las solicitudes HTTP HEAD.keepalive— Bandera que indica si se admite keepalive de conexión del cliente. El valor se infiere de la versión HTTP y del valor del encabezado "Connection".header_sent— Bandera que indica que el encabezado de salida ya ha sido enviado por la solicitud.internal— Bandera que indica 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— Bandera que indica que se puede enviar una respuesta parcial al cliente, según lo solicitado por el encabezado HTTP Range.subrequest_ranges— Bandera que indica que se puede enviar una respuesta parcial mientras se procesa una subsolicitud.single_range— Bandera que indica que solo se puede enviar un único rango continuo de datos de salida al cliente. Esta bandera generalmente se establece cuando se envía un flujo de datos, por ejemplo, desde un servidor proxy, y la respuesta completa no está disponible en un solo buffer.main_filter_need_in_memory,filter_need_in_memory— Banderas que solicitan que la salida se produzca en buffers de memoria pero no en archivos. Esta es una señal para el filtro copy de leer datos de buffers de archivo incluso si sendfile está habilitado. La diferencia entre las dos banderas 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 establecenfilter_need_in_memory, solicitando que solo la salida de la solicitud actual entre en buffers de memoria. Los filtros llamados más tarde en la cadena de filtros establecenmain_filter_need_in_memory, solicitando que tanto la solicitud principal como todas las subsolicitudes lean archivos en memoria al enviar la salida.filter_need_temporary— Bandera que solicita que la salida de la solicitud se produzca en buffers temporales, pero no en buffers de memoria de solo lectura o buffers de archivo. Esto es utilizado por filtros que pueden cambiar la salida directamente en los buffers donde se envía.
Configuración de módulos HTTP#
Cada módulo HTTP puede tener tres tipos de configuración:
Configuración principal — Se aplica a todo el bloque
http. Sirve como configuración global para el módulo.Configuración de servidor — Se aplica a un único bloque
server. Sirve como configuración específica del servidor para el módulo.Configuración de ubicación — Se aplica a un único bloque
location,ifolimit_except. Sirve como configuración específica de ubicación para el módulo.
Las estructuras de configuración se crean en la etapa de configuración de Angie
llamando a 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 un parámetro, 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, las configuraciones de servidor y ubicación no solo existen en los niveles
de servidor y ubicación, sino que también se crean para todos los niveles superiores.
Específicamente, una configuración de servidor también se crea en el nivel principal, y
las configuraciones de ubicación se crean en los niveles principal, de servidor y de ubicación.
Estas configuraciones permiten especificar parámetros específicos de servidor y ubicación
en cualquier nivel de un archivo de configuración de Angie.
Finalmente, las configuraciones se fusionan hacia abajo.
Se proporcionan varias macros, como NGX_CONF_UNSET y
NGX_CONF_UNSET_UINT, para indicar un parámetro faltante
e ignorarlo durante la fusión.
Las macros de fusión estándar de Angie, como ngx_conf_merge_value() y
ngx_conf_merge_uint_value(), proporcionan una forma conveniente de
fusionar un parámetro y establecer el valor predeterminado si ninguna de las configuraciones
proporcionó un valor explícito.
Para obtener una lista completa de macros para diferentes tipos, consulte
src/core/ngx_conf_file.h.
Las siguientes macros están disponibles
para acceder a la configuración de módulos HTTP en tiempo de configuración.
Todas 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 HTTP core estándar
y reemplaza el manejador de contenido de ubicación almacenado
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 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 de servidor puede cambiar de la predeterminada después de
elegir un servidor virtual para una 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 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);
...
}
Fases#
Cada petición HTTP pasa por una secuencia de fases. En cada fase se realiza un tipo distinto de procesamiento sobre la petición. Los manejadores específicos de 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 invocados en una etapa específica del procesamiento de la petición. Las fases se procesan sucesivamente y los manejadores de fase se llaman una vez que la petición alcanza 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 habilitar la sustitución de direcciones de cliente antes de que se invoque cualquier otro módulo.NGX_HTTP_SERVER_REWRITE_PHASE— Fase donde se procesan las directivas de reescritura definidas en un bloqueserver(pero fuera de un bloquelocation). El módulo Rewrite instala su manejador en esta fase.NGX_HTTP_FIND_CONFIG_PHASE— Fase especial donde se elige una ubicación basándose en el URI de la petición. Antes de esta fase, la ubicación predeterminada para el servidor virtual relevante se asigna a la petición, y cualquier módulo que solicite una configuración de ubicación recibe la configuración para la ubicación del servidor predeterminado. Esta fase asigna una nueva ubicación a la petición. No se pueden registrar manejadores adicionales en esta fase.NGX_HTTP_REWRITE_PHASE— Igual queNGX_HTTP_SERVER_REWRITE_PHASE, pero para reglas de reescritura definidas en la ubicación elegida en la fase anterior.NGX_HTTP_POST_REWRITE_PHASE— Fase especial donde la petición se redirige a una nueva ubicación si su URI cambió durante una reescritura. Esto se implementa haciendo que la petición pase por laNGX_HTTP_FIND_CONFIG_PHASEnuevamente. No se pueden registrar manejadores adicionales en esta fase.NGX_HTTP_PREACCESS_PHASE— Una fase común para diferentes 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 donde se verifica que el cliente esté autorizado para realizar la petición. Los módulos estándar de Angie como Access y Auth Basic registran sus manejadores en esta fase. Por defecto, el cliente debe pasar la verificación de autorización de todos los manejadores registrados en esta fase para que la petición continúe a la siguiente fase. La directiva satisfy puede usarse para permitir que el procesamiento continúe si alguno de los manejadores de fase autoriza al cliente.NGX_HTTP_POST_ACCESS_PHASE— Fase especial donde se procesa la directiva satisfy. Si algunos manejadores de fase de acceso denegaron el acceso y ninguno lo permitió explícitamente, la petición se finaliza. No se pueden registrar manejadores adicionales en esta fase.NGX_HTTP_PRECONTENT_PHASE— Fase para manejadores que se llaman antes de generar contenido. Los módulos estándar como try_files y Mirror registran sus manejadores en esta fase.NGX_HTTP_CONTENT_PHASE— Fase donde normalmente se genera la respuesta. Múltiples módulos estándar de Angie registran sus manejadores en esta fase, incluyendo Index. Se llaman secuencialmente hasta que uno de ellos produce la salida. También es posible establecer manejadores de contenido por ubicación. Si la configuración de ubicación del módulo Módulo HTTP tienehandlerestablecido, se llama como el manejador de contenido y los manejadores instalados en esta fase se ignoran.NGX_HTTP_LOG_PHASE— Fase donde se realiza el registro de la petición. Actualmente, solo el módulo Log registra su manejador en esta etapa para el registro de acceso. Los manejadores de fase de registro se llaman al final del procesamiento de la petición, justo antes de liberar la petición.
A continuación se muestra un ejemplo de un manejador de fase de 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;
}
Se espera que los manejadores de fase devuelvan códigos específicos:
NGX_OK— proceder a la siguiente fase.NGX_DECLINED— proceder al siguiente manejador de la fase actual. Si el manejador actual es el último en la fase actual, pasar a la siguiente fase.NGX_AGAIN,NGX_DONE— suspender el manejo de fase hasta algún evento futuro, que podría ser una operación de E/S asíncrona o simplemente un retraso, por ejemplo. Se asume que el manejo de fase se reanudará más tarde llamando angx_http_core_run_phases().Cualquier otro valor devuelto por el manejador de fase se trata como un código de finalización de petición, en particular, un código de respuesta HTTP. La petición se finaliza con el código proporcionado.
Para algunas fases, los códigos de retorno se tratan de 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 ubicación se considera
un código de finalización.
En la fase de acceso, en
modo satisfy any,
devolver un código distinto de NGX_OK,
NGX_DECLINED, NGX_AGAIN,
NGX_DONE se considera una denegación.
Si ningún manejador de acceso posterior permite o deniega el acceso con un código
diferente, el código de denegación se convertirá en el código de finalización.
Ejemplos#
El repositorio nginx-dev-examples proporciona ejemplos de módulos nginx adecuados para Angie también.
Estilo de código#
Reglas generales#
el ancho máximo de texto es de 80 caracteres
la sangría es de 4 espacios
sin tabulaciones, sin espacios finales
los elementos de lista en la misma línea se separan con espacios
los literales hexadecimales están en minúsculas
los nombres de archivos, funciones y tipos, y las variables globales tienen el prefijo
ngx_o un prefijo más específico comongx_http_yngx_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 copyright
inclusiones
definiciones de preprocesador
definiciones de tipos
prototipos de funciones
definiciones de variables
definiciones de funciones
Las declaraciones de copyright 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 al principio.
Los archivos ngx_config.h y ngx_core.h
siempre se incluyen primero, seguidos de uno de
ngx_http.h, ngx_stream.h,
o ngx_mail.h.
Luego siguen los 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 llamada "protección de cabecera":
#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_
#define _NGX_PROCESS_CYCLE_H_INCLUDED_
...
#endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */
Comentarios#
no se usan comentarios
//el texto está en inglés, se prefiere la ortografía americana
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 misma alineación entre diferentes estructuras en el archivo.
Una estructura que apunta a sí misma tiene un 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 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 la 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 en 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. Un asterisco dentro de un 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 casteo. 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 se separa de la condición
por un espacio.
La llave de apertura se coloca en la misma línea, o en una
línea dedicada si la condición ocupa varias líneas.
La llave de cierre se coloca en una línea dedicada, opcionalmente seguida de
else if / else.
Normalmente, hay una línea vacía 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;
}
}
Reglas de formato similares se aplican a los bucles do y
while:
while (p < last && *p == ' ') {
p++;
}
while (p < last && *p == ' ') {
p++;
}
do {
ctx->node = rn;
ctx = ctx->next;
} while (ctx);
La palabra clave switch se separa de la condición
por un espacio.
La llave de apertura se coloca en la misma línea.
La llave de cierre se coloca 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 de la siguiente manera:
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 mediante el comentario /* void */:
for (i = 0; /* void */ ; i++) {
...
}
Un bucle con un cuerpo vacío también se indica mediante el
comentario /* void */ que puede colocarse en la misma línea:
for (cl = *busy; cl->next; cl = cl->next) { /* void */ }
Un bucle infinito tiene este aspecto:
for ( ;; ) {
...
}
Etiquetas#
Las etiquetas están rodeadas de líneas vacías 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, puede
usar AddressSanitizer
(ASan), compatible con algunos compiladores modernos.
Para habilitar ASan con gcc y clang,
use la opción de compilador y enlazador -fsanitize=address.
Al compilar Angie, esto se puede hacer añadiendo la opción a
los parámetros --with-cc-opt y --with-ld-opt
del script configure.
Dado que la mayoría de las asignaciones en Angie se realizan desde el
pool interno de Angie, habilitar ASan puede no ser siempre suficiente para depurar
problemas de memoria.
El pool interno asigna un gran fragmento de memoria del sistema y corta
asignaciones más pequeñas de él.
Sin embargo, este mecanismo se puede deshabilitar estableciendo la
macro NGX_DEBUG_PALLOC en 1.
En este caso, las asignaciones se pasan directamente al asignador del sistema, dándole
control total sobre los límites del búfer.
La siguiente línea de configuración resume la información proporcionada anteriormente. Se recomienda al desarrollar módulos de terceros y 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, su tarea se puede lograr creando una configuración adecuada. Si escribir un módulo es inevitable, intente hacerlo lo más pequeño y simple posible. Por ejemplo, un módulo solo puede exportar algunas variables.
Antes de comenzar un módulo, considere las siguientes preguntas:
¿Es posible implementar una característica deseada usando módulos ya disponibles?
¿Es posible resolver un problema usando lenguajes de scripting integrados, como Perl o NJS?
Cadenas en C#
El tipo de cadena más utilizado en Angie,
ngx_str_t, no es una cadena terminada en cero
al estilo de C.
No puede pasar los datos a funciones estándar de la biblioteca de C
como strlen() o strstr().
En su lugar, se deben usar las contrapartes de Angie
que aceptan 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 como resultado del
análisis del archivo de configuración están terminadas en cero.
Variables globales#
Evite usar variables globales en sus módulos. Lo más probable es que sea un error tener una variable global. Cualquier dato global debe estar vinculado a un ciclo de configuración y asignarse desde el pool de memoria correspondiente. Esto permite a Angie realizar recargas de configuración elegantes. 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 especial atención para gestionar la reconfiguración adecuadamente. Además, verifique si las bibliotecas utilizadas por su código tienen un estado global implícito que pueda romperse al recargar.
Gestión manual de memoria#
En lugar de lidiar con el enfoque malloc/free que es propenso a errores, aprenda a usar los pools de Angie. Un pool se crea y se vincula a un objeto: configuración, ciclo, conexión <#http_connection>, o solicitud HTTP. Cuando el objeto se destruye, el pool asociado también se destruye. Así que al trabajar con un objeto, es posible asignar la cantidad necesaria desde el pool correspondiente y no preocuparse por liberar memoria incluso en caso de errores.
Hilos#
Se recomienda evitar el uso de hilos en Angie porque definitivamente romperá cosas: la mayoría de las funciones de Angie no son seguras para hilos.
Se espera que un hilo solo ejecute llamadas al sistema y
funciones de biblioteca seguras para hilos.
Si necesita ejecutar algún código que no esté relacionado con el procesamiento de solicitudes de clientes,
la forma adecuada es programar un temporizador en el manejador de módulo init_process
y realizar las acciones requeridas en el manejador del temporizador.
Internamente, Angie hace uso de hilos para
mejorar las operaciones relacionadas con E/S, pero este es un caso especial con muchas
limitaciones.
Bibliotecas bloqueantes#
Un error común es usar bibliotecas que son bloqueantes 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 del otro extremo. Como resultado, cuando se procesa una solicitud con dicha biblioteca, todo el worker de Angie se bloquea, destruyendo así el rendimiento. Use solo bibliotecas que proporcionen una interfaz asíncrona y no bloqueen todo el proceso.
Solicitudes HTTP a servicios externos#
A menudo, 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!) para la tarea que puede ser realizada por el propio Angie.
Hay dos escenarios de uso básicos cuando se necesita una solicitud externa:
en el contexto del procesamiento de una solicitud de cliente (por ejemplo, en un manejador de contenido)
en el contexto de un proceso worker (por ejemplo, manejador de temporizador)
En el primer caso, lo mejor es usar la API de subrequests. En lugar de acceder directamente al servicio externo, declare una ubicación en la configuración de Angie y dirija su subrequest a esta ubicación. Esta ubicación no se limita a hacer proxy de solicitudes, sino que puede contener otras directivas de Angie. Un ejemplo de este 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, el módulo OCSP implementa un cliente HTTP simple.