Intro to the Apache table API
一篇介绍Apache table API的文章,非常不错:)
Apache table API
Copyright 2002 Thomas EibnerTables in Apache works almost like hashes in Perl. The main difference between the two is that you can have two of the same keys in the Apache table API. The table lookups are done in a case-insensitive manner.
This reference is structured as most of the functions are grouped and tries to make examples that work for both Apache 1.3.x and 2.0.x.
The implementation of tables in the Apache API is made with the array API that Apache makes use of. A table is really just a struct that contains an array header. The array consists of table_entry structs.
typedef struct {
char *key;
char *val;
} table_entry;
Because of the chosen hash algorithm, lookups will take longer as more key/value pairs are added to the table. This is because of the fact that for every lookup the algorithm needs to go through each of the array elements until it finds a matching key or it runs out of elements to check against. In general though, the speed is more than sufficient for the applications where these tables are used; namely header-parsing.
This initial description is valid for both 1.3 and 2.0 versions of Apache. The following descriptions of each of the functions will contain examples for both 1.3 and 2.0 module writers.
Creating a Table
To create a table we use the ap_make_table and apr_make_table functions for Apache 1.3 and 2.0 respectively.
table * ap_make_table(pool *p, int nelts); /* 1.3 */
apr_table_t * apr_make_table(apr_pool_t *p, int nelts); /* 2.0 */
Arguments to the functions are the same; a pool to allocate the memory for the table from and the number of elements it will allocate for initially. If the number of elements is set to zero it will defer allocating memory until you use the table the first time. All the table functions will automatically allocate more memory as needed.
/* 1.3 example */
table *my_table;
my_table = ap_make_table(r->pool, 10);
/* 2.0 example */
apr_table_t *my_table;
my_table = apr_table_make(r->pool, 10);
In the above example we declare a new table and initialize the table with ap_make_table and allocate memory for 10 initial key/value pairs.
Setting a key/value pair in a table
Setting a key/value pair in a table is done with the ap_table_set and apr_table_set functions. The function expects a pointer to a table, the key, and the value of the entry. Internally the keys and values are copied using ap_pstrdup. If one or more key/value pairs already exists in the table the first one will be overwritten and the rest deleted.
void ap_table_set(table *t, const char *key, const char *val); /* 1.3 */
void ap_table_setn(table *t, const char *key, const char *val); /* 1.3 */
void apr_table_set(apr_table_t *t, const char *key, const char *val); /* 2.0 */
void apr_table_setn(apr_table_t *t, const char *key, const char *val); /* 2.0 */
If you choose to use the setn version of the function you will have to ensure that the value part of the arguments passed with persist for the duration of which the table is to be used, since it wont be copied internally.
/* 1.3 example */
ap_table_setn(my_table, "MyKey", "MyValue");
ap_table_setn(my_table, "MyKey", "MyOtherValue");
/* 2.0 example */
apr_table_setn(my_table, "MyKey", "MyValue");
apr_table_setn(my_table, "MyKey", "MyOtherValue");
In the above example the table my_table will end up having one element where the key is "MyKey" and the value is "MyOtherValue" since the first key/value pair was overwritten by the second set with the same key.
Adding a key/value pair to a table
Instead of setting a key/value pair and maybe overwriting existing key/value pairs with the same key you can use ap_table_add and apr_table_add variants. These functions essentially adds the key/value pair at the end of the array of which the table is made.
void ap_table_add(table *t, const char *key, const char *val); /* 1.3 */
void ap_table_addn(table *t, const char *key, const char *val); /* 1.3 */
void apr_table_add(apr_table_t *t, const char *key, const char *val); /* 2.0 */
void apr_table_addn(apr_table_t *t, const char *key, const char *val); /* 2.0 */
Like with the set functions there is a addn variant for adding elements without doing the internal copying and the same cautions apply here.
/* 1.3 example */
ap_table_addn(my_table, "MyKey", "MyValue");
/* 2.0 example */
apr_table_addn(my_table, "MyKey", "MyValue");
With this example we will now have two key/value pairs in the my_table table with the same key.
Merging values in a table
If you want to append something to the value of an already existing key/value pair you could go through the hassle of having to retrieve the current value, then merge the two, and finally set the value in the table again. Instead of going through this we can use the ap_table_merge and apr_table_merge functions.
void ap_table_merge(table *t, const char *key, const char *val); /* 1.3 */
void ap_table_mergen(table *t, const char *key, const char *val); /* 1.3 */
void apr_table_merge(apr_table_t *t, const char *key, const char *val); /* 2.0 */
void apr_table_mergen(apr_table_t *t, const char *key, const char *val); /* 2.0 */
The function merges the two values by appending a comma and a space (", ") after the first value and then append the contents of the passed value afterwards. The real advantage of using these functions are that if the key/value pair is not previously set in the table these functions will do this internally.
/* 1.3 example */
ap_table_mergen(my_table, "NewKey", "NewValue1");
ap_table_mergen(my_table, "NewKey", "NewValue2");
/* 2.0 example */
apr_table_mergen(my_table, "NewKey", "NewValue1");
apr_table_mergen(my_table, "NewKey", "NewValue2");
In our example we add a key/value pair that does not previously exist in the table. We then call the merge function once more and the value part of the key/value pair is appended to the key/value pair in the table. In the end the value field referenced by the "NewKey" key contains "NewValue1, NewValue2". Remember seeing fields ordered like this? Exactly, in header fields of an HTTP request.
Retrieving values from a table
Getting the value of an entry in a table is done with ap_table_get and apr_table_get. The first key will retrieved in case there are more than one entry in the table for the key. If you intend on modifying the retrieved value you have to make sure to copy it with ap_pstrdup before changing it.
const char * ap_table_get(const table *t, const char *key); /* 1.3 */
const char * apr_table_get(const apr_table_t *t, const char *key); /* 2.0 */
If you need to get all the values for a single key you need to use the ap_table_do function.
/* 1.3 example */
char *host = ap_pstrdup(r->pool, ap_table_get(r->headers_in, "Host"));
/* 2.0 example */
char *host = apr_pstrdup(r->pool, apr_table_get(r->headers_in, "Host"));
In this example we extract the "Host:" header value from the incoming headers table. We copy the string over in the host variable so we are free to manipulate it afterwards since we are no longer working on a const char type.
Removing entries from a table
To delete every key/value pair associated with a key in the table you should use the ap_table_unset and apr_table_unset functions.
void ap_table_unset(table *t, const char *key); /* 1.3 */
void apr_table_unset(apr_table_t *t, const char *key); /* 2.0 */
As a useful example we can make an anonymizing module that deletes the User-Agent and Cookie field your browser sends. It will delete the headers Via, Cookie, User-Agent, and X-Forwarded-For.
/* 1.3 Example */
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
module MODULE_VAR_EXPORT anon_proxy_module;
static int anonymize_request(request_rec *r) {
if (r->proxyreq) {
ap_table_unset(r->headers_in, "Via");
ap_table_unset(r->headers_in, "Cookie");
ap_table_unset(r->headers_in, "User-Agent");
ap_table_unset(r->headers_in, "X-Forwarded-For");
return OK;
}
return DECLINED;
}
module MODULE_VAR_EXPORT anon_proxy_module = {
STANDARD_MODULE_STUFF,
NULL, /* initializer */
NULL, /* dir config creater */
NULL, /* dir merger --- default is to override */
NULL, /* server config */
NULL, /* merge server configs */
NULL, /* command table */
NULL, /* handlers */
NULL, /* filename translation */
NULL, /* check_user_id */
NULL, /* check auth */
NULL, /* check access */
NULL, /* type_checker */
anonymize_request, /* fixups */
NULL, /* logger */
NULL, /* header parser */
NULL, /* child_init */
NULL, /* child_exit */
NULL /* post read-request */
};
/* 2.0 Example */
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
module AP_MODULE_DECLARE_DATA anon_proxy_module;
static int anonymize_request(request_rec *r) {
if (r->proxyreq) {
apr_table_unset(r->headers_in, "Via");
apr_table_unset(r->headers_in, "Cookie");
apr_table_unset(r->headers_in, "User-Agent");
apr_table_unset(r->headers_in, "X-Forwarded-For");
return OK;
}
return DECLINED;
}
static void anonproxy_register_hooks(apr_pool_t *p) {
ap_hook_fixups(anonymize_request, NULL, NULL, APR_HOOK_MIDDLE);
}
module AP_MODULE_DECLARE_DATA anon_proxy_module = {
STANDARD20_MODULE_STUFF,
NULL, /* per-directory config creator */
NULL, /* dir config merger */
NULL, /* server config creator */
NULL, /* server config merger */
NULL, /* command table */
anonproxy_register_hooks, /* set up other request processing hooks */
};
The module is pretty straight forward. We start by including the necessary header files and then declare our module name to be anon_proxy_module. We then have a function that if it is a proxy request will unset the fields we specified and return OK. In the last part of the module we register our function for the fixup phase which is right before the proxy sets in and requests the resource the user requested.
Clearing a Table
To remove all key/value pairs from the table you use the ap_clear_table and apr_table_clear functions.
void ap_clear_table(table *t); /* 1.3 */
void apr_table_clear(apr_table_t *t); /* 2.0 */
The only argument this function takes is the table in which it sets the number of elements in the table to zero.
Copying a Table
Sometimes it is necessary to copy the contents of a table into another table for manipulation and the ap_copy_table and apr_table_copy functions are made just for that.
table * ap_copy_table(pool *p, const table *t); /* 1.3 */
apr_table_t * apr_table_copy(apr_pool_t *p, const apr_table_t *t); /* 2.0 */
If any data existed in the table from previous operations these will be wiped from the table.
mod_proxy uses ap_copy_table to make a copy of the incoming headers to manipulate before requesting the proxy-resource. This makes it possible for the rest of the request phases to still use the original data.
Overlaying Tables
If you need to overlay two tables you can use the ap_overlay_tables or apr_table_overlay.
table * ap_overlay_tables(pool *p, const table *overlay, const table *base); /* 1.3 */
apr_table_t * apr_table_overlay(apr_pool_t *p, const apr_table_t *overlay, const apr_table_t *base); /* 2.0 */
The function will return a table where all of the key/value pairs from the overlay table is transfered to and all of the key/value pairs from the base table where the key doesn't exist in the overlay table will be transfered too.
Overlapping Tables
The functionality of the overlapping functions ap_overlap_tables and apr_table_overlap is determined by the flag that is passed to it. There are two different flags for each of the 1.3 and 2.0 versions. For 1.3 the flags are AP_OVERLAP_TABLES_SET and AP_OVERLAP_TABLES_MERGE and for 2.0 the flags are APR_OVERLAP_TABLES_SET and APR_OVERLAP_TABLES_MERGE.
void ap_overlap_tables(table *a, const table *b, unsigned flags); /* 1.3 */
void apr_table_overlap(apr_table_t *a, const apr_table_t *b, unsigned flags); /* 2.0 */
If you pass the SET flag to the function it behaves like ap_overlay_tables and apr_table_overlay. If you pass the MERGE flag to the function it will merge the overlapping key/value pairs from table b into table a that is passed to it. For the merging it uses the before descriped merging functions.
Iterating over key/value pairs from a table
To iterate over key/value pairs in a table we use the ap_table_do, apr_table_do, or apr_table_vdo functions. The function takes a pointer to a function which will be executed for each key/value pair that is to be iterated over.
int function(void *rec, const char *key, const char *value);
The function should follow the above prototype and take key and value as second and third argument. The first argument is a pointer to a structure or variable you passed to the actual ap_table_do function. This is useful if you want to pass a configuration structure to the iteration function, just remember to cast it to the right type.
void ap_table_do(int (*comp) (void *, const char *, const char *), void *rec, const table *t,...); /* 1.3 */
void apr_table_do(int (*comp)(void *, const char *, const char *), void *rec, const apr_table_t *t, ...); /* 2.0 */
void apr_table_vdo(int (*comp)(void *, const char *, const char *), void *rec, const apr_table_t *t, va_list); /* 2.0 */
The iterating function takes as the first argument the function you want called for each iteration that we described above. The second argument is the pointer to a structure or a variable you want passed to the iterating function. Third argument is the table you want processed. The last argument is a NULL-terminated list of values you want processed from the table or NULL. In the case that you pass NULL to the function the iterating function will be called for all key/value pairs.
Below is an example use of ap_table_do to dump information about the request to the browser.
/* 1.3 Example */
#include "httpd.h"
#include "http_config.h"
#include "http_main.h"
#include "http_protocol.h"
#include "http_core.h"
module MODULE_VAR_EXPORT dumpreq_module;
int iterate_func(void *req, const char *key, const char *value) {
int stat;
char *line;
request_rec *r = (request_rec *)req;
if (key == NULL || value == NULL || value[0] == '"0')
return 1;
line = ap_psprintf(r->pool, "%s => %s"n", key, value);
stat = ap_rputs(line, r);
return 1;
}
static int dump_request(request_rec *r) {
r->content_type = "text/plain";
ap_send_http_header(r);
if (r->header_only)
return OK;
ap_table_do(iterate_func, r, r->headers_in, NULL);
return OK;
}
static const handler_rec dumpreq_handlers[] = {
{"dumpreq-handler", dump_request},
{NULL}
};
module MODULE_VAR_EXPORT dumpreq_module = {
STANDARD_MODULE_STUFF,
NULL, /* initializer */
NULL, /* dir config creater */
NULL, /* dir merger --- default is to override */
NULL, /* server config */
NULL, /* merge server configs */
NULL, /* command table */
dumpreq_handlers, /* handlers */
NULL, /* filename translation */
NULL, /* check_user_id */
NULL, /* check auth */
NULL, /* check access */
NULL, /* type_checker */
NULL, /* fixups */
NULL, /* logger */
NULL, /* header parser */
NULL, /* child_init */
NULL, /* child_exit */
NULL /* post read-request */
};
/* 2.0 Example */
#include "httpd.h"
#include "http_config.h"
#include "http_main.h"
#include "http_protocol.h"
#include "http_core.h"
module AP_MODULE_DECLARE_DATA dumpreq_module;
int iterate_func(void *req, const char *key, const char *value) {
int stat;
char *line;
request_rec *r = (request_rec *)req;
if (key == NULL || value == NULL || value[0] == '"0')
return 1;
line = apr_psprintf(r->pool, "%s => %s"n", key, value);
stat = ap_rputs(line, r);
return 1;
}
static int dump_request(request_rec *r) {
if (strcmp(r->handler, "dumpreq-handler"))
return DECLINED;
ap_set_content_type(r, "text/plain");
if (r->header_only)
return OK;
apr_table_do(iterate_func, r, r->headers_in, NULL);
return OK;
}
static void dumpreq_register_hooks(apr_pool_t *p) {
ap_hook_handler(dump_request, NULL, NULL, APR_HOOK_MIDDLE);
}
module AP_MODULE_DECLARE_DATA dumpreq_module = {
STANDARD20_MODULE_STUFF,
NULL, /* per-directory config creator */
NULL, /* dir config merger */
NULL, /* server config creator */
NULL, /* server config merger */
NULL, /* command table */
dumpreq_register_hooks, /* set up other request processing hooks */
};
Other table functions
There is two more functions that we are going to show here, these are actually simple defines that will tell you wheter the table is empty and the number of elements in the table.
#define ap_table_elts(t) ((array_header *)(t)) /* 1.3 */
#define ap_is_empty_table(t) (((t) == NULL)||(((array_header *)(t))->nelts == 0)) /* 1.3 */
#define apr_table_elts(t) ((const apr_array_header_t *)(t)) /* 2.0 */
#define apr_is_empty_table(t) (((t) == NULL) || (((apr_array_header_t *)(t))->nelts == 0)) /* 2.0 */
The use of these functions should be pretty straightforward and they both only take table as arguments.
More info
The Table functions for 1.3 are declared in "src/include/ap_alloc.h" and they are implemented in "src/main/alloc.c". For 2.0 you should look in "srclib/apr/include/apr_tables.h".
Copyright 2002 Thomas Eibner