개발 가이드

소개

코드 레이아웃

  • auto — 빌드 스크립트
  • src
    • core — 기본 유형 및 함수 — 문자열, 배열, 로그, 풀 등
    • event — 이벤트 코어
      • modules — 이벤트 알림 모듈: epoll, kqueue, select 등
    • http — 코어 HTTP 모듈 및 공통 모듈
      • modules — 기타 HTTP 모듈
      • v2 — HTTP/2
    • mail — 메일 모듈
    • os — 플랫폼별 코드
      • unix
      • win32
    • stream — 스트림 모듈

파일 포함

다음의 #include 문 2개를 모든 nginx 파일 시작 시 표시해야 합니다.

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

또한, HTTP 코드에 다음을 포함해야 합니다.

#include <ngx_http.h>

메일 코드에 다음을 포함해야 합니다.

#include <ngx_mail.h>

스트림 코드에 다음을 포함해야 합니다.

#include <ngx_stream.h>

정수

일반적인 경우, nginx 코드는 ngx_int_t와 ngx_uint_t의 두 가지 정수 유형을 사용하며 이들은 각각 intptr_t 및 uintptr_t에 대한 유형 정의입니다.

공통적인 반환 코드

nginx의 대부분 함수는 다음의 코드를 반환합니다.

  • NGX_OK — 작업이 성공했습니다.
  • NGX_ERROR — 작업이 실패했습니다.
  • NGX_AGAIN — 작업이 완료되지 않았습니다. 함수를 다시 호출하세요.
  • NGX_DECLINED — 구성에서 비활성화되는 등의 이유로 인해 작업이 거부되었습니다. 이는 오류가 아닙니다.
  • NGX_BUSY — 리소스를 사용할 수 없습니다.
  • NGX_DONE — 작업이 완료되었거나 다른 곳에서 진행 중입니다. 대체 성공 코드로도 사용합니다.
  • NGX_ABORT — 함수가 중단되었습니다. 대체 오류 코드로도 사용합니다.

오류 처리

ngx_errno 매크로는 마지막 시스템 오류 코드를 반환합니다. POSIX 플랫폼에서 errno로 매핑되고, Windows에서 GetLastError() 호출로 매핑됩니다. ngx_socket_errno 매크로가 마지막 소켓 오류 번호를 반환합니다. ngx_errno 매크로와 마찬가지로 POSIX 플랫폼에서 errno로 매핑됩니다. Windows에서 WSAGetLastError() 호출로 매핑됩니다. 행에서 ngx_errno 또는 ngx_socket_errno 값에 2번 이상 액세스하면 성능에 문제가 발생합니다. 이 오류 값을 여러 번 사용할 경우, ngx_err_t 유형의 로컬 변수에 저장하세요. 오류를 설정하려면 ngx_set_errno(errno) 및 ngx_set_socket_errno(errno) 매크로를 사용하세요.

ngx_errno 및 ngx_socket_errno 값을 로깅 함수 ngx_log_error()와 ngx_log_debugX()로 전달할 수 있을 경우, 시스템 오류 텍스트가 로그 메시지에 추가됩니다.

예를 들어 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;
}

문자열

개요

C 문자열의 경우, nginx는 비부호화 문자 유형 포인터 u_char *을 사용합니다.

nginx 문자열 유형 ngx_str_t는 다음과 같이 정의합니다.

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

len 필드는 문자열 길이를 저장하고, data는 문자열 데이터를 저장합니다. ngx_str_t에 저장된 문자열은 len 바이트 이후 null 종료되거나 종료되지 않을 수 있습니다. 대부분은 null 종료되지 않습니다. 그러나 코드의 특정 부분(예: 파싱 구성)에서 ngx_str_t 객체는 null 종료됩니다. 따라서 문자열을 간단히 비교할 수 있고, 문자열을 syscall로 전달하기 쉽습니다.

nginx의 문자열 연산은 src/core/ngx_string.h에서 선언합니다. 그중 일부는 표준 C 함수를 감싸는 래퍼입니다.

  • ngx_strcmp()
  • ngx_strncmp()
  • ngx_strstr()
  • ngx_strlen()
  • ngx_strchr()
  • ngx_memcmp()
  • ngx_memset()
  • ngx_memcpy()
  • ngx_memmove()

다른 문자열 함수는 nginx 전용입니다.

  • ngx_memzero() — 0으로 메모리를 채웁니다.
  • ngx_explicit_memzero() — ngx_memzero()와 동일한 기능을 하지만, 이 호출은 컴파일러의 데드 스토어 제거 최적화로 제거되지 않습니다. 이 함수는 비밀번호, 키 등의 민감한 데이터를 삭제하는 데 사용할 수 있습니다.
  • ngx_cpymem() — ngx_memcpy()와 동일한 기능을 하지만, 최종 대상 주소를 반환합니다. 여러 문자열을 한 행에 첨부할 때 편리합니다.
  • ngx_movemem() — ngx_memmove()와 동일한 기능을 하지만, 최종 대상 주소를 반환합니다.
  • ngx_strlchr() — 문자열로 된 문자를 검색하고, 두 개의 포인터로 구분합니다.

다음의 함수는 대소문자를 변환하고 비교합니다.

  • ngx_tolower()
  • ngx_toupper()
  • ngx_strlow()
  • ngx_strcasecmp()
  • ngx_strncasecmp()

다음의 매크로는 문자열 초기화를 단순화합니다.

  • ngx_string(text) — C 문자열 리터럴 text의 ngx_str_t 유형에 대한 고정 이니셜라이저
  • ngx_null_string — ngx_str_t 유형에 대한 고정 빈 문자열 이니셜라이저
  • ngx_str_set(str, text) — C 문자열 리터럴 text로 ngx_str_t *의 문자열 str 초기화 
  • ngx_str_null(str) — 빈 문자열로 ngx_str_t * 유형의 문자열 str 초기화

서식 지정

다음의 서식 지정 함수는 nginx 고유의 유형을 지원합니다.

  • ngx_sprintf(buf, fmt, ...)
  • ngx_snprintf(buf, max, fmt, ...)
  • ngx_slprintf(buf, last, fmt, ...)
  • ngx_vslprintf(buf, last, fmt, args)
  • ngx_vsnprintf(buf, max, fmt, args)

이 함수에서 지원하는 전체 서식 지정 옵션 목록은 src/core/ngx_string.c에 있습니다. 그중 일부는 다음과 같습니다.

  • %O — off_t
  • %T — time_t
  • %z — ssize_t
  • %i — ngx_int_t
  • %p — void *
  • %V — ngx_str_t *
  • %s — u_char * (null-terminated)
  • %*s — size_t + u_char *

대부분 유형에서 u를 앞에 붙여서 비부호화할 수 있습니다. 출력을 16진수로 변환하려면 X 또는 x를 사용합니다.

예:

u_char      buf[NGX_INT_T_LEN];
size_t      len;
ngx_uint_t  n;

/* set n here */

len = ngx_sprintf(buf, "%ui", n) — buf;

숫자 변환

nginx에는 숫자 변환을 위한 여러 가지 함수가 구현되어 있습니다. 처음의 4개는 각각 특정 길이의 문자열을 지정된 유형의 양의 정수로 변환합니다. 오류 발생 시 NGX_ERROR를 반환합니다.

  • ngx_atoi(line, n) — ngx_int_t
  • ngx_atosz(line, n) — ssize_t
  • ngx_atoof(line, n) — off_t
  • ngx_atotm(line, n) — time_t

숫자 변환 함수는 2개가 더 있습니다. 처음의 4개와 마찬가지로, 오류 발생 시 NGX_ERROR를 반환합니다.

  • ngx_atofp(line, n, point) — 특정 길이의 고정 포인트 부동 소수점 숫자를 ngx_int_t 유형의 양의 정수로 변환합니다. 결과는 point 소수점으로 좌측 이동합니다. 숫자의 문자열 표현은 소수점 points 자리 이내여야 합니다. 예를 들어 ngx_atofp(“10.5”, 4, 2)는 1050을 반환합니다.
  • ngx_hextoi(line, n) — 양의 정수의 16진수 표현을 ngx_int_t로 변환합니다.

정규식

nginx의 정규식 인터페이스는 PCRE 라이브러리에 대한 래퍼입니다. 해당 헤더 파일은 src/core/ngx_regex.h입니다.

문자열 일치에서 정규식을 사용하려면 먼저 컴파일해야 합니다. 일반적으로 이 작업은 구성 단계에서 실행합니다. PCRE 지원은 선택 사항이므로, 이 인터페이스를 사용하는 모든 코드는 주변의 NGX_PCRE 매크로로 보호해야 합니다.

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

u_char                errstr[NGX_MAX_CONF_ERRSTR];

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

ngx_memzero(&rc, sizeof(ngx_regex_compile_t));

rc.pattern = value;
rc.pool = cf->pool;
rc.err.len = NGX_MAX_CONF_ERRSTR;
rc.err.data = errstr;
/* rc.options are passed as is to pcre_compile() */

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.re

컴파일이 성공하고 나면 ngx_regex_compile_t 구조의 captures 및 named_captures 필드에 정규식에서 발견된 모든 캡처 및 이름이 지정된 캡처 수가 각각 포함됩니다.

컴파일된 정규식은 문자열 매칭에 사용할 수 있습니다.

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

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

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

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

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

ngx_regex_exec()에 대한 인수는 컴파일된 정규식 re, input을 매칭할 문자열, 발견된 captures를 저장할 선택적 정수 배열, 해당 배열의 size입니다. captures 배열의 크기는 PCRE API에서 요구하듯이 3의 배수여야 합니다. 예시에서 크기는 매칭된 문자열 자체에 대해 캡처의 총 개수에 1을 더한 값으로 계산합니다.

일치가 있는 경우, 캡처는 다음과 같이 액세스할 수 있습니다.

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

ngx_regex_exec_array() 함수는 ngx_regex_elt_t 요소(관련 이름으로 컴파일된 정규식), 매칭할 문자열, 로그의 배열을 받습니다. 이 함수는 일치를 발견하거나 식이 남지 않을 때까지 배열에서 얻은 식을 문자열에 적용합니다. 일치가 있을 경우 반환 값은 NGX_OK입니다. 그 외에 다른 경우는 NGX_DECLINED를 반환합니다. 오류가 발생하면 NGX_ERROR를 반환합니다.

시간

ngx_time_t 구조는 초, 밀리초, GMT 오프셋에 대한 3가지 유형으로 시간을 표현합니다.

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

ngx_tm_t 구조는 UNIX 플랫폼에서 struct tm에 대한 별칭이고, Windows에서 SYSTEMTIME에 대한 별칭입니다.

일반적으로 현재 시간을 알아내려면 사용 가능한 전역 변수 중 하나에 액세스하여 원하는 형식으로 캐싱된 시간 값을 표현하는 것으로 충분합니다.

사용 가능한 문자열 표현은 다음과 같습니다.

  • ngx_cached_err_log_time — 오류 로그 항목에 사용: “1970/09/28 12:00:00”
  • ngx_cached_http_log_time — HTTP 액세스 로그 항목에 사용: “28/Sep/1970:12:00:00 +0600”
  • ngx_cached_syslog_time — syslog 항목에 사용: “Sep 28 12:00:00”
  • ngx_cached_http_time — HTTP 헤더에 사용: “Mon, 28 Sep 1970 06:00:00 GMT”
  • ngx_cached_http_log_iso8601 — ISO 8601 표준 형식: “1970-09-28T12:00:00+06:00”

ngx_time() 및 ngx_timeofday() 매크로는 현재 시간 값(초)을 반환하고 캐싱된 시간 값을 사용할 때 주로 사용됩니다.

명시적으로 시간을 가져오려면 ngx_gettimeofday()를 사용하여 인수(struct timeval에 대한 포인터)를 업데이트합니다. nginx가 시스템 호출에서 이벤트 루프로 돌아갈 때 항상 시간이 업데이트됩니다. 시간을 즉시 업데이트하려면 ngx_time_update()를 호출합니다. 신호 핸들러 컨텍스트에서 시간을 업데이트할 경우 ngx_time_sigsafe_update()를 호출합니다.

다음의 함수는 time_t을 지정된 세부 시간 표현으로 변환합니다. 각 쌍의 첫 함수는 time_t를 ngx_tm_t로 변환하고, 두 번째 함수(_libc_ 이항 포함)를 struct tm으로 변환합니다.

  • ngx_gmtime(), ngx_libc_gmtime() — UTC로 표현된 시간
  • ngx_localtime(), ngx_libc_localtime() — 로컬 시간대에 상대적으로 표현된 시간

ngx_http_time(buf, time) 함수는 HTTP 헤더에서 사용하기 적절한 문자열 표현을 반환합니다(예: “Mon, 28 Sep 1970 06:00:00 GMT”). ngx_http_cookie_time(buf, time)은 문자열 표현을 반환합니다. 이 함수는 HTTP 쿠키에 적절한 문자열 표현을 반환합니다(“Thu, 31-Dec-37 23:55:55 GMT”).

컨테이너

배열

nginx 배열 유형 ngx_array_t는 다음과 같이 정의됩니다.

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

배열의 요소는 elts 필드에서 제공됩니다. nelts 필드에는 요소의 개수가 저장됩니다. size 필드에는 요소 하나의 크기가 저장되고, 배열을 초기화할 때 설정됩니다.

ngx_array_create(pool, n, size) 호출을 사용하여 풀에서 배열을 생성하고, ngx_array_init(array, pool, n, size) 호출을 사용하여 이미 할당된 배열 객체를 초기화합니다.

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

다음의 함수를 사용하여 배열에 요소를 추가합니다.

  • ngx_array_push(a)는 테일 요소 1개를 추가하고 여기에 대한 포인터를 반환합니다.
  • ngx_array_push_n(a, n)은 n 테일 요소를 추가하고 첫 번째 요소에 대한 포인터를 반환합니다.

현재 할당된 메모리 용량이 새로운 요소보다 작을 경우, 새 메모리 블록이 할당되고 기존 요소가 복사됩니다. 일반적으로 새 메모리 블록은 기존 블록보다 두 배 큽니다.

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

목록

nginx에서 목록은 다수가 될 가능성이 있는 항목을 삽입하기에 최적화된 배열로 구성됩니다. ngx_list_t 목록 유형은 다음과 같이 정의됩니다.

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;

목록 부분에 저장되는 실제 항목은 다음과 같이 정의됩니다.

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

목록을 사용하기 전에 ngx_list_init(list, pool, n, size)를 호출하여 초기화하거나, ngx_list_create(pool, n, size)를 호출하여 생성해야 합니다. 두 함수는 단일 항목의 크기와 목록 부분당 개수를 인수로 받아야 합니다. 항목을 목록에 추가하려면 ngx_list_push(list) 함수를 사용합니다. 항목에 대해 이터레이션을 실행하려면 예시와 같이 목록 필드에 직접 액세스하세요.

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

목록은 주로 HTTP 입력과 출력 헤더에 사용하지 않습니다.

목록은 항목 제거를 지원하지 않습니다. 그러나 필요할 경우, 실제로 목록에서 제거하지 않더라도 항목을 내부적으로 ‘누락’으로 표시할 수 있습니다. 예를 들어 HTTP 출력 헤더(ngx_table_elt_t 객체로 저장)를 누락으로 표시하려면 ngx_table_elt_t의 hash 필드를  0으로 설정합니다. 이렇게 표시된 항목은 헤더를 이터레이션할 때 명시적으로 건너뜁니다.

대기열

nginx에서 대기열은 직접적인 이중 링크된 목록으로, 각 노드는 다음과 같이 정의됩니다.

typedef struct ngx_queue_s  ngx_queue_t;

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

헤드 대기열 노드는 데이터와 링크되지 않습니다. ngx_queue_init(q) 함수를 사용하여 사용 전에 목록 헤드를 초기화합니다. 대기열은 다음의 작업을 지원합니다.

  • ngx_queue_insert_head(h, x), ngx_queue_insert_tail(h, x) — 새 노드 삽입
  • ngx_queue_remove(x) — 대기열 노드 제거
  • ngx_queue_split(h, q, n) — 노드에서 대기열을 분할하여, 대기열 테일을 별도의 대기열로 반환
  • ngx_queue_add(h, n) — 두 번째 대기열을 첫 대기열에 추가
  • ngx_queue_head(h), ngx_queue_last(h) — 첫 번째 또는 마지막 대기열 노드 가져오기
  • ngx_queue_sentinel(h) – 이터레이션을 종료할 대기열 센티널 객체를 가져오기
  • ngx_queue_data(q, type, link) — 대기열 필드 오프셋을 고려하여 대기열 노드 데이터 구조 시작에 대한 참조를 가져오기

예:

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

레드 블랙 트리

src/core/ngx_rbtree.h 헤더 파일은 레드 블랙 트리의 효과적인 구현에 액세스를 제공합니다.

typedef struct {
    ngx_rbtree_t       rbtree;
    ngx_rbtree_node_t  sentinel;

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

typedef struct {
    ngx_rbtree_node_t  rbnode;

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

트리 전체를 처리하려면 루트와 센티널, 이렇게 두 가지 노드가 필요합니다. 일반적으로 이들은 사용자 정의 구조에 추가되어, 잎에 데이터 링크나 데이터를 포함하는 트리로 데이터를 구성할 수 있습니다.

트리 초기화 방법:

my_tree_t  root;

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

트리를 가로질러 새 값을 삽입하려면 “insert_value” 함수를 사용합니다. 예를 들어 ngx_str_rbtree_insert_value 함수는 ngx_str_t 유형을 처리합니다. 인수는 삽입하는 루트 노드에 대한 포인터, 새로 생성하여 추가할 노드, 트리 센티널입니다.

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

순회는 상당히 간단하고 다음의 검색 함수 패턴으로 보여줄 수 있습니다.

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

compare() 함수는 0보다 작거나, 같거나, 큰 값을 반환하는 전형적인 비교 함수입니다. 검색 속도를 높이고 값이 큰 사용자 객체를 비교하지 않도록 정수 해시 필드를 사용합니다.

트리에 노드를 추가하려면 add a 새 노드를 할당하여 초기화하고 ngx_rbtree_insert()를 호출합니다.

    my_node_t          *my_node;
    ngx_rbtree_node_t  *node;

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

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

    ngx_rbtree_insert(&root->rbtree, node);

노드를 제거하려면 ngx_rbtree_delete() 함수를 호출합니다.

ngx_rbtree_delete(&root->rbtree, node);

해시

해시 테이블 함수는 src/core/ngx_hash.h에서 선언됩니다. 정확한 일치와 와일드카드 일치가 모두 지원됩니다. 와일드카드 일치는 추가적 설정이 필요하고, 아래의 별도 섹션에서 설명합니다.

해시를 초기화하기 전에 nginx를 최적으로 구축하려면 요소가 몇 개 필요한지 알아야 합니다. 구성이 필요한 매개변수는 max_size와 bucket_size가 있으며, 별도의 문서에서 자세히 설명합니다. 일반적으로는 사용자가 구성합니다. 해시 초기화 설정은 ngx_hash_init_t 유형으로 저장되고, 해시 자체는 ngx_hash_t입니다.

ngx_hash_t       foo_hash;
ngx_hash_init_t  hash;

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

key는 문자열에서 해시 정수 키를 생성하는 함수에 대한 포인터입니다. 일반 키-생성 함수는 두 개가 있는데, ngx_hash_key(data, len)와 ngx_hash_key_lc(data, len)입니다. 두 번째 함수는 문자열을 모두 소문자로 변환하므로 전달된 문자열은 쓰기가 가능해야 합니다. 그렇지 않을 경우, NGX_HASH_READONLY_KEY 플래그를 함수로 전달하여 키 배열을 초기화합니다(아래 참조).

해시 키는 ngx_hash_keys_arrays_t에 저장되고 ngx_hash_keys_array_init(arr, type)로 초기화됩니다. 두 번째 매개변수(type)는 해시에 사전 할당된 리소스 용량을 제어하고 NGX_HASH_SMALL 또는 NGX_HASH_LARGE가 될 수 있습니다. 해시에 수천 개의 요소가 들어갈 것으로 예상할 경우, 두 번째 매개변수가 적절합니다.

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

키를 해시 키 배열에 삽입하려면 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);

해시 테이블을 구축하려면 ngx_hash_init(hinit, key_names, nelts) 함수를 호출합니다.

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

max_size 또는 bucket_size 매개변수가 충분히 크지 않으면 함수가 실패합니다.

해시가 구축되면 ngx_hash_find(hash, key, name, len) 함수를 사용하여 요소를 검색합니다.

my_data_t   *data;
ngx_uint_t   key;

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

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

와일드카드 일치

와일드카드와 사용하는 해시를 생성하려면 ngx_hash_combined_t 유형을 사용합니다. 여기에는 앞서 설명한 해시 유형이 포함되고, 추가적인 키 배열 dns_wc_head와 dns_wc_tail이 있습니다. 기본 속성의 초기화는 일반 해시와 유사합니다.

ngx_hash_init_t      hash
ngx_hash_combined_t  foo_hash;

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

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

이 함수는 와일드카드를 인식하고 해당 배열에 키를 추가합니다. 와일드카드 구문과 일치 알고리즘에 대한 자세한 설명은 map 모듈 문서를 참조하세요.

추가된 키의 내용에 따라 키 배열을 3개까지 초기화해야 할 수도 있습니다. 하나는 정확한 일치용이고(위에 설명), 두 개는 문자열 앞이나 끝에서부터 매칭을 지원하는 용도입니다.

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

키 배열은 정렬이 필요하고 초기화 결과를 결합된 해시에 추가해야 합니다. dns_wc_tail 배열의 초기화 방법도 유사합니다.

결합된 해시에서의 검색은 ngx_hash_find_combined(chash, key, name, len)가 처리합니다.

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

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

메모리 관리

시스템 힙에서 메모리를 할당하려면 다음 함수를 사용합니다.

  • ngx_alloc(size, log) — 시스템 힙에서 메모리를 할당합니다. 로깅을 지원하는 malloc()에 대한 래퍼입니다. 할당 오류와 디버깅 정보는 log에 로깅됩니다.
  • ngx_calloc(size, log) — ngx_alloc()처럼 시스템 힙에서 메모리를 할당하지만, 할당 후 0으로 메모리를 채웁니다.
  • ngx_memalign(alignment, size, log) — 시스템 힙에서 정렬된 메모리를 할당합니다. 이 함수를 제공하는 플랫폼에서 posix_memalign()에 대한 래퍼입니다. 그 외에 다른 구현은 최대 정렬을 제공하는 ngx_alloc()로 폴백됩니다.
  • ngx_free(p) — 할당된 메모리를 해제합니다. free()에 대한 래퍼입니다.

대부분 nginx 할당은 풀에서 실행됩니다. nginx 풀에서 할당된 메모리는 풀이 삭제되면 자동으로 해제됩니다. 따라서 할당 성능이 우수하고, 메모리를 쉽게 제어할 수 있습니다.

풀은 내부에서 연속적 메모리 블록으로 객체를 할당합니다. 블록이 가득 차면 새 블록을 할당하고 풀 메모리 블록 목록에 추가됩니다. 요청된 할당이 블록에 들어가기에 너무 클 경우, 요청을 시스템 할당자에 전달하고 반환된 포인터는 추가적인 할당 해제를 위해 풀에 저장됩니다.

nginx 풀의 유형은 ngx_pool_t입니다. 다음의 작업을 지원합니다.

  • ngx_create_pool(size, log) — 지정된 블록 크기로 풀을 생성합니다. 반환된 풀 객체도 풀에 할당됩니다. size는 NGX_MIN_POOL_SIZE 이상이고, NGX_POOL_ALIGNMENT의 배수여야 합니다.
  • ngx_destroy_pool(pool) — 풀 객체 자체를 포함한 모든 풀 메모리를 해제합니다.
  • ngx_palloc(pool, size) — 지정된 풀에서 정렬된 메모리를 할당합니다.
  • ngx_pcalloc(pool, size) — 지정된 풀에서 정렬된 메모리를 할당하고 0으로 채웁니다.
  • ngx_pnalloc(pool, size) — 지정된 풀에서 정렬되지 않은 메모리를 할당합니다. 대부분은 문자열 할당에 사용합니다.
  • ngx_pfree(pool, p) — 지정된 풀에 할당되었던 메모리를 해제합니다. 시스템 할당자에 전달된 요청에서 실행한 할당만 해제할 수 있습니다.
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);

nginx에서는 체인 링크(ngx_chain_t)를 주로 사용하므로, nginx 풀 구현에서는 이를 재사용할 수단을 제공합니다. ngx_pool_t의 chain 필드는  앞서 할당되었고 재사용 가능한 링크 목록을 저장합니다. 풀에 체인 링크를 효율적으로 할당하려면 ngx_alloc_chain_link(pool) 함수를 사용합니다. 이 함수는 풀 목록에서 사용 가능한 체인 링크를 검색하고, 풀 목록이 비어 있으면 새 체인 링크를 할당합니다. 링크를 해제하려면 ngx_free_chain(pool, cl) 함수를 호출합니다.

정리 핸들러는 풀에 등록할 수 있습니다. 정리 핸들러는 인수를 포함하는 콜백으로, 풀을 삭제할 때 호출합니다. 일반적으로 풀은 특정 nginx 객체(예: HTTP 요청)와 연동되고, 객체의 수명이 다하면 삭제됩니다. 풀 정리를 등록하면 편리하게 리소스를 해제하거나, 파일 서술자를 닫거나, 메인 객체와 관련된 공유 데이터를 최종 수정할 수 있습니다.

풀 정리를 등록하려면 ngx_pool_cleanup_add(pool, size)를 호출합니다. 그러면 호출자에서 채울 ngx_pool_cleanup_t 포인터를 반환합니다. size 인수를 사용하여 정리 핸들러에 대한 컨텍스트를 할당합니다.

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

공유 메모리

nginx에서는 공유 메모리를 사용하여 프로세스 간 공통 데이터를 공유합니다. ngx_shared_memory_add(cf, name, size, tag) 함수는 새 공유 메모리 항목 ngx_shm_zone_t를 사이클에 추가합니다. 이 함수는 영역의 name 및 size를 수신합니다. 각 공유 영역은 고유한 이름이 있습니다. 제공된 name과 tag를 가진 공유 영역 항목이 이미 존재하는 경우, 기존 영역 항목을 다시 사용합니다. 동일한 이름을 가진 기존 항목에 태그가 다를 경우, 이 함수는 오류와 함께 실패합니다. 일반적으로 모듈 구조의 주소는 tag로 전달하여 하나의 nginx 모듈에서 이름별로 공유 영역을 재사용할 수 있도록 합니다.

공유 메모리 항목 구조 ngx_shm_zone_t는 다음과 같은 필드가 있습니다.

  • init — 초기화 콜백, 공유 영역을 실제 메모리에 매핑한 후 호출
  • data — 데이터 컨텍스트, 임의의 데이터를 init 콜백에 전달
  • noreuse — 오래된 사이클에서 공유 영역 재사용을 비활성화하는 플래그
  • tag — 공유 영역 태그
  • shm — ngx_shm_t 유형의 플랫폼별 객체, 최소한 다음의 필드 포함:
    • addr — 매핑된 공유 메모리 주소, 처음에는 NULL
    • size — 공유 메모리 크기
    • name — 공유 메모리 이름
    • log — 공유 메모리 로그
    • exists — 공유 메모리를 마스터 프로세스에서 상속했는지 나타내는 플래그(Windows 전용)

공유 영역 항목은 구성을 파싱한 후 ngx_init_cycle()에서 실제 메모리로 매핑됩니다. POSIX 시스템에서 mmap() syscall을 사용하여 공유 익명 매핑을 생성합니다. Windows에서 CreateFileMapping()/ MapViewOfFileEx() 쌍을 사용합니다.

nginx는 공유 메모리에 할당할 수 있도록 슬랩 풀 ngx_slab_pool_t 유형을 제공합니다. 메모리 할당을 위한 슬랩 풀은 각 nginx 공유 영역에서 자동으로 생성됩니다. 이 풀은 공유 영역의 시작점에 위치하고 (ngx_slab_pool_t *) shm_zone->shm.addr 식으로 액세스할 수 있습니다. 공유 영역에서 메모리를 할당하려면 ngx_slab_alloc(pool, size) 또는 ngx_slab_calloc(pool, size)를 호출합니다. 메모리를 해제하려면 ngx_slab_free(pool, p)를 호출합니다.

슬랩 풀은 모든 공유 영역을 페이지로 분할합니다. 각 페이지는 같은 크기의 객체를 할당하는 데 사용합니다. 지정된 크기는 2의 배수이고, 최소 크기인 8바이트보다 커야 합니다. 여기에 일치하지 않는 값은 올림됩니다. 각 페이지의 비트마스크는 어느 블록이 사용 중인지, 어느 블록이 할당 가능한지 추적합니다. 반 페이지(일반적으로 2,048바이트)보다 클 경우, 한 번에 전체 페이지를 할당합니다.

동시 액세스에서 공유 메모리의 데이터를 보호하려면 ngx_slab_pool_t의 mutex 필드에서 제공하는 mutex를 사용합니다. mutex는 메모리를 할당하고 해제할 때 슬랩 풀에서 가장 일반적으로 사용하지만, 공유 영역에 할당된 다른 사용자 데이터 구조를 보호하는 데도 사용할 수 있습니다. mutex를 잠그거나 해제하려면 각각 ngx_shmtx_lock(&shpool->mutex) 또는 ngx_shmtx_unlock(&shpool->mutex)를 호출합니다.

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

로깅

nginx는 로깅에 ngx_log_t 객체를 사용합니다. nginx 로거는 여러 가지 유형의 출력을 지원합니다.

  • stderr — 표준 오류에 로깅(stderr)
  • file — 파일에 로깅
  • syslog — syslog에 로깅
  • memory — 개발 목적의 내부 메모리 스토리지에 로깅, 메모리는 나중에 디버거로 액세스 가능

로거 인스턴스는 로거로 구성된 체인이 될 수 있으며, next 필드로 서로 링크됩니다. 이 경우, 각 메시지는 체인의 모든 로거에 작성됩니다.

각 로거에서 심각도 수준에 따라 로그에 작성할 메시지를 제어합니다(해당 수준 이상에 할당된 이벤트만 로깅). 다음의 심각도 수준을 지원합니다.

  • NGX_LOG_EMERG
  • NGX_LOG_ALERT
  • NGX_LOG_CRIT
  • NGX_LOG_ERR
  • NGX_LOG_WARN
  • NGX_LOG_NOTICE
  • NGX_LOG_INFO
  • NGX_LOG_DEBUG

디버그 로깅의 경우, 디버그 마스크도 검사합니다. 디버그 마스크는 다음과 같습니다.

  • 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

일반적으로 로거는 error_log 명령에서 기존 nginx 코드로 생성하고, 사이클, 구성, 클라이언트 연결, 기타 객체에서 거의 모든 처리 단계에 사용할 수 있습니다.

nginx는 다음의 로깅 매크로를 제공합니다.

  • ngx_log_error(level, log, err, fmt, …) — 오류 로깅
  • ngx_log_debug0(level, log, err, fmt), ngx_log_debug1(level, log, err, fmt, arg1) 등 — 디버그 로깅, 서식 지정 인수를 최대 8개까지 지원

로그 메시지는 스택에서 버퍼 크기 NGX_MAX_ERROR_STR(현재 2048바이트)로 형식이 지정됩니다. 메시지 앞에는 심각도 수준, 프로세스 ID(PID), 연결 ID(log->connection에 저장), 시스템 오류 텍스트가 첨부됩니다. 디버그가 아닌 메시지의 경우, log->handler도 호출하여 더욱 구체적인 정보를 로그 메시지에 첨부합니다. HTTP 모듈은 ngx_http_log_error() 함수를 로그 클라이언트와 서버 주소, 현재 작업(log->action에 저장), 클라이언트 요청 행, 서버 이름 등에 대한 로그 핸들러로 설정합니다.

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

위의 예시에서 다음과 같은 로그 항목이 생성됩니다.

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

사이클

사이클 객체는 특정 구성에서 생성된 nginx 런타임 컨텍스트를 저장합니다. 유형은 ngx_cycle_t입니다. 현재 사이클은 ngx_cycle 전역 변수에서 참조되고, nginx 작업자가 시작할 때 상속됩니다. nginx 구성을 다시 로드할 때마다 새로운 nginx 구성에서 새 사이클이 생성됩니다. 일반적으로 오래된 사이클은 새 사이클을 생성한 후 삭제됩니다.

사이클은 ngx_init_cycle() 함수가 생성하는데, 이전 사이클을 인수로 받습니다. 이 함수는 이전 사이클의 구성 파일을 찾아서, 이전 사이클로부터 최대한 많은 리소스를 상속합니다. “init cycle”이라는 자리표시자 사이클은 nginx가 시작될 때 생성되고, 구성에서 구축된 실제 사이클로 대체됩니다.

사이클 구성 요소는 다음과 같습니다.

  • pool — 사이클 풀. 각각의 새 사이클에 대해 생성됩니다.
  • log — 사이클 로그. 처음에 오래된 사이클에서 상속되고, 구성이 준비되면 new_log를 가리키도록 설정됩니다.
  • new_log — 사이클 로그이며, 구성에서 생성됩니다. 루트 범위 error_log 명령에 영향을 받습니다.
  • connections, connection_n — ngx_connection_t 유형의 연결 배열로, 각 nginx 작업자를 초기화하는 동안 이벤트 모듈에서 생성됩니다. nginx 구성에서 worker_connections 명령이 연결 개수인 connection_n을 설정합니다.
  • free_connections, free_connection_n — 현재 사용 가능한 연결의 목록과 개수입니다. 사용 가능한 연결이 없을 경우, nginx 작업자가 새 클라이언트를 수락하지 않거나 업스트림 서버로의 연결을 거부합니다.
  • files, files_n — nginx 연결로 파일 서술자를 매핑하는 배열입니다. 이 매핑은 이벤트 모듈에서 사용되고 NGX_USE_FD_EVENT 플래그(현재 poll 및 devpoll)가 있습니다.
  • conf_ctx — 코어 모듈 구성의 배열입니다. 이 구성은 nginx 구성 파일을 읽는 동안 생성되어 채워집니다.
  • modules, modules_n — ngx_module_t 유형의 모듈 배열로, 고정 및 동적을 모두 포함하고 현재 구성에서 로드됩니다.
  • listening — ngx_listening_t 유형의 리스닝 객체의 배열입니다. 일반적으로 리스닝 객체는 각 모듈의 listen 명령으로 추가하고, 그러면 ngx_create_listening() 함수가 호출됩니다. 리스닝 소켓은 리스닝 객체에 따라 생성됩니다.
  • paths — ngx_path_t 유형의 경로 배열입니다. 경로는 특정 디렉터리에서 작동할 모듈에서 ngx_add_path() 함수를 호출하여 추가합니다. 이 디렉터리는 구성이 누락되었을 경우 이를 읽은 후에 nginx에서 생성합니다. 또한, 각 경로에 두 개의 핸들러를 추가할 수 있습니다.
    • path loader — nginx를 시작하거나 다시 로드하고 60초 후에 한 번만 실행됩니다. 일반적으로 로더는 디렉터리를 읽고 nginx 공유 메모리에 데이터를 저장합니다. 이 핸들러는 전용 nginx 프로세스 “nginx cache loader”에서 호출됩니다.
    • path manager — 주기적으로 실행됩니다. 일반적으로 관리자는 디렉터리에서 오래된 파일을 제거하고 변경 사항을 반영하여 nginx 메모리를 업데이트합니다. 이 핸들러는 전용 “nginx cache manager” 프로세스에서 호출합니다.
  • open_files — ngx_open_file_t 유형의 열린 파일 객체 목록으로, ngx_conf_open_file() 함수를 생성하여 호출합니다. 현재 nginx는 이 종류의 열린 파일을 로깅에 사용합니다. nginx는 구성을 읽은 후 open_files 목록에서 모든 파일을 열고 각 파일 서술자를 객체의 fd 필드에 저장합니다. 이 파일은 첨부 모드로 열리고, 파일이 누락되었다면 생성됩니다. 목록에 있는 파일은 nginx 작업자가 다시 열기 신호(주로 USR1)를 받은 후 다시 열립니다. 이 경우, fd 필드의 서술자는 새 값으로 변경됩니다.
  • shared_memory — 공유 메모리 영역의 목록으로, 각각 ngx_shared_memory_add() 함수를 호출하여 추가합니다. 공유 영역은 모든 nginx 프로세스에서 동일한 주소 범위에 매핑되고 공통적 데이터(예: HTTP 캐시 인메모리 트리)를 공유하는 데 사용합니다.

버퍼

입/출력 작업의 경우, nginx는 버퍼 유형 ngx_buf_t를 제공합니다. 일반적으로 대상에 작성하거나 소스에서 읽을 데이터를 저장하는 데 사용합니다. 버퍼는 메모리나 파일에 있는 데이터를 참조할 수 있으며, 기술적으로는 버퍼가 두 개를 동시에 참조할 수 있습니다. 버퍼에 대한 메모리는 별도로 할당되고 버퍼 구조 ngx_buf_t와는 관련이 없습니다.

ngx_buf_t 구조는 다음과 같은 필드가 있습니다.

  • start, end — 버퍼에 할당된 메모리 블록의 경계입니다.
  • pos, last — 메모리 버퍼의 경계로, 일반적으로는 start .. end의 하위 범위입니다.
  • file_pos, file_last — 파일 버퍼의 경계이며, 파일 시작점부터의 오프셋으로 표현됩니다.
  • tag — 버퍼를 구별하는 데 사용하는 고유한 값입니다. 각 niginx 모듈에서 주로 버퍼를 재사용할 목적으로 생성됩니다.
  • file — 파일 객체입니다.
  • temporary — 버퍼가 쓰기 가능한 메모리를 참조하는지 나타내는 플래그입니다.
  • memory — 버퍼가 읽기 전용 메모리를 참조하는지 나타내는 플래그입니다.
  • in_file — 버퍼가 파일 내 데이터를 참조하는지 나타내는 플래그입니다.
  • flush — 버퍼 이전의 모든 데이터를 플러시해야 하는지 나타내는 플래그입니다.
  • recycled — 버퍼를 재사용할 수 있고, 최대한 빨리 사용해야 하는지 나타내는 플래그입니다.
  • sync — 버퍼에 데이터나 flush, last_buf 등의 특수한 신호가 없는지 나타내는 플래그입니다. 기본적으로 nginx는 이러한 버퍼를 오류 조건으로 간주하지만, 이 플래그는 오류 검사를 건너뛰도록 지시합니다.
  • last_buf — 버퍼가 출력의 마지막인지 나타내는 플래그입니다.
  • last_in_chain — 요청 또는 하위 요청에 데이터 버퍼가 더 이상 없는지 나타내는 플래그입니다.
  • shadow — 현재 버퍼와 관련된 다른(“섀도”) 버퍼를 참조하며, 일반적으로는 버퍼가 섀도에 있는 데이터를 사용한다는 것을 의미합니다. 버퍼를 사용할 때 섀도 버퍼도 ‘사용됨’으로 표시하는 경우가 많습니다.
  • last_shadow — 특정 섀도 버퍼를 참조하는 마지막 버퍼인지 나타내는 플래그입니다.
  • temp_file — 버퍼가 임시 파일에 있는지 나타내는 플래그입니다.

입출력 작업의 경우, 버퍼는 체인으로 연결됩니다. 체인은 ngx_chain_t 유형의 연속적인 체인 링크로, 다음과 같이 정의됩니다.

typedef struct ngx_chain_s  ngx_chain_t;

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

각 체인 링크는 버퍼에 대한 참조와 다음 체인 링크에 대한 참조가 저장됩니다.

버퍼 및 체인 사용의 예시:

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

네트워킹

연결

연결 유형 ngx_connection_t는 소켓 서술자에 대한 래퍼입니다. 여기에는 다음의 필드가 포함됩니다.

  • fd — 소켓 서술자
  • data — 임의의 연결 컨텍스트. 일반적으로는 연결을 기반으로 한 높은 수준의 객체를 가리키는 포인터(예: HTTP 요청, 스트림 세션)입니다.
  • read, write — 연결에 대한 이벤트를 읽고 씁니다.
  • recv, send, recv_chain, send_chain — 연결에 대한 I/O 작업입니다.
  • pool — 연결 풀입니다.
  • log — 연결 로그입니다.
  • sockaddr, socklen, addr_text — 바이너리 및 텍스트 형식의 원격 소켓 주소입니다.
  • local_sockaddr, local_socklen — 바이너리 형식의 로컬 소켓 주소입니다. 처음에 이 필드는 비어 있습니다. ngx_connection_local_sockaddr() 함수를 사용하여 로컬 소켓 주소를 가져옵니다.
  • proxy_protocol_addr, proxy_protocol_port – PROXY 프로토콜 클라이언트 주소 및 포트. 연결에 PROXY 프로토콜이 활성화된 경우에 해당합니다.
  • ssl — 연결을 위한 SSL 컨텍스트입니다.
  • reusable — 연결이 재사용 가능한 상태인지 나타내는 플래그입니다.
  • close — 연결이 재사용되고 있고, 종료가 필요한지 나타내는 플래그입니다.

nginx 연결은 SSL 레이어를 투명하게 캡슐화할 수 있습니다. 이 경우, 연결의 ssl 필드에는 ngx_ssl_connection_t 구조에 대한 포인터가 저장되어, 연결에 대한 모든 SSL 관련 데이터(예: SSL_CTX, SSL)가 보관됩니다. recv, send, recv_chain 및 send_chain 핸들러는 SSL 지원 함수로도 설정됩니다.

nginx 구성에서 worker_connections 명령은 nginx 작업자당 연결 수를 제한합니다. 작업자가 사이클 객체의 connections 필드에서 시작되고 저장될 때 모든 연결 구조가 사전 생성됩니다. 연결 구조를 가져오려면 ngx_get_connection(s, log) 함수를 사용합니다. s 인수로 소켓 서술자를 받으므로, 연결 구조로 래핑해야 합니다.

작업자당 연결 개수가 제한되어 있으므로 nginx는 현재 사용 중인 연결을 가져올 수단을 제공합니다. 연결 재사용을 활성화하거나 비활성화하려면 ngx_reusable_connection(c, reusable) 함수를 호출합니다. ngx_reusable_connection(c, 1)을 호출하면 연결 구조에 reuse 플래그를 설정하고, 연결을 사이클의 reusable_connections_queue에 삽입합니다. ngx_get_connection()에서 사이클 free_connections 목록에서 사용 가능한 연결이 없다는 것을 발견한 경우, ngx_drain_connections()를 호출해 일정 수의 재사용 가능한 연결을 해제합니다. 이러한 각 연결에서 close 플래그를 설정하고, 읽기 핸들러를 호출합니다. 그러면 ngx_close_connection(c)을 호출하여 연결을 해제하고 재사용할 수 있도록 합니다. 연결을 재사용할 수 있는 상태에서 나가려면 ngx_reusable_connection(c, 0)을 호출합니다. HTTP 클라이언트 연결은 nginx에서 재사용 가능한 연결의 예시로, 클라이언트에서 첫 요청 바이트를 수신할 때까지 재사용 가능한 것으로 표시됩니다.

이벤트

Event

nginx에서 이벤트 객체 ngx_event_t는 특정 이벤트의 발생 사실을 알리는 메커니즘을 제공합니다.

ngx_event_t의 필드:

  • data — 이벤트 핸들러에서 사용하는 임의의 이벤트 컨텍스트, 일반적으로 이벤트와 관련된 연결에 대한 포인터를 나타냅니다.
  • handler — 이벤트 발생 시 호출할 콜백 함수입니다.
  • write — 쓰기 이벤트를 나타내는 플래그입니다. 이 플래그가 없으면 읽기 이벤트입니다.
  • active — 이벤트가 I/O 알림을 수신하기 위해 등록되었는지 나타내는 플래그로, 일반적으로는 epoll, kqueue, poll 등의 알림 메커니즘에서 수신합니다.
  • ready — 이벤트가 I/O 알림을 수신했는지 나타내는 플래그입니다.
  • delayed — I/O가 속도 제한으로 인해 지연되었는지 나타내는 플래그입니다.
  • timer — 이벤트를 타이머 트리로 삽입하기 위한 레드 블랙 트리 노드입니다.
  • timer_set — 이벤트 타이머가 설정되었고 아직 만료되지 않았음을 나타내는 플래그입니다.
  • timedout — 이벤트 타이머가 만료되었음을 나타내는 플래그입니다.
  • eof — 데이터를 읽는 중에 EOF가 발생했음을 나타내는 플래그입니다.
  • pending_eof — EOF가 소켓에 대기 중이지만 그 전에 일부 데이터를 사용할 수도 있다는 것을 나타내는 플래그입니다. 이 플래그는 EPOLLRDHUP epoll 이벤트나 EV_EOF kqueue 플래그를 통해 전달됩니다.
  • error — 읽거나(읽기 이벤트) 쓰는(쓰기 이벤트) 중에 오류가 발생했음을 나타내는 플래그입니다.
  • cancelable — 작업자를 종료하는 중에는 이벤트를 무시해야 한다는 것을 나타내는 타이머 이벤트 플래그입니다. 취소 가능한 타이머 이벤트가 예약되어 있지 않을 때까지 점진적 작업자 종료가 지연됩니다.
  • posted — 이벤트를 대기열에 게시한다는 것을 나타내는 플래그입니다.
  • queue — 이벤트를 대기열에 게시하기 위한 대기열 노드입니다.

I/O 이벤트

ngx_get_connection() 함수를 호출하여 얻은 각 연결은 두 개의 첨부된 이벤트 c->read와 c->write가 있으며, 소켓이 읽거나 쓸 준비가 되었다는 알림을 수신하는 데 사용합니다. 이러한 모든 이벤트는 Edge-Triggered 모드에서 작동하며, 소켓 상태가 변경할 경우에만 알림을 트리거합니다. 예를 들어, 소켓에서 부분적으로 읽기 작업을 하더라도 nginx는 더 많은 데이터가 소켓에 도착할 대까지 반복적 읽기 알림을 제공하지 않습니다. 기본 I/O 메커니즘이 Level-Triggered(예: poll, select)일 경우에도, nginx는 알림을 Edge-Triggered로 변환합니다. 각 플랫폼의 모든 알림 시스템에서 nginx 이벤트 알림의 일관성을 유지하려면 ngx_handle_read_event(rev, flags) 및 ngx_handle_write_event(wev, lowat) 함수는 I/O 소켓 알림을 처리하거나, I/O 함수를 소켓에서 호출한 뒤에 호출해야 합니다. 일반적으로 이들 함수는 각 읽기 또는 쓰기 이벤트 헨들러가 끝난 후 호출됩니다.

타이머 이벤트

이벤트는 시간제한이 끝나면 알림을 보내도록 설정할 수 있습니다. 이벤트에서 사용하는 타이머는 ms 단위를 사용합니다. 과거의 지정되지 않은 일부 포인트가 ngx_msec_t 유형으로 잘리기 때문입니다. 현재 값은 ngx_current_msec 변수에서 얻을 수 있습니다.

ngx_add_timer(ev, timer) 함수는 이벤트에 대한 시간제한을 설정하고, ngx_del_timer(ev)는 이전에 설정된 시간제한을 삭제합니다. 전역 시간제한 레드 블랙 트리 ngx_event_timer_rbtree는 현재 설정된 모든 시간제한을 저장합니다. 트리의 키는 ngx_msec_t 유형이고 이벤트 발생 시점을 나타냅니다. 트리 구조는 빠른 삽입, 삭제 작업을 지원하고, 가장 가까운 시간제한에 액세스할 수 있습니다. nginx는 이를 사용하여 I/O 이벤트와 만료되는 시간제한 이벤트까지 얼마나 남았는지 알아냅니다.

게시된 이벤트

이벤트는 게시가 가능하며, 이는 현재 이벤트 루프 이터레이션에서 나중의 어느 시점에 핸들러를 호출한다는 것을 의미합니다. 이벤트를 게시하는 작업은 코드를 단순화하고 스택 오버플로를 이스케이프하기에 좋습니다. 게시된 이벤트는 게시물 대기열에 저장됩니다. ngx_post_event(ev, q) 매크로는 ev 이벤트를 게시물 대기열 q에 게시합니다. ngx_delete_posted_event(ev) 매크로는 현재 게시된 대기열에서 이벤트 ev를 삭제합니다. 일반적으로 이벤트는 ngx_posted_events 대기열에 게시하고, 나중에 모든 I/O와 타이머 이벤트가 이미 처리된 후에 이벤트 루프에서 처리됩니다. ngx_event_process_posted() 함수를 호출하여 이벤트 대기열을 처리합니다. 대기열이 비어 있지 않을 때까지 이벤트 핸들러를 호출합니다. 즉, 게시된 이벤트 핸들러가 현재 이벤트 루프 이터레이션 내에서 처리할 더 많은 이벤트를 게시할 수 있습니다.

예:

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

이벤트 루프

nginx 마스터 프로세스를 제외한 모든 nginx 프로세스는 I/O를 처리하고 이벤트 루프가 있습니다. (대신 nginx 마스터 프로세스는 대부분의 시간을 sigsuspend() 호출에서 신호가 도착하기를 기다리는 데 사용합니다.) nginx 이벤트 루프는 ngx_process_events_and_timers() 함수에서 구현되고, 이 함수는 프로세스를 종료할 때까지 반복적으로 호출됩니다.

이벤트 루프는 다음의 단계가 있습니다.

  • ngx_event_find_timer()를 호출하여 만료가 가장 가까운 시간제한을 찾습니다. 이 함수는 타이머 트리에서 가장 왼쪽에 있는 노드를 찾아서 노드가 만료되기까지 남은 시간(ms)을 반환합니다.
  • nginx 구성에서 선택한 이벤트 알림 메커니즘에 따라, 핸들러를 호출하여 I/O 이벤트를 처리합니다. 이 핸들러는 다음 시간제한이 만료될 때까지만 1개 이상의 I/O 이벤트가 발생하기를 기다립니다. 읽기 또는 쓰기 이벤트가 발생하면 ready 플래그가 설정되고 이벤트 핸들러가 호출됩니다. Linux의 경우, 일반적으로 ngx_epoll_process_events() 핸들러를 사용하여 epoll_wait()을 호출하고 I/O 이벤트를 기다립니다.
  • ngx_event_expire_timers()를 호출하여 타이머를 만료시킵니다. 타이머 트리는 만료되지 않은 시간제한을 찾을 때까지 가장 왼쪽 요소에서 오른쪽으로 이터레이션합니다. 각 만료된 노드에 대해 timedout 이벤트 플래그가 설정되고 timer_set 플래그가 재설정되며, 이벤트 핸들러가 호출됩니다.
  • ngx_event_process_posted()를 호출하여 게시된 이벤트를 처리합니다. 이 함수는 대기열이 빌 때까지 게시된 이벤트 대기열에서 첫 요소를 반복적으로 제거하고, 요소의 핸들러를 호출합니다.

모든 nginx 프로세스는 신호도 처리합니다. 신호 핸들러는 ngx_process_events_and_timers() 호출 이후에 검사하는 전역 변수만 설정합니다.

프로세스

nginix에는 여러 가지 유형의 프로세스가 있습니다. 프로세스 유형은 ngx_process 전역 변수에 저장되고, 다음 중 하나가 됩니다.

  • NGX_PROCESS_MASTER — 마스터 프로세스입니다. NGINX 구성을 읽고, 사이클을 생성하고, 하위 프로세스를 시작, 제어합니다. I/O를 수행하지 않고 신호에만 응답합니다. 사이클 함수는 ngx_master_process_cycle()입니다.
  • NGX_PROCESS_WORKER — 작업자 프로세스이며, 클라이언트 연결을 처리합니다. 마스터 프로세스로 시작하고, 신호와 채널 명령에도 응답합니다. 사이클 함수는 ngx_worker_process_cycle()입니다. worker_processes 명령의 구성에 따라 여러 작업자 프로세스가 있을 수 있습니다.
  • NGX_PROCESS_SINGLE — 단일 프로세스입니다. master_process off 모드에만 존재하고 해당 모드에서 실행되는 유일한 프로세스입니다. (마스터 프로세스처럼) 사이클을 생성하고, (작업자 프로세스처럼) 클라이언트 연결을 처리합니다. 사이클 함수는 ngx_single_process_cycle()입니다.
  • NGX_PROCESS_HELPER — 도우미 프로세스입니다. 현재 캐시 관리자와 캐시 로더, 두 가지 유형이 있습니다. 두 유형에 대한 사이클 함수는 ngx_cache_manager_process_cycle()입니다.

nginx 프로세스는 다음의 신호를 처리합니다.

  • NGX_SHUTDOWN_SIGNAL(대부분 시스템에서 SIGQUIT) — 점진적으로 종료합니다. 이 신호를 받을 때까지 마스터 프로세스는 모든 하위 프로세스에 종료 신호를 보냅니다. 남은 하위 프로세스가 없으면 마스터가 사이클 풀을 삭제하고 종료합니다. 작업자 프로세스가 이 신호를 받으면 모든 리스닝 소켓을 닫고, 취소 가능한 이벤트가 예약되어 있지 않을 때까지 기다렸다가 사이클 풀을 삭제하고 종료합니다. 캐시 관리자 또는 캐시 로더 프로세스가 이 신호를 수신하면 즉시 종료합니다. ngx_quit 변수는 프로세스가 이 신호를 받았을 때 1로 설정되고 처리 후에 즉시 재설정됩니다. ngx_exiting 변수는 작업자 프로세스가 종료 상태일 때 1로 설정합니다.
  • NGX_TERMINATE_SIGNAL(대부분 시스템에서 SIGTERM on most systems) — 종료합니다. 마스터 프로세스는 이 신호를 받은 후 모든 하위 프로세스에 종료 신호를 보냅니다. 하위 프로세스가 1초 내로 종료하지 않으면 마스터 프로세스가 SIGKILL 신호를 보내 강제 종료합니다. 남은 하위 프로세스가 없으면 마스터 프로세스가 사이클 풀을 삭제하고 종료합니다. 작업자 프로세스, 캐시 관리자 프로세스 또는 캐시 로더 프로세스가 이 신호를 받으면 사이클 풀을 삭제하고 종료합니다. 변수 ngx_terminate는 이 신호를 수신하면 1로 설정됩니다.
  • NGX_NOACCEPT_SIGNAL(대부분 시스템에서 SIGWINCH on most systems) – 모든 작업자 및 도우미 프로세스를 종료합니다. 마스터 프로세스는 이 신호를 받은 후 하위 프로세스를 종료합니다. 앞서 새 nginx 바이너리 종료를 시작했을 경우, 기존 마스터의 하위 프로세스가 다시 시작됩니다. 작업자 프로세스가 이 신호를 수신하면 debug_points 명령에서 설정한 디버그 모드에서 종료됩니다.
  • NGX_RECONFIGURE_SIGNAL(대부분 시스템에서 SIGHUP) – 재구성합니다. 마스터 프로세스는 이 신호를 받은 후 구성을 다시 읽고 이를 바탕으로 새 사이클을 생성합니다. 새 사이클이 생성되면 오래된 사이클이 삭제되고, 새 하위 프로세스가 시작됩니다. 그동안 오래된 하위 프로세스는 NGX_SHUTDOWN_SIGNAL 신호를 받습니다. 단일 프로세스 모드에서 nginx는 새 사이클을 생성하지만 활성화된 연결이 연동된 클라이언트가 없을 때까지 기존 사이클을 유지합니다. 작업자 및 도우미 프로세스는 이 신호를 무시합니다.
  • NGX_REOPEN_SIGNAL(대부분 시스템에서 SIGUSR1) — 파일을 다시 엽니다. 마스터 프로세스는 이 신호를 작업자로 보내고, 작업자는 사이클과 관련된 모든 open_files를 다시 엽니다.
  • NGX_CHANGEBIN_SIGNAL(대부분 시스템에서 SIGUSR2) — nginx 바이너리를 변경합니다. 마스터 프로세스는 새 nginx 바이너리를 시작하고 모든 리스닝 소켓 목록에 전달합니다. “NGINX” 환경 변수에서 전달된 텍스트 형식 목록은 세미콜론으로 구분된 서술자 번호로 구성됩니다. 새 nginx 바이너리는 “NGINX” 변수를 읽고 init 사이클에 소켓을 추가합니다. 다른 프로세스는 이 신호를 무시합니다.

모든 nginx 작업자 프로세스는 POSIX 신호를 수신하고 적절히 처리할 수 있지만, 마스터 프로세스는 표준 kill() syscall을 사용하여 작업자와 도우미에 전달하지 않습니다. 대신 nginx는 프로세스 간 소켓 쌍을 사용하는데, 이는 모든 nginx 프로세스 간 메시지 전송을 지원합니다. 그러나 현재는 메시지를 마스터에서 하위 프로세스로만 전송할 수 있습니다. 메시지에는 표준 신호가 포함됩니다.

스레드

nginx 작업자 프로세스를 막을 만한 별도의 스레드 작업으로 분배할 수 있습니다. 예를 들어 nginx는 파일 I/O를 실행할 때 스레드를 사용하도록 구성할 수 있습니다. 비동기식 인터페이스가 없어서 평소에는 nginx와 사용할 수 없는 라이브러리와 같은 사용 사례도 있습니다. 스레드 인터페이스는 클라이언트 연결을 처리하는 기존 비동기식 방식을 도울 뿐, 대체할 수 없습니다.

동기화를 처리하려면 pthreads 프리미티브에 대한 다음의 래퍼를 사용합니다.

  • typedef pthread_mutex_t ngx_thread_mutex_t;
    • ngx_int_t ngx_thread_mutex_create(ngx_thread_mutex_t *mtx, ngx_log_t *log);
    • ngx_int_t ngx_thread_mutex_destroy(ngx_thread_mutex_t *mtx, ngx_log_t *log);
    • ngx_int_t ngx_thread_mutex_lock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
    • ngx_int_t ngx_thread_mutex_unlock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
  • typedef pthread_cond_t ngx_thread_cond_t;
    • ngx_int_t ngx_thread_cond_create(ngx_thread_cond_t *cond, ngx_log_t *log);
    • ngx_int_t ngx_thread_cond_destroy(ngx_thread_cond_t *cond, ngx_log_t *log);
    • ngx_int_t ngx_thread_cond_signal(ngx_thread_cond_t *cond, ngx_log_t *log);
    • ngx_int_t ngx_thread_cond_wait(ngx_thread_cond_t *cond, ngx_thread_mutex_t *mtx, ngx_log_t *log);

nginx는 각 작업에 새 스레드를 생성하는 대신, thread_pool 전략을 구현합니다. 각각의 목적에 따라 여러 스레드 풀을 생성할 수 있습니다(예: 각 디스크 세트에서 I/O 실행). 각 스레드 풀은 시작 시 생성되고 작업 대기열을 처리하는 스레드를 제한된 숫자만큼 포함합니다. 작업을 완료하면 사전 정의된 완료 핸들러를 호출합니다.

src/core/ngx_thread_pool.h 헤더 파일에는 관련 정의가 포함됩니다.

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

구성 시 스레드를 사용하려는 모듈은 ngx_thread_pool_add(cf, name)을 호출하여 스레드 풀에 대한 참조를 얻어야 합니다. 그러면 지정된 name으로 새 스레드 풀을 생성하거나, 이 이름이 이미 존재한다면 해당 이름을 가진 풀에 대한 참조를 반환합니다.

런타임에서 task를 지정된 스레드 풀 tp의 대기열에 추가하려면 ngx_thread_task_post(tp, task) 함수를 사용합니다. 스레드에서 함수를 실행하려면 ngx_thread_task_t 구조를 사용하여 매개변수를 전달하고 완료 핸들러를 설정합니다.

typedef struct {
    int    foo;
} my_thread_ctx_t;


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

    /* this function is executed in a separate thread */
}


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

    /* executed in nginx event loop */
}


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

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

    ctx = task->ctx;

    ctx->foo = 42;

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

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

    return NGX_OK;
}

모듈

새 모듈 추가

각 독립적 nginx 모듈은 2개 이상의 파일이 있는 별도의 디렉터리에 들어 있습니다. config와 모듈 소스 코드가 있는 파일로 구성됩니다. config 파일에는 다음과 같이 nginx가 모듈을 통합하는 데 필요한 모든 정보가 있습니다.

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

config 파일은 POSIX 쉘 스크립트로, 다음의 변수를 설정하고 액세스할 수 있습니다.

  • ngx_module_type — 빌드할 모듈 유형입니다. 사용 가능한 값은 CORE, HTTP, HTTP_FILTER, HTTP_INIT_FILTER, HTTP_AUX_FILTER, MAIL, STREAM 또는 MISC입니다.
  • ngx_module_name — 모듈 이름입니다. 소스 파일 세트에서 여러 모듈을 구축하려면 공백으로 구분된 이름 목록을 지정합니다. 첫 번째 이름은 동적 모듈의 출력 바이너리 이름을 나타냅니다. 목록의 이름은 소스 코드에서 사용한 이름과 일치해야 합니다.
  • ngx_addon_name — 구성 스크립트에서 콘솔에 출력되는 모듈의 이름입니다.
  • ngx_module_srcs — 모듈을 컴파일하는 데 사용한 소스 파일의 공백으로 구분된 목록입니다. $ngx_addon_dir 변수를 사용하여 모듈 디렉터리로의 경로를 나타낼 수 있습니다.
  • ngx_module_incs — 모듈을 구축하는 데 필요한 경로를 포함합니다.
  • ngx_module_deps — 모듈 종속성의 공백으로 구분된 목록입니다. 일반적으로는 헤더 파일의 목록이 됩니다.
  • ngx_module_libs — 모듈과 링크되는 라이브러리의 공백으로 구분된 목록입니다. 예를 들어 ngx_module_libs=-lpthread를 사용하여 libpthread 라이브러리를 링크합니다. 다음의 매크로는 동일한 라이브러리를 nginx로 링크하는 데 사용할 수 있습니다. LIBXSLT, LIBGD, GEOIP, PCRE, OPENSSL, MD5, SHA1, ZLIB, and PERL.
  • ngx_module_link — 빌드 시스템에서 설정한 변수로, 동적 모듈의 경우 DYNAMIC이고 고정 모듈의 경우 ADDON입니다. 링크 유형에 따라 수행해야 할 여러 가지 작업을 결정하는 데 사용합니다.
  • ngx_module_order — 모듈의 로드 순서입니다. HTTP_FILTER 및 HTTP_AUX_FILTER 모듈 유형에 유용합니다. 이 옵션의 형식은 공백으로 구분된 모듈 목록입니다. 이 목록의 모듈은 모듈 전역 목록에서 확정된 현재 모듈 이름을 따르고, 이는 모듈 초기화 순서를 설정합니다. 필터 모듈의 경우, 나중에 초기화될수록 먼저 실행됩니다.

일반적으로 다음의 모듈을 참조로 사용합니다. ngx_http_copy_filter_module은 다른 필터 모듈에 대한 데이터를 읽고, 목록 아래쪽에 위치해서 가장 먼저 실행되는 편입니다. ngx_http_write_filter_module은 클라이언트 소켓에 데이터를 작성하고 목록 위쪽에 위치해서 마지막으로 실행됩니다.

기본적으로 필터 모듈은 모듈 목록에서 ngx_http_copy_filter 앞에 위치하므로 필터 핸들러는 복사 필터 핸들러 다음에 실행됩니다. 다른 모듈 유형의 경우, 기본값은 빈 문자열입니다.

모듈을 고정적으로 nginx로 컴파일하려면 구성 스크립트에 –add-module=/path/to/module 인수를 사용합니다. 모듈을 컴파일하여 나중에 nginx에서 동적으로 로드하려면 –add-dynamic-module=/path/to/module 인수를 사용합니다.

코어 모듈

모듈은 nginx의 기본 요소이고, 대부분의 기능이 모듈로 구현됩니다. 모듈 소스 파일에는 다음과 같이 정의되는 ngx_module_t 유형의 전역 변수가 포함되어야 합니다.

struct ngx_module_s {

    /* private part is omitted */

    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;

    ngx_int_t           (*init_master)(ngx_log_t *log);

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);

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

    void                (*exit_master)(ngx_cycle_t *cycle);

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

누락된 비공개 부분에는 모듈 버전, 서명이 있고, 사전 정의된 매크로 NGX_MODULE_V1을 사용하여 채웁니다.

각 모듈은 ctx 필드에 비공개 데이터를 저장하고, commands 배열에서 지정된 구성 명령을 인식하며, nginx 수명 주기의 특정 단계에서 호출됩니다. 모듈 수명 주기는 다음과 같은 이벤트로 구성됩니다.

  • 구성 명령 핸들러는 마스터 프로세스의 컨텍스트에 따라 구성에 나타나는 대로 호출됩니다.
  • 구성을 성공적으로 파싱하면 마스터 프로세스의 컨텍스트에 따라 init_module 핸들러를 호출합니다. init_module 핸들러는 구성을 로드할 때마다 마스터 프로세스에서 호출됩니다.
  • 마스터 프로세스는 하나 이상의 작업자 프로세스를 생성하고, 각각에서 init_process 핸들러를 호출합니다.
  • 작업자 프로세스가 마스터에서 종료 또는 강제 종료 명령을 받으면 exit_process 핸들러를 호출합니다.
  • 마스터 프로세스가 종료 전에 exit_master 핸들러를 호출합니다.

스레드는 nginx에서 자체 API로 보조적 I/O 퍼실리티로만 사용되므로 현재는 init_thread 및 exit_thread 핸들러를 호출하지 않습니다. 불필요한 오버헤드인 init_master 핸들러를 사용하지 않습니다.

type 모듈은 ctx 필드에 저장할 대상을 정의합니다. 이 값은 다음 유형 중 하나가 됩니다.

  • NGX_CORE_MODULE
  • NGX_EVENT_MODULE
  • NGX_HTTP_MODULE
  • NGX_MAIL_MODULE
  • NGX_STREAM_MODULE

NGX_CORE_MODULE이 가장 기본이며, 가장 일반적이고 가장 낮은 수준의 모듈 유형입니다. 다른 모듈 유형은 이 모듈 위에 구현되고, 해당 도메인을 처리하는 더욱 편리한 방법(예: 이벤트 처리, HTTP 요청 처리)을 제공합니다.

코어 모듈에는 ngx_core_module, ngx_errlog_module, ngx_regex_module, ngx_thread_pool_module, ngx_openssl_module 등이 포함됩니다. HTTP 모듈, 스트림 모듈, 메일 모듈, 이벤트 모듈도 코어 모듈입니다. 코어 모듈의 컨텍스트는 다음과 같이 정의됩니다.

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;

name이 모듈 이름 문자열인 경우, create_conf 및 init_conf는 모듈 구성을 각각 생성하고 초기화하는 함수에 대한 포인터입니다. 코어 모듈의 경우, nginx는 create_conf를 호출한 다음 새 구성을 파싱하고, 모든 구성을 파싱한 다음 init_conf를 호출합니다. 전형적인 create_conf 함수는 구성에 메모리를 할당하고 기본값을 설정합니다.

예를 들어, ngx_foo_module이라는 간단한 모듈은 다음과 같은 형식을 취합니다.

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

구성 명령

ngx_command_t 유형은 단일 구성 명령을 정의합니다. 구성을 지원하는 각 모듈은 해당 구조의 배열을 제공하여 인수를 처리하는 방법, 호출할 핸들러를 설명합니다.

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

특수 값 ngx_null_command이 있는 배열을 종료합니다. name은 구성 파일에 나타나는 명령의 이름입니다(예: “worker_processes”, “listen”). type은 플래그의 비트 필드이며, 명령이 받는 인수 개수, 유형, 표시되는 컨텍스트를 지정합니다. 플래그는 다음과 같습니다.

  • NGX_CONF_NOARGS — 명령이 인수를 받지 않습니다.
  • NGX_CONF_1MORE — 명령이 하나 이상의 인수를 받습니다.
  • NGX_CONF_2MORE — 명령이 두 개 이상의 인수를 받습니다.
  • NGX_CONF_TAKE1..NGX_CONF_TAKE7 — 명령이 표시된 개수의 인수만 정확히 받습니다.
  • NGX_CONF_TAKE12, NGX_CONF_TAKE13, NGX_CONF_TAKE23, NGX_CONF_TAKE123, NGX_CONF_TAKE1234 — 명령이 서로 다른 개수의 인수를 받습니다. 옵션은 지정된 숫자만큼으로 제한됩니다. 예를 들어 NGX_CONF_TAKE12는 1~2개의 인수를 받습니다.

명령 유형의 플래그:

  • NGX_CONF_BLOCK — 명령이 블록 형식입니다. 즉, 괄호 안에 다른 명령을 포함하거나 파서를 구현하여 안쪽의 내용을 처리할 수 있습니다.
  • NGX_CONF_FLAG — 명령이 on 또는 off 부울 값을 받습니다.

명령의 컨텍스트에 따라 구성에서 표시할 위치가 정의됩니다.

  • NGX_MAIN_CONF — 최상위 컨텍스트.
  • NGX_HTTP_MAIN_CONF — http 블록.
  • NGX_HTTP_SRV_CONF — http 블록 내 server 블록.
  • NGX_HTTP_LOC_CONF — http 블록 내 location 블록.
  • NGX_HTTP_UPS_CONF — http 블록 내 upstream 블록.
  • NGX_HTTP_SIF_CONF — http 블록의 server 블록 내  if 블록.
  • NGX_HTTP_LIF_CONF — http 블록의 location 블록 내 if 블록.
  • NGX_HTTP_LMT_CONF — http 블록 내 limit_except 블록.
  • NGX_STREAM_MAIN_CONF — stream 블록.
  • NGX_STREAM_SRV_CONF — stream 블록 내 server 블록.
  • NGX_STREAM_UPS_CONF — stream 블록 내 upstream 블록 .
  • NGX_MAIL_MAIN_CONF — mail 블록.
  • NGX_MAIL_SRV_CONF — mail 블록 내 server 블록.
  • NGX_EVENT_CONF — event 블록.
  • NGX_DIRECT_CONF — 컨텍스트의 계층을 생성하지 않고 전역 구성만 1개 가지는 모듈에서 사용합니다. 이 구성은 핸들러에 conf 인수로 전달됩니다.

구성 파서는 이 플래그를 사용하여 명령 위치가 잘못된 경우 오류를 발생시키고, 적절한 구성 포인터에서 제공하는 명령 핸들러를 호출합니다. 서로 다른 위치에 있는 동일한 명령이 서로 다른 위치에 값을 저장하도록 합니다.

set 필드는 명령을 처리하고 파싱된 값을 해당 구성에 저장하는 핸들러를 정의합니다. 일반적인 변환을 실행하는 함수는 여러 가지가 있습니다.

  • ngx_conf_set_flag_slot — 리터럴 문자열 on과 off를 ngx_flag_t 값으로 변환합니다. 각각 1 또는 0 값이 됩니다.
  • ngx_conf_set_str_slot — 문자열을 ngx_str_t 유형의 값으로 저장합니다.
  • ngx_conf_set_str_array_slot — 값을 문자열 ngx_str_t의 배열 ngx_array_t로 첨부합니다. 배열이 존재하지 않으면 새로 생성합니다.
  • ngx_conf_set_keyval_slot — 키-값 쌍을 키-값 쌍 ngx_keyval_t의 배열 ngx_array_t에 첨부합니다. 첫 문자열이 키가 되고 두 번째 문자열이 값이 됩니다. 배열이 존재하지 않으면 새로 생성합니다.
  • ngx_conf_set_num_slot — 명령의 인수를 ngx_int_t 값으로 변환합니다.
  • ngx_conf_set_size_slot — 크기를 바이트로 표현된 size_t 값으로 변환합니다.
  • ngx_conf_set_off_slot — 오프셋를 바이트로 표현된 off_t 값으로 변환합니다.
  • ngx_conf_set_msec_slot — 시간을 ms로 표현된 ngx_msec_t 값으로 변환합니다.
  • ngx_conf_set_sec_slot — 시간을 초로 표현된 time_t 값으로 변환합니다.
  • ngx_conf_set_bufs_slot — 두 개의 제공된 인수를 버퍼 개수와 크기를 포함하는 ngx_bufs_t 객체로 변환합니다.
  • ngx_conf_set_enum_slot — 제공된 인수를 ngx_uint_t 값으로 변환합니다. post 필드에 전달된 ngx_conf_enum_t의 null 종료 배열이 허용 가능한 문자열과 해당 정수 값을 정의합니다.
  • ngx_conf_set_bitmask_slot — 제공된 인수를 ngx_uint_t 값으로 변환합니다. 각 인수의 마스크 값은 결과를 생성하는 ORed입니다. post 필드로 전달된 ngx_conf_bitmask_t의 null 종료 배열은 허용 가능한 문자열과 해당 마스크 값을 정의합니다.
  • set_path_slot — 제공되는 인수를 ngx_path_t 값으로 변환하고 모든 필수적인 초기화를 실행합니다. 자세한 내용은 proxy_temp_path 명령 문서를 참조하세요.
  • set_access_slot — 제공되는 인수를 파일 권한 마스크로 변환합니다. 자세한 내용은 proxy_store_access 명령 문서를 참조하세요.

conf 필드는 디렉터리 핸들러로 전달할 구성 구조를 정의합니다. 코어 모듈은 전역 구성만 있고, NGX_DIRECT_CONF 플래그를 설정하여 구성에 액세스합니다. HTTP, Stream, Mail 등의 모듈은 구성의 계층을 생성합니다. 예를 들어 모듈의 구성은 server, location, if 범위에 대해 생성됩니다.

  • NGX_HTTP_MAIN_CONF_OFFSET — http 블록에 대한 구성.
  • NGX_HTTP_SRV_CONF_OFFSET — http 블록 내 server 블록에 대한 구성.
  • NGX_HTTP_LOC_CONF_OFFSET — http 블록 내 location 블록에 대한 구성.
  • NGX_STREAM_MAIN_CONF_OFFSET — stream 블록에 대한 구성.
  • NGX_STREAM_SRV_CONF_OFFSET — stream 블록 내 server 블록에 대한 구성.
  • NGX_MAIL_MAIN_CONF_OFFSET — mail 블록에 대한 구성.
  • NGX_MAIL_SRV_CONF_OFFSET — mail 블록 내 server 블록에 대한 구성.

offset은 모듈 구성 구조에서 해당 명령의 값을 가지는 필드의 오프셋을 정의합니다. 일반적으로는 offsetof() 매크로를 적용하는 데 사용합니다.

post 필드는 두 가지 목적이 있습니다. 메인 핸들러를 완료한 후 호출할 핸들러를 정의하거나, 추가 데이터를 메인 핸들러에 전달하는 데 사용합니다. 첫 번째 목적에서 ngx_conf_post_t 구조를 핸들러에 대한 포인터로 초기화해야 합니다. 예를 들어, 다음과 같습니다.

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

post 인수는 ngx_conf_post_t 객체 자체이고, data는 값에 대한 포인터로, 메인 핸들러가 인수에서 적절한 유형으로 변환합니다.

HTTP

Connection

각 HTTP 클라이언트 연결은 다음의 단계를 거칩니다.

  • ngx_event_accept()는 클라이언트 TCP 연결을 수락합니다. 이 핸들러는 리스닝 소켓의 읽기 알림에 대한 응답에서 호출됩니다. 이 단계에서 새로운 ngx_connection_t 객체를 생성하여 새로 수락된 클라이언트 소켓을 래핑합니다. 각 nginx 리스너는 새 연결 객체를 전달할 핸들러를 제공합니다. HTTP 연결의 경우 ngx_http_init_connection(c)입니다.
  • ngx_http_init_connection()은 HTTP 연결을 조기에 초기화합니다. 이 단계에서 연결에 대해 ngx_http_connection_t 객체를 생성하고 여기에 대한 참조는 연결의 data 필드에 저장됩니다. 나중에는 HTTP 요청 객체로 대체됩니다. PROXY 프로토콜 파서와 SSL 핸드셰이크도 이 단계에서 시작됩니다.
  • ngx_http_wait_request_handler() 읽기 이벤트 핸들러는 클라이언트 소켓에서 데이터를 사용할 수 있을 때 호출됩니다. 이 단계에서 HTTP 요청 객체 ngx_http_request_t가 생성되고, 연결의 data 필드로 설정됩니다.
  • ngx_http_process_request_line() 읽기 이벤트 핸들러는 클라이언트 요청 행을 읽습니다. 이 핸들러는 ngx_http_wait_request_handler()가 설정합니다. 데이터는 연결의 buffer로 읽어들입니다. 버퍼의 크기는 처음에 client_header_buffer_size 명령이 설정합니다. 전체 클라이언트 헤더가 이 버퍼 안에 들어가야 합니다. 처음 크기로 부족할 경우 large_client_header_buffers 명령으로 용량을 설정한 더 큰 버퍼를 할당합니다.
  • ngx_http_process_request_headers() 읽기 이벤트 핸들러는 ngx_http_process_request_line() 이후에 설정되어, 클라이언트 요청 헤더를 읽습니다.
  • ngx_http_core_run_phases()는 요청 헤더를 완전히 읽고 파싱했을 때 호출합니다. 이 함수는 NGX_HTTP_POST_READ_PHASE에서 NGX_HTTP_CONTENT_PHASE로 요청 단계를 실행합니다. 마지막 단계에서는 응답을 생성하고 필터 체인을 따라 전달합니다. 이 단계에서 응답이 클라이언트로 전송되지 않을 수도 있습니다. 버퍼 상태로 남아 최종 단계에서 전송될 수도 있습니다.
  • ngx_http_finalize_request()는 일반적으로 요청이 모든 결과를 생성했거나 오류를 발생시켰을 때 호출됩니다. 오류가 발생한 경우, 적절한 오류 페이지를 조회하고 이를 응답으로 사용합니다. 이 시점까지 응답이 클라이언트로 완전히 전송되지 않으면 HTTP 작성기 ngx_http_writer()가 활성화되어 데이터 발송을 완료합니다.
  • ngx_http_finalize_connection()은 클라이언트에게 완전한 응답이 전송되고 요청을 삭제할 수 있을 때 호출됩니다. 클라이언트 연결 유지 기능을 활성화할 경우, ngx_http_set_keepalive()가 호출되고 현재 요청을 삭제한 다음, 연결의 다음 요청을 기다립니다. 그렇지 않을 경우, ngx_http_close_request()가 요청과 연결을 모두 삭제합니다.

Request

각 클라이언트 HTTP 요청에 대해 ngx_http_request_t 객체가 생성됩니다. 예를 들어, 이 객체는 다음과 같은 필드가 있습니다.

  • connection — ngx_connection_t 클라이언트 연결 객체에 대한 포인터입니다. 여러 요청이 동시에 같은 연결 객체를 참조할 수 있습니다. 하나는 메인 요청이 되고, 나머지는 하위 요청이 됩니다. 요청이 삭제되고 나면 동일한 연결에서 새 요청을 생성할 수 있습니다.

HTTP 연결의 경우, ngx_connection_t의 data 필드가 요청을 다시 가리킵니다. 다른 요청은 연결과 연동되지만, 이러한 요청은 ‘활성화’된다고 합니다. 활성화된 요청은 클라이언트 연결 이벤트를 처리하는 데 사용하고 클라이언트에 응답을 보낼 수 있습니다. 일반적으로 각 요청은 일정 시점이 되면 활성화되어 결과를 전송합니다.

  • ctx — HTTP 모듈 컨텍스트의 배열. NGX_HTTP_MODULE 유형의 각 모듈은 요청에 모든 값(일반적으로는 구조에 대한 포인터)을 저장할 수 있습니다. 이 값은 모듈의 ctx_index 위치에 있는 ctx 배열에 저장됩니다. 다음의 매크로는 요청 컨텍스트를 가져와서 설정하는 편리한 수단을 제공합니다.
    • ngx_http_get_module_ctx(r, module) — module의 컨텍스트를 반환합니다.
    • ngx_http_set_ctx(r, c, module) — c를 module의 컨텍스트로 설정합니다.
  • main_conf, srv_conf, loc_conf — 현재 요청 구성의 배열입니다. 구성은 모듈의 ctx_index 위치에 저장됩니다.
  • read_event_handler, write_event_handler – 요청에 대한 읽기 및 쓰기 이벤트 핸들러입니다. 일반적으로는 HTTP 연결에 대한 읽기 및 쓰기 이벤트 핸들러가 ngx_http_request_handler()로 설정됩니다. 이 함수는 현재 활성화된 요청에 대해 read_event_handler 및 write_event_handler 핸들러를 호출합니다.
  • cache — 업스트림 응답을 캐싱하기 위한 요청 캐시 객체입니다.
  • upstream — 프록시를 위한 요청 업스트림 객체입니다.
  • pool — 요청 풀입니다. 요청 객체 자체는 이 풀에 할당되고, 요청이 삭제되면 풀도 삭제됩니다. 클라이언트 연결의 수명 주기가 끝날 때까지 사용하는 할당의 경우, 대신 ngx_connection_t의 풀을 사용합니다.
  • header_in — 클라이언트 HTTP 요청 헤더를 읽는 버퍼입니다.
  • headers_in, headers_out — 입출력 HTTP 헤더 객체입니다. 두 객체는 헤더의 원본 목록을 유지하기 위해 ngx_list_t 유형의 headers 필드를 포함합니다. 또한, 별도의 필드로 가져와 설정하기 위한 특정 헤더도 있습니다(예: content_length_n, status).
  • request_body — 클라이언트 요청 본문 객체입니다.
  • start_sec, start_msec — 요청이 생성된 시점으로, 요청 지속 시간을 추적하는 데 사용합니다.
  • method, method_name — 클라이언트 HTTP 요청 메서드의 숫자 및 텍스트 표현입니다. 메서드의 숫자 값은 NGX_HTTP_GET, NGX_HTTP_HEAD, NGX_HTTP_POST 등의 매크로로 src/http/ngx_http_request.h에서 정의됩니다.
  • http_protocol — 원본 텍스트 형식의 클라이언트 HTTP 프로토콜 버전입니다(예: “HTTP/1.0”, “HTTP/1.1”).
  • http_version — 숫자 형식의 클라이언트 HTTP 프로토콜 버전입니다(예: NGX_HTTP_VERSION_10, NGX_HTTP_VERSION_11).
  • http_major, http_minor — 숫자 형식의 클라이언트 HTTP 프로토콜 버전을 주 부분과 부 부분으로 분할합니다.
  • request_line, unparsed_uri — 원본 클라이언트 요청의 요청 행과 URI입니다.
  • uri, args, exten — 현재 요청에 대한 URI, 인수 및 파일 확장자입니다. 이 URI 값은 정규화로 인해 클라이언트가 전송한 원본 URI와 다를 수 있습니다. 요청을 처리하는 동안 이 값은 내부 리디렉션 중에 변경될 수 있습니다.
  • main — 메인 요청 객체에 대한 포인터입니다. 이 객체는 하위 요청이 아니라 클라이언트 HTTP 요청을 처리하기 위해 생성됩니다. 하위 요청은 메인 요청 내에서 특정 하위 작업을 실행하기 위해 생성됩니다.
  • parent — 하위 요청의 상위 요청에 대한 포인터입니다.
  • postponed — 출력 버퍼와 하위 요청의 목록으로, 전송 및 생성 순서를 따릅니다. 이 목록은 postpone 필터가 하위 요청에서 요청 결과의 일부를 생성했을 때 일관적인 요청 결과를 제공하는 데 사용합니다.
  • post_subrequest — 하위 요청이 확정되었을 때 호출할 컨텍스트가 있는 핸들러에 대한 포인터입니다. 메인 요청에는 사용하지 않습니다.
  • posted_requests — 시작하거나 재기할 요청의 목록으로, 요청의 write_event_handler를 호출하여 실행합니다. 일반적으로 이 핸들러에 요청 메인 함수가 있는데, 먼저 요청 단계를 실행하고 나서 결과를 생성합니다.

일반적으로 요청은 ngx_http_post_request(r, NULL) 호출로 게시합니다. 항상 메인 요청 posted_requests 목록에 게시됩니다. ngx_http_run_posted_requests(c) 함수는 전달된 연결의 활성 요청의 메인 요청에 게시된 모든 요청을 실행합니다. 모든 이벤트 핸들러가 ngx_http_run_posted_requests를 호출하고, 이는 새롭게 게시된 요청으로 이어질 수 있습니다. 일반적으로는 요청의 읽기 또는 쓰기 핸들러를 호출한 뒤에 호출합니다.

  • phase_handler — 현재 요청 단계의 인덱스입니다.
  • ncaptures, captures, captures_data — 요청의 마지막 regex 일치에서 생성된 regex 캡처입니다. regex 일치는 요청 처리 중 여러 곳에서 발생할 수 있습니다. 예를 들어, 맵 조회, SNI 또는 HTTP 호스트의 서버 조회, 재작성 proxy_redirect 등이 있습니다. 조회로 생성된 캡처는 앞서 설명한 필드에 저장됩니다. ncaptures 필드에는 여러 캡처가 저장되고, captures는 캡처 경계를 저장하며, captures_data는 regex를 매칭하는 문자열을 저장합니다. 이는 캡처를 추출하는 데 사용합니다. 새로운 regex 일치가 발생할 때마다 요청 캡처를 재설정하여 새 값을 저장합니다.
  • count — 요청 참조 카운터입니다. 이 필드는 메인 요청에만 적용됩니다. 카운터는 r->main->count++로 간단하게 늘릴 수 있습니다 카운터를 낮추려면 ngx_http_finalize_request(r, rc)를 호출합니다 하위 요청을 생성하고 요청 본문 읽기 프로세스를 실행하는 작업은 모두 카운터가 증가합니다.
  • subrequests — 현재 하위 요청의 중첩 수준입니다. 각 하위 요청은 상위 중첩 수준을 상속하고 1씩 감소합니다. 값이 0에 도달하면 오류가 발생합니다. 메인 요청의 값은 NGX_HTTP_MAX_SUBREQUESTS 상수로 정의됩니다.
  • uri_changes — 요청에 남은 URI 변경 횟수입니다. 요청이 URI를 변경할 수 있는 총 횟수는 NGX_HTTP_MAX_URI_CHANGES 상수로 제한합니다. 값을 변경할 때마다 감소하고, 0에 도달하면 오류가 발생합니다. 일반 위치 또는 이름이 지정된 위치로 재작성하고 내부 리디렉션을 실행하는 것은 URI 변경 사항으로 간주됩니다.
  • blocked — 요청에 있는 블록 수입니다. 이 값이 0이 아니면 요청을 종료할 수 없습니다. 현재 이 값은 대기 중인 AIO 작업(POSIX AIO 및 스레드 작업)과 활성화된 캐시 잠금에 따라 증가합니다.
  • buffered — 요청에서 생성한 결과를 버퍼링하는 모듈을 나타내는 비트마스크입니다. 결과를 버퍼링할 수 있는 필터는 여러 가지가 있습니다. 예를 들어 sub_filter는 부분 문자열 일치로 데이터를 버퍼링하고, copy 필터는 사용 가능한 출력 버퍼가 없을 때 데이터를 버퍼링합니다. 이 값이 0이 아니면 요청은 완료되지 않고 플러시 대기 상태가 됩니다.
  • header_only — 결과에 본문이 필요하지 않다는 것을 나타내는 플래그입니다. 예를 들어 이 플래그는 HTTP HEAD 요청에서 사용합니다.
  • keepalive — 클라이언트 연결 유지를 지원하는지 나타내는 플래그입니다. 이 값은 HTTP 버전과 “Connection” 헤더 값에서 추론합니다.
  • header_sent — 출력 헤더를 요청에서 이미 전송하였음을 나타내는 플래그입니다.
  • internal — 현재 요청이 내부적임을 나타내는 플래그입니다. 내부 상태로 들어가려면 요청이 내부 리디렉션을 통과하거나 하위 요청이어야 합니다. 내부 요청은 내부 위치로 들어갈 수 있습니다.
  • allow_ranges — HTTP Range 헤더에서 요청한 대로 부분 응답을 클라이언트로 보낼 수 있음을 나타내는 플래그입니다.
  • subrequest_ranges — 하위 요청을 처리하는 동안 부분 응답을 전송할 수 있음을 나타내는 플래그입니다.
  • single_range — 클라이언트로 하나의 연속적 출력 데이터 범위만 전송할 수 있음을 나타내는 플래그입니다. 일반적으로 이 플래그는 데이터 스트림을 전송하고(예: 프록시된 서버) 하나의 버퍼에 응답 전체가 들어가지 않을 때 설정됩니다.
  • main_filter_need_in_memory, filter_need_in_memory — 파일이 아니라 메모리 버퍼에서 결과를 생성하도록 요청하는 플래그입니다. sendfile이 활성화되었더라도 copy 필터가 파일 버퍼에서 데이터를 읽으라는 신호입니다. 두 플래그의 차이는 이를 설정하는 필터 모듈의 위치에 있습니다. 필터 체인에서 postpone 필터 전에 호출되는 필터는 filter_need_in_memory를 설정하고, 현재 요청 결과만 메모리 버퍼로 들어오도록 합니다. 필터 체인에서 나중에 호출되는 필터는 main_filter_need_in_memory를 설정하고, 결과를 전송하는 도중에 메모리에서 메인 요청과 모든 하위 요청이 파일을 읽도록 합니다.
  • filter_need_temporary — 무작위 메모리 버퍼나 파일 버퍼가 아니라 임시 버퍼에서 요청 결과를 생성하도록 요청하는 플래그입니다. 이는 전송 대상인 버퍼에서 직접 결과를 변경할 수 있는 필터가 사용합니다.

Configuration

각 HTTP 모듈은 3가지 구성을 사용합니다.

  • 메인 구성 — 전체 http 블록에 적용됩니다. 모듈의 전역 설정으로 기능합니다.
  • 서버 구성 — 단일 server 블록에 적용됩니다. 모듈의 서버별 설정으로 기능합니다.
  • 위치 구성 — 하나의 location, if 또는 limit_except 블록에 적용됩니다. 모듈의 위치별 설정으로 기능합니다.

구성 구조는 nginx 구성 단계에서 구조를 할당하여 초기화, 병합하는 동안 함수를 호출하여 생성합니다. 모듈의 간단한 위치 구성을 생성하는 방법의 예시는 다음과 같습니다. 이 구성에는 비부호 정수 유형의 설정 foo가 하나 있습니다.

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

예시에서 볼 수 있듯이, ngx_http_foo_create_loc_conf() 함수는 새 구성 구조를 생성하고 ngx_http_foo_merge_loc_conf()는 더욱 높은 수준의 구성과 병합합니다. 사실, 서버와 위치 구성은 서버와 위치 수준뿐만 아니라 그 위의 모든 수준에 대해 생성됩니다. 특히나 서버 구성은 메인 수준에서 생성되고, 위치 구성은 메인, 서버, 위치 수준에서 생성됩니다. 이 구성을 사용하면 nginx 구성 파일의 모든 수준에서 서버 및 위치별 설정을 지정할 수 있습니다. 최종적으로는 구성이 병합됩니다. NGX_CONF_UNSET, NGX_CONF_UNSET_UINT 등의 여러 가지 매크로를 사용하여 누락된 설정을 나타내고 병합하는 동안 이를 무시할 수 있습니다. ngx_conf_merge_value(), ngx_conf_merge_uint_value()와 같은 표준 nginx 병합 매크로는 설정을 병합하고, 명시적 값을 제공하는 구성이 없으면 기본값을 설정합니다. 각 유형의 매크로에 대한 전체 목록은 src/core/ngx_conf_file.h를 참조하세요.

구성 시점에 HTTP 모듈의 구성에 액세스하기 위한 매크로는 다음과 같습니다. 이 매크로는 모두 ngx_conf_t 참조를 첫 인수로 받습니다.

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

다음의 예시에서는 표준 nginx 코어 모듈인 ngx_http_core_module의 위치 구성에 대한 포인터를 가져와서 구조의 handler 필드에 저장된 위치 콘텐츠 핸들러를 대체합니다.

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

다음의 매크로는 런타임에서 HTTP 모듈 구성에 액세스할 때 제공됩니다.

  • ngx_http_get_module_main_conf(r, module)
  • ngx_http_get_module_srv_conf(r, module)
  • ngx_http_get_module_loc_conf(r, module)

이 매크로는 HTTP 요청 ngx_http_request_t에 대한 참조를 받습니다. 요청의 메인 구성은 절대 바뀌지 않습니다. 서버 구성은 요청에 대한 가상 서버를 선택한 뒤에 기본값에서 변경됩니다. 요청을 처리하기 위해 선택된 위치 구성은 재작성 작업이나 내부 리디렉션의 결과에 따라 여러 번 변경될 수 있습니다. 런타임에서 모듈의 HTTP 구성에 액세스하는 방법의 예시는 다음과 같습니다.

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

    ...
}

단계

각 HTTP 요청은 일련의 단계를 거칩니다. 각 단계에서 요청에 대해 각 유형의 처리를 실행합니다. 대부분 단계에서 모듈별 핸들러를 등록할 수 있고, 대부분 표준 nginx 모듈은 단계 핸들러를 등록하여 요청 처리 중 특정 단계에서 호출하는 수단으로 사용합니다. 단계는 차례대로 처리되고, 단계 핸들러는 요청이 해당 단계에 도달하면 호출됩니다. nginx HTTP 단계의 목록은 다음과 같습니다.

  • NGX_HTTP_POST_READ_PHASE — 첫 단계입니다. ngx_http_realip_module은 이 단계에서 핸들러를 등록하고 다른 모듈을 호출하기 전에 클라이언트 주소를 교체합니다.
  • NGX_HTTP_SERVER_REWRITE_PHASE — server 블록(location 블록 외부)에서 정의한 다시 쓰기 명령을 처리하는 단계입니다. 이 단계에서는 ngx_http_rewrite_module이 핸들러를 설치합니다.
  • NGX_HTTP_FIND_CONFIG_PHASE — 요청 URI에 따라 위치를 선택하는 특수한 단계입니다. 이 단계가 되기 전에 관련 가상 서버의 기본 위치를 요청에 할당하고, 위치 구성을 요청하는 모듈이 기본 서버 위치에 대한 구성을 수신합니다. 이 단계에서는 요청에 새 위치를 할당합니다. 이 단계에서는 추가적 핸들러를 등록할 수 있습니다.
  • NGX_HTTP_REWRITE_PHASE — NGX_HTTP_SERVER_REWRITE_PHASE와 동일하지만, 이전 단계에서 선택한 위치에서 정의된 다시 쓰기 규칙에 적용됩니다.
  • NGX_HTTP_POST_REWRITE_PHASE — 다시 쓰기 중 URI가 변경된 경우 새 위치로 요청을 리디렉션하는 특수한 단계입니다. 이는 NGX_HTTP_FIND_CONFIG_PHASE를 다시 통과하는 요청에서 구현합니다. 이 단계에서는 추가적 핸들러를 등록할 수 있습니다.
  • NGX_HTTP_PREACCESS_PHASE — 액세스 제어와 관계없이 각 유형의 핸들러에 적용되는 공통적인 단계입니다. 표준 nginx 모듈인 ngx_http_limit_conn_module 및 ngx_http_limit_req_module은 이 단계에서 핸들러를 등록합니다.
  • NGX_HTTP_ACCESS_PHASE — 클라이언트가 요청을 보내도록 인증받았는지 확인하는 단계입니다. ngx_http_access_module, ngx_http_auth_basic_module 와 같은 표준 nginx 모듈은 이 단계에서 핸들러를 등록합니다. 기본적으로 요청이 다음 단계로 넘어가려면 클라이언트가 이 단계에서 등록된 모든 핸들러의 인증 검사를 통과해야 합니다. satisfy 명령은 단계 핸들러 중 하나라도 클라이언트를 승인한다면 처리를 진행하도록 허용합니다.
  • NGX_HTTP_POST_ACCESS_PHASE — satisfy any 명령을 처리하는 특수한 단계입니다. 일부 액세스 단계 핸들러가 액세스를 거부하고 명시적으로 허용하는 핸들러가 없을 경우, 요청이 확정됩니다. 이 단계에서는 추가적 핸들러를 등록할 수 있습니다.
  • NGX_HTTP_PRECONTENT_PHASE — 콘텐츠를 생성하기 전에 핸들러를 호출하는 단계입니다. ngx_http_try_files_module, ngx_http_mirror_module 등의 표준 모듈은 이 단계에서 핸들러를 등록합니다.
  • NGX_HTTP_CONTENT_PHASE — 일반적으로 응답이 생성되는 단계입니다. ngx_http_index_module, ngx_http_static_module 등의 여러 nginx 표준 모듈이 이 단계에서 핸들러를 등록합니다. 그중 하나가 결과를 생성할 때까지 순차적으로 호출됩니다. 또한, 위치별로 콘텐츠 핸들러를 설정할 수 있습니다. ngx_http_core_module의 위치 구성에 handler가 설정되어 있으면, 콘텐츠 핸들러로 호출되고 이 단계에서 설치된 핸들러는 무시합니다.
  • NGX_HTTP_LOG_PHASE — 요청 로깅을 실행하는 단계입니다. 현재 ngx_http_log_module만 이 단계에서 로깅에 액세스하기 위해 핸들러를 등록합니다. 로그 단계 핸들러는 요청이 끝나고 요청을 해제하기 직전에 호출됩니다.

사전 액세스 단계 핸들러의 예시는 다음과 같습니다.

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

단계 핸들러는 특정 코드를 반환합니다.

  • NGX_OK — 다음 단계로 진행합니다.
  • NGX_DECLINED — 현재 단계의 다음 핸들러로 진행합니다. 현재 핸들러가 현재 단계의 마지막일 경우, 다음 단계로 진행합니다.
  • NGX_AGAIN, NGX_DONE — 비동기식 I/O 작업이거나 지연 등, 나중에 이벤트가 발생할 때까지 단계 처리를 중단합니다. 단계 처리는 ngx_http_core_run_phases()를 호출해서 나중에 재개할 것으로 가정합니다.
  • 단계 핸들러가 반환하는 다른 값은 요청 확정 코드, 특히 HTTP 응답 코드로 간주합니다. 요청은 제공된 코드로 확정됩니다.

일부 단계의 경우, 반환 코드가 다소 다르게 취급됩니다. 콘텐츠 단계에서 NGX_DECLINED를 제외한 반환 코드는 확정 코드로 간주됩니다. 위치 콘텐츠 핸들러의 반환 코드는 확정 코드로 간주됩니다. 액세스 단계의 satisfy any 모드에서 NGX_OK, NGX_DECLINED, NGX_AGAIN, NGX_DONE을 제외한 다른 반환 코드는 거부로 간주됩니다. 이후의 액세스 핸들러가 다른 코드로 액세스를 허용하거나 거부할 경우, 거부 코드가 확정 코드가 됩니다.

변수

기존 변수 액세스

변수는 인덱스(가장 일반적인 방법) 또는 이름(아래 참조)으로 참조할 수 있습니다. 인덱스는 구성 단계에서 변수를 구성에 추가할 때 생성됩니다. 변수 인덱스를 얻으려면 ngx_http_get_variable_index()를 사용합니다.

ngx_str_t  name;  /* ngx_string("foo") */
ngx_int_t  index;

index = ngx_http_get_variable_index(cf, &name);

여기에서 cf는 nginx 구성에 대한 포인터이고, name는 변수 이름을 포함한 문자열을 가리킵니다. 이 함수는 오류가 발생하면 NGX_ERROR를 반환하고, 오류가 발생하지 않으면 유효한 인덱스를 반환합니다. 일반적으로 인덱스는 나중에 사용할 수 있도록 모듈 구성 어딘가에 저장합니다.

모든 HTTP 변수는 특정 HTTP 요청의 컨텍스트로 평가하고, 결과는 해당 HTTP 요청에 따라 달라지고 그에 따라 캐싱됩니다. 변수를 평가하는 모든 함수는 변수 값을 나타내는 ngx_http_variable_value_t 유형을 반환합니다.

typedef ngx_variable_value_t  ngx_http_variable_value_t;

typedef struct {
    unsigned    len:28;

    unsigned    valid:1;
    unsigned    no_cacheable:1;
    unsigned    not_found:1;
    unsigned    escape:1;

    u_char     *data;
} ngx_variable_value_t;

변수:

  • len — 값의 길이
  • data — 값 자체
  • valid — 값의 유효성
  • not_found — 변수를 찾을 수 없어서 data 및 len 필드가 영향을 미치지 않습니다. 예를 들어, 해당 인수가 요청에 전달되지 않았을 때 $arg_foo와 같은 변수에 발생할 수 있습니다.
  • no_cacheable — 결과를 캐싱하지 않습니다.
  • escape — 로깅 모듈이 내부에서 결과에 대한 이스케이프가 필요한 값을 표시합니다.

ngx_http_get_flushed_variable() 및 ngx_http_get_indexed_variable() 함수는 변수의 값을 얻는 데 사용합니다. 이들은 인터페이스가 동일합니다. HTTP 요청 r을 변수를 평가하기 위한 컨텍스트와 이를 식별하는 index로 수락합니다. 일반적 사용 방법의 예시:

ngx_http_variable_value_t  *v;

v = ngx_http_get_flushed_variable(r, index);

if (v == NULL || v->not_found) {
    /* we failed to get value or there is no such variable, handle it */
    return NGX_ERROR;
}

/* some meaningful value is found */

함수들 사이에 차이가 있다면, ngx_http_get_indexed_variable()은 캐싱된 값을 반환하고 ngx_http_get_flushed_variable()은 캐싱할 수 없는 변수에 대한 캐시를 플러싱합니다.

SSI, Perl 등의 모듈은 구성 시점에 이름을 알 수 없었던 변수를 처리해야 합니다. 그러므로 인덱스는 이런 변수에 액세스하는 데 사용할 수 없지만, ngx_http_get_variable(r, name, key) 함수를 사용할 수 있습니다. 지정된 name과 이름에서 파생된 해시 key로 변수를 검색합니다.

변수 생성

변수를 생성하려면 ngx_http_add_variable() 함수를 사용합니다. 구성(변수를 등록하는 위치), 변수 이름, 함수의 동작을 제어하는 플래그를 인수로 받습니다.

  • NGX_HTTP_VAR_CHANGEABLE — 변수의 재정의를 지원합니다. 다른 모듈이 동일한 이름의 변수를 정의하더라도 충돌이 일어나지 않습니다. set 명령을 변수를 재정의할 수 있습니다.
  • NGX_HTTP_VAR_NOCACHEABLE — 캐싱을 비활성화합니다. $time_local 등의 변수에 유용합니다.
  • NGX_HTTP_VAR_NOHASH — 이 변수는 이름이 아니라 인덱스로만 액세스할 수 있습니다. 변수가 SSI, Perl 등의 모듈에 필요하지 않다는 것을 알 때 약간의 최적화에 사용할 수 있습니다.
  • NGX_HTTP_VAR_PREFIX — 변수 이름이 접두사입니다. 이 경우, 핸들러는 특정 변수의 값을 얻기 위한 추가적 로직을 구현해야 합니다. 예를 들어, 모든 “arg_” 변수는 동일한 핸들러로 처리합니다. 요청 인수를 조회하고 특정 인수의 값을 반환합니다.

이 함수는 오류 발생 시 NULL을 반환하고, 그 외에는 ngx_http_variable_t에 대한 포인터를 반환합니다.

struct ngx_http_variable_s {
    ngx_str_t                     name;
    ngx_http_set_variable_pt      set_handler;
    ngx_http_get_variable_pt      get_handler;
    uintptr_t                     data;
    ngx_uint_t                    flags;
    ngx_uint_t                    index;
};

get 및 set 핸들러는 변수 값을 얻거나 설정하기 위해 호출합니다. data는 변수 핸들러에 전달되고 index는 변수를 참조하는 데 사용한 할당된 변수 인덱스를 저장합니다.

일반적으로 ngx_http_variable_t 구조의 null 종료 고정 배열은 모듈에서 생성하고 사전 구성 단계에서 처리되어, 구성에 변수를 추가합니다.

static ngx_http_variable_t  ngx_http_foo_vars[] = {

    { ngx_string("foo_v1"), NULL, ngx_http_foo_v1_variable, 0, 0, 0 },

      ngx_http_null_variable
};

static ngx_int_t
ngx_http_foo_add_variables(ngx_conf_t *cf)
{
    ngx_http_variable_t  *var, *v;

    for (v = ngx_http_foo_vars; v->name.len; v++) {
        var = ngx_http_add_variable(cf, &v->name, v->flags);
        if (var == NULL) {
            return NGX_ERROR;
        }

        var->get_handler = v->get_handler;
        var->data = v->data;
    }

    return NGX_OK;
}

예시에 나와 있는 이 함수는 HTTP 모듈 컨텍스트의 preconfiguration 필드를 초기화하고 HTTP 구성을 파싱하기 전에 호출해서 파서가 이 변수를 참여하도록 하는 데 사용합니다.

get 핸들러는 다음과 같이 특정 요청의 컨텍스트에서 변수를 평가합니다.

static ngx_int_t
ngx_http_variable_connection(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data)
{
    u_char  *p;

    p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN);
    if (p == NULL) {
        return NGX_ERROR;
    }

    v->len = ngx_sprintf(p, "%uA", r->connection->number) - p;
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;
    v->data = p;

    return NGX_OK;
}

내부 오류(예: 메모리 할당 실패)가 발생하면 NGX_ERROR를 반환하고, 나머지는 NGX_OK를 반환합니다. 변수 평가 상태를 확인하려면 ngx_http_variable_value_t에서 플래그를 검사합니다(위의 설명 참조).

set 핸들러는 변수에서 참조한 속성을 설정합니다. 예를 들어 $limit_rate 변수의 설정 핸들러는 요청의 limit_rate 필드를 수정합니다.

...
{ ngx_string("limit_rate"), ngx_http_variable_request_set_size,
  ngx_http_variable_request_get_size,
  offsetof(ngx_http_request_t, limit_rate),
  NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 },
...

static void
ngx_http_variable_request_set_size(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data)
{
    ssize_t    s, *sp;
    ngx_str_t  val;

    val.len = v->len;
    val.data = v->data;

    s = ngx_parse_size(&val);

    if (s == NGX_ERROR) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "invalid size \"%V\"", &val);
        return;
    }

    sp = (ssize_t *) ((char *) r + data);

    *sp = s;

    return;
}

복잡한 값

복잡한 값은 이름과는 달리, 텍스트, 변수, 그 두 가지의 조합을 포함할 수 있는 식을 평가하기 위한 간편한 방법을 제공합니다.

ngx_http_compile_complex_value의 복잡한 값 설명은 구성 단계에서 ngx_http_complex_value_t로 컴파일되는데, 이는 런타임에서 사용하여 식 평가 결과를 얻는 데 사용합니다.

ngx_str_t                         *value;
ngx_http_complex_value_t           cv;
ngx_http_compile_complex_value_t   ccv;

value = cf->args->elts; /* directive arguments */

ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

ccv.cf = cf;
ccv.value = &value[1];
ccv.complex_value = &cv;
ccv.zero = 1;
ccv.conf_prefix = 1;

if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
    return NGX_CONF_ERROR;
}

이 경우, ccv에는 복잡한 값 cv을 초기화하는 데 필요한 모든 매개변수가 있습니다.

  • cf — 구성 포인터
  • value — 파싱할 문자열(입력)
  • complex_value — 컴파일된 값(출력)
  • zero — 0으로 종료되는 값을 지원하는 플래그
  • conf_prefix — 결과에 구성 접두사 첨부(nginx가 현재 구성을 검색하는 디렉터리)
  • root_prefix — 결과에 루트 접두사 첨부(일반 nginx 설치 접두사)

zero 플래그는 0으로 종료되는 문자열이 필요한 라이브러리에 전달될 때 유용하고, 접두사는 파일 이름을 처리할 때 편리합니다.

컴파일이 끝나면 cv.lengths에 식에 변수가 있는지 나타내는 정보가 포함됩니다. NULL 값은 식에 고성 텍스트만 포함되어 있으므로, 복잡한 값이 아니라 단순 문자열로 저장할 수 있습니다.

ngx_http_set_complex_value_slot()은 명령 선언 자체에서 완전히 복잡한 값을 초기화하는 데 사용하는 편리한 함수입니다.

런타임에서 복잡한 값은 ngx_http_complex_value() 함수를 사용하여 계산할 수 있습니다.

ngx_str_t  res;

if (ngx_http_complex_value(r, &cv, &res) != NGX_OK) {
    return NGX_ERROR;
}

요청 r 및 앞서 컴파일한 값 cv에 따라, 함수가 식을 평가하고 결과를 res에 작성합니다.

Request redirection

HTTP 요청은 언제나 ngx_http_request_t 구조의 loc_conf 필드를 통해 위치에 연결됩니다. 즉, 어느 시점에서든 ngx_http_get_module_loc_conf(r, module)를 호출하여 모듈의 위치 구성을 가져올 수 있습니다. 요청 위치는 요청의 수명 주기가 끝날 때까지 여러 번 변경될 수 있습니다. 원래 기본 서버의 기본 서버 위치는 요청에 할당됩니다. 요청이 다른 서버로 전환되면(HTTP “Host” 헤더 또는 SSL SNI 확장 프로그램에서 선택), 요청이 해당 서버의 기본 위치로 전환됩니다. 다음 번에는 NGX_HTTP_FIND_CONFIG_PHASE 요청 단계에서 위치가 변경됩니다. 이 단계에서 서버에 구성된 모든 이름을 지정하지 않은 위치 중에서 요청 URI가 선택한 위치입니다. ngx_http_rewrite_module은 NGX_HTTP_REWRITE_PHASE 요청 단계에서 rewrite 명령을 실행하여 요청 URI을 변경한 후, NGX_HTTP_FIND_CONFIG_PHASE 단계로 요청을 보내 새로운 URI에 따라 새 위치를 선택하도록 할 수 있습니다.

또한, ngx_http_internal_redirect(r, uri, args) 또는 ngx_http_named_location(r, name) 중 하나를 호출하여 어느 지점에서나 새 위치로 요청을 리디렉션할 수 있습니다.

ngx_http_internal_redirect(r, uri, args) 함수는 요청 URI를 변경하고 요청을 NGX_HTTP_SERVER_REWRITE_PHASE 단계로 반환합니다. 요청은 서버 기본 위치로 처리됩니다. 나중에 NGX_HTTP_FIND_CONFIG_PHASE에서 새 요청 URI에 따라 새 위치를 선택합니다.

다음의 예시에서는 새 요청 인수로 내부 리디렉션을 실행합니다.

ngx_int_t
ngx_http_foo_redirect(ngx_http_request_t *r)
{
    ngx_str_t  uri, args;

    ngx_str_set(&uri, "/foo");
    ngx_str_set(&args, "bar=1");

    return ngx_http_internal_redirect(r, &uri, &args);
}

ngx_http_named_location(r, name) 함수는 이름이 지정된 위치로 요청을 리디렉션합니다. 위치 이름은 인수로 전달됩니다. 현재 서버에서 이름이 지정된 모든 위치에서 위치를 검색한 다음, 요청을 NGX_HTTP_REWRITE_PHASE 단계로 전환합니다.

다음의 예시에서 이름이 지정된 위치 @foo로의 리디렉션을 실행합니다.

ngx_int_t
ngx_http_foo_named_redirect(ngx_http_request_t *r)
{
    ngx_str_t  name;

    ngx_str_set(&name, "foo");

    return ngx_http_named_location(r, &name);
}

nginx 모듈이 이미 요청의 ctx 필드에 일부 컨텍스트를 저장했을 때 ngx_http_internal_redirect(r, uri, args)와 ngx_http_named_location(r, name) 함수를 모두 호출할 수 있습니다. 이 컨텍스트가 새 위치 구성과 일치하지 않을 수 있습니다. 불일치를 예방하기 위해 모든 요청 컨텍스트는 두 개의 리디렉션 함수로 삭제합니다.

ngx_http_internal_redirect(r, uri, args) 또는 ngx_http_named_location(r, name)을 호출하면 요청 count가 늘어납니다. 요청 참조 수에서 일관성을 유지하려면 요청을 리디렉션한 후 ngx_http_finalize_request(r, NGX_DONE)을 호출합니다. 그러면 현재 요청 코드 경로가 확정되고 카운트가 감소합니다.

리디렉션되어 다시 쓴 요청은 내부화되고, 내부 위치에 액세스할 수 있습니다. 내부 요청은 internal 플래그가 설정됩니다.

Subrequests

하위 요청은 주로 한 요청의 결과를 다른 요청의 결과에 넣어서, 다른 데이터와 섞는 데 사용합니다. 하위 요청은 일반 요청처럼 보이지만, 상위 요청과 일부 데이터를 공유합니다. 특히, 하위 요청은 클라이언트에서 다른 입력을 받지 않기 때문에 클라이언트 입력과 관련된 모든 필드는 공유됩니다. 하위 요청에 대한 요청 필드 parent에는 상위 요청에 대한 링크가 포함되어 있고, 메인 요청에 대해서는 NULL입니다. main 필드에는 요청 그룹의 메인 요청에 대한 링크가 포함됩니다.

하위 요청은 NGX_HTTP_SERVER_REWRITE_PHASE 단계에서 시작합니다. 동일한 하위 요청 단계를 일반 요청처럼 진행하고, 자체적인 URI에 따라 위치를 할당합니다.

하위 요청에서 출력 헤더는 항상 무시합니다. ngx_http_postpone_filter는 상위 요청에서 생성한 다른 데이터에 대해 적절한 위치에 하위 요청의 결과 본문을 배치합니다.

하위 요청은 활성화된 요청의 개념과 관련이 있습니다. 요청 r은 c->data == r일 경우 활성 상태로 간주합니다. 여기서 c는 클라이언트 연결 객체입니다. 어떤 지점에서든 요청 그룹에서 활성화된 요청만 버퍼를 클라이언트로 내보낼 수 있습니다. 비활성 요청도 결과를 파일 체인에 보낼 수 있지만, ngx_http_postpone_filter 이상 통과할 수 없고 요청이 활성 상태가 될 때까지 필터에서 버퍼링됩니다. 요청 활성화의 규칙은 다음과 같습니다.

  • 처음에 메인 요청은 활성 상태입니다.
  • 활성 요청의 첫 하위 요청이 생성 직후에 활성화됩니다.
  • 해당 요청 이전의 모든 데이터를 전송하고 나면 ngx_http_postpone_filter는 활성 요청의 하위 요청 목록에서 다음 요청을 활성화합니다.
  • 요청이 확정되면 상위 요청을 활성화합니다.

ngx_http_subrequest(r, uri, args, psr, ps, flags) 함수를 호출하여 하위 요청을 생성합니다. r은 상위 요청이고, uri 및 args는 하위 요청의 URI 및 인수이고, psr은 결과 매개변수로써 새로 생성된 하위 요청 참조를 수신합니다. ps는 상위 요청에 하위 요청이 확정 중임을 알리기 위한 콜백 객체이고 flags는 플래그의 비트마스크입니다. 사용 가능한 플래그는 다음과 같습니다.

  • NGX_HTTP_SUBREQUEST_IN_MEMORY – 결과는 클라이언트로 보내지 않고 메모리에 저장합니다. 이 플래그는 프록시되는 모듈 중 하나에서 처리되는 하위 요청에만 영향을 미칩니다. 하위 요청이 확정되고 나면 ngx_buf_t 유형의 r->out에서 결과가 제공됩니다.
  • NGX_HTTP_SUBREQUEST_WAITED – 하위 요청이 확정 시 활성 상태가 아니라도 하위 요청의 done 플래그가 설정됩니다. 이 하위 요청 플래그는 SSI 필터에서 사용합니다.
  • NGX_HTTP_SUBREQUEST_CLONE – 하위 요청은 상위 요청의 클론으로 생성됩니다. 상위 요청과 동일한 위치에서 시작되어서 동일한 단계로 진행됩니다.

다음의 예시에서는 /foo의 URI로 하위 요청을 생성합니다.

ngx_int_t            rc;
ngx_str_t            uri;
ngx_http_request_t  *sr;

...

ngx_str_set(&uri, "/foo");

rc = ngx_http_subrequest(r, &uri, NULL, &sr, NULL, 0);
if (rc == NGX_ERROR) {
    /* error */
}

이 예시는 현재 요청을 복제하고 하위 요청에 대한 확정 콜백을 설정합니다.

ngx_int_t
ngx_http_foo_clone(ngx_http_request_t *r)
{
    ngx_http_request_t          *sr;
    ngx_http_post_subrequest_t  *ps;

    ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
    if (ps == NULL) {
        return NGX_ERROR;
    }

    ps->handler = ngx_http_foo_subrequest_done;
    ps->data = "foo";

    return ngx_http_subrequest(r, &r->uri, &r->args, &sr, ps,
                               NGX_HTTP_SUBREQUEST_CLONE);
}


ngx_int_t
ngx_http_foo_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc)
{
    char  *msg = (char *) data;

    ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                  "done subrequest r:%p msg:%s rc:%i", r, msg, rc);

    return rc;
}

일반적으로 하위 요청은 본문 필터에서 생성되는데, 이 경우 명시적 요청에서 얻은 결과처럼 처리할 수 있습니다. 즉, 하위 요청 생성 전에 전달된 모든 명시적 버퍼 이후부터 생성 이후에 전달된 버퍼 전까지 하위 요청의 결과를 클라이언트로 전송합니다. 하위 요청의 계층 구조가 클 때도 이 순서는 그대로 유지됩니다. 다음의 예시에서는 모든 요청 데이터 버퍼 후부터 last_buf 플래그가 있는 최종 버퍼 전까지 하위 요청의 결과를 삽입합니다.

ngx_int_t
ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                   rc;
    ngx_buf_t                  *b;
    ngx_uint_t                  last;
    ngx_chain_t                *cl, out;
    ngx_http_request_t         *sr;
    ngx_http_foo_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module);
    if (ctx == NULL) {
        return ngx_http_next_body_filter(r, in);
    }

    last = 0;

    for (cl = in; cl; cl = cl->next) {
        if (cl->buf->last_buf) {
            cl->buf->last_buf = 0;
            cl->buf->last_in_chain = 1;
            cl->buf->sync = 1;
            last = 1;
        }
    }

    /* Output explicit output buffers */

    rc = ngx_http_next_body_filter(r, in);

    if (rc == NGX_ERROR || !last) {
        return rc;
    }

    /*
     * Create the subrequest.  The output of the subrequest
     * will automatically be sent after all preceding buffers,
     * but before the last_buf buffer passed later in this function.
     */

    if (ngx_http_subrequest(r, ctx->uri, NULL, &sr, NULL, 0) != NGX_OK) {
        return NGX_ERROR;
    }

    ngx_http_set_ctx(r, NULL, ngx_http_foo_filter_module);

    /* Output the final buffer with the last_buf flag */

    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_ERROR;
    }

    b->last_buf = 1;

    out.buf = b;
    out.next = NULL;

    return ngx_http_output_filter(r, &out);
}

또한, 하위 요청은 데이터 출력 외에 다른 목적으로도 생성할 수 있습니다. 예를 들어 ngx_http_auth_request_module 모듈은 NGX_HTTP_ACCESS_PHASE 단계에서 하위 요청을 생성합니다. 이 지점에서 출력을 비활성화하려면 하위 요청에 header_only 플래그를 설정합니다. 그러면 하위 요청 본문이 클라이언트로 전송되지 않습니다. 단, 하위 요청의 헤더는 절대 클라이언트로 전송되지 않습니다. 하위 요청의 결과는 콜백 핸들러에서 분석할 수 있습니다.

요청 확정

HTTP 요청은 ngx_http_finalize_request(r, rc) 함수를 호출하여 확정됩니다. 일반적으로 모든 출력 버퍼를 필터 체인으로 전송한 후에 콘텐츠 핸들러로 확정합니다. 이 지점에서 모든 출력을 클라이언트로 전송하지 않았을 수 있고, 일부는 필터 체인 어딘가에서 버퍼링되는 상태일 수 있습니다. 이 경우, ngx_http_finalize_request(r, rc) 함수는 특수 핸들러 ngx_http_writer(r)를 설치하여 결과 전송을 완료합니다. 요청은 오류가 발생하거나, 표준 HTTP 응답 코드를 클라이언트로 반환해야 할 경우에도 확정됩니다.

ngx_http_finalize_request(r, rc) 함수에서는 다음의 rc 값을 기대합니다.

  • NGX_DONE – 빠른 확정. 요청 count를 줄여서 0에 도달하면 요청을 제거합니다. 현재 요청을 제거한 후 다른 요청에 클라이언트 연결을 사용할 수 있습니다.
  • NGX_ERROR, NGX_HTTP_REQUEST_TIME_OUT (408), NGX_HTTP_CLIENT_CLOSED_REQUEST (499) – 오류 확정. 최대한 빠르게 요청을 종료하고 클라이언트 연결을 닫습니다.
  • NGX_HTTP_CREATED(201), NGX_HTTP_NO_CONTENT(204), NGX_HTTP_SPECIAL_RESPONSE(300) 이상의 코드 – 특수한 응답 확정. 이 값의 경우, nginx는 클라이언트로 코드에 대한 기본 응답 페이지를 전송하거나, 코드에 대해 error_page 위치가 구성되어 있으면 내부 리디렉션을 실행합니다.
  • 다른 코드는 성공적인 확정 코드로 간주되고, 요청 작성기를 활성화하여 응답 본문의 전송을 완료할 수 있습니다. 본문이 완전히 전송되면 요청 count가 감소합니다. 0에 도달하면 요청이 제거되지만 클라이언트 연결은 다른 요청에 사용할 수 있습니다. count가 양수일 경우, 요청에 완료되지 않은 활동이 있는 것입니다. 이는 나중에 확정됩니다.

요청 본문

nginx는 클라이언트 요청의 본문을 처리하기 위해 ngx_http_read_client_request_body(r, post_handler) 및 ngx_http_discard_request_body(r) 함수를 제공합니다. 첫 번째 함수는 요청 본문을 읽고 request_body 요청 필드를 통해 제공합니다. 두 번째 함수는 nginx에서 요청 본문을 닫습니다(읽고 무시). 모든 요청에서 이 함수 중 하나를 호출해야 합니다. 일반적으로 콘텐츠 핸들러가 호출을 보냅니다.

하위 요청에서 클라이언트 요청 본문을 읽거나 닫을 수 없습니다. 이 작업은 항상 메인 요청에서 처리해야 합니다. 하위 요청을 생성할 경우, 상위 요청의 request_body 객체를 상속합니다. 메인 요청이 요청 본문을 읽었다면 하위 요청에서 이를 사용할 수 있습니다.

ngx_http_read_client_request_body(r, post_handler) 함수가 요청 본문을 읽는 절차를 시작합니다. 본문을 완전히 읽고 나면 post_handler 콜백을 호출하여 요청을 계속 처리합니다. 요청 본문이 누락되었거나 이를 이미 읽은 경우, 콜백을 즉시 호출합니다. ngx_http_read_client_request_body(r, post_handler) 함수가 ngx_http_request_body_t 유형의 request_body 요청 필드를 할당합니다. 이 객체의 bufs 필드는 결과를 버퍼 체인으로 저장합니다. client_body_buffer_size 명령에서 저장한 용량으로는 메모리에 본문 전체를 넣을 수 없을 경우, 본문은 메모리 버퍼나 파일 버퍼에 저장할 수 있습니다.

다음의 예시에서는 클라이언트 요청 분문을 읽고 크기를 반환합니다.

ngx_int_t
ngx_http_foo_content_handler(ngx_http_request_t *r)
{
    ngx_int_t  rc;

    rc = ngx_http_read_client_request_body(r, ngx_http_foo_init);

    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
        /* error */
        return rc;
    }

    return NGX_DONE;
}


void
ngx_http_foo_init(ngx_http_request_t *r)
{
    off_t         len;
    ngx_buf_t    *b;
    ngx_int_t     rc;
    ngx_chain_t  *in, out;

    if (r->request_body == NULL) {
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }

    len = 0;

    for (in = r->request_body->bufs; in; in = in->next) {
        len += ngx_buf_size(in->buf);
    }

    b = ngx_create_temp_buf(r->pool, NGX_OFF_T_LEN);
    if (b == NULL) {
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }

    b->last = ngx_sprintf(b->pos, "%O", len);
    b->last_buf = (r == r->main) ? 1: 0;
    b->last_in_chain = 1;

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = b->last - b->pos;

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        ngx_http_finalize_request(r, rc);
        return;
    }

    out.buf = b;
    out.next = NULL;

    rc = ngx_http_output_filter(r, &out);

    ngx_http_finalize_request(r, rc);
}

다음의 요청 필드에서 요청 본문을 읽는 방식을 결정합니다.

  • request_body_in_single_buf – 본문을 단일 메모리 버퍼로 읽습니다.
  • request_body_in_file_only – 메모리 버퍼에 들어가더라도 항상 본문을 파일로 읽습니다.
  • request_body_in_persistent_file – 생성 후 즉시 파일의 연결을 해제하지 않습니다. 이 플래그가 있는 파일은 다른 디렉터리로 옮길 수 있습니다.
  • request_body_in_clean_file – 요청 확정 시, 파일의 연결을 해제합니다. 파일을 다른 디렉터리로 옮겨야 하지만 어떤 이유로든 옮기지 못했을 때 유용할 수 있습니다.
  • request_body_file_group_access – 기본 0600 액세스 마스크를 0660으로 바꿔서 파일에 대한 그룹 액세스를 활성화합니다.
  • request_body_file_log_level – 파일 오류를 로깅하는 심각도 수준입니다.
  • request_body_no_buffering – 버퍼링 없이 요청 본문을 읽습니다.

request_body_no_buffering 플래그를 사용하면 요청 본문을 읽는 버퍼링되지 않은 모드가 활성화됩니다. 이 모드에서 ngx_http_read_client_request_body()를 호출한 후 bufs 체인에 본문의 일부만 남을 수 있습니다. 다음 부분을 읽으려면 ngx_http_read_unbuffered_request_body(r) 함수를 호출합니다. 반환 값 NGX_AGAIN과 요청 플래그 reading_body는 더 많은 데이터를 사용할 수 있다는 것을 의미합니다. 이 함수를 호출한 후 bufs가 NULL이면 현재 읽을 내용이 없다는 것입니다. 요청 본문의 다음 부분을 읽을 수 있으면 요청 콜백 read_event_handler를 호출합니다.

요청 본문 필터

요청 본문 부분을 읽은 후, ngx_http_top_request_body_filter 변수에 저장된 첫 번째 바디 필터 핸들러를 호출하여 요청 본문 필터 체인으로 전달합니다. 마지막 핸들러 ngx_http_request_body_save_filter(r, cl)를 호출할 때까지 모든 본문 핸들러가 체인에서 다음 핸들러를 호출하는 것으로 가정합니다. 이 핸들러는 r->request_body->bufs에서 버퍼를 수집하고 필요에 따라 파일로 작성합니다. 마지막 요청 본문 버퍼는 0이 아닌 last_buf 플래그가 있습니다.

필터가 데이터 버퍼를 지연할 예정인 경우, 처음에 호출되었을 때 r->request_body->filter_need_buffering 플래그를 1로 설정해야 합니다.

다음은 요청 본문을 1초 지연하는 간단한 요청 본문 필터의 예시입니다.

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


#define NGX_HTTP_DELAY_BODY  1000


typedef struct {
    ngx_event_t   event;
    ngx_chain_t  *out;
} ngx_http_delay_body_ctx_t;


static ngx_int_t ngx_http_delay_body_filter(ngx_http_request_t *r,
    ngx_chain_t *in);
static void ngx_http_delay_body_cleanup(void *data);
static void ngx_http_delay_body_event_handler(ngx_event_t *ev);
static ngx_int_t ngx_http_delay_body_init(ngx_conf_t *cf);


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


ngx_module_t  ngx_http_delay_body_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_delay_body_module_ctx, /* module context */
    NULL,                          /* module directives */
    NGX_HTTP_MODULE,               /* module type */
    NULL,                          /* init master */
    NULL,                          /* init module */
    NULL,                          /* init process */
    NULL,                          /* init thread */
    NULL,                          /* exit thread */
    NULL,                          /* exit process */
    NULL,                          /* exit master */
    NGX_MODULE_V1_PADDING
};


static ngx_http_request_body_filter_pt   ngx_http_next_request_body_filter;


static ngx_int_t
ngx_http_delay_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                   rc;
    ngx_chain_t                *cl, *ln;
    ngx_http_cleanup_t         *cln;
    ngx_http_delay_body_ctx_t  *ctx;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "delay request body filter");

    ctx = ngx_http_get_module_ctx(r, ngx_http_delay_body_filter_module);

    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_delay_body_ctx_t));
        if (ctx == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_delay_body_filter_module);

        r->request_body->filter_need_buffering = 1;
    }

    if (ngx_chain_add_copy(r->pool, &ctx->out, in) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (!ctx->event.timedout) {
        if (!ctx->event.timer_set) {

            /* cleanup to remove the timer in case of abnormal termination */

            cln = ngx_http_cleanup_add(r, 0);
            if (cln == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            cln->handler = ngx_http_delay_body_cleanup;
            cln->data = ctx;

            /* add timer */

            ctx->event.handler = ngx_http_delay_body_event_handler;
            ctx->event.data = r;
            ctx->event.log = r->connection->log;

            ngx_add_timer(&ctx->event, NGX_HTTP_DELAY_BODY);
        }

        return ngx_http_next_request_body_filter(r, NULL);
    }

    rc = ngx_http_next_request_body_filter(r, ctx->out);

    for (cl = ctx->out; cl; /* void */) {
        ln = cl;
        cl = cl->next;
        ngx_free_chain(r->pool, ln);
    }

    ctx->out = NULL;

    return rc;
}


static void
ngx_http_delay_body_cleanup(void *data)
{
    ngx_http_delay_body_ctx_t *ctx = data;

    if (ctx->event.timer_set) {
        ngx_del_timer(&ctx->event);
    }
}


static void
ngx_http_delay_body_event_handler(ngx_event_t *ev)
{
    ngx_connection_t    *c;
    ngx_http_request_t  *r;

    r = ev->data;
    c = r->connection;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "delay request body event");

    ngx_post_event(c->read, &ngx_posted_events);
}


static ngx_int_t
ngx_http_delay_body_init(ngx_conf_t *cf)
{
    ngx_http_next_request_body_filter = ngx_http_top_request_body_filter;
    ngx_http_top_request_body_filter = ngx_http_delay_body_filter;

    return NGX_OK;
}

Response

nginx에서 HTTP 응답은 선택적 요청 본문 다음에 응답 헤더를 전송하면 생성됩니다. 헤더와 본문은 모두 필터 체인을 통과하며, 최종적으로는 클라이언트 소켓에 기록됩니다. nginx 모듈은 헤더나 본문 필터 체인에 핸들러를 설치하고 이전 핸들러에서 얻은 결과를 처리할 수 있습니다.

응답 헤더

ngx_http_send_header(r) 함수는 결과 헤더를 전송합니다. 이 함수는 r->headers_out에 HTTP 응답 헤더를 생성하는 데 필요한 모든 데이터가 포함될 때까지 호출하지 않습니다. r->headers_out의 status 필드는 항상 설정해야 합니다. 응답 상태에서 응답 본문이 헤더를 따라가는 것으로 나타날 경우, content_length_n도 설정할 수 있습니다. 이 필드의 기본값은 -1이고 본문 크기를 알 수 없다는 것을 의미합니다. 이 경우, 청크로 나뉜 전송 인코딩을 사용합니다. 임의의 헤더를 출력하려면 headers 목록을 첨부합니다.

static ngx_int_t
ngx_http_foo_content_handler(ngx_http_request_t *r)
{
    ngx_int_t         rc;
    ngx_table_elt_t  *h;

    /* send header */

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = 3;

    /* X-Foo: foo */

    h = ngx_list_push(&r->headers_out.headers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    h->hash = 1;
    ngx_str_set(&h->key, "X-Foo");
    ngx_str_set(&h->value, "foo");

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    /* send body */

    ...
}

헤더 필터

ngx_http_send_header(r) 함수는 ngx_http_top_header_filter 변수에 저장된 첫 번째 헤더 필터 핸들러를 호출하여 헤더 필터 체인을 호출합니다. 마지막 핸들러 ngx_http_header_filter(r)를 호출할 때까지 모든 헤더 핸들러가 체인에서 다음 핸들러를 호출하는 것으로 가정합니다. 마지막 헤더 핸들러는 r->headers_out에 따라 HTTP 응답을 생성하고 출력을 위해 ngx_http_writer_filter로 전달합니다.

핸들러를 헤더 필터 체인에 추가하려면 구성 시점에 전역 변수 ngx_http_top_header_filter에 주소를 저장합니다. 일반적으로 이전 핸들러 주소는 모듈의 고정 변수에 저장되고, 새로 추가된 핸들러를 종료하기 전에 호출됩니다.

다음의 헤더 필터 모듈 예시에서는 HTTP 헤더 “X-Foo: foo”를 상태가 200인 모든 응답에 추가합니다.

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


static ngx_int_t ngx_http_foo_header_filter(ngx_http_request_t *r);
static ngx_int_t ngx_http_foo_header_filter_init(ngx_conf_t *cf);


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


ngx_module_t  ngx_http_foo_header_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_foo_header_filter_module_ctx, /* module context */
    NULL,                                   /* module directives */
    NGX_HTTP_MODULE,                        /* module type */
    NULL,                                   /* init master */
    NULL,                                   /* init module */
    NULL,                                   /* init process */
    NULL,                                   /* init thread */
    NULL,                                   /* exit thread */
    NULL,                                   /* exit process */
    NULL,                                   /* exit master */
    NGX_MODULE_V1_PADDING
};


static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;


static ngx_int_t
ngx_http_foo_header_filter(ngx_http_request_t *r)
{
    ngx_table_elt_t  *h;

    /*
     * The filter handler adds "X-Foo: foo" header
     * to every HTTP 200 response
     */

    if (r->headers_out.status != NGX_HTTP_OK) {
        return ngx_http_next_header_filter(r);
    }

    h = ngx_list_push(&r->headers_out.headers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    h->hash = 1;
    ngx_str_set(&h->key, "X-Foo");
    ngx_str_set(&h->value, "foo");

    return ngx_http_next_header_filter(r);
}


static ngx_int_t
ngx_http_foo_header_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_foo_header_filter;

    return NGX_OK;
}

응답 본문

응답 본문을 전송하려면 ngx_http_output_filter(r, cl) 함수를 호출합니다. 이 함수는 여러 번 호출할 수 있습니다. 호출할 때마다 응답 본문의 일부를 버퍼 체인의 형태로 전송합니다. 마지막 본문 버퍼에서 last_buf 플래그를 설정합니다.

다음의 예시에서는 “foo”를 본문으로 하는 완전한 HTTP 응답을 생성합니다. 하위 요청 및 메인 요청 역할을 하는 예시의 경우, 결과의 마지막 버퍼에 last_in_chain 플래그를 설정합니다. 하위 요청의 마지막 버퍼는 전체 출력으로 끝나지 않으므로 last_buf 플래그는 메인 요청에 대해서만 설정됩니다.

static ngx_int_t
ngx_http_bar_content_handler(ngx_http_request_t *r)
{
    ngx_int_t     rc;
    ngx_buf_t    *b;
    ngx_chain_t   out;

    /* send header */

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = 3;

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    /* send body */

    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_ERROR;
    }

    b->last_buf = (r == r->main) ? 1: 0;
    b->last_in_chain = 1;

    b->memory = 1;

    b->pos = (u_char *) "foo";
    b->last = b->pos + 3;

    out.buf = b;
    out.next = NULL;

    return ngx_http_output_filter(r, &out);
}

응답 본문 필터

ngx_http_output_filter(r, cl) 함수는 ngx_http_top_body_filter 변수에 저장된 첫 번째 본문 필터 핸들러를 호출하여 본문 필터 체인을 호출합니다. 마지막 핸들러 ngx_http_write_filter(r, cl)를 호출할 때까지 모든 본문 핸들러가 체인에서 다음 핸들러를 호출하는 것으로 가정합니다.

본문 필터 핸들러는 버퍼 체인을 받습니다. 이 핸들러가 버퍼를 처리하고 새로운 체인을 다음 핸들러로 전달해야 합니다. 다만, 수신되는 체인의 체인 링크 ngx_chain_t는 호출자에 속하며 다시 사용하거나 변경해서는 안 됩니다. 핸들러가 완료된 직후에 호출자는 출력 체인 링크를 사용하여 전송한 버퍼를 추적할 수 있습니다. 버퍼 체인을 저장하거나 다음 필터로 전달하기 전에 일부 버퍼를 교체하려면, 핸들러가 자체 체인 링크를 할당해야 합니다.

본문의 용량(바이트)을 세는 간단한 본문 필터의 예시는 다음과 같습니다. 결과는 $counter 변수로 제공되며, 이는 액세스 로그에서 사용할 수 있습니다.

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


typedef struct {
    off_t  count;
} ngx_http_counter_filter_ctx_t;


static ngx_int_t ngx_http_counter_body_filter(ngx_http_request_t *r,
    ngx_chain_t *in);
static ngx_int_t ngx_http_counter_variable(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_counter_add_variables(ngx_conf_t *cf);
static ngx_int_t ngx_http_counter_filter_init(ngx_conf_t *cf);


static ngx_http_module_t  ngx_http_counter_filter_module_ctx = {
    ngx_http_counter_add_variables,        /* preconfiguration */
    ngx_http_counter_filter_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 */
};


ngx_module_t  ngx_http_counter_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_counter_filter_module_ctx,   /* module context */
    NULL,                                  /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};


static ngx_http_output_body_filter_pt  ngx_http_next_body_filter;

static ngx_str_t  ngx_http_counter_name = ngx_string("counter");


static ngx_int_t
ngx_http_counter_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_chain_t                    *cl;
    ngx_http_counter_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module);
    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_counter_filter_ctx_t));
        if (ctx == NULL) {
            return NGX_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_counter_filter_module);
    }

    for (cl = in; cl; cl = cl->next) {
        ctx->count += ngx_buf_size(cl->buf);
    }

    return ngx_http_next_body_filter(r, in);
}


static ngx_int_t
ngx_http_counter_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
    uintptr_t data)
{
    u_char                         *p;
    ngx_http_counter_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module);
    if (ctx == NULL) {
        v->not_found = 1;
        return NGX_OK;
    }

    p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
    if (p == NULL) {
        return NGX_ERROR;
    }

    v->data = p;
    v->len = ngx_sprintf(p, "%O", ctx->count) - p;
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;

    return NGX_OK;
}


static ngx_int_t
ngx_http_counter_add_variables(ngx_conf_t *cf)
{
    ngx_http_variable_t  *var;

    var = ngx_http_add_variable(cf, &ngx_http_counter_name, 0);
    if (var == NULL) {
        return NGX_ERROR;
    }

    var->get_handler = ngx_http_counter_variable;

    return NGX_OK;
}


static ngx_int_t
ngx_http_counter_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_counter_body_filter;

    return NGX_OK;
}

필터 모듈 구축

본문 또는 헤더 필터를 작성할 때, 필터 순서에서 필터의 위치에 특히 주의를 기울여야 합니다. nginx 표준 모듈에서 등록한 헤더와 본문 필터는 여러 개가 있습니다. nginx 표준 모듈은 여러 가지 헤드 및 본문 필터를 등록하고 이와 관련하여 적절한 위치에 새 필터 모듈을 등록하는 것이 중요합니다. 일반적으로 모듈은 구성 후 핸들러에 필터를 등록합니다. 처리 중에 필터를 호출하는 순서는 등록한 순서의 역순입니다.

타사 필터 모듈의 경우, nginx에서 특수 슬롯 HTTP_AUX_FILTER_MODULES를 제공합니다. 이 슬롯에서 필터 모듈을 등록하려면 모듈 구성 시 ngx_module_type 변수를 HTTP_AUX_FILTER로 설정합니다.

다음의 예시는 소스 파일이 ngx_http_foo_filter_module.c 1개뿐인 모듈을 가정했을 때 필터 모듈 구성 파일을 나타냅니다.

ngx_module_type=HTTP_AUX_FILTER
ngx_module_name=ngx_http_foo_filter_module
ngx_module_srcs="$ngx_addon_dir/ngx_http_foo_filter_module.c"

. auto/module

버퍼 재사용

버퍼 스트림을 내보내거나 변경할 때는 대체로 할당된 버퍼를 재사용하는 것이 좋습니다. nginx 코드에서 표준이면서도 널리 사용되는 방법은 이 용도로 free와 busy의 버퍼 체인을 유지하는 것입니다. free 체인은 모든 사용 가능한 버퍼를 보관하고, 버퍼를 재사용할 수 있습니다. busy 체인은 현재 모듈에서 전송하고 다른 필터 핸들러에서 아직 사용 중인 모든 버퍼를 보관합니다. 크기가 0보다 크면 버퍼를 사용 중인 것으로 간주합니다. 일반적으로 필터에서 버퍼를 사용할 때는 pos(파일 버퍼의 경우 file_pos)를 last(파일 버퍼의 경우 file_last)를 향해 이동합니다. 버퍼를 완전히 사용하고 나면 다시 사용할 수 있습니다. 새롭게 해제된 버퍼를 free 체인에 추가할 때는 busy 체인에 대해 이터레이션하고 헤드에 있는 크기가 0인 버퍼를 free로 옮깁니다. 이 작업은 매우 자주 사용하기 때문에 ngx_chain_update_chains(free, busy, out, tag)라는 특별한 함수도 있습니다. 이 함수는 출력 체인 out을 busy에 첨부하고, 사용 가능한 버퍼를 busy 위에서 free로 보냅니다. 지정된 tag가 있는 버퍼만 재사용합니다. 그러면 모듈에서 자체 할당된 버퍼만 다시 사용합니다.

다음의 예시는 각각 버퍼를 수신하기 전에 “foo” 문자열을 넣는 본문 필터입니다. 모듈에서 할당한 새 버퍼는 되도록 재사용합니다. 이 예시가 올바르게 작동하려면 헤더 필터를 설정하고 content_length_n을 -1로 다시 설정해야 하지만, 여기에는 관련 코드를 제공하지 않았습니다.

typedef struct {
    ngx_chain_t  *free;
    ngx_chain_t  *busy;
}  ngx_http_foo_filter_ctx_t;


ngx_int_t
ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                   rc;
    ngx_buf_t                  *b;
    ngx_chain_t                *cl, *tl, *out, **ll;
    ngx_http_foo_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module);
    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_foo_filter_ctx_t));
        if (ctx == NULL) {
            return NGX_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_foo_filter_module);
    }

    /* create a new chain "out" from "in" with all the changes */

    ll = &out;

    for (cl = in; cl; cl = cl->next) {

        /* append "foo" in a reused buffer if possible */

        tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
        if (tl == NULL) {
            return NGX_ERROR;
        }

        b = tl->buf;
        b->tag = (ngx_buf_tag_t) &ngx_http_foo_filter_module;
        b->memory = 1;
        b->pos = (u_char *) "foo";
        b->last = b->pos + 3;

        *ll = tl;
        ll = &tl->next;

        /* append the next incoming buffer */

        tl = ngx_alloc_chain_link(r->pool);
        if (tl == NULL) {
            return NGX_ERROR;
        }

        tl->buf = cl->buf;
        *ll = tl;
        ll = &tl->next;
    }

    *ll = NULL;

    /* send the new chain */

    rc = ngx_http_next_body_filter(r, out);

    /* update "busy" and "free" chains for reuse */

    ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out,
                            (ngx_buf_tag_t) &ngx_http_foo_filter_module);

    return rc;
}

로드 밸런싱

ngx_http_upstream_module은 요청을 원격 서버로 전달하는 데 필요한 기본 기능을 제공합니다. 특정 프로토콜(예: HTTP, FastCGI)을 구현하는 모듈에서 이 기능을 사용합니다. 또한, 이 모듈은 사용자 정의 부하 분산 모듈의 인터페이스를 제공하고 기본 순환 메서드를 구현합니다.

least_conn 및 hash 모듈은 다른 부하 분산 모듈을 구현하지만 실제로는 업스트림 순환 모듈의 확장으로 구현되어 대부분 코드(예: 서버 그룹 표현)를 공유합니다. keepalive 모듈은 업스트림 기능을 확장하는 독립적 모듈입니다.

ngx_http_upstream_module은 해당 upstream 블록을 구성 파일에 넣어서 명시적으로 구성하거나, proxy_pass 등의 명령을 사용하여 일부 지점에서 평가되는 URL을 서버 목록에 넣어서 명시적으로 구성할 수 있습니다. 대체 부하 분산 메서드는 명시적 업스트림 구성에서만 사용할 수 있습니다. 업스트림 모듈 구성은 자체적인 명령 컨텍스트 NGX_HTTP_UPS_CONF가 있습니다. 구조는 다음과 같이 정의됩니다.

struct ngx_http_upstream_srv_conf_s {
    ngx_http_upstream_peer_t         peer;
    void                           **srv_conf;

    ngx_array_t                     *servers;  /* ngx_http_upstream_server_t */

    ngx_uint_t                       flags;
    ngx_str_t                        host;
    u_char                          *file_name;
    ngx_uint_t                       line;
    in_port_t                        port;
    ngx_uint_t                       no_port;  /* unsigned no_port:1 */

#if (NGX_HTTP_UPSTREAM_ZONE)
    ngx_shm_zone_t                  *shm_zone;
#endif
};
  • srv_conf — 업스트림 모듈의 구성 컨텍스트입니다.
  • servers — ngx_http_upstream_server_t의 배열, upstream 블록에서 server 명령 세트를 파싱한 결과.
  • flags — 대체로 부하 분산 메서드로 지원되는 기능을 표시하는 플래그. 이 기능은 server 명령의 매개변수로 구성됩니다.
    • NGX_HTTP_UPSTREAM_CREATE — proxy_pass 명령 및 “friends”(예: FastCGI, SCGI)에서 자동으로 생성한 것에서 명시적으로 정의된 업스트림을 구분합니다.
    • NGX_HTTP_UPSTREAM_WEIGHT — “weight” 매개변수 지원
    • NGX_HTTP_UPSTREAM_MAX_FAILS — “max_fails” 매개변수 지원
    • NGX_HTTP_UPSTREAM_FAIL_TIMEOUT — “fail_timeout” 매개변수 지원
    • NGX_HTTP_UPSTREAM_DOWN — “down” 매개변수 지원
    • NGX_HTTP_UPSTREAM_BACKUP — “backup” 매개변수 지원
    • NGX_HTTP_UPSTREAM_MAX_CONNS — “max_conns” 매개변수 지원
  • host — 업스트림의 이름.
  • file_name, line — 구성 파일과 upstream 블록이 위치한 행의 이름.
  • port 및 no_port — 명시적으로 정의된 업스트림 그룹에는 사용하지 않습니다.
  • shm_zone — 이 업스트림 그룹에서 사용하는 공유 메모리 영역(있을 경우).
  • peer — 업스트림 구성을 초기화하기 위한 일반 메서드가 있는 객체:
typedef struct {
    ngx_http_upstream_init_pt        init_upstream;
    ngx_http_upstream_init_peer_pt   init;
    void                            *data;
} ngx_http_upstream_peer_t;

부하 분산 알고리즘을 구현하는 모듈은 이 메서드를 설정하고 비공개 data를 초기화해야 합니다. init_upstream이 구성 파싱에서 초기화되지 않을 경우, ngx_http_upstream_module에서 기본 ngx_http_upstream_init_round_robin 알고리즘으로 설정합니다.

  • init_upstream(cf, us) — 서버 그룹을 초기화하고 성공 시 init() 메서드를 초기화하는 구성 시간 메서드. 일반적인 부하 분산 모듈은 upstream 블록에서 서버 목록을 사용하여, 구성을 사용하고 data 필드에 저장하는 효율적인 데이터 구조를 생성합니다.
  • init(r, us) — 부하 부산에 사용되는 요청별 ngx_http_upstream_peer_t.peer 구조를 초기화합니다(앞서 설명한 ngx_http_upstream_srv_conf_t.peer는 업스트림별로 초기화하므로 혼돈하지 마세요). 서버 선택을 처리하는 모든 콜백에 data 인수로 전달됩니다.

nginx가 처리를 위해 요청을 다른 호스트로 전달할 경우, 구성된 부하 분산 메서드를 사용하여 연결할 주소를 얻습니다. 이 메서드는 ngx_peer_connection_t 유형의 ngx_http_upstream_t.peer 객체에서 얻습니다.

struct ngx_peer_connection_s {
    ...

    struct sockaddr                 *sockaddr;
    socklen_t                        socklen;
    ngx_str_t                       *name;

    ngx_uint_t                       tries;

    ngx_event_get_peer_pt            get;
    ngx_event_free_peer_pt           free;
    ngx_event_notify_peer_pt         notify;
    void                            *data;

#if (NGX_SSL || NGX_COMPAT)
    ngx_event_set_peer_session_pt    set_session;
    ngx_event_save_peer_session_pt   save_session;
#endif

    ...
};

이 구조는 다음과 같은 필드가 있습니다.

  • sockaddr, socklen, name — 연결할 업스트림 서버의 주소. 부하 분산 메서드의 출력 매개변수입니다.
  • data — 부하 분산 메서드의 요청별 데이터입니다. 선택 알고리즘의 상태를 저장하고, 일반적으로 업스트림 구성으로의 링크를 포함합니다. 서버 선택을 처리하는 모든 메서드에 인수로 전달됩니다(아래 참조).
  • tries — 업스트림 서버로 연결을 시도하는 허용 횟수입니다.
  • get, free, notify, set_session 및 save_session – 아래에 설명된 부하 분산 모듈의 메서드입니다.

모든 메서드는 2개 이상의 인수를 받습니다. 피어 연결 객체 pc와 ngx_http_upstream_srv_conf_t.peer.init()에서 생성된 data가 있습니다. 부하 분산 모듈의 “체이닝”으로 인해 pc.data와 다를 수 있습니다.

  • get(pc, data) — 업스트림 모듈이 요청을 업스트림 서버로 전달할 준비가 되어서 주소를 알아야 할 때 호출하는 메서드입니다. 이 메서드는 ngx_peer_connection_t 구조의 sockaddr, socklen, and name 필드를 채워야 합니다. 다음 중 하나를 반환합니다.
    • NGX_OK — 서버가 선택되었습니다.
    • NGX_ERROR — 내부 오류가 발생했습니다.
    • NGX_BUSY — 현재 사용 가능한 서버가 없습니다. 이런 현상은 여러 가지 이유로 발생할 수 있습니다. 예를 들어 동적 서버 그룹이 비어 있거나, 그룹 내 모든 서버가 실패한 상태이거나, 그룹 내 모든 서버가 연결을 한계까지 처리한 상태입니다.
    • NGX_DONE — 기본 연결을 다시 사용하고 업스트림 서버에 새 연결을 생성할 필요가 없습니다. 이 값은 keepalive 모듈에서 설정합니다.
  • free(pc, data, state) — 업스트림 모듈이 특정 서버 작업으로 완료되었을 때 호출하는 메서드입니다. state 인수는 업스트림 연결의 완료 상태이고, 다음의 값을 포함한 비트마스크입니다.
    • NGX_PEER_FAILED — 시도가 실패했습니다.
    • NGX_PEER_NEXT — 업스트림 서버가 403 또는 404 코드를 반환하는 특수한 사례로, 실패로 간주되지 않습니다.
    • NGX_PEER_KEEPALIVE — 현재 사용되지 않습니다.

이 메서드는 tries 카운터도 낮춥니다.

  • notify(pc, data, type) — 현재 OSS 버전에서 사용하지 않습니다.
  • set_session(pc, data) 및 save_session(pc, data) — 업스트림 서버에 대한 캐싱 세션을 활성화하는 SSL 전용 메서드입니다. 이 구현은 순환 분산 방식으로 제공됩니다.

예시

nginx-dev-examples 리포지토리는 nginx 모듈 예시를 제공합니다.

코드 스타일

일반 규칙

  • 최대 텍스트 너비는 80자
  • 들여쓰기 4자
  • 탭, 뒤쪽 공백 없음
  • 같은 행에 나열하는 요소는 공백으로 구분
  • 16진수 리터럴은 소문자
  • 파일 이름, 함수, 유형 이름, 전역 변수는 ngx_ 또는 특정한 접두사(예: ngx_http_, 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;
}

파일

일반적 소스 파일에는 다음의 섹션이 두 개의 빈 행으로 구분되어 포함됩니다.

  • 저작권 설명
  • 포함 내용
  • 전처리 프로세서 정의
  • 유형 정의
  • 함수 프로토타입
  • 변수 정의
  • 함수 정의

저작권 설명의 형식:

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

파일이 상당히 수정되면 저자 목록이 업데이트되고 새 저자가 상단에 추가됩니다.

항상 ngx_config.h 및 ngx_core.h 파일이 가장 먼저 포함되고, 그 뒤에 ngx_http.h, ngx_stream.h, ngx_mail.h 중 하나가 포함됩니다. 그런 다음, 선택적 외부 헤더 파일을 따릅니다.

#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

헤더 파일에는 이른바 “헤더 보호”가 포함되어야 합니다.

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

주석

  • “//” 주석은 사용하지 않습니다.
  • 텍스트는 영어로 작성되며, 미국식 철자를 선호합니다.
  • 다행 주석의 형식:
/*
 * 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 */

전처리 프로세서

매크로 이름은 ngx_ 또는 NGX_(보다 특정한) 접두사로 시작합니다. 상수의 매크로 이름은 대문자입니다. 매개변수화된 매크로와 이니셜라이저용 매크로는 소문자입니다. 매크로 이름과 값은 2개 이상의 공백으로 구분합니다.

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

조건은 괄호 안, 협상은 밖에 있습니다.

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

Types

유형 이름은 “_t” 접미사로 끝납니다. 정의된 유형 이름은 2개 이상의 공백으로 구분됩니다.

typedef ngx_uint_t  ngx_rbtree_key_t;

구조 유형은 typedef를 사용하여 정의합니다. 구조 내부에서 멤버 유형과 이름을 정렬합니다.

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

파일 내 각 구조에서 정렬을 동일하게 유지합니다. 자신을 가리키는 구조는 “_s”로 끝나는 이름을 가집니다. 인접한 구조 정의는 두 개의 빈 행으로 구분됩니다.

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;

각 구조 멤버는 자체 행에서 선언됩니다.

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

구조 내 함수 포인터는 정의된 유형이 “_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;

나열은 “_e”로 끝나는 유형이 있습니다.

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

변수

변수는 기본 유형 길이, 알파벳순으로 정렬되어 선언합니다. 유형 이름과 변수 이름은 정렬됩니다. 유형과 이름 “열”은 2개의 공백으로 구분됩니다. 큰 배열은 선언 블록의 끝에 넣습니다.

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

고정 및 전역 변수는 선언 시 초기화할 수 있습니다.

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

일반적으로 사용하는 유형/이름 조합은 다음과 같습니다.

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;

함수

(고정 함수를 포함한) 모든 함수는 프로토타입이 있어야 합니다. 프로토타입에 인수 이름이 포함됩니다. 긴 프로토타입은 연속선에서 들여쓰기 1번으로 줄바꿈합니다.

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

정의의 함수 이름은 새 행으로 시작합니다. 괄호를 열고 닫는 함수 본문은 행을 나눕니다. 함수 본문은 들여쓰기합니다. 함수 사이에는 빈 행이 2줄 있습니다.

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

함수 이름과 여는 괄호 이후에는 공백이 없습니다. 긴 함수 호출은 연속선이 첫 번째 함수 인수의 위치에서 시작하도록 줄바꿈합니다. 이 방법을 사용할 수 없을 경우, 위치 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 *));

inline 대신 ngx_inline 매크로를 사용해야 합니다.

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

Expressions

“.”와 “−>”를 제외한 바이너리 연산자는 공백 1개로 피연산 함수와 구분해야 합니다. 단항 연산자와 하위 스크립트는 공백으로 피연산 함수와 구분하지 않습니다.

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

유형 캐스트는 캐스팅된 식과 공백 1개로 구분합니다. 유형 캐스트 안족의 별표는 유형 이름과 공백을 두어 구분합니다.

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

식이 한 행에 들어가지 않으면 줄바꿈합니다. 줄바꿈하기에 좋은 곳은 바이너리 연산자입니다. 연속성은 식 시작과 일치합니다.

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

최종적 수단으로 연속선이 79 위치에서 끝나도록 식을 줄바꿈할 수 있습니다.

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

위의 규칙은 하위 식마다 들여쓰기 수준이 있는 하위 식에도 적용됩니다.

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

경우에 따라, 캐스트 뒤에 식을 줄바꿈하는 것이 편리할 수 있습니다. 이 경우, 연속선은 들여쓰기 합니다.

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

포인터는 (0이 아니라) NULL과 명시적으로 비교합니다.

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

조건 및 루프

“if” 키워드는 공백 1개로 조건과 구분합니다. 여는 괄호는 같은 행에 위치하거나, 조건이 여러 줄을 차지할 경우 전용 행에 넣습니다. 닫는 괄호는 전용 행에 넣습니다. 또는, 뒤에 “else if / else”를 붙일 수 있습니다. 일반적으로 “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;
    }
}

유사한 서식 지정 규칙이 “do”와 “while” 루프에도 적용됩니다.

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

“switch” 키워드는 공백 1개로 조건과 구분합니다. 요는 괄호는 같은 행에 위치합니다. 닫는 괄호는 전용 행에 위치합니다. “case” 키워드는 “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;
}

대부분 “for” 루프의 형식은 다음과 같습니다.

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

“for” 문의 일부가 생략된 경우, “/* void */” 주석으로 표시합니다.

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

본문이 빈 루프도 “/* void */” 주석으로 표시하며, 같은 행에 넣을 수 있습니다.

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

무한 루프의 형식은 다음과 같습니다.

for ( ;; ) {
    ...
}

레이블

레이블은 빈 행으로 감싸고, 이전 수준에서 들여쓰기합니다.

  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;

메모리 문제 디버깅

버퍼 오버런, 해제 후 사용 오류와 같은 메모리 문제를 디버깅하려면 일부 최신 컴파일러에서 지원하는 AddressSanitizer(ASan)를 사용할 수 있습니다. ASan을 gcc 및 clang로 활성화하려면 -fsanitize=address 컴파일러와 링커 옵션을 사용합니다. nginx를 구축할 때는 이 옵션을 configure 스크립트의 –with-cc-opt and –with-ld-opt 매개변수에 추가하면 됩니다.

nginx에서 대부분 할당은 nginx 내부 에서 실행되므로 ASan을 활성화하더라도 메모리 문제를 디버깅하지 못할 수 있습니다. 내부 풀은 시스템에서 메모리의 큰 부분을 할당하고, 여기에서 약간 떼어냅니다. 그러나 이 메커니즘은 NGX_DEBUG_PALLOC 매크로를 1로 설정하면 비활성화할 수 있습니다. 이 경우, 할당은 시스템 할당자에 직접 전달되어, 버퍼 경계를 완전히 제어할 수 있게 됩니다.

위의 정보를 요약하면 아래와 같은 구성 행이 됩니다. 타사 모듈을 개발하고 다른 플랫폼에서 nginx를 테스트할 때 권장합니다.

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

자주 실수하는 문제

C 모듈 작성

필요하지 않을 때도 완전한 C 모듈을 작성하려고 하는 실수를 흔히 저지릅니다. 대부분의 경우, 적절한 구성을 생성하는 것만으로 작업을 실행할 수 있습니다. 반드시 모듈을 작성해야 한다면 최대한 작고 간단하게 하세요. 예를 들어 모듈에서 일부 변수만 내보낼 수 있습니다.

모듈을 시작하기 전에 다음과 같은 질문을 생각해보세요.

  • 이미 사용 가능한 모듈로 원하는 기능을 구현할 수 있는가?
  • 내장 스크립트 언어(예: Perl, njs)로 문제를 해결할 수 있는가?

C 문자열

nginx에서 가장 많이 사용하는 문자열 유형인 ngx_str_t는 0으로 종료되는 C 스타일 문자열이 아닙니다. 데이터를 표준 C 라이브러리 함수(예: strlen(), strstr())에 전달할 수 없습니다. 대신 ngx_str_t를 받는 , nginx 버전이나  데이터 및 길이에 대한 포인터를 사용해야 합니다. 그러나 ngx_str_t에 0으로 종료되는 문자열에 대한 포인터가 포함되는 경우가 있습니다. 구성 파일 파싱의 결과로 얻는 문자열은 0으로 종료됩니다.

전역 변수

모듈에서는 전역 변수 사용을 삼가세요. 전역 변수를 사용하는 것은 오류일 가능성이 큽니다. 전역 데이터는 구성 사이클과 연결되고 해당 메모리 풀에서 할당해야 합니다. 그래야 nginx가 구성을 점진적으로 다시 로드할 수 있습니다. 전역 변수를 사용하면 이 기능에 장애가 일어날 가능성이 큽니다. 동시에 두 개의 구성을 적용할 수 없어서 제거하기 때문입니다. 전역 변수가 필요한 경우도 있습니다. 이 경우에는 재구성을 적절히 관리하는 데 특별히 주의를 기울여야 합니다. 또한, 코드에서 사용하는 라이브러리에 다시 로드했을 때 장애가 일어날 만한 묵시적 전역 상태가 있는지 확인합니다.

수동 메모리 관리

오류가 발생하기 쉬운 malloc/free 방식 대신 nginx 을 사용하는 방법을 배우세요. 풀은 생성되면 구성, 사이클, 연결 또는 HTTP 요청 객체와 연결됩니다. 객체가 제거되면 연결된 풀도 제거됩니다. 객체를 사용할 때는 해당 풀에서 필요한 용량을 할당하고 오류가 발생하더라도 메모리 해제에 대해 걱정할 필요가 없습니다.

스레드

nginx에서는 분명 오류가 발생할 것이기 때문에 스레드를 사용하지 않는 것이 좋습니다. 대부분 nginx 함수는 스레드에 안전하지 않습니다. 스레드는 시스템 호출과 스레드에 안전한 라이브러리 함수만 실행해야 합니다. 클라이언트 요청 처리와 관련이 없는 코드를 몇 가지 실행해야 할 경우, init_process 모듈 핸들러에 타이머를 예약하고 타이머 핸들러에서 필요한 작업을 실행해야 합니다. 내부적으로 nginx는 스레드를 활용하여 I/O 관련 작업을 강화하지만, 제약이 많은 특수한 사례입니다.

라이브러리 차단

가장 흔히 하는 실수는 내부적으로 차단되는 라이브러리를 사용하는 것입니다. 대부분의 공개된 라이브러리는 비동기식이고 기본적으로 차단됩니다. 즉, 한 번에 한 가지 작업만 수행하고 다른 피어의 응답을 기다리느라 시간을 낭비합니다. 따라서 이런 라이브러리로 요청을 처리할 때 전체 nginx 작업자가 차단되므로 성능이 저하될 수밖에 없습니다. 비동기식 인터페이스를 제공하고 프로세스 전체를 차단하지 않는 라이브러리만 사용하세요.

외부 서비스에 대한 HTTP 요청

모듈은 일부 외부 서비스에 HTTP 호출을 보내야 하는 경우가 많습니다. 외부 라이브러리(예: libcurl)를 사용해서 HTTP 요청을 실행하는 실수를 자주 합니다. nginx 자체에서 해결할 수 있는 작업에 엄청난 양의 (게다가 차단까지 발생시키는!) 외부 코드를 가져오는 것은 불필요합니다.

외부 요청이 필요한 기본 사용 시나리오는 두 가지가 있습니다.

  • 클라이언트 요청을 처리하는 컨텍스트(예: 콘텐츠 핸들러)
  • 작업자 프로세스 컨텍스트(예: 타이머 핸들러)

첫 번째 사례에서는 하위 요청 API를 사용하는 것이 가장 좋습니다. 외부 서비스에 직접 액세스하지 않고 nginx 구성에서 위치를 선언하고 하위 요청을 이 위치로 리디렉션합니다. 이 위치는 프록시 요청에만 제한되지 않고, 다른 nginx 명령을 포함할 수도 있습니다. 이러한 요청의 예시로는 ngx_http_auth_request 모듈에 구현된 auth_request 명령이 있습니다.

두 번째 사례에서는 nginx에서 제공하는 기본 HTTP 클라이언트 기능을 사용할 수 있습니다. 예를 들어 OCSP 모듈은 간단한 HTTP 클라이언트를 구현합니다.