2013년 10월 6일

Apache Module의 여러 소스 파일 사용하기

Apache 모듈 프로그래밍에서 다음의 명령어로 mod_example 모듈의 기초 파일들을 만들 수 있다.

apxs -g -n example

example>$ ls -a
.  ..  .deps  Makefile  mod_example.c  modules.mk

여기서 modules.mk의 역할이 .so의 이름과 이를 위한 소스파일의 이름을 포함한다.

다음의 그 예이다.

mod_example.la: mod_example.slo
        $(SH_LINK) -rpath $(libexecdir) -module -avoid-version  mod_example.lo
DISTCLEAN_TARGETS = modules.mk
shared =  mod_example.la


mod_example.c 파일을 위해서 mod_example.slo와 mod_example.lo가 포함되어 있다.

여기에 소스 파일을 추가하고자 한다면, 같은 형식으로 이름을 추가하면 된다.(my_src.c)
mod_example.la: mod_example.slo my_src.slo
        $(SH_LINK) -rpath $(libexecdir) -module -avoid-version  mod_example.lo my_src.lo
DISTCLEAN_TARGETS = modules.mk
shared =  mod_example.la

Apache 로그 형식 지정하기


원본문서: http://httpd.apache.org/docs/2.2/ko/mod/mod_log_config.html

위 문서의 일부내용을 발췌하여 기재합니다.


로그 형식 지정하기

LogFormat과 CustomLog 지시어의 형식 아규먼트는 문자열이다. 이 문자열에 따라 요청을 로그파일에 기록한다. 문자열에는 로그파일에 그대로 복사되는 문자와 행바꿈과 탭을 나타내는 C의 "\n"과 "\t" 제어문자를 사용할 수 있다. 로그파일에 따옴표나 백슬래쉬를 쓰려면 앞에 반드시 백슬래쉬를 적어줘야 한다.
요청의 특징은 형식 문자열에 "%" 지시어를 사용하여 기록한다. 이 지시어는 로그파일에서 다음과 같이 변경된다.
형식 문자열설명
%%퍼센트 기호
%...a원격 IP-주소
%...A(서버) IP-주소
%...BHTTP 헤더를 제외한 전송 바이트수.
%...bHTTP 헤더를 제외한 전송 바이트수. CLF 형식과 같이 전송한 내용이 없는 경우 0 대신 '-'가 나온다.
%...{Foobar}C서버가 수신한 요청에서 Foobar 쿠키의 내용.
%...D요청을 처리하는데 걸린 시간 (마이크로초 단위).
%...{FOOBAR}e환경변수 FOOBAR의 내용
%...f파일명
%...h원격 호스트
%...H요청 프로토콜
%...{Foobar}i서버가 수신한 요청에서 Foobar: 헤더의 내용.
%...l(있다면 identd가 제공한) 원격 로그인명. mod_ident가 있고 IdentityCheck가 On이 아니면 빼기기호를 기록한다.
%...m요청 메써드
%...{Foobar}n다른 모듈이 기록한 Foobar 노트(note) 내용.
%...{Foobar}o응답의 Foobar: 헤더 내용.
%...p요청을 서비스하는 서버의 정규 포트
%...P요청을 서비스하는 자식의 프로세스 ID.
%...{format}P요청을 서비스하는 자식의 프로세스 ID 혹은 쓰레드 ID. format에는 pid와 tid가 가능하다.
%...q질의문자열 (질의문자열이 있다면 앞에 ?를 붙이고, 없다면 빈 문자열)
%...r요청의 첫번째 줄
%...s상태(status). 내부 리다이렉션된 요청의 경우 *원래* 요청의 상태이다. 최종 요청의 상태는 %...>s.
%...tcommon log format 시간 형식(표준 영어 형식)의 시간
%...{format}tstrftime(3) 형식 format의 시간. (지역시간일 수 있음)
%...T요청을 처리하는데 걸린 시간 (초 단위).
%...u원격 사용자 (auth가 제공하며, 상태(%s)가 401인 경우 이상한 값을 나올 수 있음)
%...U질의문자열을 제외한 요청 URL 경로.
%...v요청을 서비스한 서버의 정규 ServerName.
%...VUseCanonicalName 설정에 따른 서버명.
%...X응답을 마쳤을때 연결 상태.
X =응답을 마치기 전에 연결이 끊어졌다.
+ =응답을 보낸후에도 연결이 살아있다(keep alive).
- =응답을 보낸후 연결이 끊어졌다.
(아파치 1.3 후반 버전에서 이 지시어는 %...c였지만, 전통적인 ssl %...{var}c 문법과 겹쳐서 변경했다.)
%...I요청과 헤더를 포함한 수신 바이트수로 0일 수 없다. 이를 사용하려면 mod_logio가 필요하다.
%...O헤더를 포함한 송신 바이트수로 0일 수 없다. 이를 사용하려면 mod_logio가 필요하다.

Apache HTTP Server 2.4 module guide (아파치 모듈 프로그래밍)


본 문서: http://httpd.apache.org/docs/2.4/developer/modguide.html

Apache HTTP 서버 2.4를 위한 모듈 개발하기


본 문서는 Apache HTTP 서버 2.4 환경에서 어떻게 모듈을 개발할 수 있는지를 기술합니다.

이 문서에서 논의될 내용은 무엇일까요?

이 문서는 Apache HTTP 서버 2.4에서 어떻게 모듈을 생성할 수 있는지를 기술하게 됩니다. 이를 위한 예제로써 mod_example를 보여드릴 것입니다. 이 문서의 첫번째 부분에서는, mod_example의 목적이 웹서버상에 저장된 파일에 대한 다양한 표현(digest) 방법을 보게 될 것입니다. 그때에 우리는 URL http://hostname/filename.sum 에 접근하는 것도 볼 수 있습니다. 예를 들어, index.html이라는 파일이 URL http://www.example.com/index.html 파일에 있고, 이 파일의 MD5를 알고 싶다면, 다음과 같이 접근하면 됩니다. http://www.example.com/index.html.sum.

이 문서의 뒷부분에서는 지시어 설정과 문맥 인지에 대해서 다룰 것입니다. 여기에서 우리는 모듈이 자신의 설정이 어떻게 되어있는지 보여주는 간단한 예제를 만나게 됩니다.

선수학습
첫번째로 중요한 것은, 우리는 당신이 C 언어에 대한 기본 지식은 가지고 있는 것으로 간주한다는 것이빈다. 대부분의 경우에는 예제에 나온 코드에 대해서 외부 링크와 자세한 설명을 통해서 이해시켜 드릴 것이지만, 어떤 경우에는 다양한 방식으로 함수가 호출될 때, 이 이유와 방법에 대해서 당연히 "잘 동작함"을 가정하기도 할 것입니다.

이제 준비가 되었습니다. Apache HTTP 서버에서 모듈이 어떻게 로드되고 설정되는지 이해하기를 시작해 보겠습니다. 또한, 요청의 header를 어떻게 가져올 수 있는지, 새로운 모듈을 컴파일 하기 위해서 무엇이 필요한지를 보게 될 것입니다.


모듈 컴파일하기

본 문서에 나오는 코드를 컴파일 하기 위해서는 APXS(APache eXtenSion 도구)를 이용할 것입니다. 소스코드의 파일 이름은 mod_example.c 라면 다음의 명령어로 간단하게 빌드하여 모듈을 활성화 시킬 수 있습니다.

apxs -i -a -c mod_example.c














모든 모듈은 동일한 선언 또는 네이태크(namge tag)로 시작합니다. 이 선언은 Apache내에서 분리된 형태로 정의되어 있습니다.

module AP_MODULE_DECLARE_DATA   example_module =
{ 
    STANDARD20_MODULE_STUFF,
    create_dir_conf, /* Per-directory configuration handler */
    merge_dir_conf,  /* Merge handler for per-directory configurations */
    create_svr_conf, /* Per-server configuration handler */
    merge_svr_conf,  /* Merge handler for per-server configurations */
    directives,      /* Any directives we may have for httpd */
    register_hooks   /* Our hook registering function */
};

위 코드는 서버에게 새로운 모듈을 등록할 것이라는 것을 알리는 역할을 합니다. 그 이름은 example_module입니다. 모듈의 이름은 기본적으로 두 부분으로 되어 있습니다.

LoadModule를 이용하여 모듈을 로딩할 때 서버에게 어떻게 알려줄 것인가.
아파치 설정에서 사용되어질 모듈을 위해서 하나의 네이스페이스를 설정하는 것.

먼저, 우리는 첫번째 부분에 대한 설명을 만족시키기 위해서 모듈 이름의 목적에 대해서만 관심을 가질 것입니다. 이것은 모듈을 로드해야할 때 필요한 사항을 포함합니다.

LoadModule example_module modules/mod_example.so

위 설정은 서버에게 mod_example.so 파일을 로드하라고 알려주는 것이고 example_module라는 모듈을 찾아야함을 알려주는 것입니다.


네임테그를 통해서 할 수 있는 것들에 대한 수많은 참조 사이트들이 있습니다. 설정에 대응되는 지시자들과 .htaccess그리고 우리가 특정 컨텍스트내에서 어떻게 동작해야하는지 등을 다룰 수 있습니다. 더불어, 서버에 등록된 핸들러들이 어떤 것들이 있는지도 알 수 있습니다. 이러한 내용들은 이 문서의 뒷부분에서 보실 수 있습니다.


시작하기: 서버의 동작 가로채기(Hook)


훅(Hook) 소개하기

Apache HTTP 서버 2.4에서 요청을 처리할 때, 제일 먼저 해야하는 일은 그 요청을 처리하는 과정안에 하나의 훅을 만들어 넣는 것입니다. 훅은 본질적으로 하나의 메시지인데, 그 의미는 클라이언트로부터 온 요청에 대해서 이를 서비스하거나 또는 적어도 한번은 처리할 필요가 있을지 서버에게 알려주는 것입니다. 모든 핸들러, 예를 들면 mod_rewrite, mod_authn_*, mod_proxy 등등의 모든 핸들러들은 클라이언트의 요청을 처리하는 과정 중에 포함되어집니다. 이미 알고 있으리라 생각하지만, 모듈들은 각각 다른 목적으로 서비스를 역할을 수행합니다. 몇몇은 인증/허가에 대한 것이고, 다른 것들은 URI rewrite 또는 컨텐츠 처리의 proxy 역할을 하는 파일 또는 스크립트 핸들러입니다. 더군다나, 마지막과정에서 각 모듈이 언제 어떻게 서비스를 제공할 것인지를 사용자가 결정할 수 있습니다. 그러므로, 서버 자체는 요청 처리에 대해서 어떤 모듈이 책임져야하는지를 미리 예측하지 않고, 각 모듈에게 이 요청을 처리하는데 관심이 있는지 물어보게 됩니다. 그러면, 모듈은 자연스럽게 거부하거나 또는 처리할 것이 있으므로 해당요청을 처리하겠다고 합니다. 인증/허가 모듈이 수행되어 처리 자체를 거부하기도 합니다.




mod_example 이라는 모듈이 클라이언트의 요청을 처리해야하는지 조금 더 알기 쉽게 하기 위해서 서버는 지시자 기능으로 각 모듈에게 힌트를 줍니다. 이것이 바로 AddHandler와 SetHandler입니다. AddHandler의 예를 보겠습니다. 앞으로 나올 예제의 경우에 우리는 모든 요청이 .sum으로 끝나면 mod_example에 의해서 처리되기를 원합니다. 이를 위해서 다음과 같은 지시자를 지정하면 됩니다.

AddHandler example-handler .sum

이것은 다음의 의미를 지닙니다: 요청 URI에서 .sum으로 끝나는 것이 있다면, example-handler를 가진 모듈을 찾습니다. 그러므로, 어떤 요청이 .sum으로 끝나면, 서버는 모든 모듈이 example-handler에 의해서 처리되어야 요청이라는 점을 알게 합니다. 나중에 보게 되겠지만, mod_example의 빌드를 시작할 때, 우리는 AddHandler에 의해서 전달되는 핸들러 태그를 확인할 것이고, 이 태그의 값에 기반하여 서버가 응답하는 것도 확인할 것입니다.

HTTP안으로 Hooking

먼저, 우리는 간단한 핸들러는 만듭니다. 이 핸들러는 브라우저의 특정 URL 요청에 대해서 응답하기 위한 것입니다. 그리고, 이 시점에서는 서버 설정을 위한 핸들러는 사용하지 않습니다. 우리의 첫번째 모듈의 정의는 다음과 같습니다.

module AP_MODULE_DECLARE_DATA   example_module =
{ 
    STANDARD20_MODULE_STUFF,
    create_dir_conf, /* Per-directory configuration handler */
    merge_dir_conf,  /* Merge handler for per-directory configurations */
    create_svr_conf, /* Per-server configuration handler */
    merge_svr_conf,  /* Merge handler for per-server configurations */
    directives,      /* Any directives we may have for httpd */
    register_hooks   /* Our hook registering function */
};


위 코드는 서버에게 우리는 어떤 화려한 것에 관심 있는 것이 아니라, 어떤 요청을 Hook 하기를 원한다는 것만 알려주려고 합니다.

register_hooks는 우리가 만들게될 함수의 이름인데, 이 함수는 요청을 어떻게 다룰 것인지를 구현하게 됩니다. 본 예제 모듈 이 함수는 단지 하나의 역할만 수행합니다. 그것은 rewrites, 접근제어(access control) 등의 처리가 끝난 후에 처리하기를 원하는 간단한 hook 함수입니다. 그러므로, 우리는 서버에게 다음 사실을 알려주고자하는데, 그것은 우리가 마지막 모듈들 중에 하나이고 그 과정 중에 하나를 처리하는 모듈이라는 점입니다.

static void register_hooks(apr_pool_t *pool)
{
    /* Create a hook in the request handler, so we get called when a request arrives */
    ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST);
}


여기에서 example_handler는 요청을 처리할 함수입니다. 다음 장에서 이 핸들러를 생성하는 방법에 대해서 다룰 것입니다.

다른 유용한 hooks
요청을 처리하는 구문에서 할 수 있는 많은 다른 hook들이 있습니다. 다음은 그 몇몇 방법들입니다:

ap_hook_child_init: 자식 프로세스가 생성될 때 실행되는 위치에 넣을 수 있습니다.(보통은 서버가 fork한 후에 모듈을 초기화하는 과정)
ap_hook_pre_config: 설정 데이터가 읽혀지기 전에 실행되는 위치에 넣을 수 있습니다.(실행의 매우 초기 단계)
ap_hook_post_config: 설정 데이터가 파싱된 후에 실행되는 위치에 넣을 수 있습니다. 다만, 서버는 fork 되기 전입니다.
ap_hook_translate_name: URI가 서버상에 파일이름으로 전환될 때 실행되는 위치에 넣을 수 있습니다.(mod_rewrite를 생각해보세요.)
ap_hook_quick_handler: 다른 요청 hook들(translation, auth, fixups 등등)이 hook 되기전에 실행되는 위치에 넣는 다는 점을 제외하면, ap_hook_handler와 유사합니다.   
ap_hook_log_transaction: 서버가 현재 요청의 로그를 추가하려고 할 때, 실행되는 위치에 넣을 수 있습니다.


본질적으로, 핸들러는 하나의 함수인데, 이 함수는 요청이 서버로 인입될 때 콜백으로 처리됩니다. 이 함수의 호출을 통해서 현재 요청과 관련된 값들(어떻게 연결되어 있는지, 어떤 헤더들을 포함하는지, 어디로부터의 요청인지 등등)을 넘겨 받을 수 있습니다. 또한, 서버가 이 요청을 처리해야하는지 그렇지 않은지도 이 콜백함수를 통해서 알 수 있습니다.

간단한 "Hello, world!" 핸들러

다음의 과정을 통해서 간단한 핸들러를 만들면 다음과 같습니다.

1. 요청이 "example-handler"에 의해서 처리되어야 하는지 검사합니다.
2. 출력의 컨텐츠 타입을 text/html로 처리합니다.
3. 클라이언트 브라우저에게 "Hello world!"라는 문장을 리턴합니다.
4. 우리가 이 요청을 처리했고, 모든 과정이 정상이었다는 것을 서버에게 알려줍니다.

아래 C 언어로 된 예제코드와 같이 작성할 수 있습니다.

static int example_handler(request_rec *r)
{
    /* First off, we need to check if this is a call for the "example-handler" handler.
     * If it is, we accept it and do our things, if not, we simply return DECLINED,
     * and the server will try somewhere else.
     */
    if (!r->handler || strcmp(r->handler, "example-handler")) return (DECLINED);
    
    /* Now that we are handling this request, we'll write out "Hello, world!" to the client.
     * To do so, we must first set the appropriate content type, followed by our output.
     */
    ap_set_content_type(r, "text/html");
    ap_rprintf(r, "Hello, world!");
    
    /* Lastly, we must tell the server that we took care of this request and everything went fine.
     * We do so by simply returning the value OK to the server.
     */
    return OK;
}


이제 지금까지 배운 것들을 하나의 소스코드안에 넣었습니다. mod_example_1.c. 예제에 나온 함수는 나중에 "우리가 알아야 하는 유용한 함수들"에서 설명될 예정입니다.

request_rec 구조체
요청 처리에 있어서 가장 중요한 부분은 'request record' 입니다. 핸들러 함수를 호출할 때, 구조체 request_rec*는 모든 함수 호출과 함께 넘어옵니다. 이 구조체는 HTTP 요청에 따른 적절한 응답을 위해서 필요로하는 모든 정보를 포함하고 있습니다. 보통 모듈 안에서 r 이라는 문자로 참조할 수 있습니다.

request_rec 구조체의 주요 멤버들을 살펴보면 다음과 같습니다.

r->handler (char*): 서버가 요청을 처리하기 위한 핸들러의 이름입니다.
r->method (char*): 사용된 HTTP 메쏘드의 이름입니다. 예를 들면, GET 또는 POST.
r->filename (char*): 클라이언트가 요청하고 있는 파일이름입니다.
r->args (char*): 요청에 포함되어 있는 쿼리스트링(query string)입니다.
r->headers_in (apr_table_t*): 클라이언트의 요청에 포함되어 있는 모든 헤더입니다.
r->connecton (conn_rec*): 현재 연결에 대한 정보를 포함합니다.
r->useragent_ip (char*): 클라이언트의 IP 주소입니다.(역자주: 여기의 IP 주소는 클라이언트 호스트가 가진 IP 주소가 아닌, 서버에 생성된 소켓으로부터 얻을 수 있는 IP 주소입니다.)
r->pool (apr_pool_t*): 요청에 따른 메모리 pool입니다. 이것에 대한 자세한 내용은 "메모리 관리"에서 다루어집니다.

request_rec 구조체에 포함되어 있는 모든 멤버들은 http.h 헤더파일에서 볼 수 있습니다. 또한, 다음 웹페이지에서도 확인할 수 있습니다. http://httpd.apache.org/docs/2.4/developer/modguide.html#functions


다음의 예제를 통해서 이러한 멤버들의 사용을 볼 수 있습니다.

static int example_handler(request_rec *r)
{
    /* Set the appropriate content type */
    ap_set_content_type(r, "text/html");

    /* Print out the IP address of the client connecting to us: */
    ap_rprintf(r, "

Hello, %s!

", r->useragent_ip); /* If we were reached through a GET or a POST request, be happy, else sad. */ if ( !strcmp(r->method, "POST") || !strcmp(r->method, "GET") ) { ap_rputs("You used a GET or a POST method, that makes us happy! ", r); } else { ap_rputs("You did not use POST or GET, that makes us sad :( ", r); } /* Lastly, if there was a query string, let's print that too! */ if (r->args) { ap_rprintf(r, "Your query string was: %s", r->args); } return OK; }



리턴 값
아파치는 클라이언트의 요청이 잘 처리되었는지의 여부를 핸들러의 리턴값를 보고 판단합니다. 모듈이 특정 요청을 처리하는 것에 관계하지 않는다면 그 모듈은 항상 'DECLINED'를 리턴해야합니다. 만약, 자신이 처리해야하는 요청이라면, 그 모듈 'OK' 또는 적절한 HTTP 상태 코드를 리턴해야 합니다. 다음의 그 예입니다.

static int example_handler(request_rec *r)
{
    /* Return 404: Not found */
    return HTTP_NOT_FOUND;
}

'OK' 또는 HTTP 상태코드를 리턴하는 것이 그 요청을 끝내는 것을 의미하지는 않습니다. 서버는 이 요청을 처리하기 위한 다른 핸드러를 가지고 있을 수도 있습니다. 예를 들어, 로깅 모듈은 성공한 요청에 대하여 어떤 요청이 있었고 어떻게 처리되었는지를 기록합니다. 우리가 작성한 모듈에서 요청의 처리가 끝난 상태에서 (다른 핸들러에서) 이 요청의 추가적인 처리가 이루어지는 것을 막기 위해서는 우리 모듈에서 'DONE'를 리턴하면 됩니다. 그러면 서버는 이 요청에 대한 처리가 모두 끝난 것으로 간주하여 다른 핸들러에게 이 요청의 처리를 맡기지 않고 다음 작업을 수행합니다.

일반적인 응답 코드들:
DECLINED: 이 요청에는 관심 없음.
OK: 요청은 잘 처리 되었음.
DONE: 요청을 처리했고, 서버는 다른 추가 처리 없이 해당 쓰레드를 종료함.

HTTP 리턴 코드들(인용):
HTTP_OK (200): 요청이 정상임.
HTTP_MOVED_PERMANENTLY (301): 리소스가 새로운 URL로 이동되었음.
HTTP_UNAUTHORIZED (401): 클라이언트는 이 페이지를 방문하기 위한 인증이 이루어지지 않음.
HTTP_FORBIDDEN (403): 권한 없음.
HTTP_NOT_FOUND (404): 해당 파일 없음.
HTTP_INTERNAL_SERVER_ERROR (500): 서버 내부 에러


우리가 알아야 하는 유용한 함수들
ap_rputs(const char *string, request_rec *r):
string의 문자들을 클라이언트에 전송합니다. ap_rwrite의 간단 버전.
ap_rputs("Hello, world!", r);

ap_rprintf:
결과를 클라이언트에게 전송한다는 점만 제외하면, printf와 유사하게 동작하는 함수입니다.

ap_rprintf(r, "Hello, %s!", r->useragent_ip);

ap_set_content_type(request_rec *r, const char *type):
클라이언트에 전송하는 결과의 컨텐츠 타입을 설정합니다.

ap_set_content_type(r, "text/plain"); /* force a raw text output */

메모리 관리
아파치 HTTP 2.4에서 리소스를 관리하는 것은 매우 쉽습니다. 이 공간을 빌어 메모리 pool 시스템에 고마움을 전합니다. 본질적으로, 서버, 커넥션, 요청 각각은  그 역할이 끝날 때 자동으로 해제되는 자신만의 메모리 pool을 가지고 있습니다. 우리가 해야할 일은 이러한 메모리 pool를 이해하고 사용하는 일 뿐입니다. 사용이 끝난 후에 메모리 해제에 대해서 걱정할 필요가 없습니다 - 정말 깔끔하지 않나요? Huh?

우리 모듈에서는 요청마다 메모리를 할당하게 됩니다. 새로운 객체를 생성할 때, r->pool를 사용하는 것은 매우 적절한 선택입니다. pool에서 제공하는, 메모리 할당을 위한 다음의 함수들이 있습니다:

  • void* apr_palloc( apr_pool_t *p, apr_size_t size): size 만큼 메모리를 확보함.
  • void* apr_pcalloc( apr_pool_t *p, apr_size_t size): size 만큼 메모리를 확보하고, 0으로 초기화.
  • char* apr_pstrdup( apr_pool_t *p, const char *s): 스트링 s의 복제본을 만듭니다. 상수 스트링을 편집하기 위한 임의의 복제본을 만들 때 유용합니다.
  • char* apr_psprintf( apr_pool_t *p, const char *fmt, ...): 타겟 변수에 필요한 메모리를 적절히 할당한다는 것을 제외하고는 sprintf와 유사함
다음은 그 사용 예제입니다.
static int example_handler(request_rec *r)
{
    const char* original = "You can't edit this!";
    char* copy;
    int* integers;
    
    /* Allocate space for 10 integer values and set them all to zero. */
    integers = apr_pcalloc(r->pool, sizeof(int)*10); 
    
    /* Create a copy of the 'original' variable that we can edit. */
    copy = apr_pstrdup(r->pool, original);
    return OK;
}

미리 초기화해야하는 변수나 구조체 없이 매우 유용한 것들입니다만, 만약 요청이 들어오기 전에 좀 더 일찍 초기화하기를 원한다면, register_hook 함수에 이러한 처리를 할 수 있습니다.
static void register_hooks(apr_pool_t *pool)
{
    /* Call a function that initializes some stuff */
    example_init_function(pool);
    /* Create a hook in the request handler, so we get called when a request arrives */
    ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST);
}

요청을 처리하기 전에 먼저 초기화되는 함수에서는 이전에 사용한(각 요청 처리를 위한 pool 리소스) 것과 동일한 pool을 사용하는 것은 아닙니다. 이때 사용하는 pool은 쓰레드기반이 아닌, 프로세스 기반으로 생성된 메모리 안에서 할당되어 있는 pool를 사용합니다.


요청 데이터를 파싱하기

우리의 모듈 예제에 어떤 기능을 추가하려고 합니다. digest 타입을 보고, 클라이언트가 원하는 MD5 또는 SHA1를 보여주는 것입니다. 쿼리 스트링을 요청에 포함시키는 방식으로 구현할 수 있습니다. 쿼리 스트링은 key와 value의 쌍으로 이루어집니다. ' valueA=yes&valueB=no&valueC=maybe'가 적절한 예라고 할 수 있습니다. 이것을 파싱하고 클라이언트가 원하는 데이터가 무엇인지 아는 것은 모듈 자체에 어떻게 구현하는냐에 달려있습니다. 우리는 digest 라는 key를 찾을 것이고, 여기에 md5라는 값을 가지면 파일의 md5를 구해서 보여주고, 그렇지 않으면 SHA1를 보여줄 것입니다.

Apache HTTP 2.4에서 소개된 것처럼, GET, POST 요청으로부터 요청 데이터를 얻는 것은 매우 쉽습니다. 요청을 파싱하기 위해서 우리가 해야하는 일은 단지 4줄의 코드를 작성하는 것 뿐입니다.
apr_table_t*GET; 
apr_array_header_t*POST; 

ap_args_to_table(r, &GET); 
ap_parse_form_data(r, NULL, &POST, -1, 8192); 

우리가 작성할 예제에서는 쿼리 스트링으로부터 digest의 value를 얻는 것입니다. 이것은 'GET'이라고 명명된 테이블 안에 포함되어 있습니다. 이 값을 추출하기 위해서 우리가 작성해야할 코드는 매우 간단합니다.

/* Get the "digest" key from the query string, if any. */
const char *digestType = apr_table_get(GET, "digest");

/* If no key was returned, we will set a default value instead. */
if (!digestType) digestType = "sha1";



POST와 GET 요청 데이터에 사용되는 구조체는 동일하지 않습니다. 그래서, 쿼리 스트링 대신에 POST 데이터로부터 어떤 value를 가져온다면, 몇줄의 구현이 더 필요합니다. 적당한 예는 아래와 같은 코드로 구현 가능합니다.

예) POST 폼 데이터로부터 변수 얻기
typedef struct {
    const char* key;
    const char* value;
} keyValuePair;

keyValuePair* readPost(request_rec* r) {
    apr_array_header_t *pairs = NULL;
    apr_off_t len;
    apr_size_t size;
    int res;
    int i = 0;
    char *buffer;
    keyValuePair* kvp;

    res = ap_parse_form_data(r, NULL, &pairs, -1, HUGE_STRING_LEN);
    if (res != OK || !pairs) return NULL; /* Return NULL if we failed or if there are is no POST data */
    kvp = apr_pcalloc(r->pool, sizeof(keyValuePair) * (pairs->nelts + 1));
    while (pairs && !apr_is_empty_array(pairs)) {
        i++;
        ap_form_pair_t *pair = (ap_form_pair_t *) apr_array_pop(pairs);
        apr_brigade_length(pair->value, 1, &len);
        size = (apr_size_t) len;
        buffer = apr_palloc(r->pool, size + 1);
        apr_brigade_flatten(pair->value, buffer, &size);
        buffer[len] = 0;
        kvp[i]->key = apr_pstrdup(r->pool, pair->name);
        kvp[i]->value = buffer;
    }
    return kvp;    
}

static int example_handler(request_rec *r) 
{
    /*~~~~~~~~~~~~~~~~~~~~~~*/
    
    keyValuePair* formData;
    /*~~~~~~~~~~~~~~~~~~~~~~*/

    formData = readPost(r);
    if (formData) {
        int i;
        for (i = 0; formData[i]; i++) {
            ap_rprintf(r, "%s = %s\n", formData[i]->key, formData[i]->value);
        }
    }
    return OK;
}


조금 더 발전된 핸들러 만들기

우리는 요청 데이터를 어떻게 파싱하는지 배웠고, 우리의 리소스를 관리하는 것도 배웠습니다. 이제는 우리 모듈을 조금 더 나은 모습으로 만들 수 있게 되었습니다.

파일의 MD5 또는 SHA1 출력하기:

static int example_handler(request_rec *r)
{
    int rc, exists;
    apr_finfo_t finfo;
    apr_file_t *file;
    char *filename;
    char buffer[256];
    apr_size_t readBytes;
    int n;
    apr_table_t *GET;
    apr_array_header_t *POST;
    const char *digestType;
    
    
    /* Check that the "example-handler" handler is being called. */
    if (!r->handler || strcmp(r->handler, "example-handler")) return (DECLINED);
    
    /* Figure out which file is being requested by removing the .sum from it */
    filename = apr_pstrdup(r->pool, r->filename);
    filename[strlen(filename)-4] = 0; /* Cut off the last 4 characters. */
    
    /* Figure out if the file we request a sum on exists and isn't a directory */
    rc = apr_stat(&finfo, filename, APR_FINFO_MIN, r->pool);
    if (rc == APR_SUCCESS) {
        exists =
        (
            (finfo.filetype != APR_NOFILE)
        &&  !(finfo.filetype & APR_DIR)
        );
        if (!exists) return HTTP_NOT_FOUND; /* Return a 404 if not found. */
    }
    /* If apr_stat failed, we're probably not allowed to check this file. */
    else return HTTP_FORBIDDEN;
    
    /* Parse the GET and, optionally, the POST data sent to us */
    
    ap_args_to_table(r, &GET);
    ap_parse_form_data(r, NULL, &POST, -1, 8192);
    
    /* Set the appropriate content type */
    ap_set_content_type(r, "text/html");
    
    /* Print a title and some general information */
    ap_rprintf(r, "

Information on %s:

", filename); ap_rprintf(r, "Size: %u bytes ", finfo.size); /* Get the digest type the client wants to see */ digestType = apr_table_get(GET, "digest"); if (!digestType) digestType = "MD5"; rc = apr_file_open(&file, filename, APR_READ, APR_OS_DEFAULT, r->pool); if (rc == APR_SUCCESS) { /* Are we trying to calculate the MD5 or the SHA1 digest? */ if (!strcasecmp(digestType, "md5")) { /* Calculate the MD5 sum of the file */ union { char chr[16]; uint32_t num[4]; } digest; apr_md5_ctx_t md5; apr_md5_init(&md5); readBytes = 256; while ( apr_file_read(file, buffer, &readBytes) == APR_SUCCESS ) { apr_md5_update(&md5, buffer, readBytes); } apr_md5_final(digest.chr, &md5); /* Print out the MD5 digest */ ap_rputs("MD5: ", r); for (n = 0; n < APR_MD5_DIGESTSIZE/4; n++) { ap_rprintf(r, "%08x", digest.num[n]); } ap_rputs("", r); /* Print a link to the SHA1 version */ ap_rputs(" View the SHA1 hash instead", r); } else { /* Calculate the SHA1 sum of the file */ union { char chr[20]; uint32_t num[5]; } digest; apr_sha1_ctx_t sha1; apr_sha1_init(&sha1); readBytes = 256; while ( apr_file_read(file, buffer, &readBytes) == APR_SUCCESS ) { apr_sha1_update(&sha1, buffer, readBytes); } apr_sha1_final(digest.chr, &sha1); /* Print out the SHA1 digest */ ap_rputs("SHA1: ", r); for (n = 0; n < APR_SHA1_DIGESTSIZE/4; n++) { ap_rprintf(r, "%08x", digest.num[n]); } ap_rputs("", r); /* Print a link to the MD5 version */ ap_rputs(" View the MD5 hash instead", r); } apr_file_close(file); } /* Let the server know that we responded to this request. */ return OK; }
실제 코드는 여기에 있습니다:mod_example_2.c

설정 옵션 추가하기


지금까지 다루어왔던 모듈에서 벗어나서 설정으로 눈을 돌릴 때가 되었습니다. 이 내용의 목적은 서버가 설정과 함께 어떻게 동작하는지를 알아보고, 우리의 모듈을 위해서 설정을 작성했을 때 어떤 일이 발생하는지를 이해하는 것입니다.


설정 지시자(An introduction to configuration directives)
설정 지시자라는 것은 간단하게 말해서 작성된 모듈이 어떻게 동작하는지를 표현해주는 방식의 하나입니다. 이를테면, 아래 지시자는 mod_rewrite가 어떻게 컨트롤되는지를 보여줍니다.

RewriteEngine On
RewriteCond %{REQUEST_URI} ^/foo/bar
RewriteRule ^/foo/bar/(.*)$ /foobar?page=$1

각각의 설정 내용은 서로 다른 함수에 의해서 다루어집니다. 이 함수가 하는 일은 파라미터를 파싱하고 적절한 설정을 만드는 것입니다.


설정 만들기 예제

먼저, 우리는 C-space내에 기본적인 설정을 생성할 것입니다.
typedef struct {
    int         enabled;      /* Enable or disable our module */
    const char *path;         /* Some path to...something */
    int         typeOfAction; /* 1 means action A, 2 means action B and so on */
} example_config;

이 예제를 가지는 설정을 출력해주는 간단한 모듈을 만들어보겠습니다. 주의깊게 보아야하는 부분은 기본 설정 값을 초기화하기 위해서 register_hooks 함수를 이용하고 있다는 점입니다.
typedef struct {
    int         enabled;      /* Enable or disable our module */
    const char *path;         /* Some path to...something */
    int         typeOfAction; /* 1 means action A, 2 means action B and so on */
} example_config;

static example_config config;

static int example_handler(request_rec *r)
{
    if (!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED);
    ap_set_content_type(r, "text/plain");
    ap_rprintf(r, "Enabled: %u\n", config.enabled);
    ap_rprintf(r, "Path: %s\n", config.path);
    ap_rprintf(r, "TypeOfAction: %x\n", config.typeOfAction);
    return OK;
}

static void register_hooks(apr_pool_t *pool) 
{
    config.enabled = 1;
    config.path = "/foo/bar";
    config.typeOfAction = 0x00;
    ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST);
}

/* Define our module as an entity and assign a function for registering hooks  */

module AP_MODULE_DECLARE_DATA   example_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,            /* Per-directory configuration handler */
    NULL,            /* Merge handler for per-directory configurations */
    NULL,            /* Per-server configuration handler */
    NULL,            /* Merge handler for per-server configurations */
    NULL,            /* Any directives we may have for httpd */
    register_hooks   /* Our hook registering function */
};

새로운 핸들러에 접근하기 위해서, 다음의 내용을 설정에 포함시킵니다.
<Location /example>
    SetHandler example-handler
</Location>

해당 모듈이 호출되었을 때, 현재 모듈의 설정 내용을 확인할 수 있습니다.


서버 설정과 함께 지시자 등록하기
고정된 설정값을 모듈에 고정시키지 않고, http.conf 파일 또는 .htaccess 파일의 설정을 이용하여 우리의 설정을 변경하고자한다면 어떻게 해야할까요? 우리가 이러한 설정방법을 원하고 있다는 것을 서버에게 알려줄 수 있습니다. 이것을 적용하기 위해서는 우리는 가장 먼저 해야할 일은 네임태그(Name tag)를 바꾸는 것입니다. 이 네임태그는 우리가 서버에 적용되기를 원하는 설정 지시자의 러페런스에 포함시켜야 합니다. 다음의 example_directives를 참고하세요.

module AP_MODULE_DECLARE_DATA   example_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,               /* Per-directory configuration handler */
    NULL,               /* Merge handler for per-directory configurations */
    NULL,               /* Per-server configuration handler */
    NULL,               /* Merge handler for per-server configurations */
    example_directives, /* Any directives we may have for httpd */
    register_hooks      /* Our hook registering function */
};

이렇게 코드를 작성하면, 서버에게 설정 파일로부터 지시자를 인식하겠다는 것을 알려주게 됩니다. example_directives라는 구조체는  우리의 지시자가 무엇인지를 보관하고 동작시키는 역할을 합니다. 우리는 앞에서 3개의 다른 변수를 통해서 모듈을 설정하고 있었습니다.(enabled, path, typeofaction) 이것을 3개의 지시어로 만들어서 추가할 것입니다. 마지막에 NULL를 추가하는 것도 잊지 않고 말이죠.

static const command_rec        example_directives[] =
{
    AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, RSRC_CONF, "Enable or disable mod_example"),
    AP_INIT_TAKE1("examplePath", example_set_path, NULL, RSRC_CONF, "The path to whatever"),
    AP_INIT_TAKE2("exampleAction", example_set_action, NULL, RSRC_CONF, "Special action value!"),
    { NULL }
};






예제에 나와 있듯이, 각 지시자는 적어도 5개를 파라미터를 가집니다:

1. AP_INIT_TAKE1: 서버에게 하나의 지시자와 단지 1개의 파라미터를 가진다는 것을 알려주는 매크로입니다. 2개의 파라미터를 가지려면 AP_INIT_TAKE2 매크로를 이용해야합니다.(더 자세한 매크로는 httpd_conf.h 파일을 참고하세요.)

2. exampleEnabled: 지시자의 이름. 좀 더 정확한 표현으로는, 우리 모듈을 사용할 사용자가 설정의 변경을 알기 위해서 사용자 설정에 넣는 이름입니다.

3. example_set_enabled: 함수의 이름입니다. 이 함수는 지시자를 파싱하고 적절하게 설정하는 역할을 합니다. 다음절에서 이 함수를 어떻게 만드는지 설명할 예정입니다.

4. RSRC_CONF: 지시자가 허용된 곳을 서버에게 알려주는 것입니다. 다음 장에서 여기에 대한 자세한 설명을 볼 수 있습니다. 지금은 RSRC_CONF가 단지 서버가 지시자를 인식할 수 있게 해주는 의미로 사용되었구나 정도로 이해하면 됩니다.

5. "Enable or disable …": 지시자에 대한 간단한 설명을 적는 곳입니다.

(NULL에는 함수가 들어갈 수 있는데, 이 함수는 파라미터들의 파싱하는 초기화 함수가 수행된 이후에 실행될 수 있는 함수입니다. 이 함수는 보통 NULL로 설정해서 생략합니다. 이 함수를 이용하면 인자들을 검사할 수 있고, 설정을 할 수도 있습니다. 지금은 사용되지 않습니다.)

지시자 핸들러 함수
서버에게 우리의 모듈 일부 지시자를 가질수 있음을 알게 했습니다. 이제는 이것들을 다룰 수 있는 함수를 만들어보겠습니다. 설정파일에서 서버가 인식하는 것은 텍스트입니다. 그래서, 자연스럽게 우리의 지시어는 하나 또는 여러 개의 문자열이라고 할 수 있습니다. 앞의 예제에서 exampleAction 지시자는 2개의 인자를 받아 들이는 것으로 구성했습니다. 따라서, 그의 C 함수도 하나의 파라미터를 더 받아들이도록 구현합니다.

/* Handler for the "exampleEnabled" directive */
const char *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg)
{
    if(!strcasecmp(arg, "on")) config.enabled = 1;
    else config.enabled = 0;
    return NULL;
}

/* Handler for the "examplePath" directive */
const char *example_set_path(cmd_parms *cmd, void *cfg, const char *arg)
{
    config.path = arg;
    return NULL;
}

/* Handler for the "exampleAction" directive */
/* Let's pretend this one takes one argument (file or db), and a second (deny or allow), */
/* and we store it in a bit-wise manner. */
const char *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char* arg2)
{
    if(!strcasecmp(arg1, "file")) config.typeOfAction = 0x01;
    else config.typeOfAction = 0x02;
    
    if(!strcasecmp(arg2, "deny")) config.typeOfAction += 0x10;
    else config.typeOfAction += 0x20;
    return NULL;
}


설정 합치기
이제 지시자를 모두 만들었고, 핸들러도 추가했습니다. 우리의 설정을 한곳에 모아보겠습니다.

/* mod_example_config_simple.c: */
#include 
#include "apr_hash.h"
#include "ap_config.h"
#include "ap_provider.h"
#include "httpd.h"
#include "http_core.h"
#include "http_config.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"

/*
 ==============================================================================
 Our configuration prototype and declaration:
 ==============================================================================
 */
typedef struct {
    int         enabled;      /* Enable or disable our module */
    const char *path;         /* Some path to...something */
    int         typeOfAction; /* 1 means action A, 2 means action B and so on */
} example_config;

static example_config config;

/*
 ==============================================================================
 Our directive handlers:
 ==============================================================================
 */
/* Handler for the "exampleEnabled" directive */
const char *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg)
{
    if(!strcasecmp(arg, "on")) config.enabled = 1;
    else config.enabled = 0;
    return NULL;
}

/* Handler for the "examplePath" directive */
const char *example_set_path(cmd_parms *cmd, void *cfg, const char *arg)
{
    config.path = arg;
    return NULL;
}

/* Handler for the "exampleAction" directive */
/* Let's pretend this one takes one argument (file or db), and a second (deny or allow), */
/* and we store it in a bit-wise manner. */
const char *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char* arg2)
{
    if(!strcasecmp(arg1, "file")) config.typeOfAction = 0x01;
    else config.typeOfAction = 0x02;
    
    if(!strcasecmp(arg2, "deny")) config.typeOfAction += 0x10;
    else config.typeOfAction += 0x20;
    return NULL;
}

/*
 ==============================================================================
 The directive structure for our name tag:
 ==============================================================================
 */
static const command_rec        example_directives[] =
{
    AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, RSRC_CONF, "Enable or disable mod_example"),
    AP_INIT_TAKE1("examplePath", example_set_path, NULL, RSRC_CONF, "The path to whatever"),
    AP_INIT_TAKE2("exampleAction", example_set_action, NULL, RSRC_CONF, "Special action value!"),
    { NULL }
};
/*
 ==============================================================================
 Our module handler:
 ==============================================================================
 */
static int example_handler(request_rec *r)
{
    if(!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED);
    ap_set_content_type(r, "text/plain");
    ap_rprintf(r, "Enabled: %u\n", config.enabled);
    ap_rprintf(r, "Path: %s\n", config.path);
    ap_rprintf(r, "TypeOfAction: %x\n", config.typeOfAction);
    return OK;
}

/*
 ==============================================================================
 The hook registration function (also initializes the default config values):
 ==============================================================================
 */
static void register_hooks(apr_pool_t *pool) 
{
    config.enabled = 1;
    config.path = "/foo/bar";
    config.typeOfAction = 3;
    ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST);
}
/*
 ==============================================================================
 Our module name tag:
 ==============================================================================
 */
module AP_MODULE_DECLARE_DATA   example_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,               /* Per-directory configuration handler */
    NULL,               /* Merge handler for per-directory configurations */
    NULL,               /* Per-server configuration handler */
    NULL,               /* Merge handler for per-server configurations */
    example_directives, /* Any directives we may have for httpd */
    register_hooks      /* Our hook registering function */
};

httpd.conf 파일에 다음의 설정을 추가합니다:

ExampleEnabled On
ExamplePath "/usr/bin/foo"
ExampleAction file allow

잘 적용되었는지 확인하기 위해서 브라우저를 이용하여 /example에 접근합니다. 그러면 우리가 설정한 내용을 볼 수 있습니다.

컨텍스트 연계설정의 소개
Apache HTTP 서버 2.4에서는 다른 URL, virtual hosts, 디렉토리 등으로 전혀 다른 의미의 설정을 제공합니다. 따라서, 어떤 모듈이 동작할 때 다른 컨텍스트를 가질 수 있습니다. 예를 들어, mod_rewrite를 위해서 다음의 설정을 사용한다고 가정해 봅니다.

<Directory "/var/www">
    RewriteCond %{HTTP_HOST} ^example.com$
    RewriteRule (.*) http://www.example.com/$1
</Directory>
<Directory "/var/www/sub">
    RewriteRule ^foobar$ index.php?foobar=true
</Directory>

이 예제에서는 mod_rewrite를 위한 두 개의 다른 컨텍스트를 설정한 것입니다.

1. /var/www 디렉토리 안에서 http://example.com를 위한 모든 요청은 반드시 http://www.example.com으로 갑니다.

2. /var/www/sub 디렉토리 안에서 모든 foobar 요청은 index.php?foobar=true로 갑니다.


mod_rewrite 모듈이 컨텍스트를 인식하지 못한다면, 위 예제의 rewrite 규칙은 모든 요청에 적용될 것입니다. 그러나, 모듈은 서버로부터 특정 컨텍스트의 설정을 가져올 수 있고, 그 설정이 어떠한지를 알 필요도 없습니다. 단지, 해당 컨텍스트내에서 올바른(valid) 지시자라면 서버가 그냥 알아서 해 줍니다. 우리는 아래 함수만 사용하면 됩니다.
example_config *config = (example_config*) ap_get_module_config(r->per_dir_config, &example_module);
물론, 이 함수 뒤에는 많은 일들이 일어납니다. 이것들에 대해서 계속 설명할 것입니다. 먼저, 서버가 어떻게 우리의 설정을 알게 되는지, 특정 컨텍스트안에서 어떻게 설정되는지에 대해서 알아보겠습니다.

기본적인 설정 만들기
본 장에서는 이전 컨텍스트 구조에서 일부 변경된 내용을 다루었습니다. 추가로, context라는 변수를 사용할 것이고, 이 변수는 다양한 장소에서 서버에 의해서 사용되고 있는 컨텍스트 설정을 추적하는 용도로 사용됩니다.
typedef struct {
    char        context[256];
    char        path[256];
    int         typeOfAction;
    int         enabled;
} example_config;

핸들러도 일부 수정합니다. 수정했지만 여전히 복잡하지 않은:

static int example_handler(request_rec *r)
{
    if(!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED);
    example_config *config = (example_config*) ap_get_module_config(r->per_dir_config, &example_module);
    ap_set_content_type(r, "text/plain");
    ap_rprintf("Enabled: %u\n", config->enabled);
    ap_rprintf("Path: %s\n", config->path);
    ap_rprintf("TypeOfAction: %x\n", config->typeOfAction);
    ap_rprintf("Context: %s\n", config->context);
    return OK;
}

컨텍스트 고르기
우리의 모듈이 컨텍스트를 인식하도록 만들기 전에 우선 정해야하는 것이 있습니다. 우리가 사용할 켄텍스트가 무엇인지 정하는 작업이 바로 그것입니다. 이전 장에서 본 바와 같이, 지시어는 5개의 파라미터를 가지고 설정됩니다:

AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, RSRC_CONF, "Enable or disable mod_example"),

RSRC_CONF는 지시자가 서버 설정 전체에 영향을 미치는 것으로 설정하는 것입니다. 지금 우리는 컨텍스트를 인지하는 모듈 설정을 할 수 있으므로, 좀 더 유연한 설정을 적용할 수 있습니다. ACCESS_CONF를 사용하면 또는 안쪽에서 지시자를 적용할 수 있습니다. 지시어를 적절히 배치하는 것 이상으로 더 세부적으로 컨트롤하기 위해서는 다음에 해당하는 것들을 조합하여 이 일을 할 수 있습니다.

RSRC_CONF: 또는 바깥에서 .conf 파일(.htaccess 파일 아닌)에서 허용합니다.
ACCESS_CONF:  또는 안쪽에서 .conf 파일(.htaccess 파일 아닌)에서 허용합니다.
OR_OPTIONS: AllowOverride Options이 설정되어 있을 때, .conf 파일과 .htaccess 파일에서 허용합니다.
OR_FILEINFO: AllowOverride FileInfo이 설정되어 있을 때, .conf 파일과 .htaccess 파일에서 허용합니다. 
OR_AUTHCFG: AllowOverride AuthConfig이 설정되어 있을 때, .conf 파일과 .htaccess 파일에서 허용합니다.
OR_INDEXES: AllowOverride Indexes이 설정되어 있을 때, .conf 파일과 .htaccess 파일에서 허용합니다.
OR_ALL: 모든 곳의 .conf 파일과 .htaccess 파일에서 허용합니다.

설정 공간을 할당 해주는 서버를 사용하기
우리가 직접 설정을 만들 수 있으면 그 방법이 설정을 더 똑똑하게 관리하는 방법이 될 수 있습니다. 이 방법을 사용하기 위해서 먼저 해야하는 일은 서버가 알 수 있는 네임태그로 변경하는 것입니다. 우리는 모듈 설정을 각 디렉토리(또는 location) 컨텍스트내에서 설정했기때문에 태그안에 각 디렉토리마다 생성과 통합을 수행하도록 해야합니다.
module AP_MODULE_DECLARE_DATA   example_module =
{
    STANDARD20_MODULE_STUFF,
    create_dir_conf, /* Per-directory configuration handler */
    merge_dir_conf,  /* Merge handler for per-directory configurations */
    NULL,            /* Per-server configuration handler */
    NULL,            /* Merge handler for per-server configurations */
    directives,      /* Any directives we may have for httpd */
    register_hooks   /* Our hook registering function */
};

새로운 컨텍스트 설정 만들기
앞에서 서버가 컨텍스트 설정을 만들게 도와주면 더 좋겠다고 했습니다. 이를 위해서 첫번째로 해야하는 일은 새롭지만 비어 있는 설정을 위한 함수를 만드는 것입니다. 각 디렉토리마다 설정 핸들러로써 네임태크를 참조하는 함수를 만드는 일이 됩니다.
module AP_MODULE_DECLARE_DATA   example_module =
{
    STANDARD20_MODULE_STUFF,
    create_dir_conf, /* Per-directory configuration handler */
    merge_dir_conf,  /* Merge handler for per-directory configurations */
    NULL,            /* Per-server configuration handler */
    NULL,            /* Merge handler for per-server configurations */
    directives,      /* Any directives we may have for httpd */
    register_hooks   /* Our hook registering function */
};

설정 통합하기
컨텍스트를 인식하는 설정은 만드는 그 다음 과정은 설정을 통합하는 것입니다. 이 부분은 우리가 상위 디렉토리 설정과 하위 디렉토리 설정을 가지는 상황에 적용될 수 있습니다. 다음은 그 예입니다:
<Directory "/var/www">
    ExampleEnabled On
    ExamplePath /foo/bar
    ExampleAction file allow
</Directory>
<Directory "/var/www/subdir">
    ExampleAction file deny
</Directory>
이 예제에서, /var/www/subdir 디렉토리는 /var/www 디렉토리의 설정을 상속받는다는 가정은 매우 자연스러운 것이고, 이것은 하위 디렉토리가 ExampleEnabled 설정과 ExamplePath 설정을 가지고 있지 않은 이유이기도 합니다. 서버는 이러한 사항을 염두해두고 미리 추측하지는 않습니다. 단지, 서버는 다음 과정을 통해서 설정을 이해합니다:

1. /var/www 디렉토리를 위해서 새로운 설정을 생성한다.
2. /var/www 디렉토리의 지시자에 따라서 설정값을 만든다.
3. /var/www/subdir 디렉토리를 위해서 새로운 설정을 생성한다.
4. /var/www/subdir 디렉토리의 지시자에 따라서 설정값을 만든다.
5. 두 개의 설정(/var/www, /var/www/subdir)을 /var/www/subdir 디렉토리의 설정으로의 통합을 제안한다.

이 제안은 우리의 네임태그에서 참조하는 merge_dir_conf에 의해서 다루어집니다. 이 함수의 목적은 두 설정을 합치는 것과 구체적으로 어떻게 통합할 것인가를 결정하는 것입니다.
void* merge_dir_conf(apr_pool_t* pool, void* BASE, void* ADD) {
    example_config* base = (example_config *) BASE ; /* This is what was set in the parent context */
    example_config* add = (example_config *) ADD ;   /* This is what is set in the new context */
    example_config* conf = (example_config *) create_dir_conf(pool, "Merged configuration"); /* This will be the merged configuration */
    
    /* Merge configurations */
    conf->enabled = ( add->enabled == 0 ) ? base->enabled : add->enabled ;
    conf->typeOfAction = add->typeOfAction ? add->typeOfAction : base->typeOfAction;
    strcpy(conf->path, strlen(add->path) ? add->path : base->path);
    
    return conf ;
}

새로운 컨텍스트를 인식하는 설정 해보기
이제 적절한 예제를 보일 때가 되었습니다. 새로운 설정을 만들고 어떻게 테스트 할 수 있는지 보여드립니다.
<Location "/a">
    SetHandler example-handler
    ExampleEnabled on
    ExamplePath "/foo/bar"
    ExampleAction file allow
</Location>

<Location "/a/b">
    ExampleAction file deny
    ExampleEnabled off
</Location>

<Location "/a/b/c">
    ExampleAction db deny
    ExamplePath "/foo/bar/baz"
    ExampleEnabled on
</Location>

이제 우리의 모듈 코드를 보여드립니다. 여기서 주의해야할 점은, 우리는 우리의 핸들러 함수는 설정을 가져오는 역할을 수행하는 함수라는 점입니다. 그리고, 컴파일을 위해서 몇몇 프로토타입들도 추가했습니다.
/*$6
 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 * mod_example_config.c
 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */


#include 
#include "apr_hash.h"
#include "ap_config.h"
#include "ap_provider.h"
#include "httpd.h"
#include "http_core.h"
#include "http_config.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"

/*$1
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Configuration structure
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */

typedef struct
{
    char    context[256];
    char    path[256];
    int     typeOfAction;
    int     enabled;
} example_config;

/*$1
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Prototypes
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */

static int    example_handler(request_rec *r);
const char    *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg);
const char    *example_set_path(cmd_parms *cmd, void *cfg, const char *arg);
const char    *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char *arg2);
void          *create_dir_conf(apr_pool_t *pool, char *context);
void          *merge_dir_conf(apr_pool_t *pool, void *BASE, void *ADD);
static void   register_hooks(apr_pool_t *pool);

/*$1
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Configuration directives
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */

static const command_rec    directives[] =
{
    AP_INIT_TAKE1("exampleEnabled", example_set_enabled, NULL, ACCESS_CONF, "Enable or disable mod_example"),
    AP_INIT_TAKE1("examplePath", example_set_path, NULL, ACCESS_CONF, "The path to whatever"),
    AP_INIT_TAKE2("exampleAction", example_set_action, NULL, ACCESS_CONF, "Special action value!"),
    { NULL }
};

/*$1
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Our name tag
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */

module AP_MODULE_DECLARE_DATA    example_module =
{
    STANDARD20_MODULE_STUFF,
    create_dir_conf,    /* Per-directory configuration handler */
    merge_dir_conf,     /* Merge handler for per-directory configurations */
    NULL,               /* Per-server configuration handler */
    NULL,               /* Merge handler for per-server configurations */
    directives,         /* Any directives we may have for httpd */
    register_hooks      /* Our hook registering function */
};

/*
 =======================================================================================================================
    Hook registration function
 =======================================================================================================================
 */
static void register_hooks(apr_pool_t *pool)
{
    ap_hook_handler(example_handler, NULL, NULL, APR_HOOK_LAST);
}

/*
 =======================================================================================================================
    Our example web service handler
 =======================================================================================================================
 */
static int example_handler(request_rec *r)
{
    if(!r->handler || strcmp(r->handler, "example-handler")) return(DECLINED);

    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    example_config    *config = (example_config *) ap_get_module_config(r->per_dir_config, &example_module);
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    ap_set_content_type(r, "text/plain");
    ap_rprintf(r, "Enabled: %u\n", config->enabled);
    ap_rprintf(r, "Path: %s\n", config->path);
    ap_rprintf(r, "TypeOfAction: %x\n", config->typeOfAction);
    ap_rprintf(r, "Context: %s\n", config->context);
    return OK;
}

/*
 =======================================================================================================================
    Handler for the "exampleEnabled" directive
 =======================================================================================================================
 */
const char *example_set_enabled(cmd_parms *cmd, void *cfg, const char *arg)
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    example_config    *conf = (example_config *) cfg;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    if(conf)
    {
        if(!strcasecmp(arg, "on"))
            conf->enabled = 1;
        else
            conf->enabled = 0;
    }

    return NULL;
}

/*
 =======================================================================================================================
    Handler for the "examplePath" directive
 =======================================================================================================================
 */
const char *example_set_path(cmd_parms *cmd, void *cfg, const char *arg)
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    example_config    *conf = (example_config *) cfg;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    if(conf)
    {
        strcpy(conf->path, arg);
    }

    return NULL;
}

/*
 =======================================================================================================================
    Handler for the "exampleAction" directive ;
    Let's pretend this one takes one argument (file or db), and a second (deny or allow), ;
    and we store it in a bit-wise manner.
 =======================================================================================================================
 */
const char *example_set_action(cmd_parms *cmd, void *cfg, const char *arg1, const char *arg2)
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    example_config    *conf = (example_config *) cfg;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    if(conf)
    {
        {
            if(!strcasecmp(arg1, "file"))
                conf->typeOfAction = 0x01;
            else
                conf->typeOfAction = 0x02;
            if(!strcasecmp(arg2, "deny"))
                conf->typeOfAction += 0x10;
            else
                conf->typeOfAction += 0x20;
        }
    }

    return NULL;
}

/*
 =======================================================================================================================
    Function for creating new configurations for per-directory contexts
 =======================================================================================================================
 */
void *create_dir_conf(apr_pool_t *pool, char *context)
{
    context = context ? context : "Newly created configuration";

    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    example_config    *cfg = apr_pcalloc(pool, sizeof(example_config));
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    if(cfg)
    {
        {
            /* Set some default values */
            strcpy(cfg->context, context);
            cfg->enabled = 0;
            memset(cfg->path, 0, 256);
            cfg->typeOfAction = 0x00;
        }
    }

    return cfg;
}

/*
 =======================================================================================================================
    Merging function for configurations
 =======================================================================================================================
 */
void *merge_dir_conf(apr_pool_t *pool, void *BASE, void *ADD)
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    example_config    *base = (example_config *) BASE;
    example_config    *add = (example_config *) ADD;
    example_config    *conf = (example_config *) create_dir_conf(pool, "Merged configuration");
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    conf->enabled = (add->enabled == 0) ? base->enabled : add->enabled;
    conf->typeOfAction = add->typeOfAction ? add->typeOfAction : base->typeOfAction;
    strcpy(conf->path, strlen(add->path) ? add->path : base->path);
    return conf;
}
POST form 데이터로부터 변수들을 받아들이기
typedef struct {
    const char* key;
    const char* value;
} keyValuePair;

keyValuePair* readPost(request_rec* r) {
    apr_array_header_t *pairs = NULL;
    apr_off_t len;
    apr_size_t size;
    int res;
    int i = 0;
    char *buffer;
    keyValuePair* kvp;

    res = ap_parse_form_data(r, NULL, &pairs, -1, HUGE_STRING_LEN);
    if (res != OK || !pairs) return NULL; /* Return NULL if we failed or if there are is no POST data */
    kvp = apr_pcalloc(r->pool, sizeof(keyValuePair) * (pairs->nelts + 1));
    while (pairs && !apr_is_empty_array(pairs)) {
        i++;
        ap_form_pair_t *pair = (ap_form_pair_t *) apr_array_pop(pairs);
        apr_brigade_length(pair->value, 1, &len);
        size = (apr_size_t) len;
        buffer = apr_palloc(r->pool, size + 1);
        apr_brigade_flatten(pair->value, buffer, &size);
        buffer[len] = 0;
        kvp[i]->key = apr_pstrdup(r->pool, pair->name);
        kvp[i]->value = buffer;
    }
    return kvp;    
}

static int example_handler(request_rec *r) 
{
    /*~~~~~~~~~~~~~~~~~~~~~~*/
    
    keyValuePair* formData;
    /*~~~~~~~~~~~~~~~~~~~~~~*/

    formData = readPost(r);
    if (formData) {
        int i;
        for (i = 0; formData[i]; i++) {
            ap_rprintf(r, "%s = %s\n", formData[i]->key, formData[i]->value);
        }
    }
    return OK;
}
HTTP 요청의 헤더를 모두 출력하기
static int example_handler(request_rec *r) 
{
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
    const apr_array_header_t    *fields;
    int                         i;
    apr_table_entry_t           *e = 0;
    /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

    fields = apr_table_elts(r->headers_in);
    e = (apr_table_entry_t *) fields->elts;
    for(i = 0; i < fields->nelts; i++) {
        ap_rprintf(r, "%s: %s\n", e[i].key, e[i].val);
    }
    return OK;
}
요청 바디를 메모리내에서 읽어들이기
static int util_read(request_rec *r, const char **rbuf, apr_off_t *size)
{
    /*~~~~~~~~*/
    int rc = OK;
    /*~~~~~~~~*/

    if((rc = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) {
        return(rc);
    }

    if(ap_should_client_block(r)) {

        /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
        char         argsbuffer[HUGE_STRING_LEN];
        apr_off_t    rsize, len_read, rpos = 0;
        apr_off_t length = r->remaining;
        /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

        *rbuf = (const char *) apr_pcalloc(r->pool, (apr_size_t) (length + 1));
        *size = length;
        while((len_read = ap_get_client_block(r, argsbuffer, sizeof(argsbuffer))) > 0) {
            if((rpos + len_read) > length) {
                rsize = length - rpos;
            }
            else {
                rsize = len_read;
            }

            memcpy((char *) *rbuf + rpos, argsbuffer, (size_t) rsize);
            rpos += rsize;
        }
    }
    return(rc);
}

static int example_handler(request_rec* r) 
{
    /*~~~~~~~~~~~~~~~~*/
    apr_off_t   size;
    const char  *buffer;
    /*~~~~~~~~~~~~~~~~*/

    if(util_read(r, &data, &size) == OK) {
        ap_rprintf(r, "We read a request body that was %u bytes long", size);
    }
    return OK;
}