Le code est très inspiré du chapitre « Reading the Request body » du livre Writing Apache Modules with Perl and C.
Pour expérimenter un peu les modules Apache (suite à un nouvel entretien d’embauche), je me suis mis en tête de coder un mini service via un module pour générer des nombres aléatoire.
Le service devra être très simple et sera exécuté lors de l’appel de l’url http://localhost/randomint, et l’on pourra l’appeler en POST en donnant, par exemple, l’argument count pour préciser le nombre d’entiers aléatoires que l’on désire.
Et finalement, l’étape la plus complexe aura été de reconstruire le body envoyé lors de la requête POST, et de le parser pour en sortir un tableau associatif (key/value).
Reconstruction du body
Cette fonction attend l’envoie du body par le client et reconstruit en un seul block toutes les données envoyées.
static int chunk_reader(request_rec *r, const char **rbuf)
{
int ret;
if(OK != (ret = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) {
return ret;
}
if(ap_should_client_block(r)) {
char argsbuffer[HUGE_STRING_LEN];
int rsize, len_read, rpos=0;
long length = r->remaining;
*rbuf = apr_pcalloc(r->pool, length + 1);
while(0 < (len_read = ap_get_client_block(r, argsbuffer, sizeof(argsbuffer)))) {
if ((rpos + len_read) > length) {
rsize = length - rpos;
}
else {
rsize = len_read;
}
memcpy((char*)*rbuf + rpos, argsbuffer, rsize);
rpos += rsize;
}
}
return ret;
}
Création du tableau associatif
Avec le block recomposé, on fait ensuite appel aux fonctions ap_getword ap_getword et aux fonctions apr_tables de apr afin de découper et stocker les différentes valeurs envoyées:
static int read_post(request_rec *r, apr_table_t **tab)
{
const char *data;
const char *key, *val, *type;
int ret = OK;
if(r->method_number != M_POST) {
return ret;
}
// On verifie que le content type soit bien application/x-www-form-urlencoded
type = apr_table_get(r->headers_in, "Content-Type");
if(strcasecmp(type, DEFAULT_ENCTYPE) != 0) {
return DECLINED;
}
if((ret = chunk_reader(r, &data)) != OK) {
return ret;
}
// Creation ou RAZ de la apr_table_t
if(*tab) {
apr_table_clear(*tab);
}
else {
*tab = apr_table_make(r->pool, 8);
}
// Tant qu'il y a des datas...
while(*data && (val = ap_getword(r->pool, &data, '&'))) {
key = ap_getword(r->pool, &val, '=');
ap_unescape_url((char*)key);
ap_unescape_url((char*)val);
apr_table_merge(*tab, key, val);
}
return OK;
}
Récupérer la bonne valeur
Une fois qu’on a le tableau associatif, il nous faut une fonction pour récupérer la valeur voulue:
int get_value(request_rec *r, const char *name)
{
apr_table_t *post_values = NULL;
apr_array_header_t *arr;
apr_table_entry_t *elt;
int i, ret, value;
if(OK != (ret = read_post(r, &post_values))) {
return -1;
}
if(NULL == post_values) {
return -1;
}
arr = apr_table_elts(post_values);
elt = (apr_table_entry_t*)arr->elts;
for(i = 0; i < arr->nelts; ++i) {
if(strcmp(elt[i].key, name) == 0
&& LONG_MIN != (value = strtol(elt[i].val, NULL, 10))) {
return value;
}
}
return -1;
}
Revenons au code du module lui-même…
Il ne reste plus qu’à coder le handler de génération, et rajouter les structures nécessaires pour le module:
static int mod_randomint_method_handler (request_rec *r)
{
int ret = OK, i, count = 8, new_count;
unsigned int random_value;
new_count = get_value(r, "count");
if(new_count > 0 && new_count < MAX_COUNT) {
count = new_count;
}
ap_set_content_type(r, "text/plain");
int random_fd = open("/dev/urandom", O_RDONLY);
for(i = 0; i < count; i ++) {
read(random_fd, (void*)&random_value, sizeof(random_value));
ap_rprintf(r, "%u\n", random_value);
}
close(random_fd);
// fprintf(stderr, "Count: %d\n", count);
// fflush(stderr);
return ret;
}
static void mod_randomint_register_hooks (apr_pool_t *p)
{
ap_hook_handler(mod_randomint_method_handler, NULL, NULL,APR_HOOK_LAST);
}
module AP_MODULE_DECLARE_DATA randomint_module =
{
STANDARD20_MODULE_STUFF,
NULL,
NULL,
NULL,
NULL,
NULL,
mod_randomint_register_hooks, /* callback for registering hooks */
}
Il ne reste plus qu’à compiler le code …:
apxs2 -c -a -i mod_randomint.c
… et le configurer dans httpd.conf:
LoadModule randomint_module /usr/lib/apache2/modules/mod_randomint.so
<Location /randomint>
SetHandler randomint-handler
</Location>
Et après un redémarrage d’apache:
$ curl http://localhost/randomint
4162010288
1220025110
2641785880
On pourra voir le résultat dans mes snippets et le télécharger dans mes projets directement.
Mise à jour:
J’ai oublié de filtrer par handler dans le code. On incluera donc dans mod_randomint_method_handler:
if(strcmp(r->handler, "randomint-handler")) {
return DECLINED;
}