[APACHE DOCUMENTATION]

Apache HTTP Server Version 1.3

Notes sur l'API Apache

Nous présentons ici quelques notes et remarques sur l'API Apache et les structures de données avec lesquelles vous devrez travailler. Ces notes ne sont pas encore exhaustives, mais nous espérons qu'elles vous apporteront déjà un bon nombre d'éclaircissements. Gardez néanmoins à l'esprit que cette API peut changer au fur et à mesure que nous expérimentons. Cependant, adapter vos modules à ces modifications futures ne devraient pas poser de problèmes particuliers. (Nous avons nous-mêmes certainement plus de modules à adapter que vous n'en aurez jamais !).

Quelques remarques sur l'approche pédagogique générale de ce qui suit. Pour rester concis, toutes les déclarations de structures sont volontairement incomplètes --- Les structures réelles ont en général plus de membres données que ce qui apparaît ici. Pour la plupart d'entre eux, ils sont réservés à un composant du noyau du serveur, et ne devront être modifiés par des modules qu'après un examen attentif des conséquences. Malgré tout, dans certains cas, il peut s'agir simplement de membres pour lesquels nous n'avons pas encore écrit la documentation. Bienvenue dans l'antre de la bête !

Voici un résumé, pour vous donner une petite idée de ce qui est traité dans ce document, et dans quel ordre :

Concepts de base.

Nous commençons notre discussion par les concepts de base tournant autour de l'API, et comment ces concepts interviennent dans le code.

Handlers, Modules, et Requêtes

Apache découpe le traitement des requêtes en une série d'étapes, comme le fait plus ou moins l'API du serveur Netscape API (bien que cette dernière ait moins d'étages que l'API Apache). Celles-ci sont :

Ces étapes sont traitées en se référant à une succession de modules, pour lesquels un Handler correspondant à l'étape en cours est recherché, et invoqué s'il en existe. Un Handler peut typiquement exécuter l'une des trois procédures suivantes :

La plupart des étapes sont achevées par le premier module qui les traite ; Exceptés l'enregistrement de traces, les `fixups', et la vérification d'authentification pour l'accès, tous les Handlers sont toujours activés (reportant une erreur). De plus, l'étape fournissant la réponse est unique même si le module déclare plusieurs Handlers pour cette étape, dont un sera choisi selon le type MIME de l'objet requis via une table. Un Module peut déclarer un Handler pour l'étape de réponse, capable de traiter toutes les requêtes, en y associant la clef */* (c-à-d., une spécification de metatype MIME) dans la table. Cependant, un tel Handler ne pourra être invoqué que si le serveur a déjà essayé de trouver sans succès un Handler de réponse plus spécifique pour le type MIME de l'objet requis (soit qu'aucun n'existe, soit ils ont tous rejeté la requête).

Les Handlers eux-mêmes sont des fonctions à un argument (une structure request_rec ), renvoyant un entier, tel que précisé ci-avant.

Petite visite d'un module

Rendu à ce point, nous devons expliquer la structure d'un module. Le candidat que nous avons choisi pour cette explication est le module CGI --- Il effectue le traitement des scripts CGI et de la commande ScriptAlias du fichier de configuration. Ce module est notablement plus complexe que la plupart des autres modules, mais tant qu'à prendre un exemple, autant que c'en soit un qui fourre son nez partout.

Commençons par les Handlers. Pour pouvoir mettre un traitement en face des scripts CGI, le module déclare un Handler de réponse pour ces derniers. Du fait de la directive ScriptAlias, ce module définit aussi des Handlers pour l'étape de translation de nom (notamment pour reconnaître des URI ScriptAliasées), l'étape de vérification du type de média (toute requête ScriptAliasée a comme type "script CGI").

Le module doit maintenir un certain nombre d'informations pour chaque serveur (virtuel), entre autres pour les ScriptAliases ; la structure du module contient pour cela un certain nombre de pointeurs sur des fonctions construisant ces structures de données, et vers une autre fonction qui combine deux d'entre elles (dans le cas ou le serveur principal et un serveur virtuel ont tous deux déclaré des ScriptAlias).

Enfin, ce module contient le code permettant de prendre en compte la commande ScriptAlias elle-même. Ce module particulier ne déclare qu'une commande, mais plus pourraient être définies ; les modules ont donc une table de commandes permettant de déclarer des commandes multiples, décrire dans quel contexte elles sont autorisées, et comment elles doivent être invoquées.

Dernière note concernant les déclarations de type des arguments de ces commandes : un "pool" est un pointeur sur une structure "réservoir de ressources"; l'information qui y est contenue est exploitée par le serveur pour garder la trace de la mémoire allouée, les fichiers ouverts, etc., soit dans le contexte du traitement d'une requête particulière, soit dans le contexte général de configuration du module. Ainsi, lorsque la requête a été traitée (ou, dans le contexte de configuration, lorsque le serveur redémarre), ces ressources mémoire peuvent être libérées, et les fichiers refermés, en masse, sans qu'il soit nécessaire d'écrire du code explicite pour rechercher ces ressources. De plus, la structure cmd_parms contient diverses informations dur le fichier de configuration en cours de lecture, ainsi que d'autres informations d'état, utilisées dans certains cas par la fonction qui analyse les commandes de configuration (comme par exemple ScriptAlias). En final, la structure du module lui-même :

/* Déclarations des Handlers. */

int translate_scriptalias (request_rec *);
int type_scriptalias (request_rec *);
int cgi_handler (request_rec *);

/* Table de routage optionnelle pour les Handlers de réponse, par type MIME */

handler_rec cgi_handlers[] = {
{ "application/x-httpd-cgi", cgi_handler },
{ NULL }
};

/* Déclarations des routines manipulant l'information de configuration du
 * module. Notez que celles-ci sont renvoyées comme des void *;
 * le noyau serveur en garde une trace, mais il ne peut
 * en connaître la structure interne.
 */

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

/* Déclaration des routines de traitement des directives de configuration */

extern char *script_alias(cmd_parms *, void *per_dir_config, char *fake,
                          char *real);

command_rec cgi_cmds[] = {
{ "ScriptAlias", script_alias, NULL, RSRC_CONF, TAKE2,
    "a fakename and a realname"},
{ NULL }
};

module cgi_module = {
   STANDARD_MODULE_STUFF,
   NULL,                     /* initializer */
   NULL,                     /* dir config creator */
   NULL,                     /* dir merger --- default is to override */
   make_cgi_server_config,   /* server config */
   merge_cgi_server_config,  /* merge server config */
   cgi_cmds,                 /* command table */
   cgi_handlers,             /* handlers */
   translate_scriptalias,    /* filename translation */
   NULL,                     /* check_user_id */
   NULL,                     /* check auth */
   NULL,                     /* check access */
   type_scriptalias,         /* type_checker */
   NULL,                     /* fixups */
   NULL,                     /* logger */
   NULL                      /* header parser */
};

Comment fonctionnent les gestionnaires (handlers)

Le seul argument passé à un gestionnaire est une structure request_rec. Cette structure décrit un requête particulière qui a été adressée au serveur par un client. Dans la plupart des cas, chaque connexion d'un client ne génère qu'une seule structure request_rec.

Un bref apperçu de la structure request_rec

La structure request_rec contient des pointeurs vers un ensemble de ressources qui sera détruit lorsque le serveur a terminé le traitement complet de la requête ; cet ensemble lui-même contient des structures donnant les informations de contexte serveur et connexion, et sans doute plus important, les informations sur la requête elle-même.

L'information la plus importante est le petit ensemble de chaînes de caractères qui décrit les attributs de l'objet requis, dont son URI, son nom de fichier, le type de contenu et son encodage (lesquelles sont données par les gestionnaires de translation et de vérification de type qui ont vu passer la requête).

D'autres données couramment exploitées sont des tables donnant les en-têtes MIME présentes dans la requête originale en provenance du client, et pointant sur les en-têtes MIME à envoyer dans la réponse (que les divers modules peuvent renseigner et compléter à loisir). On ajoutera à ceci les variables d'environnement pour chacun des sous-processus qui sont lancés pour pouvoir traiter la requête. Ces tables sont manipulées via des primitives table_get et table_set.

Notez que la valeur du champ Content-type ne peut pas être redéfini par les fonctions de traitement du contenu des modules à partir des fonctions table_*(). Au lieu de cela, on la définira par accès au champ content_type de la structure request_rec. Par exemple :

  r->content_type = "text/html";

Finalement, deux pointeurs vers deux structures de données pointent vers les configurations de contexte "module". Plus précisément, ces deux pointeurs visent les structures de données que le module a mis en place pour décrire comment il a été configuré pour agir dans tel ou tel répertoire (à l'aide d'un fichier .htaccess ou des sections <Directory>). On y trouvera des données "privées" qu'il a défini lors de la prise en charge de la requête (par ce biais, les gestionnaires d'un module agissant sur une phase peuvent "passer" des informations aux gestionnaires d'autres phases du même module). Un autre vecteur de configuration de ce type se trouve dans la structure server_rec pointée par la structure request_rec, lequel enregistre des données de configuration de contexte serveur (en fait un contexte particulier d'hôte, principal ou virtuel).

En voici une déclaration abrégée, indiquant les champs les plus souvant exploités :

struct request_rec {

  pool *pool;
  conn_rec *connection;
  server_rec *server;

  /* Quel objet est requis */

  char *uri;
  char *filename;
  char *path_info;
  char *args;           /* QUERY_ARGS, si c'est le cas */
  struct stat finfo;    /* renseigné par le noyau serveur ;
                         * st_mode mis à zéro si aucun fichier */

  char *content_type;
  char *content_encoding;

  /* environnements d'en-tête MIME, entrée et sortie. Egalement, un tableau 
   * contenant les variables d'environnement à passer à des sous-processus, 
   * éventuels de façon à pouvoir rajouter cet environnement dans des modules
   * personnalisés.
   *
   * la différence entre headers_out et err_headers_out est que le deuxième
   * reste transmis même en cas d'erreur, et persiste tout au long des
   * redirections internes (il figureront donc dans les en-têtes d'éventuels
   * ErrorDocument).
   */

  table *headers_in;
  table *headers_out;
  table *err_headers_out;
  table *subprocess_env;

  /* Infos sur la requête elle-même... */

  int header_only;     /* HEAD request, as opposed to GET */
  char *protocol;      /* Protocol, as given to us, or HTTP/0.9 */
  char *method;        /* GET, HEAD, POST, etc. */
  int method_number;   /* M_GET, M_POST, etc. */

  /* Infos pour la trace */

  char *the_request;
  int bytes_sent;

  /* Un commutateur que peut marquer les modules, pour indiquer que les données 
   * renvoyées sont volatiles, et que les clients ne doivent pas enregistrer
   * ces dernières en cache.
   */

  int no_cache;

  /* D'autres informations de configuration qui dépendent des fichiers .htaccess
   * Il s'agit de vecteurs de configuration, sous forme de pointeurs void* pour
   * chaque module
   * (le type de l'objet pointé est l'affaire de chaque module).
   */

  void *per_dir_config;   /* Options des fichiers de configuration, etc. */
  void *request_config;   /* Notes à propos de *cette* requête */

};

D'où viennent les structures request_rec

La plupart des structures request_rec sont construites à partir de la requête HTTP d'un client. Cependant, il y a à cela quelques exceptions :

De tels gestionnaires peuvent constituer des sous-requêtes, via des fonctions sub_req_lookup_file et sub_req_lookup_uri ; elles construisent une nouvelle structure request_rec et la traitent, comme vous pourriez vous y attendre, jusqu'au point ou la réponse serait en principe renvoyée au client. (Ces fonctions sautent toutes les vérifications d'autorisation d'accès si la sous-requête porte sur des fichiers situées dans le même répertoire que la requête originale).

(Les inclusions SSI fonctionnent par génération de sous-requêtes puis invoquent le gestionnaire de réponse pour ces sous-requêtes, via la fonction run_sub_request).

Gestion des requêtes, refus de service, et renvoi de codes d'erreur

Comme il a été précisé ci-avant, chaque gestionnaire, lorsqu'il est invoqué pour traiter une structure request_rec particulière, doit retourner un int pour indiquer le résultat de son traitement. Cet entier peut être soit :

Notez que si le code d'erreur renvoyé est REDIRECT, alors le module devra insérer un champ Location dans le membre headers_out, pour indiquer sur quelle URL le client doit être redirigé.

Considérations particulières au traitement des réponses

Les gestionnaires associés à la majorité des phases n'ont qu'à modifier certains champs dans le structure request_rec (ou, s'il ne s'agit que de vérifier un droit d'accès, retourner un code de résultat approprié). Par contre, les gestionnaires de réponse doivent quant à eux renvoyer une réponse au client.

Leur première tâche est de renvoyer une en-tête HTTP au client, via la fonction send_http_header. (Vous n'avez rien à préciser pour le cas particulier des requêtes sous HTTP/0.9 ; cette fonction décide d'elle-même si elle doit envoyer ou ne pas envoyer l'en-tête). Si la requête est notifiée header_only, cette tâche sera la seule ; le gestionnaire pourra finir son exécution après cette action, sans tenter d'émettre une quelconque information supplémentaire.

Dans le cas contraire, la deuxième tâche du gestionnaire de réponse est de générer et d'émettre un corps d'entité vers le client. Les primitives destinées à cet usage sont rputc et rprintf, pour toute sortie générée en interne, et send_fd, pour copier le contenu d'un certain fichier FILE * directement sur le flux d'entrée du client.

A ce moment, il vous faudra plus ou moins comprendre les parties suivantes du code, constituant le gestionnaire qui gère les requêtes GET ; ce code montre également comment des requêtes GET conditionnelles peuvent être gérées, si cela est désirable dans certains gestionnaires de réponse --- set_last_modified teste la valeur du champ If-modified-since: donnée par le client, si elle existe, et renvoie un code approprié (qui sera, si non nul, USE_LOCAL_COPY). Aucune construction similaire ne s'applique à set_content_length, bien qu'un code d'erreur soit retourné pour des raisons de symétrie.

int default_handler (request_rec *r)
{
    int errstatus;
    FILE *f;

    if (r->method_number != M_GET) return DECLINED;
    if (r->finfo.st_mode == 0) return NOT_FOUND;

    if ((errstatus = set_content_length (r, r->finfo.st_size))
        || (errstatus = set_last_modified (r, r->finfo.st_mtime)))
        return errstatus;

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

    if (f == NULL) {
        log_reason("file permissions deny server access",
                   r->filename, r);
        return FORBIDDEN;
    }

    register_timeout ("send", r);
    send_http_header (r);

    if (!r->header_only) send_fd (f, r);
    pfclose (r->pool, f);
    return OK;
}

Enfin, si tout ceci vous parait un défi démesuré, vous avez quelques autres moyens de vous en sortir. Tout d'abord, comme il est indiqué ci-dessus, un gestionnaire de réponse qui n'a pas encore produit quoi que ce soit en sortie peut simplement renvoyer un code d'erreur, auquel cas le serveur générera automatiquement un message d'erreur approprié. Deuxièmement, ce gestionnaire pourra invoquer un autre handler par un appel à internal_redirect, qui implémente les redirections internes que nous avons évoqué plus haut. Un gestionnaire de réponse qui a été redirigé en interne doit toujours renvoyer OK.

(Invoquer internal_redirect à partir de gestionnaire qui ne sont pas des gestionnaires de réponse peut amener à une confusion certaine).

Considérations particulières pour le traitement d'authentification

Les sujets dont nous parlerons ici en détail:

Considérations particulières pour la gestion des traces

Lorsqu'une requête a été redirigée en interne, la question "quoi tracer ?" se pose. Apache se sort de ce dilemme en passant la chaîne complète de redirections dans une liste de structures request_rec reliées par les pointeurs classiques r->prev et r->next. La structure request_rec qui est passée au gestionnaire de trace dans un tel cas est celle qui aura originellement été constituée à partir de la requête client ; notez que le champ bytes_sent n'affichera une valeur correcte que dans la dernière structure de cette liste, correspondant à la réponse réellement envoyée au client à l'issue de toutes les redirections.

Allocation et "réserve" de ressources

L'un des problèmes de la conception et de l'écriture d'un serveur de ressources et la prévention du manque potentiel de place pour les ressources nécessaires au serveur (mémoire, descripteurs de fichiers, etc.), en général du au fait d'une mauvaise libération de celles-ci. Le mécanisme de "réserve" de ressources est conçu pour faciliter la prévention d'une telle situation, en allouant les ressources d'une telle manière qu'elles soient automatiquement libérées dès que le serveur n'en a plus l'utilité.

Voici comment cela fonctionne : la mémoire allouée, les fichiers ouverts, etc., pour le traitement d'une requête particulière sont liées à un espace de ressources globalement alloué pour les requêtes. Cet espace, ou "réserve" est une structure de données qui décrit les ressources en question.

Une fois la requête traitée, la réserve est vidée. A ce moment, toute la mémoire associée à cette requête est donc libérée pour être réutilisée, tous les fichiers ouverts pour cette requête sont refermés, et toutes les autres fonctions de "nettoyage" associée à cette réserve sont appelées. Lorsque tout ceci est fini, nous pouvons croire avec certitude que toutes les ressources liées à cette réserve ont été libérées, et qu'aucune d'entre elles ne reste prise.

Lorsque le serveur redémarre, l'allocation de la mémoire et des ressources pour la configuration "serveur par serveur" est gérée d'une façon similaire. On utilise une réserve de configuration, qui garde la trace de toutes les ressources allouées lors de la lecture des fichiers de configuration du serveur (par exemple, la mémoire allouée pour la configuration des modules de chaque serveur, les fichiers de trace et autres fichiers ouverts, etc.), et avec laquelle toutes les commandes de configuration travaillent. Lorsque le serveur redémarre, et doit de ce fait relire les fichiers de configuration, la "réserve de configuration" est libérée, et la mémoire et les descripteurs de fichiers utilisés pour la lecture précédente de ces fichiers sont libérés pour réutilisation future.

Il doit être noté ici que l'utilisation de cette machinerie de réserve mémoire n'est pas en général obligatoire, sauf pour ce qui concerne les gestionnaires de trace, lorsqu'il est fondamental d'enregistrer les moments de purge des tampons pour pouvoir être sûr que les fichiers de trace ont bien été refermés lorsque le serveur redémarre. Ceci est obtenu le plus facilement en appelant la fonction pfopen, qui s'arrange de plus pour que le descripteur de fichier sous-jacent soit fermé avant que tout processus fils, comme les scripts CGI, soient executés), ou dans le cas où vous utiliseriez la machinerie de temporisation (qui n'est pas implémentée pour l'instant). Cependant, vous aurez deux avantages à l'utiliser : vous ne manquerez jamais de ressources allouées dans une réserve (même si vous allouez une chaîne bidon, et l'oubliez au passage); De plus, en termes de primitives d'allocation mémoire, palloc est en général plus rapide que malloc.

Nous allons expliquer ensuite comment la mémoire est allouée dans les réserves, puis expliquerons comment les ressources sont suivies par la machinerie de gestion des réserves.

Allocation de mémoire dans les réserves

De la mémoire est allouée dans une réserve par appel d'une fonction palloc, laquelle prend deux arguments. Le premier est un pointeur sur la structure définissant une réserve, l'autre étant le nombre d'octets à allouer dans cette réserve. Dans les gestionnaires de traitement des requêtes, la façon la plus rapide d'accéder à une réserve de ressources est en regardant dans le champ pool de la structure request_rec appropriée ; d'où l'apparition répétitive de cette séquence dans le code source d'un tel module :

int my_handler(request_rec *r)
{
    struct my_structure *foo;
    ...

    foo = (foo *)palloc (r->pool, sizeof(my_structure));
}

Notez qu'il n'existe pas de pfree --- La mémoire pallouée n'est libérée que lorsque la réserve de mémoire à laquelle elle appartient est effacée. Ceci signifie que palloc n'a pas autant d'opération a effectuer que sa soeur plus commune malloc() ; les seules opérations effectuées dans une situation normale est l'arrondi de la taille à la valeur de segment supérieur, la génération d'un pointeur, et une vérification de plage.

(La possibilité qu'un usage intensif de fonctions palloc accroissent démesurément la taille de mémoire requise par processus. Il y a deux méthodes pour éviter un tel problème, lesquelles sont décrites plus loin ; brièvement, vous pouvez utiliser malloc, en essayant de s'assurer que toute cette mémoire est effectivement et explicitement libérée par une fonction free(). Une autre méthode est d'allouer une sous-réserve d'une réserve principale, puis d'allouer de la mémoire dans la sous-réserve, et libérer cette sous-réserve périodiquement. Cette dernière technique est détaillée dans la section qui suit traitant des sous-réserves, et est utilisée dans le code implémentant l'indexation des répertoires, pour éviter une consommation excessive de ressources lors de l'affichage de listes de très gros répertoires contenant plusieurs milliers d'entrées).

Allouer de la mémoire initialisée

Certaines fonctions allouent de la mémoire en l'initialisant, lesquelles se révèlent très souvent bien utiles. La fonction pcalloc a la même interface que la fonction palloc, mais initialise la mémoire à 0 avant de retourner le pointeur. La fonction pstrdup accepte comme arguments une réserve de mémoire et un pointeur char *, et alloue la mémoire pour une copie de la chaîne contenue dans ce pointeur, renvoyant un pointeur sur cette copie. Enfin pstrcat est une fonction à arguments variables, qui prend un pointeur sur une réserve, et au moins deux pointeurs char *, le dernier pointeur de la liste d'arguments doit être le pointeur NULL. Elle alloue autant de mémoire nécessaire pour une copie de chacune des chaînes données, par exemple :

     pstrcat (r->pool, "foo", "/", "bar", NULL);

renvoie un pointeur sur 8 octets de mémoire, initialisés avec la chaîne "foo/bar".

Suivi des ouvertures de fichiers, etc.

Comme mentionné ci-dessus, les réserves de ressource sont également utilisées pour suivre d'autres types de ressources. Les plus communes sont les descripteurs des fichiers ouverts. La routine typiquement utilisée pour cela est pfopen, qui prend comme arguments un pointeur sur une réserve et deux chaînes de caractères en arguments ; ces chaînes correspondent aux arguments habituels de la fonction C standard fopen, ex.,

     ...
     FILE *f = pfopen (r->pool, r->filename, "r");

     if (f == NULL) { ... } else { ... }

Est également définie une routine popenf, qui est l'équivalente de l'appel système de bas niveau open. Ces deux routines s'arrangent pour que les descripteurs des fichiers ouverts soient libérés (et les fichiers refermés) lorsque la réserve de ressources est libérée.

COntrairement au cas de la mémoire, il existe des fonctions permettant de refermer les fichiers ouverts par pfopen et popenf, soient les fonctions pfclose et pclosef. (Ceci est dû au fait que sur un bon nombre de systèmes, le nombre de fichiers simultanément ouverts par un processus unique est limité). Il est important d'utiliser ces fonctions pour refermer les fichiers alloués par pfopen et popenf, car toute autre méthode pour refermer ces fichiers provoquerait une erreur fatale sur des systèmes comme Linux, qui réagissent assez mal lorsque le même pointeur FILE* est refermé plus d'une fois.

(L'utilisation de fonctions de fermeture n'est pas impérieuse, car ces fichiers seront de toutes façons fermés tôt ou tard par le mécanisme des réserves, mais elle reste à considérer si votre module a besoin d'ouvrir, ou peut avoir potentiellement à ouvrir un grand nombre de fichiers).

Autres types de ressources --- fonction de "nettoyage"

Un texte ultérieur doit être écrit ici. Il décrira les primitives de nettoyage de ressources ; en plus, spawn_process.

Contrôle précis des ressources --- création et utilisation de sous-réserves, note sur les sous-requêtes

En de rares occasions, un usage trop libéral de la fonction palloc() et primitives associées peut résulter en une allocation inconsidérée de ressources. Vous pouvez mieux en contrôler l'usage par la création de sous-réserves, et allouer de la mémoire dans ces sous-réserves plutôt que dans une grande réserve, travailler avec cette mémoire, puis détruire cette sous-réserve, libérant ainsi les ressources qui lui seront associées. (Ceci est en fait une utilisation assez rare ; le seul cas où ce principe est utilisé dans le jeu de modules standard est le traitement des listes de répertoires, et seulement dans le cas de répertoires très volumineux. Un usage inconsidéré de ces primitives risque de compliquer notablement votre code, en apportant qu'un gain mineur en performance).

Vous pouvez créer une sous-réserve par la primitive make_sub_pool, qui accepte une réserve en argument (la réserve mère). Lorsque cette réserve mère est libérée, la sous-réserve le sera également. La sous-réserve peut également être libérée à tout moment par l'appel de fonctions clear_pool et destroy_pool. (La différence essentielle est que clear_pool ne libère que les ressources associées à la sous-réserve, tandis que destroy_pool désalloue également la description de la sous-réserve elle-même. Dans le premier cas, vous pouvez recommencer à allouer de la mémoire dans cette sous-réserve, puis la libérer à nouveau, et ainsi de suite ; dans le second cas, il faudra recréer une nouvelle sous-réserve avant de pouvoir la réutiliser).

Note finale --- les sous-requêtes ont leur propre réserve de ressources, qui est une sous-réserve de la réserve associée au traitement de la requête principale. La façon la plus licite de réclamer les ressources associées à une sous-requête que vous avez allouées (par des fonctions sub_req_lookup_...) se fait par un appel à destroy_sub_request, qui libère cette réserve particulière. Avant d'appeler cette fonction, assurez-vous d'avoir recopié toutes les données que vous désirez conserver, et allouées dans cette réserve de la sous-requête, dans une zone un peu moins volatile (par exemple, le nom de fichier dans sa structure request_rec).

(Encore une fois, en de nombreuses circonstances, vous ne devrez pas vous sentir obligé d'appeler ces fonctions ; seuls 2Ko de mémoire sont allouées pour une sous-requête "normale", et seront libérés de toutes manières lorsque la réserve associée à la requête principale sera elle-même libérée. Ce n'est que si vous prévoyez de générer de nombreuses, très nombreuses sous-requêtes d'une même requête principale que vous commencerez à considérer l'opportunité d'utiliser ces fonctions destroy...).

Configuration, commandes et assimilés

L'un des objectif de l'implémentation de ce serveur était de maintenir la compatibilité externe avec le serveur NCSA 1.3 --- c'est-à-dire, être capable de lire les mêmes fichiers de configuration, interpréter correctement les mêmes directives qui y seraient inscrites, et plus généralement d'être une seconde source pour le serveur NCSA. D'un autre côté, un autre de nos objectifs était de reporter le maximum de fonctionnalités sous formes de modules, pour en finir avec l'aspect monolithique du noyau du serveur. La seule façon de réconcilier ces deux objectifs est de déléguer l'interprétation de nombreuses commandes depuis le serveur central vers les modules.

Cependant, le simple fait de donner les tables des commandes aux modules n'est pas suffisant pour les éliminer du noyau central. Le serveur devra se rappeler les commandes pour pouvoir y réagir plus tard. Ceci suppose de maintenir des données privées des modules, et qui peuvent être propres à un serveur, ou à un répertoire particulier. La plupart de ces paramètres sont basés sur répertoires, incluant en particulier, les données de contrôle d'accès et les informations d'autorisation, mais aussi l'information servant à déterminer le type de fichier en fonction de son suffixe, laquelle peut être modifiée par les directives AddType et DefaultType, et ainsi de suite. En général, l'idée maîtresse est que tout ce qui peut être rendu configurable sur une base de répertoire devra l'être ; les informations sur une base de serveurs seront en général utilisées dans l'ensemble de modules standard telles que les Alias et Redirections qui entrent en jeu avant que la cible de la requête ne soit considérée en termes de localisation dans le système de fichiers sous-jacent.

Une autre contrainte pour émuler le serveur NCSA est la capacité à gérer des fichiers de configuration locaux à un répertoire, souvent appelés .htaccess, bien que ces fichiers, y compris ceux du NCSA, puissent contenir des informations qui n'ont strictement rien à voir avec le contrôle d'accès. Selon le cas, une fois la translation URI -> fichier effectuée, mais avant le traitement de toute autre phase, le serveur descend la hiérarchie des répertoires du système de fichier physique, en suivant le chemin d'accès translaté, à la recherche d'un éventuel fichier .htaccess. Les informations trouvées dans ce fichier doivent être considérées en plus des paramètres applicables par l'analyse des fichiers de configuration propres du serveur (soit dans les sections <Directory> du fichier access.conf, ou des défauts définis dans srm.conf, qui se comportent pour la plupart des fonctionnalités comme les clauses <Directory />).

Enfin, une fois la requête traitée en fonction des paramètres locaux obtenus par lecture du fichier .htaccess, ces derniers devront être effacés. Ce fonctionnement est obtenu de la même façon que nous avons résolu des problèmes similaires, en "attachant" les structures nécessaires à la réserve de ressources associée à la transaction.

Structures de configuration "par répertoire"

Regardons comment tout ceci s'agence dans le fichier mod_mime.c, qui paramètre le gestionnaire de typage des fichiers émulant le comportement du serveur NCSA quant à la détermination des types de fichiers à partir de leurs suffixes. Nous inspecterons ici le code qui implémente les commandes AddType et AddEncoding. Ces commandes peuvent apparaître dans des fichiers .htaccess, et doivent être de ce fait gérées via des données propriétaires du module pour ses opérations par répertoire, ce qui en fait, consiste en deux tables pour les types MIME et pour les encodages, et qui sont déclarées ainsi :

typedef struct {
    table *forced_types;      /* Additional AddTyped stuff */
    table *encoding_types;    /* Added with AddEncoding... */
} mime_dir_config;

Lorsque le serveur lit un fichier de configuration, ou une section <Directory>, qui contient une commande du module des types MIME, il devra créer une structure mime_dir_config, de sorte que cette commande ait des données sur lesquelles agir. Il fait ceci en invoquant la fonction trouvée dans le "slot" dit de "création des configurations par répertoire" du module, à laquelle deux arguments doivent être donnés : le nom du répertoire auquel les informations de configuration s'appliquent (ou NULL dans srm.conf), et un pointeur vers une réserve de ressources dans laquelle doit être prise la mémoire allouée.

(Si nous lisons un fichier .htaccess, cette réserve de ressource est la réserve attribuée à la requête ; autrement il s'agit d'une réserve commune aux données de configuration, uniquement libérée sur redémarrage du serveur. Dans les deux cas, il est important de faire disparaître la structure ainsi créée lorsque la réserve est effacée, en déclenchant un nettoyage de la réserve si nécessaire).

Pour le module MIME, la fonction de création des configurations par répertoire ne fait qu'un palloc des structures mentionnées ci-dessus, et crée un couple de tables pourles remplir. Cela ressemble à ceci :

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;
}

A partir de maintenant, nous supposerons que nous lisons dans un fichier .htaccess. Nous disposons déjà de la structure de configuration de portée répertoire pour le répertoire juste au-dessus dans la hiérarchie. Si le fichier .htaccess que nous venons de lire ne mentionne aucune directive AddType ni AddEncoding, la structure de configuration du module MIME de portée répertoire précédente reste valide, et nous l'utiliserons. Autrement, nous devrons combiner l'ancienne structure avec les nouvelles informations d'une façon ou d'une autre.

Pour réaliser cette combinaison, le serveur invoque la fonction de combinaison des configurations de niveau répertoire pour ce module, si elle est définie. Cette fonction accepte trois arguments : les deux structures à combiner, et une réserve de ressources dans laquelle le résultat doit être alloué. Pour le module MIME, il suffit de "recouvrir" les tables de l'ancienne structure de configuration de répertoire (celles du père) par les nouvelles :

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;
}

Note --- si une telle fonction de combinaison n'est pas définie pour le module, le serveur ne pourra exploiter que les informations de configuration relatives au répertoire courant, en ignorant l'état initial déduit du père. Pour certains modules, cette réaction est adéquate (ex., le module traitant des inclusions SSI, pour lequel la configuration par répertoire se limite à la seule position du bit XBITHACK), qui n'a pas à se transmettre dans la sous-hiérarchie. Pour ces modules, il suffit de ne déclarer aucune fonction de ce type, et de laisser le "slot" de la structure à NULL.

Traitement des directives

Maintenant que nous disposons de ces structures, nous devons comprendre comment nous allons les remplir. Ceci implique l'étude du traitement effectué par les directives AddType et AddEncoding. Pour trouver une référence des ces commandes, le serveur examine la table de commande du module considéré. Cette table contient des informations sur le nombre d'arguments pris par la commande, et sous quel format, dans quel contexte la commande est permise, etc. Ces informations sont suffisantes pour permettre au serveur d'invoquer la plupart des fonctions de traitement des commandes avec une pré-interprétation des arguments. Sans plus attendre, examinons le gestionnaire de traitement de la directive AddType, qui ressemble à ceci (la directive AddEncoding est implémentée de façon similaire, et nous ne la détaillerons pas ici):

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;
}

Ce traitement de directive est inhabituellement simple. Comme vous pouvez le constater, ce gestionnaire accepte quatre arguments, dont deux sont pré-interprétés, le troisième est la structure de configuration par répertoire pour ce module, le quatrième étant un pointeur sur une structure cmd_parms. Cette structure contient un ensemble d'arguments utilisés presque systématiquement dans bon nombre de traitements de directives (mais pas pour toutes), incluant une réserve de ressources (dans laquelle toutes les allocations doivent être faites, et sur laquelle doit être dirigée toute de mande de nettoyage), et les références du serveur (virtuel) concerné par la configuration.

Une autre raison pour laquelle ce gestionnaire est particulièrement simple est que ce traitement ne peut rencontrer aucune condition d'erreur. Si des erreurs pouvaient être détectées, il pourrait renvoyer un message d'erreur au lieu du NULL; si cela intervient dans un fichier de configuration de niveau serveur, le message d'erreur sera affiché sur le flux standard d'erreur (stderr) du serveur, et le serveur s'arrêtera de fonctionner ; lorsqu'elle intervient dans un fichier .htaccess, l'erreur de syntaxe détectée est tracée dans le fichier error_log du serveur (en indiquant où l'erreur s'est produite), et la requête est redirigée sur un gestionnaire de réponse d'erreur (qui provoquera une réponse d'erreur HTTP de code 500).

La table de commandes du module MIME dispose d'une entrée pour ces directives, qui ressemblera à ceci :

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 }
};

Les entrées dans cette table sont :

Une fois tout ceci configuré, nous devons l'exploiter, et précisément dans les modules ; en particulier les traitements d'écritures de fichiers, qui ressembleront peu ou prou à ce qui suit ; notez que la structure de configuration de niveau répertoire est extraite du vecteur de configuration "par répertoire" de la structure request_rec via un appel à get_module_config.

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;
}

Notes supplémentaires --- configuration par répertoire, serveur virtuels, etc.

L'idée de base qui sous-tend cette configuration des modules sur une base par serveur est en gros la même que celle qui nous a conduit à des configurations par répertoire ; il existe de même des fonctions de création et de combinaison de structures de configuration, la deuxième étant invoquée dans le cas où la configuration d'un serveur virtuel surchargerait partiellement la configuration initiale du serveur principal, et où une structure combinée doit être produite. (Comme dans le cas des configurations sur une base de répertoires, il est établi qu'aucune fonction de combinaison n'est définie "par défaut", et donc qu'à priori, une configuration définie pour un serveur virtuel, remplace intégralement la configuration du serveur principal).

La seule différence substantielle et que, lorsqu'une directive vient configurer sur une base de serveur des données privées à un module, il faudra utiliser des données cmd_parms pour les atteindre. Voici un exemple, tiré du module Alias, qui montre de plus comment retourner un message d'erreur (notez que l'argument de configuration "par répertoire" est déclaré "dummy", du fait que le module ne dispose effectivement d'aucune donnée de configuration de niveau répertoire):

char *add_redirect(cmd_parms *cmd, void *dummy, char *f, char *url)
{
    server_rec *s = cmd->server;
    alias_server_conf *conf = (alias_server_conf *)
            get_module_config(s->module_config,&alias_module);
    alias_entry *new = push_array (conf->redirects);

    if (!is_url (url)) return "Redirect to non-URL";

    new->fake = f; new->real = url;
    return NULL;
}


Apache HTTP Server Version 1.3

Index Home

Version française © Valery Fremaux / EISTI 1998