An output filter is given
a bucket brigade, does whatever it
does, and hands a new brigade (or brigades) down to the next filter
in the output filter stack. To be used at all, a filter must first be
registered. This is normally done in the hook registering function by
calling ap_register_output_filter()
, like so:
ap_register_output_filter("filter name",filter_function,AP_FTYPE_RESOURCE);
where the first parameter is the name of the filter — this can be
used in the configuration file to specify when a filter should be
used. The second is the actual filter function, and the third says
what type of filter it is (the possible types being
AP_FTYPE_RESOURCE
,
AP_FTYPE_CONTENT_SET
,
AP_FTYPE_PROTOCOL
,
AP_FTYPE_TRANSCODE
,
AP_FTYPE_CONNECTION
or
AP_FTYPE_NETWORK
). In reality, all the type does
is determine where in the stack the filter appears. The filter
function is called by the filter above it in the stack, which hands
it its filter structure and a bucket brigade.
Once the filter is registered, it can be invoked either by configuration, or for more complex cases, the module can decide whether to insert it in the filter stack. If this is desired, the thing to do is to hook the “insert filter” hook, which is called when the filter stack is being set up. A typical hook would look like this:
ap_hook_insert_filter(filter_inserter,NULL,NULL,APR_HOOK_MIDDLE);
where filter_inserter()
is a function that
decides whether to insert the filter, and if so, inserts it. To do
the insertion of the filter, you call:
ap_add_output_filter("filter name",ctx,r,r->connection);
where "filter name"
is the same name as was used
to register the filter in the first place and r
is
the request structure. The second parameter, ctx
in this example, is an optional pointer to a context structure to be
set in the filter structure. This can contain arbitrary information
that the module needs the filter function to know in the usual way.
The filter can retrieve it from the filter structure it is handed on
each invocation:
static apr_status_t filter_function(ap_filter_t *f,apr_bucket_brigade *pbbIn) { filter_context *ctx=f->ctx;
where filter_context
is a type you can choose
freely (but had better match the type of the context variable you
passed to ap_add_output_filter()
). The third and
fourth parameters are the request and connection structures — the
connection structure is always required, but the request structure is
only needed if the filter applies to a single request rather than the
whole connection.
As an example, I have written a complete output filter. This one is pretty frivolous — it simply converts the output to all uppercase. The current source should be available in modules/experimental/mod_case_filter.c. (Note that the comments to this example fall after the line(s) to which they refer.)
#include "httpd.h" #include "http_config.h" #include "apr_general.h" #include "util_filter.h" #include "apr_buckets.h" #include "http_request.h"
First, we include the necessary headers.
static const char s_szCaseFilterName[]="CaseFilter";
Next, we declare the filter name — this registers the filter and later inserts it to declare it as a const string.
module case_filter_module;
This is simply a forward declaration of the module structure.
typedef struct { int bEnabled; } CaseFilterConfig;
The module allows us to enable or disable the filter in the server configuration — if it is disabled, it doesn’t get inserted into the output filter chain. Here’s the structure where we store that info.
static void *CaseFilterCreateServerConfig(apr_pool_t *p,server_rec *s) { CaseFilterConfig *pConfig=apr_pcalloc(p,sizeof *pConfig); pConfig->bEnabled=0; return pConfig; }
This creates the server configuration structure (note that this means it must be a per-server option, not a location-dependent one). All modules that need per-server configuration must do this.
static void CaseFilterInsertFilter(request_rec *r) { CaseFilterConfig *pConfig=ap_get_module_config(r->server->module_config, &case_filter_module); if(!pConfig->bEnabled) return; ap_add_output_filter(s_szCaseFilterName,NULL,r,r->connection); }
This function inserts the output filter into the filter
stack — note that it does this purely by the name of the filter.
It is also possible to insert the filter automatically by using the
AddOutputFilter
or
SetOutputFilter
directives.
static apr_status_t CaseFilterOutFilter(ap_filter_t *f, apr_bucket_brigade *pbbIn) { apr_bucket *pbktIn; apr_bucket_brigade *pbbOut; pbbOut=apr_brigade_create(f->r->pool);
Since we are going to pass on data every time, we need to create a brigade to which to add the data.
APR_BRIGADE_FOREACH(pbktIn,pbbIn) {
Now loop over each of the buckets passed into us.
const char *data; apr_size_t len; char *buf; apr_size_t n; apr_bucket *pbktOut; if(APR_BUCKET_IS_EOS(pbktIn)) { apr_bucket *pbktEOS=apr_bucket_eos_create(); APR_BRIGADE_INSERT_TAIL(pbbOut,pbktEOS); continue; }
If the bucket is an EOS, then pass it on down.
apr_bucket_read(pbktIn,&data,&len,APR_BLOCK_READ);
Read all the data in the bucket, blocking to ensure there actually is some!
buf=malloc(len);
Allocate a new buffer for the output data. (We need to do this because we may add another to the bucket brigade, so using a transient wouldn’t do — it would get overwritten on the next loop.) However, we use a buffer on the heap rather than the pool so it can be released as soon as we’re finished with it.
for(n=0 ; n < len ; ++n) buf[n]=toupper(data[n]);
Convert whatever data we read into uppercase and store it in the new buffer.
pbktOut=apr_bucket_heap_create(buf,len,0);
Create the new bucket, and add our data to it. The final
0
means “don’t
copy this, we’ve already allocated memory for
it.”
APR_BRIGADE_INSERT_TAIL(pbbOut,pbktOut);
And add it to the tail of the output brigade.
} return ap_pass_brigade(f->next,pbbOut); }
Once we’ve finished, pass the brigade down the filter chain.
static const char *CaseFilterEnable(cmd_parms *cmd, void *dummy, int arg) { CaseFilterConfig *pConfig=ap_get_module_config(cmd->server->module_config, &case_filter_module); pConfig->bEnabled=arg; return NULL; }
This just sets the configuration option to enable or disable the filter.
static const command_rec CaseFilterCmds[] = { AP_INIT_FLAG("CaseFilter", CaseFilterEnable, NULL, RSRC_CONF, "Run a case filter on this host"), { NULL } };
And this creates the command to set it.
static void CaseFilterRegisterHooks(void) { ap_hook_insert_filter(CaseFilterInsertFilter,NULL,NULL,APR_HOOK_MIDDLE);
Every module must register its hooks, so this module registers the filter inserter hook.
ap_register_output_filter(s_szCaseFilterName,CaseFilterOutFilter, AP_FTYPE_CONTENT);
It is also a convenient (and correct) place to register the filter itself, so we do.
} module case_filter_module = { STANDARD20_MODULE_STUFF, NULL, NULL, CaseFilterCreateServerConfig, NULL, CaseFilterCmds, NULL, CaseFilterRegisterHooks };
Finally, we have to register the various functions in the module structure. And there we are: a simple output filter. There are two ways to invoke this filter, either add:
CaseFilter on
in a Directory
or Location
section, invoking it through its own directives, or (for example):
AddOutputFilter CaseFilter html
which associates it with all .html files using the standard filter directives.