Apache서버의 확장 기술

목차

1.

2. Apache Server Module

2.1 개요

2.2 Module

2.3 Request

2.4 Handler

2.4.1 Name Translation Handler

2.4.2 Header Parsing Handler

2.4.3 Auth Id cheching handler

2.4.4 Auth access checking handler

2.4.5 Access checking other than auth handler

2.4.6 Fixups handler

2.4.7 Response handler

2.4.8 Log handler

2.5 Resource

2.5.1 Resource관련 루틴

2.6 Configuration

2.6.1 Per-directory configuration structures

2.6.2 Command handling

3. 부록 - module_struct와 request_rec에 대한 설명

1. 서론

지금까지 WWW에서 서버의 확장을 위하여 사용되는 방식은 주로 CGI(Common Gateway Interface)였다. CGI는 사용이 편리하고 쉬운 반면에, 시스템 리소스를 많이 사용하여 효율을 저하시키며, 취약한 기능으로 인하여 구현이 불가능한 문제도 많다. Netscape server에서는 NSAPI를 제공하여 더 빠른 CGI를 작성할 수 있도록 하였고, 공개 프로그램인 Apache 서버에서도 Apache Server API를 도입하여 이를 해결하고 있다. Apache Server는 성능도 우수할 뿐만 아니라, 모든 소스가 공개되어 있으므로, 필요에 따라 마음대로 고쳐 사용할 수 있으므로 웹서버 확장에 있어서 우수한 platform이라고 생각된다.

Apache 서버는 핵심적인 구동부분을 제외하고는 모두 "모듈"을 사용하여 제작되었고, 모듈을 수정하거나 추가하여 새로운 기능을 쉽게 추가할 수 있다. cgi, XSSI, log, image map등의 기능이 모두 모듈로 작성되어 있으며, 원하면 사용자가 추가할 수도 있다.

하지만 cgi에 비하여 개발자가 숙지하여야 할 내용이 너무 많아서, 실제로 모듈을 작성하기 위해서는 Apache 서버의 구조를 전반적으로 이해하여야 하므로, 초보자들을 위한 개발 환경은 아니며, 전문적인 개발자에게 적합하다.

여기서는 아래의 사항을 조금이나마 이해하고 있다는 가정하에서 설명하겠다.

□ C programming language

□ Hyper Text Transfer Protocol

□ Apache administration

여기에서 설명된 내용은 "http://www.apache.org/docs/misc/API.html"을 토대로 만들어졌으며, 더 자세한 자료는 나와있지 않으므로, 소스 코드를 분석하여 얻었다.

Apache 서버는 97년 5월 현재 버전 1.2 beta10까지 나와 있으며, 여기서에서는 버전 1.2 beta8을 기준으로 설명하였다. "http://www.apache.org/docs/misc/API.html"는 최근 버전과는 맞지 않는 부분도 있으므로 주의하기 바란다.

2. Apache Server Module

2.1 개요

Apache서버의 전체적인 흐름은 아래의 그림과 같이 이해할 수 있다.

Apache서버는 클라이언트로부터 하나의 request를 받아, 파생하여 request_rec 타입의 스트럭쳐를 만들고, 각각의 모듈에게 이를 처리하도록 한다. 각각의 모듈들은 서버의 여러 수행단계에 대한 handler를 가질 수 있으며, Apache서버는 각 모듈에 대하여 handler를 수행하여 클라이언트의 request를 처리한다. 우리는 Apache서버에 새로운 모듈을 추가함으로써, 새로운 기능을 추가할 수 있다.

Apache서버는 httpd.conf, srm.conf, access.conf의 3가지 configuration파일을 읽어 초기화하며, 각 디렉토리별로 .htaccess파일을 두어 디렉토리마다 설정을 변경할 수 있다. httpd.conf, srm.conf, access.conf에서 설정되는 내용은 전체 서버의 configuration이므로, per-server configuration이라고 하고, .htaccess에서 설정되는 내용과 지시자에서 설정된 내용은 디렉토리에 대한 개별 설정이므로, per-directory configuration이라고 한다.

각각의 모듈은 configuration 정보중, 자신이 필요로 하는 configuration을 읽어서 처리하여야 한다. 예를 들면, mime type을 관리하는 mime모듈은 configuraion중에서 "AddType", "AddHandler", "AddEncoding"등의 command를 처리하여야 하며, XSSI를 지원하는 include 모듈은 "XBitHack" command를 처리하여야 한다.

2.2 Module

Apache서버는 Netscape server API가 하는 것과 비슷하게, HTTP를 통하여 클라이언트에서 보내진 request를 아래와 같은 여러 단계로 나누어 처리한다. 모든 모듈은 각 단계를 처리하기 위한 handler를 필요에 따라 포함할 수 있다.

순서	handler	설명	Run All
단계 1	URI-filename translation	URI를 서버상의 로컬파일이름으로 변환한다.	No
단계 2	Header Parse	Apache에서 아직 document되어있지 않은 부분으로, 최근에 추가된 것 같다. HTTP request header내의 정보를 파싱하는데 사용하기 위한 것 같다. 실제 1.2b8에서는 browser 모듈에서 사용되며, HTTP request header내의 User-Agent필드를 파싱한다.	Yes
단계 3	Auth Id checking	유저의 id를 확인한다.	No
단계 4	Auth access checking	URI에 대한 유저의 사용권한을 확인한다.	No
단계 5	Access checking other than auth	Auth 이외의 접근권한을 확인한다.	Yes
단계 6	Determining MIME type	요청한 객체의 MIME 타입을 확인한다.	No
단계 7	Fixups	이 단계는 다른 어떤 단계에도 적합하지 않은 것들을 위한 단계로, SetEnv같은 hook를 위하여 사용한다.	Yes
단계 8	Response	실제로 클라이언트에게 응답을 보낸다	No
단계 9	log	request에 대하여 로그를 남긴다	Yes

위의 여러 단계들은 각각의 module들에 의하여 처리되는데, Apache 서버는 단순히 각각의 모듈이 각 단계를 처리할 handler를 가지고 있는지 확인하고, handler가 있다면 그 handler를 수행시킨다. handler에 대한 자세한 설명은 다음절에서 하겠다.

모듈은 C++의 생성자와 비슷하게 자신을 초기화시키기 위한 루틴을 가질 수 있다. mime 모듈에서는 초기화 시에 mime.types파일을 읽는 루틴이 있으며, log를 위한 모듈에서는 이때 로그파일을 오픈하는 루틴이 들어있다.

위의 초기화와는 별도로 정해진 configuration(httpd.conf, access.conf, srm.conf, .htaccess)파일을 읽어서 처리하는 부분은 모듈내에 따로 마련되어 있으며, 위의 초기화 루틴에서는 그 외의 초기화를 관리하면 된다.

대부분의 초기화가 configuration을 읽음으로 가능해 지므로, 실제로 Apache서버에서 제공하는 기본 모듈에는 초기화를 사용하는 module은 많지 않다.

하나의 모듈이 어떻게 구성되어 있는지에 관한 예제를 아래에 들었다. cgi 모듈(mod_cgi.c)을 예로 들은 것인데, 1.2b8버전 이전의 예제인 것 같으며, 현 버전에서는 "ScriptAlias" command를 처리하기 위한 부분이 alias 모듈(mod_alias.c)로 넘어갔다. 하지만 모듈을 이해하는 데에는 아무런 문제가 없으므로 예제로 선택했다.

/* handler의 선언 */

int translate_scriptalias (request_rec *);

int type_scriptalias (request_rec *);

int cgi_handler (request_rec *);

/* mime 타입에 의한 response 단계의 handler의 정의

* mime타입이 "application/x-httpd-cgi"일 경우 response단계 handler인

* cgi_handler()가 호출된다.

* 마지막에는 NULL을 넣어 더이상 handler가 없음을 명시한다.

*/

handler_rec cgi_handlers[] = {

{ "application/x-httpd-cgi", cgi_handler },

{ NULL }

};

/* 모듈의 configuration 정보를 관리하기 위한 루틴을 정의한다.

* 주의할 점은 실제로 어떤 타입으로 설정했던지 간에, void * 타입을

* 넘겨준다. 서버는 이 포인터를 계속 유지하지만, 실제 어떻게

* 구현되었는지는 알 방법이 없다.

*/

void *make_cgi_server_config (pool *);

void *merge_cgi_server_config (pool *, void *, void *);

/* configuration파일의 "ScriptAlias" command를 처리하기 위한 함수를 선언 */

extern char *script_alias(cmd_parms *, void *per_dir_config, char *fake,

char *real);

/* 모듈이 처리할 수 있는 configuration파일의 command의 list */

command_rec cgi_cmds[] = {

{ "ScriptAlias", script_alias, NULL, RSRC_CONF, TAKE2,

"a fakename and a realname"},

{ NULL }

};

module cgi_module = {

STANDARD_MODULE_STUFF,

NULL,                     /* 모듈의 초기화 루틴 */

NULL,                     /* per-dir. config.에 대한 자료구조를 만든다 */

NULL,                     /* 현 디렉토리와 상위 디렉토리의 config를 합친다 */

/* 디폴트는 override로 상위 디렉토리에서 */

/* 설정된 정보를 무시한다. */

make_cgi_server_config,   /* per-server config.에 대한 자료구조를 만든다 */

merge_cgi_server_config,  /* merge server config */

cgi_cmds,                 /* configuration파일에서 인식하는 command 테이블 */

cgi_handlers,             /* Response 단계의 handler의 테이블 */

translate_scriptalias,    /* URI filename translation 단계의 handler */

NULL,                     /* Auth id checking단계의 handler */

NULL,                     /* Auth access checking단계의 handler */

NULL,                     /* Access checking other than auth단계의 handler */

type_scriptalias,         /* Determining MIME type단계의 handler */

NULL,                     /* Fixups단계의 handler*/

NULL,                     /* Log단계의 handler */

NULL                      /* Header parse단계의 handler */

};

2.3 Request

클라이언트로부터 들어온 request는 파싱되어 request_rec타입은 스트럭처에 저장된다. Apache서버는 request를 처리하기 위하여 handler를 실행하는데, 각 handler는 이 request_rec 스트럭쳐의 포인터를 인수로 받아서 request를 확인하여 작업을 수행하게 되며, 이 스트럭쳐의 특정 필드를 기입하거나 수정할 수도 있다. 예를 들면, URI-filename translation 단계의 handler는 request_rec 스트럭처의 uri필드를 읽어 filename필드에 기입하는 역할을 한다.

대부분의 request_rec 스트럭처는 클라이언트로부터의 HTTP request에 의해 결정이 되나 몇 가지 예외가 있다.

request가 imagemap이나 type map, 또는 CGI 스크립트인 경우.

이 경우 실제로 요청된 URI에 대한 오브젝트가 아닌 다른 오브젝트를 전달하여야 한다. 이런 경우 서버는 internal redirect방식를 사용하는데, 이는 새로운 URI에 대한 request_rec 스트럭처를 만들고, 이것이 마치 클라이언트로부터 온 request인 것처럼 수행하게 한다.

어떤 handler가 에러를 발생할 경우.

ErrorDocument를 요구하게 되는데, 역시 internal redirect방식을 사용한다.

디렉토리를 인덱싱하는 모듈과 같은 상황.

이 경우에서는 아이콘을 지정하기 위하여 각각의 디렉토리의 요소에 대하여, 어떤 mime 타입을 가지는지 알 필요가 있는데, 이와 같이 handler가 "다른 어떤 request가 들어왔을 경우 어떻게 동작할까?"라는 질문에 대한 대답을 원하는 경우가 있다.

이런 handler는 sub_req_lookup_file()이나 sub_req_lookup_uri()를 사용하여 sub-request를 만들 수 있고, 이 함수들은 새로운 request_rec 스트럭쳐를 만들며, 클라이언트로 부터 날아온 request와 동일하게 처리된다. 다만, response를 실제로 전송하지는 않는다. Apache서버의 모듈을 작성하기 위해서는 request_rec 스트럭처를 이해하는 것이 상당히 중요한데, 이는 마지막의 부록에서 설명한다.

2.4 Handler

handler는 일반적으로 아래 3가지 중 한가지 일을 수행하면 된다.

request를 처리하고, 요구가 처리되었음을 OK를 리턴하여 알린다.

request를 거절하고 요구가 거절되었음을 DECLINED를 리턴하여 알린다. 즉, handler 자신이 처리할 내용이 없는 request인 경우이며, 이 경우에 서버는 handler가 없었던 것과 동일하게 처리한다.

에러를 내고 HTTP의 에러코드를 리턴한다. 이는 request의 일반적인 처리를 종료한다. 그렇지만, 에러도큐먼트는 불려지며, 로그도 남긴다.

각 단계마다 handler가 처리한 결과에 대하여 수행하는 방식이 다르다. 대부분의 단계에서는 request를 처리하는 첫번째 module에서 그 단계의 수행을 마치지만, 로그나, Fixups, non-access authentication에서는 모든 handler가 수행된다.

response 단계에서는 한 모듈이 여러개의 handler를 포함할 수 있으며, 이는 MIME type table을 사용하여 어느 handler를 사용할 것인지 결정한다. 모듈은 MIME type 스펙에 */*를 줌으로써 어떤 request도 처리할 수 있도록 할 수 있다.

지금부터는 각각의 handler가 해야할 일이 어떤 것인지 알아보도록 하자.

2.4.1 URI-filename translation handler

URI-filename translation handler는 말 그대로, 클라이언트가 요청한 URI에 대하여 실제 서버상의 filename을 지정해 주는 역할을 한다. Handler는 request_rec의 uri필드를 읽어서, filename필드에 기록하면 된다. Apache서버의 userdir 모듈(mod_userdir.c)에서는 uri가 "/~"으로 시작하는 uri에 대하여 filename을 유저의 홈 디렉토리밑의 파일로 지정해 준다.

2.4.2 Header parse handler

생략

2.4.3 Auth Id checking handler

User의 id와 그에 해당하는 password를 확인하는 handler이다. id나 password가 올바르지 않다면 에러코드를 리턴하고, 그렇지 않으면 OK를 리턴한다. 주의할 점은, 클라이언트가 요구한 오브젝트가 들어있는 디렉토리에 대하여 권한설정(auth)이 되어있지 않다면 서버는 handler를 호출하지 않는다. 일반적인 권한에 관한 configuration은 core 모듈(http_core.c)의 per-directory configuration에 포함되어 있으며, 이 중에서 auth_type, auth_name, requires 필드를 사용하여 읽을 수 있다.

이때 자주 사용되는 루틴은 아래와 같다.

get_basic_auth_pw() call

request_rec 스트럭처에서 password를 추출한다.

note_basic_auth_failure() call

권한확인에 실패했을 경우 호출한다. 이 함수는 response에 "WWW-Authenticate" 헤더를 알아서 넣어준다.

2.4.4 Auth access checking handler

현재 사용하는 유저가 요구한 오브젝트에 관하여 사용권한이 있는지 확인한다. 만약 사용 권한이 없다면 에러코드를 리턴하고, 사용권한이 있다면 OK를 리턴한다. 현재 사용유저는 request_rec의 connection필드의 user필드(char *user = r->connection->user)를 참조할 수 있고, 요청된 파일은 request_rec의 filename을 참조하면 된다. Auth Id checking handler와 마찬가지로, 권한설정이 되어있지 않은 디렉토리내의 오브젝트를 요하는 request에 대해서는 호출되지 않는다.

2.4.5 Access checking other than auth handler

Authentication이외의 접근확인을 실시한다. Apache 서버에서는 access 모듈(mod_access.c)에서 사용하고 있으며, 이 모듈은 host나 User-Agent에 의한 접근 제어를 하고있다. 마찬가지로 접근확인에 성공하면 OK를 실패하면 에러코드를 리턴하면 된다.

2.4.6 Fixups handler

다른 단계들에 적합하지 않은 handling을 위하여 사용하도록 만들어 졌다.

2.4.7 Response handler

다른 대부분의 단계의 handler들은 단지 request_rec 스트럭처에 몇가지 필드를 수정하는 것이 대부분이다. 그렇지만, Response handler는 실제로 클라이언트에게 request를 넘기는 역할까지 담당한다.

Response handler는 send_http_header() call을 사용하여 HTTP response header를 클라이언트로 직접 전송한다. HTTP/0.9버젼에서는 response header를 보내지 않고, status-line만을 보내야 하는데, send_http_header() call에서 알아서 처리하므로 신경쓸 필요가 없다.

request_rec에 header_only 필드가 1일 경우 body를 전송하지 않아야 0일 경우 직접 HTTP body를 전송하여야 한다. 이를 위해서 rputc(), rprintf(), rwrite(), send_fd()등의 함수가 준비되어 있다.

rputc()

클라이언트로 한 바이트의 문자를 전송한다.

rprintf()

printf()와 사용법이 같으며 서식에 지정된 문자열을 클라이언트로 전송한다.

rwrite()

write() system call과 비슷하며 클라이언트로 지정된 길이의 데이터를 전송한다.

send_fd()

FILE 스트럭쳐의 포인터에 해당하는 파일을 클라이언트로 통째로 보낸다.

아래의 예는 response handler에 관한 default handler로, 다른 response handler가 지정되어 있지 않으면, 아래의 handler를 사용하여 클라이언트에게 response를 보낸다. 아래의 default_handler()는 Apache 1.2b8보다 이전 버젼으로 생각되나 현 버전의 default_handler()보다 간단하고 이해가 쉬우므로 같아서 예제로 들었으며, 주석을 달아 두었으므로 자세한 설명은 하지 않겠다.

int default_handler (request_rec *r)

{

int errstatus;

FILE *f;

/* 여기서는 GET method만을 처리한다

*/

if (r->method_number != M_GET) return DECLINED;

/* r->finfo.st_mode는 r->filename이 가리키는 파일의 모드를

* 저장하는데, 0 일경우 파일이 존재하지 않는 경우이다.

*/

if (r->finfo.st_mode == 0) return NOT_FOUND;

/* response header의 "Content-Length"와 "Last-Modified"필드를

* 설정한다. 만약 set_content_length()나 set_last_modified()의

* 리턴값이 0이 아닐 경우 이 코드 값을 전송한다.

*/

if ((errstatus = set_content_length (r, r->finfo.st_size))

|| (errstatus = set_last_modified (r, r->finfo.st_mtime)))

return errstatus;

/* 실제로 클라이언트에게 전송할 파일을 연다.

*/

f = pfopen (r->filename, "r");

/* 파일 열기에 실패했을 경우는 FORBIDDEN을 리턴한다.

*/

if (f == NULL) {

log_reason("file permissions deny server access",

r->filename, r);

return FORBIDDEN;

}

/* timeout을 설정한다. 네트웍의 문제로 인하여, 장시간동안 블럭킹되는

* 것을 방지한다. soft_timeout()과 hard_timeout(), keepalive_timeout()

* 있다. "send"는 timeout의 이름이다.

*/

soft_timeout ("send", r);

/* http header를 클라이언트로 보낸다.

*/

send_http_header (r);

/* header_only가 세팅되어 있지 않은 경우에 한해서만 HTTP body를 보낸다.

* body는 파일포인터 f가 가리키는 파일 전체이다.

*/

if (!r->header_only) send_fd (f, r);

/* 전송이 끝이 났으므로, 파일을 닫는다.

*/

pfclose (r->pool, f);

return OK;

}

2.4.8 Log handler

만약 request가 internal redirect되었다면, 어떤 request를 로그로 남겨야 할 지에 대한 문제가 생긴다. Apache 서버에서는 internal redirect를 사용한 request_rec 스트럭처를 r->prev와 r->next를 통하여 리스트로 관리한다. log handler는 이 리스트의 맨 앞의 request를 로그로 남기면 된다.

2.5 Resource

Web서버와 같이 많은 request를 처리하는 서버에서는 메모리 유출(memory leak)이 문제가 되며, 각 module을 메모리 유출을 막도록 제작하는 것은 상당한 노력을 필요로 한다. Apache서버에서는 이 문제를 해결하기 위하여 resource pool을 사용한다. 모든 resource를 지정된 resource pool에 등록하여 사용하면, Apache서버는 resource가 더 이상 필요 없을 때, 자동적으로 resource pool에 등록된 resource를 OS로 반환하므로 모듈 프로그래머는 메모리 유출에 신경을 쓰지 않아도 된다.

서버가 시작될 때 할당받는 메모리나 per-server configuration들 역시 이와 비슷하게 configuration pool을 사용하여 할당한다. 이 경우, 서버가 종료될 때 configuration pool을 사용하는 모든 리소스는 반환된다.

반드시 resource pool을 사용하여야 하는 것은 아니다. log handler의 경우 서버가 종료될 때, log파일을 닫아야 하는데 서버가 종료됨을 알 수 없으므로, log파일을 닫을 수 없다. 하지만 resource pool을 사용하면 서버가 종료시 자동으로 파일을 닫아 주므로 resource pool을 사용하는 것이 유리하다.

2.5.1 Resource관련 루틴

메모리 관련 루틴

void *palloc (struct pool *a, int reqsize)

reqsize만큼의 메모리영역을 할당받아, pool a에 등록하고 그 포인터를 리턴한다.

void *pcalloc(struct pool *a, int size)

size만큼의 메모리영역을 할당받아, pool a에 등록하고 그 포인터를 리턴한다. palloc()와 다른점은 할당받은 메모리를 0으로 초기화한다는 점이다.

스트링 관련 루틴

char *pstrdup(struct pool *a, const char *s)

스트링 s를 새로 할당받은 영역에 복사하고, pool a에 등록한다. 리턴값은 할당받은 스트링의 포인터가 된다.

char *pstrndup(struct pool *a, const char *s, int n)

strdup()와 비슷하나, 스트링의 길이가 n으로 제한된다.

char *pstrcat(pool *a, ...)

나열된 스트링을 모두 붙여서(concatenation), 할당받은 영역에 복사하고, pool a에 등록한다. 스트링의 마지막은 반드시 NULL 포인터가 있어야 한다.

(ex, char *new_string = pstrcat( r->pool, "WWW", "-", "KR", NULL );

파일 관련 루틴

FILE *pfopen(pool *a, const char *name, const char *mode)

pool a를 사용하여 name의 이름을 가진 파일을 mode의 모드로 연다. fopen()에서 첫번째 인수가 추가되었다.

(ex, FILE *f = pfopen( r->pool, "foo.html", "r" ); )

int popenf(pool *a, const char *name, int flg, int mode)

pool a를 사용하면서, open() system call과 같은 역활을 수행한다.

int pfclose(pool *a, FILE *fd)

pfopen()으로 연 파일을 닫는다.

int pclosef(pool *a, int fd)

popenf()으로 연 파일을 닫는다.

테이블 관련 루틴

table *make_table (pool *p, int nelts)

pool p를 통하여, nelts개의 원소를 가지는 테이블을 만든다.

table *copy_table (pool *p, const table *t)

pool p를 통하여, 테이블 t를 복사한 테이블을 만든다.

메모리나 스트링 관련 루틴과는 다르게 파일 관련 루틴은 리소스 반환을 위한 루틴이 마련되어 있다. 대부분의 시스템에서 동시에 열 수 있는 파일의 갯수가 제한이 되어있기 때문이다. 물론, 닫혀지지 않은 파일들은 나중에 자동으로 반환이 되므로, 너무 많은 파일을 열지 않은 경우라면 구태여 일일이 닫아주지 않아도 상관없다. 리눅스와 같은 시스템에서는 닫힌 파일을 다시 닫게되면 심각한 에러를 발생시킬 수 있으므로, pfopen()이나 popenf()를 사용하는 것을 권한다.

때때로 상당히 많은 메모리를 필요로 하는 작업이 있을 수 있으며, 이런 경우 한꺼번에 너무 많은 palloc()을 사용하게 된다. 이런 경우 실행중에 할당받은 메모리를 반환하고 싶은 경우가 생기는데, sub-pool을 사용하여 해결한다.

struct pool *make_sub_pool (struct pool *p)

pool p에 대하여 sub-pool을 만드는 함수이다.

void clear_pool (struct pool *a)

sub-pool인 a를 초기화한다. sub-pool a에 등록된 resource는 모두 반환된다.

void destory_pool (struct pool *a)

sub-pool인 a를 삭제한다.

main pool이 초기화되면 sub-pool은 자동적으로 삭제된다. sub-pool은 언제든지 clear_pool()이나 destory_pool()을 사용하여 초기화하거나 삭제할 수 있다.

2.6 Configuration

Apache서버의 설계목표중 하나는 NCSA 1.3서버와 호환성을 유지하는 것이다. 즉, NCSA 1.3 서버에서 사용된 것과 같은 configuration 파일을 사용하도록 하는 것이다. 다른 하나의 설계목표는 일관성이 보장되는 한 서버의 기능을 가능한 한 모듈로 넘기도록 하는 것이다. 이렇게 하기 위해서는 configuration 파일내의 command(directive라고도 불림)를 모듈에서 처리할 수 있어야 한다.

그러나 각 모듈에 command table을 추가하는 것으로는 이 문제를 완전히 해결할 수는 없다. 서버는 나중에 command table의 조작을 위해서 각 command들을 기억하고 있어야 한다. 즉, module에 관련된 private 데이터를 유지할 수 있어야 한다. 이 configuration 데이터는 특정 디렉토리에만 관련된 것일 수도 있고, 서버 전체에 관련된 것일 수도 있다. 하지만, 접근제어, 권한설정등과 같은 대부분의 configuration은 디렉토리단위로 설정이 가능하다. 이에 대하여 디렉토리단위로 설정할 수 있는 것들은 모두 디렉토리 단위로 configuration을 지정할 수 있어야 한다는 것이다.

또 다른 요구사항은 NCSA에서 사용되었던 per-directory configuration인 .htaccess파일을 사용할 수 있어야 한다는 것이다. NCSA 서버에서는 .htaccess파일에 접근제어 이외의 지시어(directive)도 사용이 가능했다. 즉, URI filename translation 단계 이후에, 다른 단계를 수행하기 전에, 서버는 파일시스템내의 .htaccess에 들어있는 configuration을 읽어 현재 서버가 가지고 있는 configuration파일과 합쳐야 한다.

.htaccess를 사용한 request를 처리한 후에는 이때 사용되었던 기억장소를 반환해야한다. 이는 위에서 해결했던 것과 비슷하게 per-transaction resource pool을 사용하여 해결한다.

2.6.1 Per-directory configuration structures

per-directory configuration을 사용하는 예제로 mime 모듈(mod_mime.c)을 들겠다. mime모듈이 하는 일 중 하나는 "AddType"와 "AddEncoding" command를 구현하는 일이다. 이 command는 httpd.conf등의 configuration파일에도, .htaccess파일 내에서도 존재할 수 있다.

이 두가지 command에 해당하는 정보를 저장하기 위해서는 2개의 table이 필요한데, 하나는 AddType에서 지정된 mime타입을 저장하는 테이블이고, 하나는 encoding방식을 저장하는 table이다. 이를 위한 pre-directory configuration의 자료구조는 아래와 같이 만들 수 있다.

typedef struct {

table *forced_types;     /* Additional AddTyped stuff */

table *encoding_types;   /* Added with AddEncoding... */

} mime_dir_config;

서버가 mime모듈의 command가 포함된 configuration파일을 읽거나 섹션을 읽을 때, 서버는 mime_dir_config 스트럭쳐를 만들 필요가 있고, 서버는 모듈내의 create_dir_config() 함수 포인터가 가리키는 함수를 2개의 인수를 가지고 수행한다. 인수중 하나는 resource pool에 대한 포인터이며, 하나는 디렉토리이름을 가리키는 스트링이다. 이때 만약 .htaccess파일을 읽었을 경우에는 resource pool은 per-request resource pool이 사용된다.

mime모듈에서 create_dir_config()는 아래와 같이 단순히 palloc()와 make_table()을 사용하여 자료구조를 만든다.

void *create_mime_dir_config() (pool *p, char *dummy)

{

mime_dir_config *new =

(mime_dir_config *) palloc (p, sizeof(mime_dir_config));

new->forced_types = make_table(p, 4);

new->encoding_types = make_table(p, 4);

return new;

}

우리가 .htaccess파일을 읽었다고 하자. 디렉토리 구조상 현재 디렉토리보다 상위의 디렉토리에 있는 per-directory configuration을 가지고 있을 것이다. 만약 상위의 .htaccess 파일이 "AddType"이나 "AddEncoding" command를 포함하지 않았을 경우에는 문제가 없지만, 포함했을 경우에는 지금 읽은 configuration과 상위 디렉토리에 해당하는 configuration을 합쳐야한다.

이를 위해서 서버는 module에 있는 merge_dir_config() 함수 포인터가 가리키는 함수를 수행한다. mime타입에 대한 예제는 아래와 같다.

void *merge_mime_dir_configs (pool *p, void *parent_dirv, void *subdirv)

{

mime_dir_config *parent_dir = (mime_dir_config *)parent_dirv;

mime_dir_config *subdir = (mime_dir_config *)subdirv;

mime_dir_config *new =

(mime_dir_config *) palloc (p, sizeof(mime_dir_config));

new->forced_types = overlay_tables (p, subdir->forced_types,

parent_dir->forced_types);

new->encoding_types = overlay_tables (p, subdir->encoding_types,

parent_dir->encoding_types);

return new;

}

merge_mime_dir_configs()의 두번째 인수인 parent_dirv는 기존의 mime_dir_config 스트럭처를 가리키는 포인터이며, 두번째 인수인 subdirv는 합칠 새로운 스트럭처를 가리키는 포인터이다. 여기서는 새로운 mime_dir_config 스트럭처를 만들고, 기존의 두 table을 overlay_tables()라는 함수를 사용하여 합쳐 새로 만든 스트럭처에 넣고있다.

per-directory merge 함수가 필요없는 경우도 있다. 즉, 상위 디렉토리의 정보를 무시하여도 되는 경우인데, 이런 경우에는 별도로 함수를 만들지 않고, 그냥 NULL을 적어주면 된다.

per-server configuration에 관하여 create_server_config()와 merge_server_config()의 함수 포인터가 있는데, 사용방법은 per-directory와 동일하므로 생략한다.

2.6.2 Command handling

이제 마지막으로 configuration파일내의 command를 모듈이 어떻게 처리하는가에 대하여 알아보자. 스트럭처는 위에서 이미 만들어 졌으므로, "AddType"와 "AddEncoding" command를 읽을 때, 이를 파싱하고 위에서 만든 스트럭처에 저장하는 루틴을 작성하여야 한다. 원하는 command를 지정하기 위하여 module은 command 테이블을 가지며, 서버는 command 테이블에 등록된 command를 configuration파일에서 발견하면 그에 해당하는 handler를 command 테이블에서 찾아 호출한다.

command 테이블은 command의 이름뿐만 아니라, handling function, 인수의 갯수, 형식(format)및 command가 사용될 수 있는 범위까지 지정할 수 있다.

"AddType" command에 대한 handler를 예로 든다.

char *add_type(cmd_parms *cmd, mime_dir_config *m, char *ct, char *ext)

{

if (*ext == '.') ++ext;

table_set (m->forced_types, ext, ct);

return NULL;

}

위의 command handler는 상당히 간단하다. 4개의 인수를 넘겨받는데, 첫번째 인수는 cmd_parms 스트럭처의 포인터이다. cmd_parms 스트럭처는 resource pool등을 포함한 command handler에서 자주 사용되는 자료구조를 모아놓은 스트럭처이다. 하지만, 사용할 수 있는 모든 정보를 포함하지는 않는다. 두번째 인수는 per-directory configuration 스트럭처의 포인터이며, 세번째 인수와 네번째 인수는 "AddType" command에서 읽은 인수이다.

만약 command handler를 수행중에 에러가 발행할 경우 NULL이외의 값을 리턴하면 된다. 만약 main config파일을 읽다가 에러가 발생했을 경우 서버는 stderr로 에러메시지를 출력한 후 종료하며, .htaccess파일을 읽다가 에러가 발생되었을 경우 HTTP error(code 500)을 발생시킨다.

mime 모듈의 command 테이블은 아래와 같다.

command_rec mime_cmds[] = {

{ "AddType", add_type, NULL, OR_FILEINFO, TAKE2,

"a mime type followed by a file extension" },

{ "AddEncoding", add_encoding, NULL, OR_FILEINFO, TAKE2,

"an encoding (e.g., gzip), followed by a file extension" },

{ NULL }

};

각각의 필드에 대한 설명은 아래와 같다.

□ 첫번째 필드 ; char *name

command의 이름이다.

□ 두번째 필드 ; const char *(*func)()

command를 처리할 handler의 함수 포인터

□ 세번째 필드 ; void *cmd_data

cmd_parms 스트럭처로 전달할 void *형의 포인터. 이것은 여러 비슷한 command들을 하나의 함수를 사용하여 처리하고자 할 때 편리하다. cmd_parms의 info필드에 저장된다.

□ 네번째 필드 ; req_override

command를 사용할 수 있는 위치를 지정하는 bit mask. RSRC_CONF는 .htaccess가 아닌 main configuration에만 위치할 수 있음을 가리킨다.

□ 다섯번째 필드 ; enum cmd_how args_how

몇개의 argument를 사용하려고 하는지 지정한다. TAKE1은 1개의 인수가 있음을 나타내고, TAKE2는 2개의 인수가 있음을 나타낸다. FLAG는 인수가 On이나 Off밖에 될 수 없음을 나타내고, RAW_ARGS는 server가 인수를 파싱하지 않음을 이야기한다.(command name도 파싱하지 않는다.) ITERATE는 여러개의 인수를 갖고, handler를 그 수만큼 하나의 인수를 가지고 호출함을 의미하고, ITERATE2는 여러개의 인수를 갖는데, handler를 인수의 수보다 하나 적은 갯수만큼, 두개의 인수를 가지고 호출한다. 호출시 첫번째 인수는 항상 맨 앞의 인수로 고정된다.

□ 여섯번째 필드 ; char *errmsg

command의 인수로 사용될 수 있는 것들을 설명하는 스트링을 지정한다. 이는 자세한 에러 메시지를 출력하기 위하여 사용된다.

이제 configuration 정보는 모두 setting하였고, 남은 일은 Determining MIME type handler를 작성하는 일이다. 예제는 아래와 같다. get_module_config()를 사용하면 자기 모듈의 configuration을 얻을 수 있다.

int find_ct(request_rec *r)

{

int i;

char *fn = pstrdup (r->pool, r->filename);

mime_dir_config *conf = (mime_dir_config *)

get_module_config(r->per_dir_config, &mime_module);

char *type;

if (S_ISDIR(r->finfo.st_mode)) {

r->content_type = DIR_MAGIC_TYPE;

return OK;

}

if((i=rind(fn,'.')) < 0) return DECLINED;

++i;

if ((type = table_get (conf->encoding_types, &fn[i]))) {

r->content_encoding = type;

/* go back to previous extension to try to use it as a type */

fn[i-1] = '\0';

if((i=rind(fn,'.')) < 0) return OK;

++i;

}

if ((type = table_get (conf->forced_types, &fn[i]))) {

r->content_type = type;

}

return OK;

}

여기에서는 per-directory configuration만을 사용하여 mime 모듈을 구성하였는데, per-server configuration을 사용할 경우 r -> per_dir_config 대신에 r -> server -> module_config를 사용하 면 된다. 만약, 지금 처리하는 command가 per-server인지 per-directory인지 알기를 원한다면, cmd_parms의 path가 NULL인지 확인하면 된다. path필드는 per-directory configuration이면, 해당 directory의 path로 설정된다. 그러므로, path가 NULL이면 per-server의 경우이고 반대는 per-directory의 경우이다.

3. 부록 - module_struct와 request_rec에 대한 설명

module_struct

module_struct 스트럭쳐 멤버	설명
int version; int module_index; const char *name; struct module_struct *next;	API version과 그 외의 내부적인 사항으로, STANDARD_MODULE_STUFF로 정의되어 있다.
void (*init)(server_rec *, pool *);	서버를 초기화 하는 함수를 등록한다.
void *(*create_dir_config)(pool *p, char *dir);	모듈의 per-directory configuration을 저장할 자료 구조를 만들고, 그 포인터를 void * 타입으로 반환하는 함수를 등록한다.
void *(*merge_dir_config)(pool *p, void *base_conf, void *new_conf);	모듈의 per-directory configuration을 합치는 함수 를 등록한다. .htaccess의 configuration 정보는 현 디렉토리뿐 아니라 상위 디렉토리의 configuration 정보도 포함하는데 이 정보를 합 치는 일을 하는 루틴이다.
void *(*create_server_config)(pool *p, server_rec *s);	모듈의 per-server configuration정보를 저장할 자 료구조를 만들고, 그 포인터를 void * 타입으로 반환하는 함수를 등록한다.
void *(*merge_server_config)(pool *p, void *base_conf, void *new_conf);	모듈의 per-server configuration정보를 합치는 함 수를 등록한다. 보통의 경우는 필요 없으나, virtual host를 사용하는 경우 필요하다.
command_rec *cmds;	command 레코드를 등록한다
handler_rec *handlers;	response handler 레코드를 등록한다
int (*translate_handler)(request_rec *);	URI-filename translation 단계의 handler를 등록 한다.
int (*check_user_id)(request_rec *);	Auth Id checking 단계의 handler를 등록한다.
int (*auth_checker)(request_rec *);	Auth access checking 단계의 handler를 등록한 다.
int (*access_checker)(request_rec *);	Access checking other than auth 단계의 handler 를 등록한다.
int (*type_checker)(request_rec *);	Determining MIME type 단계의 handler를 등록 한다.
int (*fixer_upper)(request_rec *);	Fixups 단계의 handler를 등록한다.
int (*logger)(request_rec *);	log 단계의 handler를 등록한다.
int (*header_parser)(request_rec *);	header parse단계의 handler를 등록한다.

request_re

request_rec 스트럭처 멤버	설명
pool *pool;	request의 처리를 위하여 사용될 resource pool의 포인터
conn_rec *connection;	클라이언트와의 연결상태에 관한 스트럭처에 대한 포인터. connection->user에는 접속한 user의 id가 저장되어 있다. 그 외 여러 connection에 관한 field들이 있다. 더 자세한 내용은 httpd.h를 참조하기 바란다.
server_rec *server;	전체 서버내의 총괄적인 자료구조이다. 이 중 module_config 필드는 각 모듈들의 per-server configuration들의 포인터를 저장하는 array이며, 그 외에는 모듈 작성시 신경쓸 필요가 없을 것 같다.
request_rec *next;	계속 internal redirect를 통하여 request가 이루어 질때, 자신의 sub-request에 해당하는 request_rec의 포인터.
request_rec *prev;	계속 internal redirect를 통하여 request가 이루어 질때, 자신의 바로 전의 request에 해당하는 request_rec의 포인터.
request_rec *main;	만약 sub_request일 경우, 클라이언트로 부터 요청된 실제 request에 해당하는 request_rec의 포인터.
char *the_request;	요청된 request (ex, GET /index.html HTTP/1.0)
int assbackwards;	HTTP/0.9 simple request
int proxyreq;	Proxy request
int header_only;	HTTP중 header only일 경우 1이 세팅된다
char *protocol;	protocol및 버젼 (ex, HTTP/1.0)
int proto_num;	protocol의 버젼 1.1은 1001로 표기
char *hostname;	full URI나 Host:에서 지정된 host이름.
int hostlen;	full URI에서 "http://host:port"부분의 길이
time_t request_time;	reuqest가 시작된 시간
char *status_line;	/* Status line, if set by script */
int status;	/* In any case */
char *method;	HTTP method (ex, GET, HEAD, POST...)
int method_number;	HTTP method의 정수값 (ex, M_GET, M_POST,...)
int allowed;	allowed methods
int send_bodyct;	body의 stream상의 길이
long bytes_sent;	현재까지 보낸 바이트의 수, 헤더는 제외한다
int chunked;	/* sending chunked transfer-coding */
int byterange;	/* number of byte ranges */
char *boundary;	/* multipart/byteranges boundary */
char *range;	/* The Range: header */
long clength;	/* The "real" content length */
long remaining;	/* bytes left to read */
long read_length;	/* bytes that have been read */
int read_body;	/* how the request body should be read */
int read_chunked;	/* reading chunked transfer-coding */
table *headers_in;	request header
table *headers_out;	response header
table *err_headers_out;	에러가 났을 경우의 response header
table *subprocess_env;	subprocess를 위한 environment table
table *notes;	모듈간의 정보교환을 위한 테이블
char *content_type;	요구된 오브젝트의 mime type
char *handler;	what we *really* dispatch on
char *content_encoding;	요구된 오브젝트의 encoding방식
char *content_language;	호환을 위해서 남겨둔 field. 아래의 array_header타입이 사용된다.
array_header *content_languages;	요구된 오브젝트의 language방식
int no_cache;	no-cache를 사용. on되면 client는 cache하지 않는다.
int no_local_copy;	local copy를 하지 않도록 한다.
char *uri;	클라이언트에서 요청한 URI (ex, http://cair.kaist.ac.kr/index.html)
char *filename;	요청된 리소스의 서버상의 파일네임 (ex, /usr/apache/htdocs/index.html)
char *path_info;	path에 관한 정보
char *args;	URL에 포함된 argument string (ex, name=john)
struct stat finfo;	요청된 URI에 해당하는 파일에 관한 정보. stat() system call을 사용하여 얻은 정보와 같다.
void *per_dir_config;	per-directory configuration 스트럭처를 가리키는 포인터.
void *request_config;	request에 관한 부가적인 설명을 담는다
const struct htaccess_result *htaccess;	.htaccess에 관한 structure