아래 예제들의 샘플은 httpd 2.2.3과 mod_cband 0.9.7.5, mod_bw 0.8을 참고로 했다.
Apache의 filter들은 모두 ap_register_output_filter 라는 함수를 통해서 등록되는데, include/util_filter.h를 보면 아래와 같이 선언되어 있다.
include/util_filter.h: 152 Line
/**
* Filters have different types/classifications. These are used to group
* and sort the filters to properly sequence their operation.
*
* The types have a particular sort order, which allows us to insert them
* into the filter chain in a determistic order. Within a particular grouping,
* the ordering is equivalent to the order of calls to ap_add_*_filter().
*/
typedef enum {
/** These filters are used to alter the content that is passed through
* them. Examples are SSI or PHP. */
AP_FTYPE_RESOURCE = 10,
/** These filters are used to alter the content as a whole, but after all
* AP_FTYPE_RESOURCE filters are executed. These filters should not
* change the content-type. An example is deflate. */
AP_FTYPE_CONTENT_SET = 20,
/** These filters are used to handle the protocol between server and
* client. Examples are HTTP and POP. */
AP_FTYPE_PROTOCOL = 30,
/** These filters implement transport encodings (e.g., chunking). */
AP_FTYPE_TRANSCODE = 40,
/** These filters will alter the content, but in ways that are
* more strongly associated with the connection. Examples are
* splitting an HTTP connection into multiple requests and
* buffering HTTP responses across multiple requests.
*
* It is important to note that these types of filters are not
* allowed in a sub-request. A sub-request's output can certainly
* be filtered by ::AP_FTYPE_RESOURCE filters, but all of the "final
* processing" is determined by the main request. */
AP_FTYPE_CONNECTION = 50,
/** These filters don't alter the content. They are responsible for
* sending/receiving data to/from the client. */
AP_FTYPE_NETWORK = 60
} ap_filter_type;
그러니까 RESOURCE 단계부터 NETWORK 단계까지 흡사 OSI 7 Layer를 보는 것 처럼 구성되어 있다.
ap_register_output_filter 함수는 ap_register_output_filter_protocol의 wrapper 함수로 구성되어 있고, ap_register_output_filter_protocol은 간단하게 register_filter 함수를 통해 registered_output_filters 라는 필터 그룹에 입력된 필터링 함수를 등록시킨다.
server/util_filter.c: 251 Line
/* Prepare to make this a #define in 2.2 */
AP_DECLARE(ap_filter_rec_t *) ap_register_output_filter(const char *name,
ap_out_filter_func filter_func,
ap_init_filter_func filter_init,
ap_filter_type ftype)
{
return ap_register_output_filter_protocol(name, filter_func,
filter_init, ftype, 0) ;
}
AP_DECLARE(ap_filter_rec_t *) ap_register_output_filter_protocol(
const char *name,
ap_out_filter_func filter_func,
ap_init_filter_func filter_init,
ap_filter_type ftype,
unsigned int proto_flags)
{
ap_filter_rec_t* ret ;
ap_filter_func f;
f.out_func = filter_func;
ret = register_filter(name, f, filter_init, ftype,
®istered_output_filters);
ret->proto_flags = proto_flags ;
return ret ;
}
이렇게 등록된 filter 들을 요청(request_rec)과 연결(conn_rec)에 대한 구조체들과 연결시키는 함수는 ap_add_output_filter 로부터 파생되는 일단의 함수 군(群)들인데, 가지고 있는 샘플들은 이 연결에 대한 방식이 조금씩 다르다. (동작 역시 같지는 않다)
일단 아파치에 내장되어 있는 core_output_filter를 예를 들어보면, ap_register_output_filter의 호출은 다음과 같다.
server/core.c: 3979 Line
ap_core_output_filter_handle =
ap_register_output_filter("CORE", ap_core_output_filter,
NULL, AP_FTYPE_NETWORK);
하지만 core_output_filter는 ap_add_output_filter가 호출되지 않고 ap_add_output_filter_handle이 호출되어 등록되는데, 이것은 PRE_CONNECTION 단계의 HOOK에서 이루어진다. (2번째 인자가 입력되고, 3번째 인자가 NULL이다. 2번째 인자로 입력되는 net은 conn_net_rec라는 구조체로, 새로 생성된다. 아마도 connection 단계에서 output filter를 적용하는 방법이라고 생각된다.)
server/core.c: 3937 Line ap_add_output_filter_handle(ap_core_output_filter_handle, net, NULL, net->c);
mod_cband의 경우에는 ap_register_output_filter의 호출은 다음과 같다.
mod_cband.c: 3674 Line
ap_register_output_filter("mod_cband", mod_cband_filter, NULL, AP_FTYPE_TRANSCODE);
CORE Filter와 다소 다른 점이 있는데, CORE Filter는 AP_FTYPE_NETWORK 단계에 등록되고, cband는 AP_FTYPE_TRANSCODE 단계에 등록된다.
ap_add_output_filter의 호출은 2군데에 등록되어 있다. 둘 다 ap_hook_handler을 통해 등록된 handler function이며, 위치는 아래와 같다. (2번째 인자가 NULL이고 3번째 인자에 request rec 구조체가 입력된다. 아마도 request가 모두 parsing 된 후에 호출되는 방식인듯 하다.)
mod_cband.c: 3085 Line
ap_add_output_filter("mod_cband", NULL, r, r->connection); mod_cband.c: 3261 Line
ap_add_output_filter("mod_cband", NULL, r, r->connection);
mod_bw의 경우이다.
mod_bw.c: 1128 Line
ap_register_output_filter("mod_bw", bw_filter, NULL, AP_FTYPE_TRANSCODE);
역시 AP_FTYPE_TRANSCODE 단계에 등록된다. 역시 ap_add_output_filter는 handler function에서 호출된다.
mod_bw.c: 783 Line
ap_add_output_filter("mod_bw", NULL, r, r->connection);
실제로 mod_bw와 mod_cband는 자신이 등록하고 있는 Filter Function에서 먼저 Bucket을 수신하고, 원하는 Bandwidth Control 단위대로 Bucket을 잘라서 Delay 루틴을 적용한 다음에 이것을 실제로 Buffer를 전송하는 Filter로 전달하는 역할을 한다. (이런 동작이기 때문에 Core와는 다른 방식의 Filter 등록을 하고 있는지는 모르겠다.)
이 동작은 실제로 커다란 하나의 Bucket을 작은 여러개의 Bucket으로 잘라 그것을 다른 Filter로 전달하는 동작을 하는 것이기 때문에 다소 전송에 부담이 된다고 하겠다. (실제 패킷을 보내는 부분과 속도 제어를 하는 부분의 구현 자체가 분리되어 직관적이긴 하지만, 전체적인 속도가 느려질 수 있다는 단점이 있다)
실제로 mod_bw나 mod_cband가 나오기 이전 유사한 동작을 하는 모듈 구성을 위해 Core Output Filter를 동일하게 module 형식으로 구성해 보았으며, 위에서와 같이 ap_register_output_filter를 AP_FTYPE_NETWORK 단계로 (실제로는 AP_FTYPE_CONNECTION 단계로 적용되어 있지만, NETWORK 단계에서도 적용이 가능할 것 같다), PRE_CONNECTION 단계에서 HOOK_LAST로 ap_add_output_filter를 connection 단계로 적용시켜주면 (그러니까 core filter와 동일하게), 정상적으로 원하는 Filter로 Override 되는 것을 볼 수 있다.
core의 output filter들은 server/core_filters.c에 구현되어 있다.