Desenvolvimento#

Angie é um projeto de código aberto que recebe bem todos os contribuidores.

Código-Fonte#

Você pode clonar o código-fonte do Angie de nossos repositórios públicos: Mercurial, Git.

Estilo de Codificação#

Suas alterações devem ser consistentes com o resto do código do Angie; as convenções de codificação são um bom ponto de partida.

Dica

Em caso de dúvida, examine o código próximo para seguir seu exemplo, ou simplesmente use grep na base de código para inspiração.

Mensagens de Commit#

Historicamente, o log de commits é mantido em inglês.

Comece com um resumo de uma linha do que foi feito. Pode ter um prefixo que o log de commits usa para a porção de código afetada. O resumo pode ter até 67 caracteres e pode ser seguido por uma linha em branco e mais detalhes.

Uma boa mensagem conta o que causou a mudança, o que foi feito sobre isso, e qual é a situação agora:

API: bad things removed, good things added.

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

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

[1] https://example.com

Detalhes que podem passar despercebidos:

  • O resumo termina com um ponto e começa com letra maiúscula.

  • Se um prefixo é usado, é seguido por uma letra minúscula.

  • Espaço duplo separa frases dentro de uma única linha.

Verificações Finais#

  • Faça o seu melhor para verificar que as alterações funcionam em todas as plataformas alvo.

  • Para cada plataforma, execute a suíte de testes para garantir que não há regressão:

    $ cd tests
    $ prove .
    

    Veja o arquivo tests/README para detalhes.

  • Certifique-se de que está confortável com os termos legais.

Enviando Contribuições#

Para enviar um patch, crie um pull request em nosso espelho do GitHub.

Para perguntas e sugestões, entre em contato com os desenvolvedores via GitHub Issues.

Convenções de codificação#

O código-fonte segue a seguinte estrutura e convenções.

Layout do código#

  • auto — Scripts de build

  • src

    • core — Tipos e funções básicas — string, array, log, pool, etc.

    • event — Núcleo de eventos

      • modules — Módulos de notificação de eventos: epoll, kqueue, select etc.

    • http — Módulo HTTP principal e código comum

      • modules — Outros módulos HTTP

      • v2 — HTTP/2

    • mail — Módulos de mail

    • os — Código específico da plataforma

      • unix

      • win32

    • stream — Módulos de stream

Arquivos de inclusão#

As duas declarações #include a seguir devem aparecer no início de todo arquivo Angie:

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

Além disso, código HTTP deve incluir

#include <ngx_http.h>

Código de mail deve incluir

#include <ngx_mail.h>

Código de stream deve incluir

#include <ngx_stream.h>

Inteiros#

Para propósitos gerais, o código Angie usa dois tipos de inteiros: ngx_int_t e ngx_uint_t, que são typedefs para intptr_t e uintptr_t respectivamente.

Códigos de retorno comuns#

A maioria das funções no Angie retorna os seguintes códigos:

  • NGX_OK — Operação bem-sucedida.

  • NGX_ERROR — Operação falhou.

  • NGX_AGAIN — Operação incompleta; chame a função novamente.

  • NGX_DECLINED — Operação rejeitada, por exemplo, porque está desabilitada na configuração. Isso nunca é um erro.

  • NGX_BUSY — Recurso não está disponível.

  • NGX_DONE — Operação completa ou continuada em outro lugar. Também usado como um código de sucesso alternativo.

  • NGX_ABORT — Função foi abortada. Também usado como um código de erro alternativo.

Tratamento de erros#

A macro ngx_errno retorna o último código de erro do sistema. Ela é mapeada para errno em plataformas POSIX e para a chamada GetLastError() no Windows. A macro ngx_socket_errno retorna o último número de erro de socket. Como a macro ngx_errno, ela é mapeada para errno em plataformas POSIX. Ela é mapeada para a chamada WSAGetLastError() no Windows. Acessar os valores de ngx_errno ou ngx_socket_errno mais de uma vez seguida pode causar problemas de desempenho. Se o valor do erro pode ser usado múltiplas vezes, armazene-o em uma variável local do tipo ngx_err_t. Para definir erros, use as macros ngx_set_errno(errno) e ngx_set_socket_errno(errno).

Os valores de ngx_errno e ngx_socket_errno podem ser passados para as funções de log ngx_log_error() e ngx_log_debugX(), em que caso o texto de erro do sistema é adicionado à mensagem de log.

Exemplo usando ngx_errno:

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

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

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

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

        return 1;
    }

    return 0;
}

Strings#

Visão geral#

Para strings C, o Angie usa o ponteiro de tipo de caractere não assinado u_char *.

O tipo de string do Angie ngx_str_t é definido como segue:

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

O campo len contém o comprimento da string e data contém os dados da string. A string, mantida em ngx_str_t, pode ou não ser terminada em null após os len bytes. Na maioria dos casos não é. No entanto, em certas partes do código (por exemplo, ao analisar configuração), objetos ngx_str_t são conhecidos por serem terminados em null, o que simplifica a comparação de strings e torna mais fácil passar as strings para syscalls.

As operações de string no Angie são declaradas em src/core/ngx_string.h. Algumas delas são wrappers em torno de funções C padrão:

  • ngx_strcmp()

  • ngx_strncmp()

  • ngx_strstr()

  • ngx_strlen()

  • ngx_strchr()

  • ngx_memcmp()

  • ngx_memset()

  • ngx_memcpy()

  • ngx_memmove()

Outras funções de string são específicas do Angie

  • ngx_memzero() — Preenche memória com zeros.

  • ngx_explicit_memzero() — Faz o mesmo que ngx_memzero(), mas esta chamada nunca é removida pela otimização de eliminação de armazenamento morto do compilador. Esta função pode ser usada para limpar dados sensíveis como senhas e chaves.

  • ngx_cpymem() — Faz o mesmo que ngx_memcpy(), mas retorna o endereço de destino final Esta é útil para anexar múltiplas strings em sequência.

  • ngx_movemem() — Faz o mesmo que ngx_memmove(), mas retorna o endereço de destino final.

  • ngx_strlchr() — Procura por um caractere em uma string, delimitada por dois ponteiros.

As seguintes funções realizam conversão de caso e comparação:

  • ngx_tolower()

  • ngx_toupper()

  • ngx_strlow()

  • ngx_strcasecmp()

  • ngx_strncasecmp()

As seguintes macros simplificam a inicialização de strings:

  • ngx_string(text) — inicializador estático para o tipo ngx_str_t a partir do literal de string C text

  • ngx_null_string — inicializador de string vazia estático para o tipo ngx_str_t

  • ngx_str_set(str, text) — inicializa string str do tipo ngx_str_t * com o literal de string C text

  • ngx_str_null(str) — inicializa string str do tipo ngx_str_t * com a string vazia

Formatação#

As seguintes funções de formatação suportam tipos específicos do Angie:

  • ngx_sprintf(buf, fmt, ...)

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

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

  • ngx_vslprintf(buf, last, fmt, args)

  • ngx_vsnprintf(buf, max, fmt, args)

A lista completa de opções de formatação, suportadas por essas funções está em src/core/ngx_string.c. Algumas delas são:

  • %Ooff_t

  • %Ttime_t

  • %zssize_t

  • %ingx_int_t

  • %pvoid *

  • %Vngx_str_t *

  • %su_char * (terminado em null)

  • %*ssize_t + u_char *

Você pode antepor u na maioria dos tipos para torná-los não assinados. Para converter saída para hex, use X ou x.

Conversão numérica#

Várias funções para conversão numérica são implementadas no Angie. As primeiras quatro convertem cada uma uma string de comprimento dado para um inteiro positivo do tipo indicado. Elas retornam NGX_ERROR em caso de erro.

  • ngx_atoi(line, n)ngx_int_t

  • ngx_atosz(line, n)ssize_t

  • ngx_atoof(line, n)off_t

  • ngx_atotm(line, n)time_t

Há duas funções adicionais de conversão numérica. Como as primeiras quatro, elas retornam NGX_ERROR em caso de erro.

  • ngx_atofp(line, n, point) — Converte um número de ponto fixo de comprimento dado para um inteiro positivo do tipo ngx_int_t. O resultado é deslocado para a esquerda por point posições decimais. A representação em string do número deve ter no máximo points dígitos fracionários. Por exemplo, ngx_atofp("10.5", 4, 2) retorna 1050.

  • ngx_hextoi(line, n) — Converte uma representação hexadecimal de um inteiro positivo para ngx_int_t.

Expressões regulares#

A interface de expressões regulares no Angie é um wrapper em torno da biblioteca PCRE. O arquivo de cabeçalho correspondente é src/core/ngx_regex.h.

Para usar uma expressão regular para correspondência de strings, ela primeiro precisa ser compilada, o que geralmente é feito na fase de configuração. Note que como o suporte PCRE é opcional, todo código usando a interface deve ser protegido pela macro NGX_PCRE circundante:

#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

Após compilação bem-sucedida, os campos captures e named_captures na estrutura ngx_regex_compile_t contêm a contagem de todas as capturas e capturas nomeadas, respectivamente, encontradas na expressão regular.

A expressão regular compilada pode então ser usada para correspondência contra strings:

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

Os argumentos para ngx_regex_exec() são a expressão regular compilada re, a string a ser correspondida input, um array opcional de inteiros para armazenar quaisquer captures que sejam encontradas, e o size do array. O tamanho do array captures deve ser um múltiplo de três, conforme exigido pela API PCRE. No exemplo, o tamanho é calculado a partir do número total de capturas mais um para a própria string correspondida.

Se houver correspondências, as capturas podem ser acessadas da seguinte forma:

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

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

/* accessing named captures */

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

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

    /* capture name */
    name.data = &p[2];
    name.len = ngx_strlen(name.data);

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

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

A função ngx_regex_exec_array() aceita o array de elementos ngx_regex_elt_t (que são apenas expressões regulares compiladas com nomes associados), uma string para correspondência e um log. A função aplica expressões do array à string até que uma correspondência seja encontrada ou não restem mais expressões. O valor de retorno é NGX_OK quando há uma correspondência e NGX_DECLINED caso contrário, ou NGX_ERROR em caso de erro.

Tempo#

A estrutura ngx_time_t representa tempo com três tipos separados para segundos, milissegundos e o deslocamento GMT:

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

A estrutura ngx_tm_t é um alias para struct tm em plataformas UNIX e SYSTEMTIME no Windows.

Para obter o tempo atual, geralmente é suficiente acessar uma das variáveis globais disponíveis, representando o valor de tempo em cache no formato desejado.

As representações de string disponíveis são:

  • ngx_cached_err_log_time — Usado em entradas de log de erro: "1970/09/28 12:00:00"

  • ngx_cached_http_log_time — Usado em entradas de log de acesso HTTP: "28/Sep/1970:12:00:00 +0600"

  • ngx_cached_syslog_time — Usado em entradas de syslog: "Sep 28 12:00:00"

  • ngx_cached_http_time — Usado em cabeçalhos HTTP: "Mon, 28 Sep 1970 06:00:00 GMT"

  • ngx_cached_http_log_iso8601 — O formato padrão ISO 8601: "1970-09-28T12:00:00+06:00"

As macros ngx_time() e ngx_timeofday() retornam o valor de tempo atual em segundos e são a forma preferida de acessar o valor de tempo em cache.

Para obter o tempo explicitamente, use ngx_gettimeofday(), que atualiza seu argumento (ponteiro para struct timeval). O tempo é sempre atualizado quando o Angie retorna ao loop de eventos das chamadas do sistema. Para atualizar o tempo imediatamente, chame ngx_time_update(), ou ngx_time_sigsafe_update() se estiver atualizando o tempo no contexto do manipulador de sinal.

As seguintes funções convertem time_t na representação de tempo decomposto indicada. A primeira função em cada par converte time_t para ngx_tm_t e a segunda (com o infixo _libc_) para struct tm:

  • ngx_gmtime(), ngx_libc_gmtime() — Tempo expresso como UTC

  • ngx_localtime(), ngx_libc_localtime() — Tempo expresso relativo ao fuso horário local

A função ngx_http_time(buf, time) retorna uma representação de string adequada para uso em cabeçalhos HTTP (por exemplo, "Mon, 28 Sep 1970 06:00:00 GMT"). A função ngx_http_cookie_time(buf, time) retorna uma representação de string adequada para cookies HTTP ("Thu, 31-Dec-37 23:55:55 GMT").

Contêineres#

Array#

O tipo de array do Angie ngx_array_t é definido da seguinte forma

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

Os elementos do array estão disponíveis no campo elts. O campo nelts contém o número de elementos. O campo size contém o tamanho de um único elemento e é definido quando o array é inicializado.

Use a chamada ngx_array_create(pool, n, size) para criar um array em um pool, e a chamada ngx_array_init(array, pool, n, size) para inicializar um objeto array que já foi alocado.

ngx_array_t  *a, b;

/* create an array of strings with preallocated memory for 10 elements */
a = ngx_array_create(pool, 10, sizeof(ngx_str_t));

/* initialize string array for 10 elements */
ngx_array_init(&b, pool, 10, sizeof(ngx_str_t));

Use as seguintes funções para adicionar elementos a um array:

  • ngx_array_push(a) adiciona um elemento no final e retorna ponteiro para ele

  • ngx_array_push_n(a, n) adiciona n elementos no final e retorna ponteiro para o primeiro

Se a quantidade de memória atualmente alocada não for grande o suficiente para acomodar os novos elementos, um novo bloco de memória é alocado e os elementos existentes são copiados para ele. O novo bloco de memória normalmente é duas vezes maior que o existente.

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

Lista#

No Angie uma lista é uma sequência de arrays, otimizada para inserir um número potencialmente grande de itens. O tipo de lista ngx_list_t é definido da seguinte forma:

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;

Os itens reais são armazenados em partes da lista, que são definidas da seguinte forma:

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 do uso, uma lista deve ser inicializada chamando ngx_list_init(list, pool, n, size) ou criada chamando ngx_list_create(pool, n, size). Ambas as funções recebem como argumentos o tamanho de um único item e um número de itens por parte da lista. Para adicionar um item a uma lista, use a função ngx_list_push(list). Para iterar sobre os itens, acesse diretamente os campos da lista conforme mostrado no exemplo:

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 */ }

/* add items to the list */

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");

/* iterate over the list */

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

As listas são usadas principalmente para cabeçalhos de entrada e saída HTTP.

As listas não suportam remoção de itens. No entanto, quando necessário, os itens podem ser marcados internamente como ausentes sem serem realmente removidos da lista. Por exemplo, para marcar cabeçalhos de saída HTTP (que são armazenados como objetos ngx_table_elt_t) como ausentes, defina o campo hash em ngx_table_elt_t como zero. Itens marcados desta forma são explicitamente ignorados quando os cabeçalhos são iterados.

Fila#

No Angie uma fila é uma lista duplamente ligada intrusiva, com cada nó definido da seguinte forma:

typedef struct ngx_queue_s  ngx_queue_t;

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

O nó cabeça da fila não está ligado a nenhum dado. Use a chamada ngx_queue_init(q) para inicializar o cabeça da lista antes do uso. As filas suportam as seguintes operações:

  • ngx_queue_insert_head(h, x), ngx_queue_insert_tail(h, x) — Inserir um novo nó

  • ngx_queue_remove(x) — Remover um nó da fila

  • ngx_queue_split(h, q, n) — Dividir uma fila em um nó, retornando a cauda da fila em uma fila separada

  • ngx_queue_add(h, n) — Adicionar uma segunda fila à primeira fila

  • ngx_queue_head(h), ngx_queue_last(h) — Obter o primeiro ou último nó da fila

  • ngx_queue_sentinel(h) - Obter um objeto sentinela da fila para terminar a iteração

  • ngx_queue_data(q, type, link) — Obter uma referência ao início de uma estrutura de dados do nó da fila, considerando o deslocamento do campo da fila nela

Um exemplo:

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

Árvore Rubro-Negra#

O arquivo de cabeçalho src/core/ngx_rbtree.h fornece acesso à implementação eficaz de árvores rubro-negras.

typedef struct {
    ngx_rbtree_t       rbtree;
    ngx_rbtree_node_t  sentinel;

    /* dados personalizados por árvore aqui */
} my_tree_t;

typedef struct {
    ngx_rbtree_node_t  rbnode;

    /* dados personalizados por nó */
    foo_t              val;
} my_node_t;

Para lidar com uma árvore como um todo, você precisa de dois nós: raiz e sentinela. Tipicamente, eles são adicionados a uma estrutura personalizada, permitindo que você organize seus dados em uma árvore na qual as folhas contêm um link para ou incorporam seus dados.

Para inicializar uma árvore:

my_tree_t  root;

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

Para percorrer uma árvore e inserir novos valores, use as funções "insert_value". Por exemplo, a função ngx_str_rbtree_insert_value lida com o tipo ngx_str_t. Seus argumentos são ponteiros para um nó raiz de uma inserção, o nó recém-criado a ser adicionado, e uma sentinela da árvore.

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

O percurso é bastante direto e pode ser demonstrado com o seguinte padrão de função de busca:

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

A função compare() é uma função comparadora clássica que retorna um valor menor que, igual a, ou maior que zero. Para acelerar as buscas e evitar comparar objetos de usuário que podem ser grandes, um campo hash inteiro é usado.

Para adicionar um nó a uma árvore, aloque um novo nó, inicialize-o e chame 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 remover um nó, chame a função ngx_rbtree_delete():

ngx_rbtree_delete(&root->rbtree, node);

Hash#

As funções de tabela hash são declaradas em src/core/ngx_hash.h. Tanto correspondência exata quanto com curingas são suportadas. A última requer configuração extra e é descrita em uma seção separada abaixo.

Antes de inicializar um hash, você precisa saber o número de elementos que ele irá conter para que o Angie possa construí-lo de forma otimizada. Dois parâmetros que precisam ser configurados são max_size e bucket_size, conforme detalhado em um documento separado. Eles são geralmente configuráveis pelo usuário. As configurações de inicialização do hash são armazenadas com o tipo ngx_hash_init_t, e o hash em si é 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;

O key é um ponteiro para uma função que cria a chave inteira do hash a partir de uma string. Existem duas funções genéricas de criação de chave: ngx_hash_key(data, len) e ngx_hash_key_lc(data, len). A última converte uma string para todos os caracteres minúsculos, então a string passada deve ser gravável. Se isso não for verdade, passe a flag NGX_HASH_READONLY_KEY para a função, inicializando o array de chaves (veja abaixo).

As chaves do hash são armazenadas em ngx_hash_keys_arrays_t e são inicializadas com ngx_hash_keys_array_init(arr, type): O segundo parâmetro (type) controla a quantidade de recursos pré-alocados para o hash e pode ser NGX_HASH_SMALL ou NGX_HASH_LARGE. O último é apropriado se você espera que o hash contenha milhares 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 inserir chaves em um array de chaves hash, use a função 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 a tabela hash, chame a função ngx_hash_init(hinit, key_names, nelts):

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

A função falha se os parâmetros max_size ou bucket_size não forem grandes o suficiente.

Quando o hash é construído, use a função 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) {
    /* chave não encontrada */
}

Correspondência com curingas#

Para criar um hash que funciona com curingas, use o tipo ngx_hash_combined_t. Ele inclui o tipo hash descrito acima e tem dois arrays de chaves adicionais: dns_wc_head e dns_wc_tail. A inicialização das propriedades básicas é similar a um hash regular:

ngx_hash_init_t      hash
ngx_hash_combined_t  foo_hash;

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

É possível adicionar chaves curinga usando a flag 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);

A função reconhece curingas e adiciona chaves nos arrays correspondentes. Por favor, consulte a documentação do módulo Map para a descrição da sintaxe de curingas e do algoritmo de correspondência.

Dependendo do conteúdo das chaves adicionadas, você pode precisar inicializar até três arrays de chaves: um para correspondência exata (descrito acima), e dois mais para habilitar correspondência começando do início ou fim de uma string:

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

O array de chaves precisa ser ordenado, e os resultados da inicialização devem ser adicionados ao hash combinado. A inicialização do array dns_wc_tail é feita de forma similar.

A busca em um hash combinado é tratada pela ngx_hash_find_combined(chash, key, name, len):

/* key = "bar.example.org"; - irá corresponder a ".example.org" */
/* key = "foo.example.com"; - irá corresponder a "foo.*"        */

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

Gerenciamento de memória#

Heap#

Para alocar memória do heap do sistema, use as seguintes funções:

  • ngx_alloc(size, log) — Aloca memória do heap do sistema. Este é um wrapper em torno de malloc() com suporte a logging. Erros de alocação e informações de depuração são registrados em log.

  • ngx_calloc(size, log) — Aloca memória do heap do sistema como ngx_alloc(), mas preenche a memória com zeros após a alocação.

  • ngx_memalign(alignment, size, log) — Aloca memória alinhada do heap do sistema. Este é um wrapper em torno de posix_memalign() nas plataformas que fornecem essa função. Caso contrário, a implementação recorre a ngx_alloc() que fornece alinhamento máximo.

  • ngx_free(p) — Libera memória alocada. Este é um wrapper em torno de free()

Pool#

A maioria das alocações do Angie são feitas em pools. A memória alocada em um pool do Angie é liberada automaticamente quando o pool é destruído. Isso fornece boa performance de alocação e torna o controle de memória fácil.

Um pool aloca internamente objetos em blocos contínuos de memória. Uma vez que um bloco está cheio, um novo é alocado e adicionado à lista de blocos de memória do pool. Quando a alocação solicitada é muito grande para caber em um bloco, a solicitação é encaminhada para o alocador do sistema e o ponteiro retornado é armazenado no pool para posterior desalocação.

O tipo para pools do Angie é ngx_pool_t. As seguintes operações são suportadas:

  • ngx_create_pool(size, log) — Cria um pool com tamanho de bloco especificado. O objeto pool retornado também é alocado no pool. O size deve ser pelo menos NGX_MIN_POOL_SIZE e um múltiplo de NGX_POOL_ALIGNMENT.

  • ngx_destroy_pool(pool) — Libera toda a memória do pool, incluindo o próprio objeto pool.

  • ngx_palloc(pool, size) — Aloca memória alinhada do pool especificado.

  • ngx_pcalloc(pool, size) — Aloca memória alinhada do pool especificado e a preenche com zeros.

  • ngx_pnalloc(pool, size) — Aloca memória não alinhada do pool especificado. Usado principalmente para alocar strings.

  • ngx_pfree(pool, p) — Libera memória que foi previamente alocada no pool especificado. Apenas alocações que resultam de solicitações encaminhadas para o alocador do sistema podem 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);

Links de cadeia (ngx_chain_t) são ativamente usados no Angie, então a implementação de pool do Angie fornece uma maneira de reutilizá-los. O campo chain de ngx_pool_t mantém uma lista de links previamente alocados prontos para reutilização. Para alocação eficiente de um link de cadeia em um pool, use a função ngx_alloc_chain_link(pool). Esta função procura por um link de cadeia livre na lista do pool e aloca um novo link de cadeia se a lista do pool estiver vazia. Para liberar um link, chame a função ngx_free_chain(pool, cl).

Manipuladores de limpeza podem ser registrados em um pool. Um manipulador de limpeza é um callback com um argumento que é chamado quando o pool é destruído. Um pool geralmente está vinculado a um objeto específico do Angie (como uma requisição HTTP) e é destruído quando o objeto atinge o fim de seu tempo de vida. Registrar uma limpeza de pool é uma maneira conveniente de liberar recursos, fechar descritores de arquivo ou fazer ajustes finais nos dados compartilhados associados ao objeto principal.

Para registrar uma limpeza de pool, chame ngx_pool_cleanup_add(pool, size), que retorna um ponteiro ngx_pool_cleanup_t para ser preenchido pelo chamador. Use o argumento size para alocar contexto para o manipulador de limpeza.

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

Memória compartilhada#

A memória compartilhada é usada pelo Angie para compartilhar dados comuns entre processos. A função ngx_shared_memory_add(cf, name, size, tag) adiciona uma nova entrada de memória compartilhada ngx_shm_zone_t a um ciclo. A função recebe o name e size da zona. Cada zona compartilhada deve ter um nome único. Se uma entrada de zona compartilhada com o name e tag fornecidos já existe, a entrada de zona existente é reutilizada. A função falha com um erro se uma entrada existente com o mesmo nome tem uma tag diferente. Geralmente, o endereço da estrutura do módulo é passado como tag, tornando possível reutilizar zonas compartilhadas por nome dentro de um módulo do Angie.

A estrutura de entrada de memória compartilhada ngx_shm_zone_t tem os seguintes campos:

  • init — Callback de inicialização, chamado após a zona compartilhada ser mapeada para memória real

  • data — Contexto de dados, usado para passar dados arbitrários para o callback init

  • noreuse — Flag que desabilita a reutilização de uma zona compartilhada do ciclo antigo

  • tag — Tag da zona compartilhada

  • shm — Objeto específico da plataforma do tipo ngx_shm_t, tendo pelo menos os seguintes campos:

    • addr — Endereço de memória compartilhada mapeada, inicialmente NULL

    • size — Tamanho da memória compartilhada

    • name — Nome da memória compartilhada

    • log — Log da memória compartilhada

    • exists — Flag que indica que a memória compartilhada foi herdada do processo mestre (específico do Windows)

As entradas de zona compartilhada são mapeadas para memória real em ngx_init_cycle() após a configuração ser analisada. Em sistemas POSIX, a syscall mmap() é usada para criar o mapeamento anônimo compartilhado. No Windows, o par CreateFileMapping()/ MapViewOfFileEx() é usado.

Para alocar em memória compartilhada, o Angie fornece o slab pool tipo ngx_slab_pool_t. Um slab pool para alocar memória é automaticamente criado em cada zona compartilhada do Angie. O pool está localizado no início da zona compartilhada e pode ser acessado pela expressão (ngx_slab_pool_t *) shm_zone->shm.addr. Para alocar memória em uma zona compartilhada, chame ngx_slab_alloc(pool, size) ou ngx_slab_calloc(pool, size). Para liberar memória, chame ngx_slab_free(pool, p).

O slab pool divide toda a zona compartilhada em páginas. Cada página é usada para alocar objetos do mesmo tamanho. O tamanho especificado deve ser uma potência de 2, e maior que o tamanho mínimo de 8 bytes. Valores não conformes são arredondados para cima. Uma máscara de bits para cada página rastreia quais blocos estão em uso e quais estão livres para alocação. Para tamanhos maiores que meia página (que geralmente é 2048 bytes), a alocação é feita uma página inteira por vez

Para proteger dados na memória compartilhada de acesso concorrente, use o mutex disponível no campo mutex de ngx_slab_pool_t. Um mutex é mais comumente usado pelo slab pool ao alocar e liberar memória, mas pode ser usado para proteger quaisquer outras estruturas de dados do usuário alocadas na zona compartilhada. Para bloquear ou desbloquear um mutex, chame ngx_shmtx_lock(&shpool->mutex) ou 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;
}

Logging#

Para logging, o Angie usa objetos ngx_log_t. O logger do Angie suporta vários tipos de saída:

  • stderr — Logging para erro padrão (stderr)

  • file — Logging para um arquivo

  • syslog — Logging para syslog

  • memory — Logging para armazenamento de memória interna para fins de desenvolvimento; a memória pode ser acessada posteriormente com um debugger

Uma instância de logger pode ser uma cadeia de loggers, vinculados uns aos outros com o campo next. Neste caso, cada mensagem é escrita para todos os loggers na cadeia.

Para cada logger, um nível de severidade controla quais mensagens são escritas no log (apenas eventos atribuídos a esse nível ou superior são registrados). Os seguintes níveis de severidade são suportados:

  • NGX_LOG_EMERG

  • NGX_LOG_ALERT

  • NGX_LOG_CRIT

  • NGX_LOG_ERR

  • NGX_LOG_WARN

  • NGX_LOG_NOTICE

  • NGX_LOG_INFO

  • NGX_LOG_DEBUG

Para logging de debug, a máscara de debug também é verificada. As máscaras de debug são:

  • NGX_LOG_DEBUG_CORE

  • NGX_LOG_DEBUG_ALLOC

  • NGX_LOG_DEBUG_MUTEX

  • NGX_LOG_DEBUG_EVENT

  • NGX_LOG_DEBUG_HTTP

  • NGX_LOG_DEBUG_MAIL

  • NGX_LOG_DEBUG_STREAM

Normalmente, os loggers são criados pelo código existente do Angie a partir de diretivas error_log e estão disponíveis em quase todas as etapas de processamento em ciclo, configuração, conexão do cliente e outros objetos.

O Nginx fornece as seguintes macros de logging:

  • ngx_log_error(level, log, err, fmt, ...) — Logging de erro

  • ngx_log_debug0(level, log, err, fmt), ngx_log_debug1(level, log, err, fmt, arg1) etc — Logging de debug com até oito argumentos de formatação suportados

Uma mensagem de log é formatada em um buffer de tamanho NGX_MAX_ERROR_STR (atualmente, 2048 bytes) na pilha. A mensagem é precedida pelo nível de severidade, ID do processo (PID), ID da conexão (armazenado em log->connection), e o texto de erro do sistema. Para mensagens não-debug, log->handler também é chamado para preceder informações mais específicas à mensagem de log. O módulo HTTP define a função ngx_http_log_error() como manipulador de log para registrar endereços do cliente e servidor, ação atual (armazenada em log->action), linha de requisição do cliente, nome do 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);

O exemplo acima resulta em entradas de log como estas:

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

Ciclo#

Um objeto de ciclo armazena o contexto de execução do Angie criado a partir de uma configuração específica. Seu tipo é ngx_cycle_t. O ciclo atual é referenciado pela variável global ngx_cycle e herdado pelos workers do Angie quando eles iniciam. Cada vez que a configuração do Angie é recarregada, um novo ciclo é criado a partir da nova configuração do Angie; o ciclo antigo é normalmente excluído após o novo ser criado com sucesso.

Um ciclo é criado pela função ngx_init_cycle(), que recebe o ciclo anterior como argumento. A função localiza o arquivo de configuração do ciclo anterior e herda o máximo de recursos possível do ciclo anterior. Um ciclo temporário chamado "ciclo de inicialização" é criado quando o Angie inicia, e então é substituído por um ciclo real construído a partir da configuração.

Os membros do ciclo incluem:

  • pool — Pool do ciclo. Criado para cada novo ciclo.

  • log — Log do ciclo. Inicialmente herdado do ciclo antigo, é definido para apontar para new_log após a configuração ser lida.

  • new_log — Log do ciclo, criado pela configuração. É afetado pela diretiva error_log de escopo raiz.

  • connections, connection_n — Array de conexões do tipo ngx_connection_t, criado pelo módulo de eventos durante a inicialização de cada worker do Angie. A diretiva worker_connections na configuração do Angie define o número de conexões connection_n.

  • free_connections, free_connection_n — Lista e número de conexões atualmente disponíveis. Se não há conexões disponíveis, um worker do Angie recusa aceitar novos clientes ou conectar a servidores upstream.

  • files, files_n — Array para mapear descritores de arquivo para conexões do Angie. Este mapeamento é usado pelos módulos de eventos, tendo a flag NGX_USE_FD_EVENT (atualmente, são poll e devpoll).

  • conf_ctx — Array de configurações de módulos principais. As configurações são criadas e preenchidas durante a leitura dos arquivos de configuração do Angie.

  • modules, modules_n — Array de módulos do tipo ngx_module_t, tanto estáticos quanto dinâmicos, carregados pela configuração atual.

  • listening — Array de objetos de escuta do tipo ngx_listening_t. Objetos de escuta são normalmente adicionados pela diretiva listen de diferentes módulos que chamam a função ngx_create_listening(). Sockets de escuta são criados com base nos objetos de escuta.

  • paths — Array de caminhos do tipo ngx_path_t. Caminhos são adicionados chamando a função ngx_add_path() de módulos que vão operar em determinados diretórios. Estes diretórios são criados pelo Angie após ler a configuração, se estiverem ausentes. Além disso, dois manipuladores podem ser adicionados para cada caminho:

    • carregador de caminho — Executa apenas uma vez em 60 segundos após iniciar ou recarregar o Angie. Normalmente, o carregador lê o diretório e armazena dados na memória compartilhada do Angie. O manipulador é chamado do processo dedicado do Angie "carregador de cache".

    • gerenciador de caminho — Executa periodicamente. Normalmente, o gerenciador remove arquivos antigos do diretório e atualiza a memória do Angie para refletir as mudanças. O manipulador é chamado do processo dedicado "gerenciador de cache".

  • open_files — Lista de objetos de arquivo aberto do tipo ngx_open_file_t, que são criados chamando a função ngx_conf_open_file(). Atualmente, o Angie usa este tipo de arquivos abertos para logging. Após ler a configuração, o Angie abre todos os arquivos na lista open_files e armazena cada descritor de arquivo no campo fd do objeto. Os arquivos são abertos em modo de anexação e são criados se estiverem ausentes. Os arquivos na lista são reabertos pelos workers do Angie ao receber o sinal de reabertura (mais frequentemente USR1). Neste caso o descritor no campo fd é alterado para um novo valor.

  • shared_memory — Lista de zonas de memória compartilhada, cada uma adicionada chamando a função ngx_shared_memory_add(). Zonas compartilhadas são mapeadas para a mesma faixa de endereços em todos os processos do Angie e são usadas para compartilhar dados comuns, por exemplo a árvore em memória do cache HTTP.

Buffer#

Para operações de entrada/saída, o Angie fornece o tipo de buffer ngx_buf_t. Normalmente, é usado para armazenar dados a serem escritos em um destino ou lidos de uma fonte. Um buffer pode referenciar dados na memória ou em um arquivo e é tecnicamente possível para um buffer referenciar ambos ao mesmo tempo. A memória para o buffer é alocada separadamente e não está relacionada à estrutura do buffer ngx_buf_t.

A estrutura ngx_buf_t tem os seguintes campos:

  • start, end — Os limites do bloco de memória alocado para o buffer.

  • pos, last — Os limites do buffer de memória; normalmente um subintervalo de start .. end.

  • file_pos, file_last — Os limites de um buffer de arquivo, expressos como deslocamentos do início do arquivo.

  • tag — Valor único usado para distinguir buffers; criado por diferentes módulos do Angie, normalmente para fins de reutilização de buffer.

  • file — Objeto de arquivo.

  • temporary — Flag indicando que o buffer referencia memória gravável.

  • memory — Flag indicando que o buffer referencia memória somente leitura.

  • in_file — Flag indicando que o buffer referencia dados em um arquivo.

  • flush — Flag indicando que todos os dados anteriores ao buffer precisam ser liberados.

  • recycled — Flag indicando que o buffer pode ser reutilizado e precisa ser consumido o mais rápido possível.

  • sync — Flag indicando que o buffer não carrega dados ou sinal especial como flush ou last_buf. Por padrão o Angie considera tais buffers uma condição de erro, mas esta flag diz ao Angie para pular a verificação de erro.

  • last_buf — Flag indicando que o buffer é o último na saída.

  • last_in_chain — Flag indicando que não há mais buffers de dados em uma requisição ou sub-requisição.

  • shadow — Referência a outro buffer ("sombra") relacionado ao buffer atual, normalmente no sentido de que o buffer usa dados da sombra. Quando o buffer é consumido, o buffer sombra é normalmente também marcado como consumido.

  • last_shadow — Flag indicando que o buffer é o último que referencia um buffer sombra específico.

  • temp_file — Flag indicando que o buffer está em um arquivo temporário.

Para operações de entrada e saída os buffers são ligados em cadeias. Uma cadeia é uma sequência de links de cadeia do tipo ngx_chain_t, definida como segue:

typedef struct ngx_chain_s  ngx_chain_t;

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

Cada link de cadeia mantém uma referência ao seu buffer e uma referência ao próximo link de cadeia.

Um exemplo de uso de buffers e cadeias:

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

Rede#

Conexão#

O tipo de conexão ngx_connection_t é um wrapper em torno de um descritor de socket. Ele inclui os seguintes campos:

  • fd — Descritor de socket

  • data — Contexto de conexão arbitrário. Normalmente, é um ponteiro para um objeto de nível superior construído sobre a conexão, como uma requisição HTTP ou uma sessão Stream.

  • read, write — Eventos de leitura e escrita para a conexão.

  • recv, send, recv_chain, send_chain — Operações de I/O para a conexão.

  • pool — Pool da conexão.

  • log — Log da conexão.

  • sockaddr, socklen, addr_text — Endereço do socket remoto em formas binária e texto.

  • local_sockaddr, local_socklen — Endereço do socket local em forma binária. Inicialmente, esses campos estão vazios. Use a função ngx_connection_local_sockaddr() para obter o endereço do socket local.

  • proxy_protocol_addr, proxy_protocol_port - Endereço e porta do cliente do protocolo PROXY, se o protocolo PROXY estiver habilitado para a conexão.

  • ssl — Contexto SSL para a conexão.

  • reusable — Flag indicando que a conexão está em um estado que a torna elegível para reutilização.

  • close — Flag indicando que a conexão está sendo reutilizada e precisa ser fechada.

Uma conexão Angie pode encapsular transparentemente a camada SSL. Neste caso, o campo ssl da conexão mantém um ponteiro para uma estrutura ngx_ssl_connection_t, mantendo todos os dados relacionados ao SSL para a conexão, incluindo SSL_CTX e SSL. Os manipuladores recv, send, recv_chain e send_chain também são definidos para funções habilitadas para SSL.

A diretiva worker_connections na configuração do Angie limita o número de conexões por worker do Angie. Todas as estruturas de conexão são pré-criadas quando um worker inicia e armazenadas no campo connections do objeto cycle. Para recuperar uma estrutura de conexão, use a função ngx_get_connection(s, log). Ela recebe como argumento s um descritor de socket, que precisa ser envolvido em uma estrutura de conexão.

Como o número de conexões por worker é limitado, o Angie fornece uma maneira de capturar conexões que estão atualmente em uso. Para habilitar ou desabilitar a reutilização de uma conexão, chame a função ngx_reusable_connection(c, reusable). Chamar ngx_reusable_connection(c, 1) define a flag reuse na estrutura de conexão e insere a conexão na reusable_connections_queue do cycle. Sempre que ngx_get_connection() descobre que não há conexões disponíveis na lista free_connections do cycle, ela chama ngx_drain_connections() para liberar um número específico de conexões reutilizáveis. Para cada conexão desse tipo, a flag close é definida e seu manipulador de leitura é chamado, que deve liberar a conexão chamando ngx_close_connection(c) e torná-la disponível para reutilização. Para sair do estado quando uma conexão pode ser reutilizada, ngx_reusable_connection(c, 0) é chamada. Conexões de cliente HTTP são um exemplo de conexões reutilizáveis no Angie; elas são marcadas como reutilizáveis até que o primeiro byte de requisição seja recebido do cliente.

Eventos#

Evento#

O objeto de evento ngx_event_t no Angie fornece um mecanismo para notificação de que um evento específico ocorreu.

Os campos em ngx_event_t incluem:

  • data — Contexto de evento arbitrário usado em manipuladores de eventos, geralmente como ponteiro para uma conexão relacionada ao evento.

  • handler — Função de callback a ser invocada quando o evento acontece.

  • write — Flag indicando um evento de escrita. A ausência da flag indica um evento de leitura.

  • active — Flag indicando que o evento está registrado para receber notificações de I/O, normalmente de mecanismos de notificação como epoll, kqueue, poll.

  • ready — Flag indicando que o evento recebeu uma notificação de I/O.

  • delayed — Flag indicando que o I/O está atrasado devido à limitação de taxa.

  • timer — Nó da árvore rubro-negra para inserir o evento na árvore de timer.

  • timer_set — Flag indicando que o timer do evento está definido e ainda não expirou.

  • timedout — Flag indicando que o timer do evento expirou.

  • eof — Flag indicando que EOF ocorreu durante a leitura de dados.

  • pending_eof — Flag indicando que EOF está pendente no socket, mesmo que possa haver alguns dados disponíveis antes dele. A flag é entregue via o evento EPOLLRDHUP epoll ou flag EV_EOF kqueue.

  • error — Flag indicando que um erro ocorreu durante a leitura (para um evento de leitura) ou escrita (para um evento de escrita).

  • cancelable — Flag de evento de timer indicando que o evento deve ser ignorado durante o desligamento do worker. O desligamento gracioso do worker é atrasado até que não haja eventos de timer não canceláveis agendados.

  • posted — Flag indicando que o evento está postado em uma fila.

  • queue — Nó da fila para postar o evento em uma fila.

Eventos de I/O#

Cada conexão obtida chamando a função ngx_get_connection() tem dois eventos anexados, c->read e c->write, que são usados para receber notificação de que o socket está pronto para leitura ou escrita. Todos esses eventos operam no modo Edge-Triggered, significando que eles apenas disparam notificações quando o estado do socket muda. Por exemplo, fazer uma leitura parcial em um socket não faz o Angie entregar uma notificação de leitura repetida até que mais dados cheguem no socket. Mesmo quando o mecanismo de notificação de I/O subjacente é essencialmente Level-Triggered (poll, select etc), o Angie converte as notificações para Edge-Triggered. Para tornar as notificações de eventos do Angie consistentes em todos os sistemas de notificação em diferentes plataformas, as funções ngx_handle_read_event(rev, flags) e ngx_handle_write_event(wev, lowat) devem ser chamadas após manipular uma notificação de socket de I/O ou chamar qualquer função de I/O nesse socket. Normalmente, as funções são chamadas uma vez no final de cada manipulador de evento de leitura ou escrita.

Eventos de timer#

Um evento pode ser definido para enviar uma notificação quando um timeout expira. O timer usado pelos eventos conta milissegundos desde algum ponto não especificado no passado truncado para o tipo ngx_msec_t. Seu valor atual pode ser obtido da variável ngx_current_msec.

A função ngx_add_timer(ev, timer) define um timeout para um evento, ngx_del_timer(ev) exclui um timeout previamente definido. A árvore rubro-negra global de timeout ngx_event_timer_rbtree armazena todos os timeouts atualmente definidos. A chave na árvore é do tipo ngx_msec_t e é o tempo quando o evento ocorre. A estrutura da árvore permite operações rápidas de inserção e exclusão, bem como acesso aos timeouts mais próximos, que o Angie usa para descobrir quanto tempo esperar por eventos de I/O e para expirar eventos de timeout.

Eventos postados#

Um evento pode ser postado, o que significa que seu manipulador será chamado em algum ponto posterior dentro da iteração atual do loop de eventos. Postar eventos é uma boa prática para simplificar código e escapar de estouros de pilha. Eventos postados são mantidos em uma fila de postagem. A macro ngx_post_event(ev, q) posta o evento ev na fila de postagem q. A macro ngx_delete_posted_event(ev) exclui o evento ev da fila em que está atualmente postado. Normalmente, eventos são postados na fila ngx_posted_events, que é processada tarde no loop de eventos — após todos os eventos de I/O e timer já terem sido manipulados. A função ngx_event_process_posted() é chamada para processar uma fila de eventos. Ela chama manipuladores de eventos até que a fila esteja vazia. Isso significa que um manipulador de evento postado pode postar mais eventos para serem processados dentro da iteração atual do loop de eventos.

Um exemplo:

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 */ }
}

Loop de eventos#

Exceto pelo processo master do Angie, todos os processos do Angie fazem I/O e, portanto, têm um loop de eventos. (O processo master do Angie, em vez disso, passa a maior parte do tempo na chamada sigsuspend() aguardando a chegada de sinais.) O loop de eventos do Angie é implementado na função ngx_process_events_and_timers(), que é chamada repetidamente até que o processo termine.

O loop de eventos tem os seguintes estágios:

  • Encontrar o timeout que está mais próximo de expirar, chamando ngx_event_find_timer(). Esta função encontra o nó mais à esquerda na árvore de timers e retorna o número de milissegundos até que o nó expire.

  • Processar eventos de I/O chamando um manipulador, específico para o mecanismo de notificação de eventos, escolhido pela configuração do Angie. Este manipulador aguarda que pelo menos um evento de I/O aconteça, mas apenas até que o próximo timeout expire. Quando um evento de leitura ou escrita ocorre, a flag ready é definida e o manipulador do evento é chamado. Para Linux, o manipulador ngx_epoll_process_events() é normalmente usado, que chama epoll_wait() para aguardar eventos de I/O.

  • Expirar timers chamando ngx_event_expire_timers(). A árvore de timers é iterada do elemento mais à esquerda para a direita até que um timeout não expirado seja encontrado. Para cada nó expirado, a flag de evento timedout é definida, a flag timer_set é resetada, e o manipulador de evento é chamado

  • Processar eventos postados chamando ngx_event_process_posted(). A função remove repetidamente o primeiro elemento da fila de eventos postados e chama o manipulador do elemento, até que a fila esteja vazia.

Todos os processos do Angie também manipulam sinais. Os manipuladores de sinais apenas definem variáveis globais que são verificadas após a chamada ngx_process_events_and_timers().

Processos#

Existem vários tipos de processos no Angie. O tipo de um processo é mantido na variável global ngx_process e é um dos seguintes:

  • NGX_PROCESS_MASTER — O processo master, que lê a configuração do NGINX, cria ciclos, e inicia e controla processos filhos. Ele não executa nenhum I/O e responde apenas a sinais. Sua função de ciclo é ngx_master_process_cycle().

  • NGX_PROCESS_WORKER — O processo worker, que manipula conexões de clientes. Ele é iniciado pelo processo master e responde aos seus sinais e comandos de canal também. Sua função de ciclo é ngx_worker_process_cycle(). Pode haver múltiplos processos worker, conforme configurado pela diretiva worker_processes.

  • NGX_PROCESS_SINGLE — O processo único, que existe apenas no modo master_process off, e é o único processo executando nesse modo. Ele cria ciclos (como o processo master faz) e manipula conexões de clientes (como o processo worker faz). Sua função de ciclo é ngx_single_process_cycle().

  • NGX_PROCESS_HELPER — O processo auxiliar, do qual atualmente existem dois tipos: gerenciador de cache e carregador de cache. A função de ciclo para ambos é ngx_cache_manager_process_cycle().

Os processos do Angie manipulam os seguintes sinais:

  • NGX_SHUTDOWN_SIGNAL (SIGQUIT na maioria dos sistemas) — Desligamento gracioso. Ao receber este sinal, o processo master envia um sinal de desligamento para todos os processos filhos. Quando não restam processos filhos, o master destrói o pool de ciclo e sai. Quando um processo worker recebe este sinal, ele fecha todos os sockets de escuta e aguarda até que não haja eventos não canceláveis agendados, então destrói o pool de ciclo e sai. Quando o gerenciador de cache ou o processo carregador de cache recebe este sinal, ele sai imediatamente. A variável ngx_quit é definida como 1 quando um processo recebe este sinal, e é imediatamente resetada após ser processada. A variável ngx_exiting é definida como 1 enquanto um processo worker está no estado de desligamento.

  • NGX_TERMINATE_SIGNAL (SIGTERM na maioria dos sistemas) — Terminar. Ao receber este sinal, o processo master envia um sinal de terminação para todos os processos filhos. Se um processo filho não sair dentro de 1 segundo, o processo master envia o sinal SIGKILL para matá-lo. Quando não restam processos filhos, o processo master destrói o pool de ciclo e sai. Quando um processo worker, o processo gerenciador de cache ou o processo carregador de cache recebe este sinal, ele destrói o pool de ciclo e sai. A variável ngx_terminate é definida como 1 quando este sinal é recebido.

  • NGX_NOACCEPT_SIGNAL (SIGWINCH na maioria dos sistemas) - Desligar todos os processos worker e auxiliares. Ao receber este sinal, o processo master desliga seus processos filhos. Se um binário do Angie novo iniciado anteriormente sair, os processos filhos do master antigo são iniciados novamente. Quando um processo worker recebe este sinal, ele desliga no modo de debug definido pela diretiva debug_points.

  • NGX_RECONFIGURE_SIGNAL (SIGHUP na maioria dos sistemas) - Reconfigurar. Ao receber este sinal, o processo master relê a configuração e cria um novo ciclo baseado nela. Se o novo ciclo for criado com sucesso, o ciclo antigo é deletado e novos processos filhos são iniciados. Enquanto isso, os processos filhos antigos recebem o sinal NGX_SHUTDOWN_SIGNAL. No modo de processo único, o Angie cria um novo ciclo, mas mantém o antigo até que não haja mais clientes com conexões ativas vinculadas a ele. Os processos worker e auxiliares ignoram este sinal.

  • NGX_REOPEN_SIGNAL (SIGUSR1 na maioria dos sistemas) — Reabrir arquivos. O processo master envia este sinal para os workers, que reabrem todos os open_files relacionados ao ciclo.

  • NGX_CHANGEBIN_SIGNAL (SIGUSR2 na maioria dos sistemas) — Alterar o binário do Angie. O processo master inicia um novo binário do Angie e passa uma lista de todos os sockets de escuta. A lista em formato texto, passada na variável de ambiente "NGINX", consiste em números de descritores separados por ponto e vírgula. O novo binário do Angie lê a variável "NGINX" e adiciona os sockets ao seu ciclo de inicialização. Outros processos ignoram este sinal.

Embora todos os processos worker do Angie sejam capazes de receber e manipular adequadamente sinais POSIX, o processo master não usa a chamada de sistema kill() padrão para passar sinais para workers e auxiliares. Em vez disso, o Angie usa pares de sockets entre processos que permitem enviar mensagens entre todos os processos do Angie. Atualmente, no entanto, as mensagens são enviadas apenas do master para seus filhos. As mensagens carregam os sinais padrão.

Threads#

É possível descarregar para uma thread separada tarefas que de outra forma bloqueariam o processo worker do Angie. Por exemplo, o Angie pode ser configurado para usar threads para executar I/O de arquivo. Outro caso de uso é uma biblioteca que não tem interface assíncrona e, portanto, não pode ser normalmente usada com o Angie. Tenha em mente que a interface de threads é um auxiliar para a abordagem assíncrona existente para processar conexões de clientes, e de forma alguma pretende ser um substituto.

Para lidar com sincronização, os seguintes wrappers sobre primitivos pthreads estão disponíveis:

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

Em vez de criar uma nova thread para cada tarefa, o Angie implementa uma estratégia de thread_pool. Múltiplos pools de threads podem ser configurados para diferentes propósitos (por exemplo, executar I/O em diferentes conjuntos de discos). Cada pool de threads é criado na inicialização e contém um número limitado de threads que processam uma fila de tarefas. Quando uma tarefa é concluída, um manipulador de conclusão predefinido é chamado.

O arquivo de cabeçalho src/core/ngx_thread_pool.h contém definições 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);

No momento da configuração, um módulo que deseja usar threads deve obter uma referência para um pool de threads chamando ngx_thread_pool_add(cf, name), que ou cria um novo pool de threads com o name fornecido ou retorna uma referência para o pool com esse nome se ele já existir.

Para adicionar uma task em uma fila de um pool de threads especificado tp em tempo de execução, use a função ngx_thread_task_post(tp, task).

Para executar uma função em uma thread, passe parâmetros e configure um manipulador de conclusão usando a estrutura ngx_thread_task_t:

typedef struct {
    int    foo;
} my_thread_ctx_t;


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

    /* esta função é executada em uma thread separada */
}


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

    /* executada no loop de eventos do Angie */
}


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

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

    ctx = task->ctx;

    ctx->foo = 42;

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

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

    return NGX_OK;
}

Módulos#

Adicionando novos módulos#

Cada módulo Angie independente reside em um diretório separado que contém pelo menos dois arquivos: config e um arquivo com o código-fonte do módulo. O arquivo config contém todas as informações necessárias para o Angie integrar o módulo, por exemplo:

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

O arquivo config é um script shell POSIX que pode definir e acessar as seguintes variáveis:

  • ngx_module_type — Tipo de módulo a ser construído. Os valores possíveis são CORE, HTTP, HTTP_FILTER, HTTP_INIT_FILTER, HTTP_AUX_FILTER, MAIL, STREAM, ou MISC.

  • ngx_module_name — Nomes dos módulos. Para construir múltiplos módulos a partir de um conjunto de arquivos-fonte, especifique uma lista de nomes separados por espaços em branco. O primeiro nome indica o nome do binário de saída para o módulo dinâmico. Os nomes na lista devem corresponder aos nomes usados no código-fonte.

  • ngx_addon_name — Nome do módulo como aparece na saída no console do script de configuração.

  • ngx_module_srcs — Lista separada por espaços em branco de arquivos-fonte usados para compilar o módulo. A variável $ngx_addon_dir pode ser usada para representar o caminho para o diretório do módulo.

  • ngx_module_incs — Caminhos de inclusão necessários para construir o módulo

  • ngx_module_deps — Lista separada por espaços em branco das dependências do módulo. Geralmente, é a lista de arquivos de cabeçalho.

  • ngx_module_libs — Lista separada por espaços em branco de bibliotecas para vincular com o módulo. Por exemplo, use ngx_module_libs=-lpthread para vincular a biblioteca libpthread. As seguintes macros podem ser usadas para vincular às mesmas bibliotecas que o Angie: LIBXSLT, LIBGD, GEOIP, PCRE, OPENSSL, MD5, SHA1, ZLIB, e PERL.

  • ngx_module_link — Variável definida pelo sistema de construção para DYNAMIC para um módulo dinâmico ou ADDON para um módulo estático e usada para determinar diferentes ações a serem executadas dependendo do tipo de vinculação.

  • ngx_module_order — Ordem de carregamento para o módulo; útil para os tipos de módulo HTTP_FILTER e HTTP_AUX_FILTER. O formato para esta opção é uma lista separada por espaços em branco de módulos. Todos os módulos na lista seguindo o nome do módulo atual ficam depois dele na lista global de módulos, que define a ordem para inicialização dos módulos. Para módulos de filtro, inicialização posterior significa execução anterior.

    Os seguintes módulos são tipicamente usados como referências. O ngx_http_copy_filter_module lê os dados para outros módulos de filtro e é colocado próximo ao final da lista para que seja um dos primeiros a ser executado. O ngx_http_write_filter_module escreve os dados para o socket do cliente e é colocado próximo ao topo da lista, sendo o último a ser executado.

    Por padrão, módulos de filtro são colocados antes do ngx_http_copy_filter na lista de módulos para que o manipulador de filtro seja executado após o manipulador do filtro de cópia. Para outros tipos de módulo, o padrão é a string vazia.

Para compilar um módulo no Angie estaticamente, use o argumento --add-module=/path/to/module para o script de configuração. Para compilar um módulo para carregamento dinâmico posterior no Angie, use o argumento --add-dynamic-module=/path/to/module.

Módulos Principais#

Módulos são os blocos de construção do Angie, e a maior parte de sua funcionalidade é implementada como módulos. O arquivo-fonte do módulo deve conter uma variável global do tipo ngx_module_t, que é definida da seguinte forma:

struct ngx_module_s {

    /* parte privada é omitida */

    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;

    ngx_int_t           (*init_master)(ngx_log_t *log);

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);

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

    void                (*exit_master)(ngx_cycle_t *cycle);

    /* stubs para extensões futuras são omitidos */
};

A parte privada omitida inclui a versão do módulo e uma assinatura e é preenchida usando a macro predefinida NGX_MODULE_V1.

Cada módulo mantém seus dados privados no campo ctx, reconhece as diretivas de configuração, especificadas no array commands, e pode ser invocado em certas etapas do ciclo de vida do Angie. O ciclo de vida do módulo consiste nos seguintes eventos:

  • Manipuladores de diretivas de configuração são chamados conforme aparecem nos arquivos de configuração no contexto do processo mestre.

  • Após a configuração ser analisada com sucesso, o manipulador init_module é chamado no contexto do processo mestre. O manipulador init_module é chamado no processo mestre cada vez que uma configuração é carregada.

  • O processo mestre cria um ou mais processos worker e o manipulador init_process é chamado em cada um deles.

  • Quando um processo worker recebe o comando de desligamento ou término do mestre, ele invoca o manipulador exit_process.

  • O processo mestre chama o manipulador exit_master antes de sair.

Como threads são usadas no Angie apenas como uma facilidade de E/S suplementar com sua própria API, os manipuladores init_thread e exit_thread não são chamados atualmente. Também não há manipulador init_master, porque seria sobrecarga desnecessária.

O type do módulo define exatamente o que é armazenado no campo ctx. Seu valor é um dos seguintes tipos:

  • NGX_CORE_MODULE

  • NGX_EVENT_MODULE

  • NGX_HTTP_MODULE

  • NGX_MAIL_MODULE

  • NGX_STREAM_MODULE

O NGX_CORE_MODULE é o tipo de módulo mais básico e, portanto, o mais genérico e de mais baixo nível. Os outros tipos de módulo são implementados sobre ele e fornecem uma maneira mais conveniente de lidar com domínios correspondentes, como manipular eventos ou requisições HTTP.

O conjunto de módulos principais inclui os módulos ngx_core_module, ngx_errlog_module, ngx_regex_module, ngx_thread_pool_module e ngx_openssl_module. O módulo HTTP, o módulo stream, o módulo mail e módulos de evento também são módulos principais. O contexto de um módulo principal é definido 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;

onde name é uma string com o nome do módulo, create_conf e init_conf são ponteiros para funções que criam e inicializam a configuração do módulo respectivamente. Para módulos principais, o Angie chama create_conf antes de analisar uma nova configuração e init_conf após toda a configuração ser analisada com sucesso. A função create_conf típica aloca memória para a configuração e define valores padrão.

Por exemplo, um módulo simplista chamado ngx_foo_module poderia parecer assim:

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

Diretivas de Configuração#

O tipo ngx_command_t define uma única diretiva de configuração. Cada módulo que suporta configuração fornece um array de tais estruturas que descrevem como processar argumentos e quais manipuladores chamar:

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 o array com o valor especial ngx_null_command. O name é o nome de uma diretiva como ela aparece no arquivo de configuração, por exemplo "worker_processes" ou "listen". O type é um campo de bits de flags que especifica o número de argumentos que a diretiva aceita, seu tipo, e o contexto no qual ela aparece. As flags são:

  • NGX_CONF_NOARGS — A diretiva não aceita argumentos.

  • NGX_CONF_1MORE — A diretiva aceita um ou mais argumentos.

  • NGX_CONF_2MORE — A diretiva aceita dois ou mais argumentos.

  • NGX_CONF_TAKE1..:samp:NGX_CONF_TAKE7 — A diretiva aceita exatamente o número indicado de argumentos.

  • NGX_CONF_TAKE12, NGX_CONF_TAKE13, NGX_CONF_TAKE23, NGX_CONF_TAKE123, NGX_CONF_TAKE1234 — A diretiva pode aceitar diferentes números de argumentos. As opções são limitadas aos números dados. Por exemplo, NGX_CONF_TAKE12 significa que aceita um ou dois argumentos.

As flags para tipos de diretiva são:

  • NGX_CONF_BLOCK — A diretiva é um bloco, ou seja, pode conter outras diretivas dentro de suas chaves de abertura e fechamento, ou até mesmo implementar seu próprio parser para lidar com conteúdo interno.

  • NGX_CONF_FLAG — A diretiva aceita um valor booleano, seja on ou off.

O contexto de uma diretiva define onde ela pode aparecer na configuração:

  • NGX_MAIN_CONF — No contexto de nível superior.

  • NGX_HTTP_MAIN_CONF — No bloco http.

  • NGX_HTTP_SRV_CONF — Em um bloco server dentro do bloco http.

  • NGX_HTTP_LOC_CONF — Em um bloco location dentro do bloco http.

  • NGX_HTTP_UPS_CONF — Em um bloco upstream dentro do bloco http.

  • NGX_HTTP_SIF_CONF — Em um bloco if dentro de um bloco server no bloco http.

  • NGX_HTTP_LIF_CONF — Em um bloco if dentro de um bloco location no bloco http.

  • NGX_HTTP_LMT_CONF — Em um bloco limit_except dentro do bloco http.

  • NGX_STREAM_MAIN_CONF — No bloco stream.

  • NGX_STREAM_SRV_CONF — Em um bloco server dentro do bloco stream.

  • NGX_STREAM_UPS_CONF — Em um bloco upstream dentro do bloco stream.

  • NGX_MAIL_MAIN_CONF — No bloco mail.

  • NGX_MAIL_SRV_CONF — Em um bloco server dentro do bloco mail.

  • NGX_EVENT_CONF — No bloco event.

  • NGX_DIRECT_CONF — Usado por módulos que não criam uma hierarquia de contextos e têm apenas uma configuração global. Esta configuração é passada para o manipulador como o argumento conf.

O parser de configuração usa essas flags para gerar um erro em caso de uma diretiva mal posicionada e chama manipuladores de diretiva fornecidos com um ponteiro de configuração apropriado, para que as mesmas diretivas em locais diferentes possam armazenar seus valores em lugares distintos.

O campo set define um manipulador que processa uma diretiva e armazena valores analisados na configuração correspondente. Há várias funções que realizam conversões comuns:

  • ngx_conf_set_flag_slot — Converte as strings literais on e off em um valor ngx_flag_t com valores 1 ou 0, respectivamente.

  • ngx_conf_set_str_slot — Armazena uma string como um valor do tipo ngx_str_t.

  • ngx_conf_set_str_array_slot — Adiciona um valor a um array ngx_array_t de strings ngx_str_t. O array é criado se ainda não existir.

  • ngx_conf_set_keyval_slot — Adiciona um par chave-valor a um array ngx_array_t de pares chave-valor ngx_keyval_t. A primeira string torna-se a chave e a segunda o valor. O array é criado se ainda não existir.

  • ngx_conf_set_num_slot — Converte um argumento da diretiva para um valor ngx_int_t.

  • ngx_conf_set_size_slot — Converte um tamanho para um valor size_t expresso em bytes.

  • ngx_conf_set_off_slot — Converte um offset para um valor off_t expresso em bytes.

  • ngx_conf_set_msec_slot — Converte um tempo para um valor ngx_msec_t expresso em milissegundos.

  • ngx_conf_set_sec_slot — Converte um tempo para um valor time_t expresso em segundos.

  • ngx_conf_set_bufs_slot — Converte os dois argumentos fornecidos em um objeto ngx_bufs_t que contém o número e tamanho dos buffers.

  • ngx_conf_set_enum_slot — Converte o argumento fornecido em um valor ngx_uint_t. O array terminado em null de ngx_conf_enum_t passado no campo post define as strings aceitáveis e os valores inteiros correspondentes.

  • ngx_conf_set_bitmask_slot — Converte os argumentos fornecidos em um valor ngx_uint_t. Os valores de máscara para cada argumento são combinados com OR produzindo o resultado. O array terminado em null de ngx_conf_bitmask_t passado no campo post define as strings aceitáveis e os valores de máscara correspondentes.

  • set_path_slot — Converte os argumentos fornecidos para um valor ngx_path_t e realiza todas as inicializações necessárias. Para detalhes, veja a documentação da diretiva proxy_temp_path.

  • set_access_slot — Converte os argumentos fornecidos para uma máscara de permissões de arquivo. Para detalhes, veja a documentação da diretiva proxy_store_access.

O campo conf define qual estrutura de configuração é passada para o manipulador de diretório. Módulos principais têm apenas a configuração global e definem a flag NGX_DIRECT_CONF para acessá-la. Módulos como HTTP, Stream ou Mail criam hierarquias de configurações. Por exemplo, a configuração de um módulo é criada para escopos server, location e if.

  • NGX_HTTP_MAIN_CONF_OFFSET — Configuração para o bloco http.

  • NGX_HTTP_SRV_CONF_OFFSET — Configuração para um bloco server dentro do bloco http.

  • NGX_HTTP_LOC_CONF_OFFSET — Configuração para um bloco location dentro do http.

  • NGX_STREAM_MAIN_CONF_OFFSET — Configuração para o bloco stream.

  • NGX_STREAM_SRV_CONF_OFFSET — Configuração para um bloco server dentro do bloco stream.

  • NGX_MAIL_MAIN_CONF_OFFSET — Configuração para o bloco mail.

  • NGX_MAIL_SRV_CONF_OFFSET — Configuração para um bloco server dentro do bloco mail.

O offset define o deslocamento de um campo em uma estrutura de configuração de módulo que contém valores para esta diretiva específica. O uso típico é empregar a macro offsetof().

O campo post tem dois propósitos: pode ser usado para definir um manipulador a ser chamado após o manipulador principal ter completado, ou para passar dados adicionais para o manipulador principal. No primeiro caso, a estrutura ngx_conf_post_t precisa ser inicializada com um ponteiro para o manipulador, por exemplo:

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

O argumento post é o próprio objeto ngx_conf_post_t, e o data é um ponteiro para o valor, convertido dos argumentos pelo manipulador principal com o tipo apropriado.

HTTP#

Conexão#

Cada conexão de cliente HTTP passa pelas seguintes etapas:

  • ngx_event_accept() aceita uma conexão TCP do cliente. Este manipulador é chamado em resposta a uma notificação de leitura em um socket de escuta. Um novo objeto ngx_connection_t é criado nesta etapa para encapsular o socket do cliente recém-aceito. Cada ouvinte do Angie fornece um manipulador para passar o novo objeto de conexão. Para conexões HTTP é ngx_http_init_connection(c).

  • ngx_http_init_connection() executa a inicialização inicial da conexão HTTP. Nesta etapa um objeto ngx_http_connection_t é criado para a conexão e sua referência é armazenada no campo data da conexão. Posteriormente será substituído por um objeto de requisição HTTP. Um analisador de protocolo PROXY e o handshake SSL são iniciados nesta etapa também.

  • O manipulador de evento de leitura ngx_http_wait_request_handler() é chamado quando dados estão disponíveis no socket do cliente. Nesta etapa um objeto de requisição HTTP ngx_http_request_t é criado e definido no campo data da conexão.

  • O manipulador de evento de leitura ngx_http_process_request_line() lê a linha de requisição do cliente. O manipulador é definido por ngx_http_wait_request_handler(). Os dados são lidos no buffer da conexão. O tamanho do buffer é inicialmente definido pela diretiva client_header_buffer_size. Todo o cabeçalho do cliente deve caber no buffer. Se o tamanho inicial não for suficiente, um buffer maior é alocado, com a capacidade definida pela diretiva large_client_header_buffers.

  • O manipulador de evento de leitura ngx_http_process_request_headers(), é definido após ngx_http_process_request_line() para ler o cabeçalho da requisição do cliente.

  • ngx_http_core_run_phases() é chamado quando o cabeçalho da requisição é completamente lido e analisado. Esta função executa as fases da requisição de NGX_HTTP_POST_READ_PHASE até NGX_HTTP_CONTENT_PHASE. A última fase destina-se a gerar uma resposta e passá-la pela cadeia de filtros. A resposta não é necessariamente enviada ao cliente nesta fase. Pode permanecer em buffer e ser enviada na etapa de finalização.

  • ngx_http_finalize_request() é geralmente chamado quando a requisição gerou toda a saída ou produziu um erro. No último caso, uma página de erro apropriada é procurada e usada como resposta. Se a resposta não foi completamente enviada ao cliente neste ponto, um escritor HTTP ngx_http_writer() é ativado para terminar de enviar os dados pendentes.

  • ngx_http_finalize_connection() é chamado quando a resposta completa foi enviada ao cliente e a requisição pode ser destruída. Se o recurso keepalive da conexão do cliente estiver habilitado, ngx_http_set_keepalive() é chamado, que destrói a requisição atual e aguarda a próxima requisição na conexão. Caso contrário, ngx_http_close_request() destrói tanto a requisição quanto a conexão.

Requisição#

Para cada requisição HTTP do cliente o objeto ngx_http_request_t é criado. Alguns dos campos deste objeto são:

  • connection — Ponteiro para um objeto de conexão do cliente ngx_connection_t. Várias requisições podem referenciar o mesmo objeto de conexão ao mesmo tempo - uma requisição principal e suas sub-requisições. Após uma requisição ser excluída, uma nova requisição pode ser criada na mesma conexão.

    Note que para conexões HTTP o campo data de ngx_connection_t aponta de volta para a requisição. Tais requisições são chamadas ativas, em oposição às outras requisições vinculadas à conexão. Uma requisição ativa é usada para manipular eventos de conexão do cliente e tem permissão para enviar sua resposta ao cliente. Normalmente, cada requisição torna-se ativa em algum ponto para que possa enviar sua saída.

  • ctx — Array de contextos de módulos HTTP. Cada módulo do tipo NGX_HTTP_MODULE pode armazenar qualquer valor (normalmente, um ponteiro para uma estrutura) na requisição. O valor é armazenado no array ctx na posição ctx_index do módulo. As seguintes macros fornecem uma maneira conveniente de obter e definir contextos de requisição:

    • ngx_http_get_module_ctx(r, module) — Retorna o contexto do module

    • ngx_http_set_ctx(r, c, module) — Define c como o contexto do module

  • main_conf, srv_conf, loc_conf — Arrays das configurações da requisição atual. As configurações são armazenadas nas posições ctx_index do módulo.

  • read_event_handler, write_event_handler - Manipuladores de eventos de leitura e escrita para a requisição. Normalmente, tanto os manipuladores de eventos de leitura quanto de escrita para uma conexão HTTP são definidos como ngx_http_request_handler(). Esta função chama os manipuladores read_event_handler e write_event_handler para a requisição atualmente ativa.

  • cache — Objeto de cache da requisição para armazenar em cache a resposta upstream.

  • upstream — Objeto upstream da requisição para proxy.

  • pool — Pool da requisição. O próprio objeto de requisição é alocado neste pool, que é destruído quando a requisição é excluída. Para alocações que precisam estar disponíveis durante toda a vida útil da conexão do cliente, use o pool de ngx_connection_t em vez disso.

  • header_in — Buffer no qual o cabeçalho da requisição HTTP do cliente é lido.

  • headers_in, headers_out — Objetos de cabeçalhos HTTP de entrada e saída. Ambos os objetos contêm o campo headers do tipo ngx_list_t para manter a lista bruta de cabeçalhos. Além disso, cabeçalhos específicos estão disponíveis para obtenção e definição como campos separados, por exemplo content_length_n, status etc.

  • request_body — Objeto do corpo da requisição do cliente.

  • start_sec, start_msec — Ponto no tempo quando a requisição foi criada, usado para rastrear a duração da requisição.

  • method, method_name — Representação numérica e textual do método de requisição HTTP do cliente. Valores numéricos para métodos são definidos em src/http/ngx_http_request.h com as macros NGX_HTTP_GET, NGX_HTTP_HEAD, NGX_HTTP_POST, etc.

  • http_protocol — Versão do protocolo HTTP do cliente em sua forma textual original ("HTTP/1.0", "HTTP/1.1" etc).

  • http_version — Versão do protocolo HTTP do cliente em forma numérica (NGX_HTTP_VERSION_10, NGX_HTTP_VERSION_11, etc.).

  • http_major, http_minor — Versão do protocolo HTTP do cliente em forma numérica dividida em partes maior e menor.

  • request_line, unparsed_uri — Linha de requisição e URI na requisição original do cliente.

  • uri, args, exten — URI, argumentos e extensão de arquivo para a requisição atual. O valor do URI aqui pode diferir do URI original enviado pelo cliente devido à normalização. Durante o processamento da requisição, esses valores podem mudar conforme redirecionamentos internos são executados.

  • main — Ponteiro para um objeto de requisição principal. Este objeto é criado para processar uma requisição HTTP do cliente, em oposição a sub-requisições, que são criadas para executar uma subtarefa específica dentro da requisição principal.

  • parent — Ponteiro para a requisição pai de uma sub-requisição.

  • postponed — Lista de buffers de saída e sub-requisições, na ordem em que são enviados e criados. A lista é usada pelo filtro postpone para fornecer saída consistente da requisição quando partes dela são criadas por sub-requisições.

  • post_subrequest — Ponteiro para um manipulador com o contexto a ser chamado quando uma sub-requisição é finalizada. Não usado para requisições principais.

  • posted_requests — Lista de requisições a serem iniciadas ou retomadas, o que é feito chamando o write_event_handler da requisição. Normalmente, este manipulador contém a função principal da requisição, que primeiro executa as fases da requisição e depois produz a saída.

    Uma requisição é geralmente postada pela chamada ngx_http_post_request(r, NULL). É sempre postada na lista posted_requests da requisição principal. A função ngx_http_run_posted_requests(c) executa todas as requisições que são postadas na requisição principal da requisição ativa da conexão passada. Todos os manipuladores de eventos chamam ngx_http_run_posted_requests, que pode levar a novas requisições postadas. Normalmente, é chamado após invocar um manipulador de leitura ou escrita de uma requisição.

  • phase_handler — Índice da fase atual da requisição.

  • ncaptures, captures, captures_data — Capturas de regex produzidas pela última correspondência de regex da requisição. Uma correspondência de regex pode ocorrer em vários lugares durante o processamento da requisição: busca em mapa, busca de servidor por SNI ou Host HTTP, rewrite, proxy_redirect, etc. Capturas produzidas por uma busca são armazenadas nos campos mencionados acima. O campo ncaptures contém o número de capturas, captures contém os limites das capturas e captures_data contém a string contra a qual o regex foi correspondido e que é usada para extrair capturas. Após cada nova correspondência de regex, as capturas da requisição são redefinidas para conter novos valores.

  • count — Contador de referência da requisição. O campo só faz sentido para a requisição principal. Aumentar o contador é feito por simples r->main->count++. Para diminuir o contador, chame ngx_http_finalize_request(r, rc). A criação de uma sub-requisição e a execução do processo de leitura do corpo da requisição ambos incrementam o contador.

  • subrequests — Nível atual de aninhamento de subrequisições. Cada subrequisição herda o nível de aninhamento de seu pai, diminuído em um. Um erro é gerado se o valor chegar a zero. O valor para a requisição principal é definido pela constante NGX_HTTP_MAX_SUBREQUESTS.

  • uri_changes — Número de mudanças de URI restantes para a requisição. O número total de vezes que uma requisição pode alterar seu URI é limitado pela constante NGX_HTTP_MAX_URI_CHANGES. A cada mudança o valor é decrementado até chegar a zero, momento em que um erro é gerado. Reescritas e redirecionamentos internos para localizações normais ou nomeadas são considerados mudanças de URI.

  • blocked — Contador de bloqueios mantidos na requisição. Enquanto este valor for diferente de zero, a requisição não pode ser terminada. Atualmente, este valor é aumentado por operações AIO pendentes (POSIX AIO e operações de thread) e bloqueio de cache ativo.

  • buffered — Máscara de bits mostrando quais módulos fizeram buffer da saída produzida pela requisição. Vários filtros podem fazer buffer da saída; por exemplo, sub_filter pode fazer buffer de dados devido a uma correspondência parcial de string, copy filter pode fazer buffer de dados devido à falta de buffers de saída livres etc. Enquanto este valor for diferente de zero, a requisição não é finalizada aguardando o flush.

  • header_only — Flag indicando que a saída não requer um corpo. Por exemplo, esta flag é usada por requisições HTTP HEAD.

  • keepalive — Flag indicando se o keepalive da conexão do cliente é suportado. O valor é inferido da versão HTTP e do valor do cabeçalho "Connection".

  • header_sent — Flag indicando que o cabeçalho de saída já foi enviado pela requisição.

  • internal — Flag indicando que a requisição atual é interna. Para entrar no estado interno, uma requisição deve passar por um redirecionamento interno ou ser uma subrequisição. Requisições internas têm permissão para entrar em localizações internas.

  • allow_ranges — Flag indicando que uma resposta parcial pode ser enviada ao cliente, conforme solicitado pelo cabeçalho HTTP Range.

  • subrequest_ranges — Flag indicando que uma resposta parcial pode ser enviada enquanto uma subrequisição está sendo processada.

  • single_range — Flag indicando que apenas um único intervalo contínuo de dados de saída pode ser enviado ao cliente. Esta flag é geralmente definida ao enviar um fluxo de dados, por exemplo de um servidor proxy, e a resposta inteira não está disponível em um buffer.

  • main_filter_need_in_memory, filter_need_in_memory — Flags solicitando que a saída seja produzida em buffers de memória em vez de arquivos. Este é um sinal para o copy filter ler dados de buffers de arquivo mesmo se sendfile estiver habilitado. A diferença entre as duas flags é a localização dos módulos de filtro que as definem. Filtros chamados antes do postpone filter na cadeia de filtros definem filter_need_in_memory, solicitando que apenas a saída da requisição atual venha em buffers de memória. Filtros chamados posteriormente na cadeia de filtros definem main_filter_need_in_memory, solicitando que tanto a requisição principal quanto todas as subrequisições leiam arquivos em memória ao enviar a saída.

  • filter_need_temporary — Flag solicitando que a saída da requisição seja produzida em buffers temporários, mas não em buffers de memória somente leitura ou buffers de arquivo. Isto é usado por filtros que podem alterar a saída diretamente nos buffers onde ela é enviada.

Configuração#

Cada módulo HTTP pode ter três tipos de configuração:

  • Configuração principal — Aplica-se a todo o bloco http. Funciona como configurações globais para um módulo.

  • Configuração de servidor — Aplica-se a um único bloco server. Funciona como configurações específicas do servidor para um módulo.

  • Configuração de localização — Aplica-se a um único bloco location, if ou limit_except. Funciona como configurações específicas da localização para um módulo.

As estruturas de configuração são criadas na etapa de configuração do Angie chamando funções, que alocam as estruturas, as inicializam e as mesclam. O exemplo a seguir mostra como criar uma configuração de localização simples para um módulo. A configuração tem uma configuração, foo, do tipo unsigned integer.

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 visto no exemplo, a função ngx_http_foo_create_loc_conf() cria uma nova estrutura de configuração, e ngx_http_foo_merge_loc_conf() mescla uma configuração com configuração de um nível superior. Na verdade, as configurações de servidor e localização não existem apenas nos níveis de servidor e localização, mas também são criadas para todos os níveis acima deles. Especificamente, uma configuração de servidor também é criada no nível principal e configurações de localização são criadas nos níveis principal, servidor e localização. Essas configurações tornam possível especificar configurações específicas de servidor e localização em qualquer nível de um arquivo de configuração do Angie. Eventualmente as configurações são mescladas para baixo. Várias macros como NGX_CONF_UNSET e NGX_CONF_UNSET_UINT são fornecidas para indicar uma configuração ausente e ignorá-la durante a mesclagem. Macros de mesclagem padrão do Angie como ngx_conf_merge_value() e ngx_conf_merge_uint_value() fornecem uma maneira conveniente de mesclar uma configuração e definir o valor padrão se nenhuma das configurações forneceu um valor explícito. Para uma lista completa de macros para diferentes tipos, veja src/core/ngx_conf_file.h.

As seguintes macros estão disponíveis para acessar configuração para módulos HTTP no momento da configuração. Todas elas recebem uma referência ngx_conf_t como primeiro 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)

O exemplo a seguir obtém um ponteiro para uma configuração de localização do módulo core padrão do Angie e substitui o manipulador de conteúdo de localização mantido no campo handler da estrutura.

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

As seguintes macros estão disponíveis para acessar configuração para módulos HTTP em tempo de execução.

  • ngx_http_get_module_main_conf(r, module)

  • ngx_http_get_module_srv_conf(r, module)

  • ngx_http_get_module_loc_conf(r, module)

Essas macros recebem uma referência para uma requisição HTTP ngx_http_request_t. A configuração principal de uma requisição nunca muda. A configuração do servidor pode mudar do padrão após o servidor virtual para a requisição ser escolhido. A configuração de localização selecionada para processar uma requisição pode mudar múltiplas vezes como resultado de uma operação de reescrita ou redirecionamento interno. O exemplo a seguir mostra como acessar a configuração HTTP de um módulo em tempo de execução.

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 requisição HTTP passa por uma sequência de fases. Em cada fase um tipo distinto de processamento é executado na requisição. Manipuladores específicos de módulo podem ser registrados na maioria das fases, e muitos módulos padrão do Angie registram seus manipuladores de fase como uma forma de serem chamados em um estágio específico do processamento da requisição. As fases são processadas sucessivamente e os manipuladores de fase são chamados uma vez que a requisição atinge a fase. A seguir está a lista das fases HTTP do Angie.

  • NGX_HTTP_POST_READ_PHASE — Primeira fase. O módulo RealIP registra seu manipulador nesta fase para habilitar substituição de endereços de cliente antes que qualquer outro módulo seja invocado.

  • NGX_HTTP_SERVER_REWRITE_PHASE — Fase onde diretivas de reescrita definidas em um bloco server (mas fora de um bloco location) são processadas. O módulo Rewrite instala seu manipulador nesta fase.

  • NGX_HTTP_FIND_CONFIG_PHASE — Fase especial onde uma localização é escolhida baseada na URI da requisição. Antes desta fase, a localização padrão para o servidor virtual relevante é atribuída à requisição, e qualquer módulo solicitando uma configuração de localização recebe a configuração para a localização padrão do servidor. Esta fase atribui uma nova localização à requisição. Nenhum manipulador adicional pode ser registrado nesta fase.

  • NGX_HTTP_REWRITE_PHASE — Igual a NGX_HTTP_SERVER_REWRITE_PHASE, mas para regras de reescrita definidas na localização, escolhida na fase anterior.

  • NGX_HTTP_POST_REWRITE_PHASE — Fase especial onde a requisição é redirecionada para uma nova localização se sua URI mudou durante uma reescrita. Isso é implementado pela requisição passando pela NGX_HTTP_FIND_CONFIG_PHASE novamente. Nenhum manipulador adicional pode ser registrado nesta fase.

  • NGX_HTTP_PREACCESS_PHASE — Uma fase comum para diferentes tipos de manipuladores, não associados com controle de acesso. Os módulos padrão do Angie Limit Conn e Limit Req registram seus manipuladores nesta fase.

  • NGX_HTTP_ACCESS_PHASE — Fase onde é verificado se o cliente está autorizado a fazer a requisição. Módulos padrão do Angie como Access e Auth Basic registram seus manipuladores nesta fase. Por padrão o cliente deve passar na verificação de autorização de todos os manipuladores registrados nesta fase para que a requisição continue para a próxima fase. A diretiva satisfy pode ser usada para permitir que o processamento continue se qualquer um dos manipuladores de fase autorizar o cliente.

  • NGX_HTTP_POST_ACCESS_PHASE — Fase especial onde a diretiva satisfy any é processada. Se alguns manipuladores de fase de acesso negaram acesso e nenhum explicitamente permitiu, a requisição é finalizada. Nenhum manipulador adicional pode ser registrado nesta fase.

  • NGX_HTTP_PRECONTENT_PHASE — Fase para manipuladores serem chamados antes de gerar conteúdo. Módulos padrão como try_files e Mirror registram seus manipuladores nesta fase.

  • NGX_HTTP_CONTENT_PHASE — Fase onde a resposta é normalmente gerada. Múltiplos módulos padrão do Angie registram seus manipuladores nesta fase, incluindo Index. Eles são chamados sequencialmente até que um deles produza a saída. Também é possível definir manipuladores de conteúdo por localização. Se a configuração de localização do Módulo HTTP tem handler definido, ele é chamado como o manipulador de conteúdo e os manipuladores instalados nesta fase são ignorados.

  • NGX_HTTP_LOG_PHASE — Fase onde o registro de requisições é executado. Atualmente, apenas o módulo Log registra seu manipulador neste estágio para registro de acesso. Manipuladores de fase de log são chamados no final do processamento da requisição, logo antes de liberar a requisição.

A seguir está o exemplo de um manipulador de fase de pré-acesso.

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

Espera-se que os manipuladores de fase retornem códigos específicos:

  • NGX_OK — Prosseguir para a próxima fase.

  • NGX_DECLINED — Prosseguir para o próximo manipulador da fase atual. Se o manipulador atual for o último na fase atual, mover para a próxima fase.

  • NGX_AGAIN, NGX_DONE — Suspender o processamento da fase até algum evento futuro que pode ser uma operação de E/S assíncrona ou apenas um atraso, por exemplo. Assume-se que o processamento da fase será retomado posteriormente chamando ngx_http_core_run_phases().

  • Qualquer outro valor retornado pelo manipulador de fase é tratado como um código de finalização da requisição, em particular, um código de resposta HTTP. A requisição é finalizada com o código fornecido.

Para algumas fases, os códigos de retorno são tratados de forma ligeiramente diferente. Na fase de conteúdo, qualquer código de retorno diferente de NGX_DECLINED é considerado um código de finalização. Qualquer código de retorno dos manipuladores de conteúdo de localização é considerado um código de finalização. Na fase de acesso, no modo satisfy any, qualquer código de retorno diferente de NGX_OK, NGX_DECLINED, NGX_AGAIN, NGX_DONE é considerado uma negação. Se nenhum manipulador de acesso subsequente permitir ou negar acesso com um código diferente, o código de negação se tornará o código de finalização.

Exemplos#

O repositório nginx-dev-examples fornece exemplos de módulos do Angie.

Estilo de código#

Regras gerais#

  • largura máxima do texto é 80 caracteres

  • indentação é 4 espaços

  • sem tabs, sem espaços no final

  • elementos de lista na mesma linha são separados com espaços

  • literais hexadecimais são minúsculos

  • nomes de arquivos, funções e tipos, e variáveis globais têm o prefixo ngx_ ou mais específico como ngx_http_ e ngx_mail_

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

    last = p + n;

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

        c = *p;

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

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

    return len;
}

Arquivos#

Um arquivo fonte típico pode conter as seguintes seções separadas por duas linhas vazias:

  • declarações de copyright

  • includes

  • definições do pré-processador

  • definições de tipos

  • protótipos de funções

  • definições de variáveis

  • definições de funções

As declarações de copyright ficam assim:

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

Se o arquivo for modificado significativamente, a lista de autores deve ser atualizada, o novo autor é adicionado ao topo.

Os arquivos ngx_config.h e ngx_core.h são sempre incluídos primeiro, seguidos por um de ngx_http.h, ngx_stream.h, ou ngx_mail.h. Então seguem arquivos de cabeçalho externos opcionais:

#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

Arquivos de cabeçalho devem incluir a chamada "proteção de cabeçalho":

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

Comentários#

  • comentários "//" não são usados

  • texto é escrito em inglês, ortografia americana é preferida

  • comentários multi-linha são formatados assim:

    /*
     * 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 */
    

Pré-processador#

Nomes de macro começam com prefixo ngx_ ou NGX_ (ou mais específico). Nomes de macro para constantes são maiúsculos. Macros parametrizadas e macros para inicializadores são minúsculas. O nome da macro e o valor são separados por pelo menos dois espaços:

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

Condições ficam dentro de parênteses, negação fica fora:

#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#

Nomes de tipos terminam com o sufixo "_t". Um nome de tipo definido é separado por pelo menos dois espaços:

typedef ngx_uint_t  ngx_rbtree_key_t;

Tipos de estrutura são definidos usando typedef. Dentro de estruturas, tipos de membros e nomes são alinhados:

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

Mantenha alinhamento idêntico entre diferentes estruturas no arquivo. Uma estrutura que aponta para si mesma tem o nome terminando com "_s". Definições de estruturas adjacentes são separadas com duas linhas vazias:

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 membro da estrutura é declarado em sua própria linha:

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

Ponteiros de função dentro de estruturas têm tipos definidos terminando com "_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;

Enumerações têm tipos terminando com "_e":

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

Variáveis#

Variáveis são declaradas ordenadas por comprimento do tipo base, depois alfabeticamente. Nomes de tipos e nomes de variáveis são alinhados. As "colunas" de tipo e nome são separadas com dois espaços. Arrays grandes são colocados no final de um bloco de declaração:

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

Variáveis estáticas e globais podem ser inicializadas na declaração:

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

Existe um conjunto de combinações tipo/nome comumente usadas:

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;

Funções#

Todas as funções (mesmo as estáticas) devem ter protótipos. Os protótipos incluem nomes de argumentos. Protótipos longos são quebrados com uma única indentação nas linhas de continuação:

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

O nome da função em uma definição começa com uma nova linha. As chaves de abertura e fechamento do corpo da função ficam em linhas separadas. O corpo de uma função é indentado. Há duas linhas vazias entre as funções:

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)
{
    ...
}

Não há espaço após o nome da função e o parêntese de abertura. Chamadas de função longas são quebradas de forma que as linhas de continuação comecem na posição do primeiro argumento da função. Se isso for impossível, formate a primeira linha de continuação de forma que ela termine na posição 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 *));

A macro ngx_inline deve ser usada em vez de inline:

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

Expressões#

Operadores binários exceto "." e "->" devem ser separados de seus operandos por um espaço. Operadores unários e subscritos não são separados de seus operandos por espaços:

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

Conversões de tipo são separadas por um espaço das expressões convertidas. Um asterisco dentro da conversão de tipo é separado com espaço do nome do tipo:

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

Se uma expressão não cabe em uma única linha, ela é quebrada. O ponto preferido para quebrar uma linha é um operador binário. A linha de continuação é alinhada com o início da expressão:

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, é possível quebrar uma expressão de forma que a linha de continuação termine na posição 79:

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

As regras acima também se aplicam a sub-expressões, onde cada sub-expressão tem seu próprio nível de indentação:

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

Às vezes, é conveniente quebrar uma expressão após uma conversão. Neste caso, a linha de continuação é indentada:

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

Ponteiros são explicitamente comparados com NULL (não 0):

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

Condicionais e Loops#

A palavra-chave "if" é separada da condição por um espaço. A chave de abertura fica na mesma linha, ou em uma linha dedicada se a condição ocupar várias linhas. A chave de fechamento fica em uma linha dedicada, opcionalmente seguida por "else if / else". Geralmente, há uma linha vazia antes da 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;
    }
}

Regras de formatação similares são aplicadas aos loops "do" e "while":

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

A palavra-chave "switch" é separada da condição por um espaço. A chave de abertura fica na mesma linha. A chave de fechamento fica em uma linha dedicada. As palavras-chave "case" são alinhadas com "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;
}

A maioria dos loops "for" são formatados assim:

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

Se alguma parte da instrução "for" for omitida, isso é indicado pelo comentário "/* void */":

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

Um loop com corpo vazio também é indicado pelo comentário "/* void */" que pode ser colocado na mesma linha:

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

Um loop infinito fica assim:

for ( ;; ) {
    ...
}

Rótulos#

Rótulos são cercados por linhas vazias e são indentados no nível 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;

Depurando problemas de memória#

Para depurar problemas de memória como estouro de buffer ou erros de uso após liberação, você pode usar o AddressSanitizer (ASan) suportado por alguns compiladores modernos. Para habilitar o ASan com gcc e clang, use a opção de compilador e linker -fsanitize=address. Ao compilar o Angie, isso pode ser feito adicionando a opção aos parâmetros --with-cc-opt e --with-ld-opt do script configure.

Como a maioria das alocações no Angie são feitas a partir do pool interno do Angie, habilitar o ASan pode nem sempre ser suficiente para depurar problemas de memória. O pool interno aloca um grande bloco de memória do sistema e corta alocações menores dele. No entanto, esse mecanismo pode ser desabilitado definindo a macro NGX_DEBUG_PALLOC como 1. Neste caso, as alocações são passadas diretamente para o alocador do sistema dando a ele controle total sobre os limites dos buffers.

A seguinte linha de configuração resume as informações fornecidas acima. É recomendada durante o desenvolvimento de módulos de terceiros e teste do Angie em diferentes plataformas.

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

Armadilhas Comuns#

Escrevendo um módulo C#

A armadilha mais comum é uma tentativa de escrever um módulo C completo quando isso pode ser evitado. Na maioria dos casos sua tarefa pode ser realizada criando uma configuração adequada. Se escrever um módulo for inevitável, tente torná-lo o mais pequeno e simples possível. Por exemplo, um módulo pode apenas exportar algumas variáveis.

Antes de começar um módulo, considere as seguintes questões:

Strings C#

O tipo de string mais usado no Angie, ngx_str_t não é uma string terminada em zero no estilo C. Você não pode passar os dados para funções padrão da biblioteca C como strlen() ou strstr(). Em vez disso, devem ser usadas as contrapartes do Angie que aceitam ngx_str_t ou ponteiro para dados e um comprimento. No entanto, há um caso em que ngx_str_t contém um ponteiro para uma string terminada em zero: strings que vêm como resultado da análise do arquivo de configuração são terminadas em zero.

Variáveis Globais#

Evite usar variáveis globais em seus módulos. Muito provavelmente é um erro ter uma variável global. Qualquer dado global deve estar vinculado a um ciclo de configuração e ser alocado do pool de memória correspondente. Isso permite ao Angie realizar recarregamentos de configuração suaves. Uma tentativa de usar variáveis globais provavelmente quebrará essa funcionalidade, porque será impossível ter duas configurações ao mesmo tempo e se livrar delas. Às vezes variáveis globais são necessárias. Neste caso, atenção especial é necessária para gerenciar a reconfiguração adequadamente. Além disso, verifique se as bibliotecas usadas pelo seu código têm estado global implícito que pode ser quebrado no recarregamento.

Gerenciamento Manual de Memória#

Em vez de lidar com a abordagem malloc/free que é propensa a erros, aprenda como usar os pools do Angie. Um pool é criado e vinculado a um objeto - configuração, ciclo, conexão, ou requisição HTTP. Quando o objeto é destruído, o pool associado também é destruído. Então ao trabalhar com um objeto, é possível alocar a quantidade necessária do pool correspondente e não se preocupar em liberar memória mesmo em caso de erros.

Threads#

É recomendado evitar usar threads no Angie porque isso definitivamente quebrará as coisas: a maioria das funções do Angie não são thread-safe. É esperado que uma thread execute apenas chamadas de sistema e funções de biblioteca thread-safe. Se você precisar executar algum código que não está relacionado ao processamento de requisições de cliente, a maneira adequada é agendar um timer no manipulador de módulo init_process e executar as ações necessárias no manipulador do timer. Internamente o Angie faz uso de threads para acelerar operações relacionadas a IO, mas este é um caso especial com muitas limitações.

Bibliotecas Bloqueantes#

Um erro comum é usar bibliotecas que são bloqueantes internamente. A maioria das bibliotecas por aí são síncronas e bloqueantes por natureza. Em outras palavras, elas executam uma operação por vez e desperdiçam tempo esperando resposta de outro peer. Como resultado, quando uma requisição é processada com tal biblioteca, todo o worker do Angie é bloqueado, destruindo assim a performance. Use apenas bibliotecas que fornecem interface assíncrona e não bloqueiam todo o processo.

Requisições HTTP para Serviços Externos#

Frequentemente módulos precisam executar uma chamada HTTP para algum serviço externo. Um erro comum é usar alguma biblioteca externa, como libcurl, para executar a requisição HTTP. É absolutamente desnecessário trazer uma quantidade enorme de código externo (provavelmente bloqueante!) para a tarefa que pode ser realizada pelo próprio Angie.

Há dois cenários básicos de uso quando uma requisição externa é necessária:

  • no contexto de processamento de uma requisição de cliente (por exemplo, em manipulador de conteúdo)

  • no contexto de um processo worker (por exemplo, manipulador de timer)

No primeiro caso, o melhor é usar a API de subrequests. Em vez de acessar diretamente o serviço externo, você declara uma localização na configuração do Angie e direciona sua subrequest para esta localização. Esta localização não está limitada a fazer proxy de requisições, mas pode conter outras diretivas do Angie. Um exemplo de tal abordagem é a diretiva auth_request implementada em Auth Request.

Para o segundo caso, é possível usar a funcionalidade básica de cliente HTTP disponível no Angie. Por exemplo, o módulo OCSP implementa um cliente HTTP simples.