[펌] NGINX 모듈 제작하기

출처 : http://helloworld.naver.com/helloworld/textyle/192785
NHN Business Platform 글로벌플래폼개발랩 최영석

사용자에게 더 빠른 체감 성능을 제공하기 위해 NHN에서도 NGINX 사용을 확대하고 있습니다. NGINX 사용의 확대에 따라 기존에 Apache로 제작한 모듈을 NGINX용으로 다시 개발할 필요가 있습니다. 이 글에서는 NGINX 모듈을 어떻게 제작하는지 설명하겠습니다.

NGINX 모듈 개발

NGINX는 C 언어로 작성되어 있고 모듈 또한 C 언어로 개발해야 하기 때문에, NGINX 모듈을 개발하려면 C 언어를 능숙하게 사용할 수 있어야 한다. 물론 C 언어 이외에도 HTTP에 대한 이해도가 높을수록 웹 서버용 모듈을 잘 작성할 수 있다는 것은 두말할 필요가 없다.

시작부터 선행 지식을 강조했지만 의기소침해질 필요는 없다. 이 글에서 설명하는 대로 따라 한다면, 동작 가능한 NGINX 모듈을 제작해 볼 수 있을 것이다.

NGINX 모듈 개발 과정을 이해하려면 NGINX 설정 방법과 NGINX의 기본적인 동작 방법을 이해해야 한다. 이 글에서는 NGINX 설정 파일의 지시어를 이용해 실제로 동작하는 NGINX 모듈을 개발하는 방법에 대해서 알아볼 것이다.

NGINX 모듈의 역할과 종류

NGINX에서 HTTP 메시지 자체를 처리하는 것은 코어 부분이 아니라 모듈이다. NGINX의 코어는 특정 프로토콜을 처리하기 위한 것이 아니라 네트워크 메시지 처리를 위한 저수준의 기능 위주로 구현되어 있다(이런 이유 때문에 NGINX를 웹 서버라 부르는 것에는 무리가 있을 수 있다. SMTP 등 메일 서버로 사용될 수 있기도 하고, 다른 프로토콜을 지원하도록 확장할 수 있기 때문이다.).

NGINX 모듈의 역할은 다음과 같이 크게 3가지로 나눌 수 있다.

  • 핸들러(handler): HTTP 요청을 처리하고 요청에 대한 응답을 만든다.
    • 파일을 요청한 클라이언트에게 파일을 제공하는 핸들러 모듈
    • HTTP 요청을 다른 서버로 리다이렉트하는 핸들러 모듈
  • 필터(filter): 핸들러가 만든 응답을 추가로 가공하거나 분석한다.
  • 로드 밸런서(load-balancer): HTTP 요청을 어느 백엔드(back-end) 서버에 보낼 것인지 결정한다.

NGINX의 Request 처리 과정

NGINX의 핸들러 모듈과 필터 모듈은 <그림 1>에서 나타난 방법으로 HTTP 요청을 처리한다.

클라이언트의 HTTP 요청은 설정 파일(Configuration file)인 NGINX.conf 파일에서 지정한 핸들러 모듈로 전달된다. 핸들러 모듈은 전달된 HTTP 요청에 대한 HTTP 응답을 생성하고, 이를 필터 모듈로 다시 전달한다. 필터 모듈은 필터 체인으로 구성되어 있는데, HTTP 응답은 필터 체인의 각 필터를 통과하면서 추가로 가공되거나 분석된 후 클라이언트로 전송된다.

4c010fcd0c9c647daf80af6615e8ea4b.png

그림 1 NGINX의 HTTP 요청 처리

<그림 1>에서 필터 모듈의 역할은 핸들러 모듈이 생성한 HTTP 응답을 클라이언트에 보내기 전에 추가로 가공하는 것이다. NGINX는 한 개 이상의 필터 모듈을 체인 구조로 만들어 HTTP 응답을 처리한다. 필터 체인은 컴파일 시에 생성되기 때문에, 이후 동적으로 NGINX에 추가할 수 없다. 새로운 필터 모듈을 추가하려면 NGINX를 다시 빌드해 새 바이너리로 업데이트해야 한다.

HTTP 요청은 하나의 핸들러에만 전달되지만 HTTP 응답은 <그림 2>와 같이 필터 체인의 각 필터에 순차적으로 전달되어 처리된다. 필터에 전달되는 최소 단위는 ngx_chain_t 구조체다.

395c66919d738f3a29eb77913ede82e5.png

그림 2 NGINX 필터 체인

NGINX 모듈 빌드

동적 링크가 가능한 Apache 모듈과 달리 NGINX 모듈은 정적 링크만 가능하다. 즉 새로운 NGINX 모듈을 NGINX에 링크하려면 NGINX 빌드 설정에 모듈 소스 코드를 추가한 다음 NGINX를 다시 빌드하고 새 바이너리로 업데이트해야 한다.

NGINX 바이너리에 모듈을 추가해 빌드하려면 별도의 디렉터리에 다음 2개의 파일을 준비한다.

  • ngx_http_<your module>_module.c 파일: 모듈 소스 코드 파일
  • config 파일: 빌드 설정 파일

‘<your module>’에는 모듈 이름을 넣는다. 모듈 이름이 “hello”라면 파일 이름은 ngx_http_hello_module.c가 된다.

빌드 설정 파일인 config 파일에는 configure 명령을 실행할 때 NGINX 바이너리에 포함할 내용을 입력한다. 모듈의 종류에 따라 입력하는 내용은 다음과 같다.

  • 필터 모듈용 config 파일
ngx_addon_name=ngx_http_ <your module>_module
HTTP_AUX_FILTER_MODULES=”$HTTP_AUX_FILTER_MODULES ngx_http_<your module>_module”
NGX_ADDON_SRCS=”$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_<your module >_module.c”
  • 기타 모듈용 config 파일
ngx_addon_name=ngx_http_<your module>_module
HTTP_MODULES=”$HTTP_MODULES ngx_http_<your module>_module”
NGX_ADDON_SRCS=”$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_<your module>_module.c”

만약 작성한 모듈에서 외부 공유 라이브러리를 사용한다면 config 파일에 다음과 같이 -l 옵션으로 라이브러리를 명시하는 내용을 추가한다.

ngx_addon_name=ngx_http_<your module >_module
HTTP_AUX_FILTER_MODULES=”$HTTP_AUX_FILTER_MODULES ngx_http_<your module>_module”
NGX_ADDON_SRCS=”$NGX_ADDON_SRCS
CORE_LIBS=”$CORE_LIBS -lfoo”

NGINX 바이너리를 다시 빌드하기 전 configure 명령을 실행할 때 다음과 같이 –add-module 옵션에 준비한 파일이 있는 디렉터리 경로를 입력한다.

1
./configure --add-module=path/to/your/new/module/directory

이후 make install 명령을 실행하여 NGINX 바이너리를 다시 빌드하면 작성한 모듈이 NGINX 바이너리에 추가된다.

NGINX 모듈의 구성 요소

모듈 설정 구조체

NGINX의 설정 파일 종류는 다음과 같이 3가지가 있다.

  • main: 전체 NGINX 웹 서버에 하나만 존재
  • server: 웹 서버 안의 각 가상 호스트(virtual host)마다 존재
  • location: 가상 호스트 안에 여러 개가 존재

NGINX 모듈은 이 3단계 설정에 맞는 세 종류의 설정 구조체로 이루어진다.

설정 구조체 이름을 지을 때는 다음 규칙을 따른다.

1
2
ngx_http_<module name="">_<main |srv|loc="">_conf_t
</main></module>

<module name>에는 모듈 이름을 넣고 main, srv(server), loc(location) 중 하나를 선택한다.

모듈 이름이 ‘mymodule’인 location 설정 구조체라면 이름은 “ngx_http_mymodule_loc_conf_t”다. 그리고 다음 예와 같이 설정 구조체 안에는 설정 값을 저장할 변수를 선언한다.

1
2
3
4
5
typedef struct {
    ngx_int_t price;
    ngx_int_t is_brandNew;
    ngx_string_t name;
} ngx_http_mymodule_loc_conf_t;

변수를 선언할 때는 ngx_int_t, ngx_string_t 등과 같이 NGINX에 내장된 프리미티브 데이터 타입을 사용한다(NGINX 소스의 core/ngx_config.h 참고).

모듈 지시어(Module Directives)

모듈 지시어는 ngx_command_t 구조체로 선언된다. 설정 구조체를 만들었으면, 설정 파일에서 값을 가져와서 설정 구조체 변수에 저장할 수 있도록 구현한다. NGINX에 내장된 ngx_command_t 구조체를 이용하면 설정 파일의 값과 설정 구조체 변수를 연결할 수 있다. ngx_command_t 구조체의 타입 선언은 다음과 같다.

1
2
3
4
5
6
7
8
9
typedef 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_command_t;

ngx_command_t 구조체의 name 필드는 설정 파일(NGINX.conf)에 입력된 키워드 이름을 지정한다. type 필드는 키워드가 설정된 위치와 키워드 뒤에 입력된 인수의 개수를 지정하고 다음의 값들을 가질 수 있다.

  • NGX_HTTP_MAIN_CONF: 메인 설정(main config)에서 유효함
  • NGX_HTTP_SRV_CONF: 서버 설정(server (host) config) 유효함
  • NGX_HTTP_LOC_CONF: 로케이션 설정(location config)에서 유효함
  • NGX_HTTP_UPS_CONF: 업스트림 설정(upstream config)에서 유효함
  • NGX_CONF_NOARGS: 지시어(directive)가 0개의 인자를 가짐
  • NGX_CONF_TAKE1: 지시어(directive)가 1개의 인자를 가짐
  • NGX_CONF_TAKE2: 지시어(directive)가 2개의 인자를 가짐
  • ……
  • NGX_CONF_TAKE7: 지시어(directive)가 7개의 인자를 가짐
  • NGX_CONF_FLAG: 지시어(directive) 가 부울 인자(“on” or “off”)를 가짐
  • NGX_CONF_1MORE: 지시어(directive)가 1개 이상의 인자를 가짐
  • NGX_CONF_2MORE: 지시어(directive)가 2개 이상의 인자를 가짐

함수 포인터인 set 필드는 지정한 키워드의 값을 가져올 때 사용할 함수를 설정한다. ngx_conf_set_num_slot 함수와 ngx_conf_set_str_slot 함수는 NGINX에서 제공하는 함수로 정수 값과 문자열 값을 가져온다(각 프리미티브 데이터 타입별 함수는 NGINX 소스의 core/ngx_conf_file.h 참고). 배열이나 사용자가 정의한 데이터 타입인 경우는 사용자가 정의한 함수를 직접 등록할 수 있다.

다음으로 conf 필드는 설정 값을 main, sever, location 설정 중 어느 곳에 저장할지 지정한다. NGX_HTTP_LOC_CONF_OFFSET으로 지정하면 설정 값을 location 설정에 저장한다.

그리고 offset 필드는 키워드에 설정된 값을 설정 구조체의 어느 변수에 저장할지 지정한다. offsetof 매크로를 사용하여 지정한다.

마지막으로 post 필드는 디폴트로 NULL을 지정한다.

다음 예는 ngx_http_circle_gif_module.c 파일에서 모듈 지시어의 선언 부분이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static ngx_command_t
ngx_http_mymodule_commands[] = {
    {
        ngx_string("price"),
        NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
        ngx_conf_set_num_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_mymodule_loc_conf_t,price),
        NULL
    },
    {
        ngx_string("is_brandNew"),
        NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
        ngx_conf_set_num_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_mymodule_loc_conf_t,is_brandNew),
        NULL
    },
    {
        ngx_string("name"),
        NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
        ngx_conf_set_str_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_mymodule_loc_conf_t, name),
        NULL
    },
    ngx_null_command
};

모듈 구조체(Module Context)

설정 구조체를 만들고 앞에서 설명한 것과 같이 ngx_command_t 구조체를 이용하여 설정 파일의 값과 설정 구조체 변수를 연결한 다음, NGINX에 내장된 ngx_http_module_t 구조체로 모듈 구조체를 정의한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct {
    ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
    ngx_int_t (*postconfiguration)(ngx_conf_t *cf);
    void *(*create_main_conf)(ngx_conf_t *cf);
    char *(*init_main_conf)(ngx_conf_t *cf,
    void *conf);
    void *(*create_srv_conf)(ngx_conf_t *cf);
    char *(*merge_srv_conf)(ngx_conf_t *cf,
    void *prev, void *conf);
    void *(*create_loc_conf)(ngx_conf_t *cf);
    char *(*merge_loc_conf)(ngx_conf_t *cf,
    void *prev, void *conf);
} ngx_http_module_t;

ngx_http_module_t 구조체는 다음과 같이 콜백 함수 포인터 변수로 구성된다. 필요에 따라 콜백 함수를 구현한 다음 모듈 구조체에 콜백 함수의 포인터를 등록한다.

  • preconfiguration 설정 시작 전 호출 함수의 포인터를 저장
  • postconfiguration 설정 완료 후 호출 함수의 포인터를 저장
  • create_main_conf main 설정 생성 함수의 포인터를 저장
  • init_main_conf main 설정 초기화 함수의 포인터를 저장
  • create_srv_conf server 설정 생성 함수의 포인터를 저장
  • merge_srv_conf server 설정 조합 함수의 포인터를 저장
  • create_loc_conf location 설정 생성 함수의 포인터를 저장
  • merge_loc_conf location 설정 조합 함수의 포인터를 저장

create_loc_conf 콜백 함수는 location 설정 구조체를 저장할 메모리 공간을 할당하고 구조체 안의 변수를 초기화하도록 구현한다. 예를 들어 다음 예에서 중요한 점은 C 표준 라이브러리의 malloc 함수 대신 ngx_palloc 함수를 사용하여 설정 구조체 메모리를 할당한 것이다. ngx_palloc 함수는 NGINX 모듈을 개발할 때 항상 사용해야 하는 함수로서 메모리 풀(pool) 포인터와 메모리 크기를 파라미터로 받는다.

1
2
3
4
5
6
7
8
9
static void *ngx_http_mymodule_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_mymodule_loc_conf_t *conf;
    conf = ngx_pcalloc(cf->pool,sizeof(ngx_http_mymodule_loc_conf_t));
    if (conf == NULL) return NGX_CONF_ERROR;
    conf->price = NGX_CONF_UNSET;
    conf->is_brandNew = NGX_CONF_UNSET;
    return conf;
}

ngx_palloc 함수를 사용하면 C 표준 라이브러리의 free 함수를 명시적으로 호출하여 메모리를 해제할 필요가 없다. 어떤 메모리 풀에서 메모리를 할당받는지에 따라 ngx_palloc 함수로 할당받은 메모리는 메모리 풀을 소유한 객체의 수명(life time)이 다할 때까지 유효하다. 위 예에서 할당한 메모리는 ngx_conf_t 객체의 수명 동안 유효하다.

merge_loc_conf 콜백 함수나 다른 콜백 함수도 해당 로직에 맞게 구현을 해준다. 구현을 한 후에는 다음과 같이 콜백 함수를 등록해 준다.

1
2
3
4
5
6
7
8
9
10
11
12
static ngx_http_module_t ngx_http_mymodule_module_ctx = {
        NULL, /* preconfiguration */
        NULL, /* postconfiguration */
        NULL, /* create main configuration */
        NULL, /* init main configuration */
        NULL, /* create server configuration */
        NULL, /* merge server configuration */
        ngx_http_mymodule_create_loc_conf,
        /* create location configuration */
        ngx_http_mymodule_merge_loc_conf
        /* merge location configuration */
};

대부분의 NGINX 핸들러 모듈은 위와 같이 create_loc_conf, merge_loc_conf 콜백 함수만 등록하지만, NGINX 필터 모듈을 개발한다면 postconfiguration 콜백 함수도 등록해야 한다.

모듈 정의(Module Definition)

설정 파일에서 설정 구조체 변수로 값을 연결하는 ngx_command_t 구조체 배열을 정의하고, 설정 구조체를 생성/조합하는 함수를 구현하여 모듈 구조체인 ngx_http_module_t 구조체를 정의하면 NGINX 모듈의 필수 구성 요소가 모두 갖춰진다. 필수 구성 요소를 모두 완성하면 이를 이용하여 NGINX 모듈 자체를 정의한다.

NGINX 모듈을 정의할 때는 NGINX에 내장된 ngx_module_t 구조체를 이용한다. 다음과 같이 ngx_http_mymodule_commands와 ngx_http_mymodule_module_ctx를 등록한다. NULL로 입력한 나머지 부분은 일반적으로 필요가 없는 부분이므로 설명을 생략한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ngx_module_t ngx_http_mymodule_module = {
        NGX_MODULE_V1, /* module version */
        &ngx_http_mymodule_module_ctx,
        /* module context */
        ngx_http_mymodule_commands,
        /* module directives */
        NGX_HTTP_MODULE, /* module type */
        NULL, /* init master */
        NULL, /* init module */
        NULL, /* init process */
        NULL, /* init thread */
        NULL, /* exit thread */
        NULL, /* exit process */
        NULL, /* exit master */
        NGX_MODULE_V1_PADDING
};

모듈 정의까지 완료하면 기본적인 NGINX 모듈 구성은 끝난다.

핸들러(Handler) 모듈

핸들러 모듈 구현

핸들러 모듈 구현은 다음의 네 가지 단계로 나눌 수 있다.

  1. 로케이션(location) 설정 가져오기
  2. HTTP 요청에 대한 HTTP 응답 생성
  3. HTTP 응답 헤더 전송
  4. HTTP 응답 본체 전송

1. 로케이션(location) 설정 가져오기

핸들러는 파라미터로 ngx_http_request_t 구조체 타입의 HTTP 요청을 받는다. HTTP 요청에 대한 HTTP 응답을 생성하기 전, HTTP 요청을 설정 파일에서 정의한 대로 처리할 수 있도록 location 설정을 핸들러로 가져온다.

다음 예를 보면 NGINX 모듈 구성 요소 중 모듈 설정 구조체 (ngx_http_mymodule_loc_conf_t)와 모듈 정의(ngx_http_mymodule_module)를 사용하여 location 설정을 가져온다.

1
2
3
4
5
6
static ngx_int_t ngx_http_mymodule_handler(ngx_http_request_t *r)
{
    ngx_http_mymodule_loc_conf_t *my_config = NULL;
    myconfig = ngx_http_get_module_loc_conf(r, ngx_http_mymodule_module);
    ...
}

ngx_http_get_module_loc_conf 함수는 location 설정 구조체 값을 가져올 때 사용하는 매크로 함수다. location 설정 구조체 값을 가져오면 구조체의 각 변수 값을 확인하여 설정 파일에서 정의한 대로 HTTP 요청을 처리하도록 구현한다.

2. HTTP 요청에 대한 HTTP 응답 생성

location 설정을 가져와서 설정 값을 확인한 다음에는 파라미터로 전달받은 ngx_http_request_t 구조체 내용을 확인하여 HTTP 응답을 만들어야 한다.

HTTP 응답을 생성할 때 참조할 ngx_http_request_t 구조체 변수 안의 여러 변수 중 꼭 알아두어야 할 변수가 있다(ngx_http_request_t 구조체 타입은 NGINX 소스의 http/ngx_http_request.h 참고).

1
2
3
4
5
6
7
typedef struct ngx_http_request_s {
    ...
    ngx_str_t uri;
    ngx_str_t args;
    ngx_http_headers_in_t headers_in;
    ...
} ngx_http_request_t;
  • uri: 요청한 페이지의 경로(예: /query.cgi)
  • args: 요청할 때 “?” 뒤에 오는 파라미터(예: name=john)
  • headers_in: 요청의 기타 인수(accept-range, cookie 등)가 저장된 구조체

3. HTTP 응답 헤더 전송

HTTP 응답을 생성한 다음에는 클라이언트에게 HTTP 응답 헤더를 전송한다. HTTP 응답 헤더를 전송하기 전, 핸들러의 파라미터로 전달받은 ngx_http_request_t 구조체 변수의 headers_out에 HTTP 응답 헤더 내용을 입력한다.

핸들러 모듈이 Content-Type을 “image/gif”로, Content-Length를 100으로 설정하고 응답 결과를 200(NGX_HTTP_OK)를 반환한다고 가정하면 다음과 같이 구현한다.

1
2
3
4
5
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = 100;
r->headers_out.content_type.len = sizeof("image/gif") - 1;
r->headers_out.content_type.data = (u_char *) "image/gif";
ngx_http_send_header(r);

headers_out을 수정한 ngx_http_request_t 구조체 변수를 파라미터로 전달하여 ngx_http_send_header 함수를 호출하면 HTTP 응답 헤더가 전송된다.

4. HTTP 응답 본체 전송

HTTP 응답 헤더를 전송한 다음에는 HTTP 응답 본체를 전송한다. HTTP 응답 본체는 응답 내용을 포함하는 데이터 부분이다.

HTTP 응답 본체를 전송하려면 NGINX에서 정의한 ngx_buf_t와 ngx_chain_t 구조체를 사용한다. HTTP 응답 본체를 ngx_buf_t 구조체 타입의 버퍼에 저장하고, 버퍼를 다시 ngx_chain_t 구조체에 연결하고 체인 구조를 만들어 필터로 전송한다(참고로, ngx_buf_t 구조체 타입은 core/ngx_buf.h에 선언되어 있고 ngx_chain_t 구조체 타입은 core/ngx_core.h에 선언되어 있다). 다음 예와 같은 과정으로 HTTP 응답 본체(Body)를 전달한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 버퍼와 체인 구조 선언 */
ngx_buf_t *b = NULL;
ngx_chain_t out;
u_char *my_response = NULL;
size_t my_response_length = 0;
...
/* 버퍼 할당과 HTTP 응답을 버퍼에 저장 */
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
    "Failed to allocate response buffer.");
    return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b->pos = my_response;
b->last = my_response + my_response_length;
b->memory = 1;
b->last_buf = 1;
  
/* 버퍼를 체인 구조에 연결*/
out.buf = b;
out.next = NULL;
  
/* ngx_http_output_filter 함수를 호출하여 필터 모듈로 체인을 전송*/
return ngx_http_output_filter(r, &out);

핸들러 모듈 등록

핸들러를 구현한 다음에는 핸들러를 등록한다. NGINX에 핸들러를 등록하려면 다음과 같이 코어(core) 모듈의 핸들러 콜백 함수 포인터에 핸들러 함수를 등록한다.

1
2
3
4
5
6
7
8
9
static char *ngx_http_mymodule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t *clcf;
    /* 코어 모듈의 location 설정 가져오기 */
    clcf = ngx_http_get_module_loc_conf(cf, ngx_http_core_module);
    /* 핸들러 등록 */
    clcf->handler = ngx_http_empty_gif_handler;
    return NGX_CONF_OK;
}

필터(Filter) 모듈

1. 필터 모듈 구현

필터는 2가지 종류로 나뉜다.

  • 헤더 필터(Header filter) : HTTP 응답 헤더를 처리하는 필터
  • 본체 필터(Body filter) : HTTP 응답 본체를 처리하는 필터

일반적으로 헤더 필터는 HTTP 응답 헤더를 확인하여 본체 필터에서 HTTP 응답 본체를 처리할지 결정하고 본체 필터는 헤더 필터의 결정에 따라 HTTP 응답 본체를 처리한다.

  • 헤더 필터 구현
    헤더 필터는 다음 3단계의 순서로 HTTP 응답 헤더를 처리하도록 구현한다.
  1. HTTP 응답을 처리할지 결정한다.
  2. HTTP 응답을 처리하기로 결정한 경우 HTTP 응답 헤더를 처리한다.
  3. 필터 체인의 다음 헤더 필터를 호출한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static ngx_int_t
ngx_http_not_modified_header_filter(ngx_http_request_t *r)
{
    time_t if_modified_since;
    if_modified_since = ngx_http_parse_time
    (r->headers_in.if_modified_since->value.data, r->headers_in.if_modified_since->value.len);
    /* 1. HTTP 응답을 처리할지 결정한다. */
    if (if_modified_since != NGX_ERROR && if_modified_since == r->headers_out.last_modified_time) {
        /* 2. HTTP 응답 헤더를 처리한다.*/
        r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
        r->headers_out.content_type.len = 0;
        ngx_http_clear_content_length(r);
        ngx_http_clear_accept_ranges(r);
    }
    /* 3. 필터체인의 다음 헤더 필터를 호출한다. */
    return ngx_http_next_header_filter(r);
  • 본체 필터 구현
    NGINX는 하나의 HTTP 응답 본체 내용을 여러 조각의 버퍼로 나누고 각 버퍼를 포함하는 ngx_chain_t 구조체로 체인을 구성한다. 구성된 체인은 본체 필터에 전달되어 처리된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    /* HTTP 응답 끝 확인*/
    ngx_chain_t *chain_link;
    int chain_contains_last_buffer = 0;
    ngx_buf_t *b;
    ngx_chain_t added_link;
    for (chain_link = in; chain_link != NULL;chain_link = chain_link->next) {
        if (chain_link->buf->last_buf)
        chain_contains_last_buffer = 1;
    }
    if (!chain_contains_last_buffer) return ngx_http_next_body_filter(r, in);
    /* 문자열 추가 */
    b = ngx_calloc_buf(r->pool);
    if (b == NULL) return NGX_ERROR;
    b->pos = (u_char *) "<!-- Served by NGINX -->";
    b->last = b->pos + sizeof("<!-- Served by NGINX -->") - 1;
    added_link.buf = b;
    added_link.next = NULL;
    added_link.last_buf = 1;
    added_link.memory = 1;
    /*새로운 체인을 기존의 체인에 추가 */
    chain_link->next = added_link;
    chain_link->buf->last_buf = 0;
    /*다음 본체 필터 추가 */
    return ngx_http_next_body_filter(r, in);
}

2. 필터 등록

NGINX에 필터를 등록하려면 모듈 구조체를 정의할 때 필터를 초기화하는 함수를 등록한다.

1
2
3
4
5
6
7
static ngx_http_module_t
ngx_http_mymodule_module_ctx = {
    NULL, /* preconfiguration */
    ngx_http_mymodule_filter_init,
    /* postconfiguration */
   
}

필터 초기화 함수는 NGINX의 헤더 필터 체인과 본체 필터 체인의 맨 앞에 모듈 개발자가 구현한 헤더 필터 함수와 본체 필터 함수를 추가한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
static ngx_int_t
ngx_http_mymodule_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter =
    ngx_http_top_header_filter;
    ngx_http_top_header_filter =
    ngx_http_mymodule_header_filter;
    ngx_http_next_body_filter =
    ngx_http_top_body_filter;
    ngx_http_top_body_filter =
    ngx_http_mymodule_body_filter;
    return NGX_OK;
}

마치며

이 글에서는 NGINX 모듈을 이루는 기본적인 요소와 핸들러, 필터 모듈을 만드는 방법을 예를 통해 알아보았다. 이 글에서 본 내용을 바탕으로 NGINX 모듈 개발에 익숙해지면 NGINX 웹 서버에 추가할 모듈을 직접 개발할 수 있을 것이다.

그 외에도 “Emiller’s Guide To NGINX Module Development“를 참고하면 좀 더 자세한 내용과 다양한 예를 볼 수 있을 것이다. 또한 “Creating a Hello World! Nginx Module” 문서를 참고하면 NGINX 버전의 ‘Hello, world’ 모듈의 전체 소스를 볼 수 있다.

출처 : http://helloworld.naver.com/helloworld/textyle/192785

댓글 남기기