Mots-clé : modules

Dix règles d'or pour les filtres de sortie.

Le contenu en Anglais non traduit se trouve ici.

Voilà les règles que doivent suivre à la lettre les filtres de sortie :

  1. Les filtres de sortie ne devraient pas passer de brigades vides le long de la chaine de sortie, mais devraient être tolérantes sur l’arrivée de brigades vides.
  2. Les filtres de sortie doivent passer tous les seaux de métadonnées (metadata buckets) le long de la chaine de sortie ; les seaux de vidages (FLUSH buckets) devraient être respectés en passant tous les seaux en attentes le long de la chaine de sortie.
  3. Les filtres de sortie devraient ignorer tous les seaux qui suivent un seaux de fin de fichier (EOS buckets).
  4. Les filtres de sortie doivent traiter une quantité fixe de données, à la fois, afin de s’assurer que la consommation mémoire n’est pas proportionelle à la taille du contenu qui est filtré.
  5. Les filtres de sortie devraient ignorer les types de seaux, et doivent être capables de traiter des seaux de type inconnu.
  6. Après avoir appelé ap_pass_brigade pour faire suivre une brigade le long de la chaine de filtres, les filtres de sortie devraient appeler apr_brigade_cleanup pour s’assurer que la brigade est vide avant de s’en resservir ; les filtres ne devraient jamais utiliser apr_brigade_destroy pour « détruire » des brigades.
  7. Les filtres de sortie doivent mettre de côté les seaux qui sont destinés à être gardés plus longtemps que la durée du filtrage.
  8. Les filtres de sortie ne doivent pas ignorer la valeur de retour de ap_pass_brigade, et doivent renvoyer les erreurs appropriées en retour, à la chaine de filtres.
  9. Les filtres de sortie doivent seulement créer un nombre fixe de brigades de seaux pour chaque réponse, plutôt que une par invocation.
  10. Les filtres de sortie devraient en tout premier lieu essayer des lectures non bloquantes sur chaque seau, et envoyer un buffer de vidage (FLUSH bucket) le long de la chaine de filtres si la lecture doit être faite à nouveau, et de manière bloquante, avant de recommencer une lecture bloquante.

Sachant que la règle 9 n’est pas très claire pour moi, je vous la relivre en Anglais :
Output filters must only create a fixed number of bucket brigades for each response, rather than one per invocation.

Les filtres Apache

Il existe plusieurs filtres, qui sont passés dans l’ordre lorsqu’ils arrivent au serveur, puis sont passés exactement dans l’ordre inverse lorsqu’ils repartent vers le client (un seul m’intéresse, le premier : à l’arrivé ce sont les filtre d’entrée, et lorsque cela repart ce sont les filtres de sortie) :

  1. AP_FTYPE_RESOURCE
    Pour les filtres de contenu : cela englobe tous les types de filtres qui peuvent modifier le contenu, cela va de la décomposition de pages XML qui arrivent, à la transformation d’images ou encore à l’assemblage de contenu qui arrive en plusieurs fois. Ce type de filtre peut changer complètement la nature du contenu de la requête. Par exemple, un filtre XSLT peut transformer le contenu d’une page XML en une page HTML ou en un fichier PDF ;
  2. AP_FTYPE_CONTENT_SET
    C’est la seconde étape du filtrage du contenu, qui est principalement destinée à empaqueter le contenu, par exemple le module mod_deflate (qui applique une compression de type gzip) ;
  3. AP_FTYPE_PROTOCOL
    Le troisième filtre, : le filtre habituel a pour fonction d’insérer les entêtes HTTP au début des données qui ressortent des filtres de contenu. C’est réalisé par un filtre intégré nativement à Apache : le filtre HTTP_HEADER(la fonction ap_http_header_filter exactement), donc les applications, en général, l’ignorent.
  4. AP_FTYPE_TRANSCODE
    On s’en tape, ça ne sert pratiquement jamais.
  5. AP_FTYPE_CONNECTION
    On s’en tape, ça ne sert pratiquement jamais.
  6. AP_FTYPE_NETWORK
    On s’en tape, ça ne sert pratiquement jamais.

Le redirection interne

Il est possible dans Apache, lors de la gestion de la requête, de faire une redirection interne, c’est à dire de faire croire qu’il y a eu une redirection, mais au lieu de la renvoyer au client, et que le client demande la nouvelle URL, elle est faite en interne. Pour pouvoir faire cela, deux possibilités :

  1. Location: http://olivier.pons.free.fr/apache/
  2. Location: /apache/

La première est basée sur la nature double de l’entête Location d’un script CGI : elle force à envoyer une redirection au client, alors que la seconde fait une redirection en interne, sans envoyer quelque chose au client.

Seule la seconde possibilité nous intéresse. Il est possible de le faire de deux façons dans un module :

  1. void ap_internal_redirect(
      const char *new_uri, request_rec *r)
    .

    Cette forme de redirection interne est le mécanisme à utiliser dans tous les cas à de rares exceptions près. Cette fonction crée un nouvel objet requête pour l’URL passée en paramètre, et lance la nouvelle requête comme si new_uri avait été demandée en premier lieu.

  2. void ap_internal_fast_redirect(
      request_rec *new_req, request_rec *r)

    Cette fonction clone une structure existante new_req dans la requête en cours afin de pouvoir mettre en place une nouvelle requête et simuler le fait qu’on lui passe le contrôle. Ce mécanisme est parfois utilisé pour promouvoir une sous-requête en requête principale. Ce qui est important de noter c’est qu’on ne repart pas de zéro, donc on ne refait pas complètement la requête.

MPM : Multi-Processing-Modules

A la fin de la phase de démarrage, après que la configuration ait été lue, le contrôle général d’Apache est passé à un module de gestion de processus, le Multi-Processing-Module.

Un MPM est une interface entre le serveur Apache qui tourne et l’OS sous-jacent. Son rôle principal est d’optimiser Apache en fonction de la plateforme sur laquelle le serveur tourne. Comme indiqué dans le titre, le MPM est lui-même un module. C’est, à l’inverse de tous les autres, modules, un module au niveau système et non pas un module au niveau applicatif.

Il exite plusieurs MPM‘s.

  • MPM Prefork : c’est un modèle non orienté thread, ressemblant fortement aux mode de fonctionnement des serveurs Apache 1.x
  • MPM Worker : modèle orienté thread. Il a une plus grande souplesse et s’adapte beaucoup mieux en fonction de la demande. Il est particulièrement plus performant que le modèle précédent, notamment avec certains types d’applications.

Tous les deux souffrent d’une limite qui affecte tous les serveurs très demandés. Alors que le HTTP Keepalive est nécéssaire pour réduite le nombre de connexions TCP et la surcharge réseau, il « ligotte » le processus ou le thread d’un serveur pendant toute la durée de la connexion, tant que le Keepalive est actif.

Le nombre de threads disponibles est une ressource critique pour la plateforme sur laquelle est le serveur. Par exemple, un serveur basé sur le module MPM Worker est réellement occupé s’il doit se charger de gérer plusieurs dizaines de milliers de demandes simultanément. L’OS sous-jacent peut parfois arriver à court de threads.

C’est pourquoi un nouveau MPM est en cours de développement. Il en est à un stade avancé, mais pas encore au stade de production, stade qu’ont atteint les deux modules cités précédemment. C’est le MPM basé sur des événements, le Event MPM. Notez bien que ce type de module ne fonctionnera pas en mode sécurisé (https). Par contre, il aura la possiblité de gérer un nombre nettement plus important de hits que les autres modules.

Apache : le lancement du processus

Le démarrage d’Apache se décompose en deux temps :

  1. démarrage ;
  2. mode opérationnel.

C’est à dire que quelque chose peut porter à confusion, mais c’est normal : le code de configuration est appelé deux fois.

La première fois c’est uniquement pour vérifier si toute la configuration est valide, et la seconde fois, c’est pour le démarrage réel, la phase « opérationnelle ».

La plupart des modules peuvent ignorer ce comportement, mais il possible que, par exemple, si votre module charge du code dynamiquement au démarrage, il n’ait pas besoin de le faire deux fois. Les modules qui veulent faire cela peuvent, par exemple, déclarer un drapeau statique et, vérifier lors de l’appel, s’il est déjà initialisé. Si ce n’est pas le cas, le mettre à vrai, et charger tout ce qu’il faut. Dans les autres cas, ne rien faire.

Notez bien qu’au démarrage, Apache n’a qu’un seul thread actif et est root, ce qui signifie qu’il est possible de faire tout et n’importe quoi, mais après le démarrage, il ne sera plus jamais root, par mesure de sécurité.

Développement de module

Ce paragraphe est un résumé de mes notes personnelles, pour que je puisse m’en sortir si je reconsulte ce que j’ai fait dans 6 mois.

Lorsqu’on veut développer un module, il va falloir taper du code. Celui-ci se trouvera à plusieurs endroits possibles (dans le cadre du développement d’un module) :

  • lors des phases de lecture de configuration ;
  • lors des phases de gestion de la requête client.

Le gestion de la requête client, elle-même, se décompose en plusieurs phases et il faudra préciser parmi laquelle de ces phases, il faudra appeler une routine qu’on aura développé. Le problème c’est que les créateurs d’Apache ont tellement optimisé le code, qu’il y a des rotations qui se font entre les appels des fonctions, alors ils ont décidés de créer, grossièrement, des « groupes » auxquels on pourra associer nos fonctions. Par exemple, si on veut qu’une fonction A soit appelée de manière certaine avant une fonction B, on dit que la fonction A doit appartenir au groupe HOOK_REALLY_FIRST, et la B au groupe HOOK_MIDDLE, qui sera éxécuté forcément après. Il faut bien avoir en tête que si jamais un autre mec fait un module en C dans lequel il a enregistré une fonction A’ dans le groupe HOOK_REALLY_FIRST, il sera possible, que dans certains cas sa fonction A’ soit appelée avant la fonction A, et dans d’autres cas, l’inverse. Ce dont on est sûr, c’est que la B sera éxécutée après les fonctions du groupe HOOK_REALLY_FIRST.

Apache : les générateurs de contenu

Un et un seul générateur de contenu est éxécuté pour chaque requête HTTP.

N’importe quel module peut s’inscrire en tant que générateur de contenu, en définissant une fonction à appeler. Habituellement, on le fait en activant un « handler » qui peut être configuré via les directives SetHandler et AddHandler dans le fichier de configuration httpd.conf.

Le générateur de contenu par défaut, qui est utilisé lorsqu’aucun générateur n’a été défini, renvoie simplement un fichier, directement récupéré à partir de la requête, dans le système de fichiers de la plateforme sur lequel le serveur tourne.

Les modules qui implémentent des générateurs de contenu sont appelés des générateurs de contenu, en Anglais content generator, ou encore le plus couramment, et c’est le point le plus important, des modules handler.

Apache : où mettre les routines de son module ?

Lors d’une requête, plusieurs routines (hooks) sont appelés successivement, et c’est à ce moment là que l’on peut, ou non, développer du code dans le module que l’on écrit :

  1. post_read_request :
    C’est habituellement le premier hook disponible.
    Il est disponible pour tous les modules qui veulent vraiment gérer la requête le plus tôt possible.
  2. translate_name :
    C’est lorsqu’Apache transforme l’URL de la requête en nom de fichier.
    Un module peut placer du code pour ce hook afin d’y mettre sa propre logique de transformation.
    C’est ce que fait le module mod_alias, par exemple.
  3. map_to_storage :
    Maintenant que l’URL de la requête a été transformée en nom de fichier, on y applique les directives de configuration des répertoires définies dans httpd.conf. Ce sont les directives <Directory>, <Files> et les ordres dans les fichiers <.htacces>.
    C’est grâce à ce hook qu’Apache applique les options spécifiques à la requête en cours. Il applique les directives de configuration de tous les modules actifs, donc peu de modules ont du code à développer par ici. Le seul module officiel qui fait quelque chose est le module mod_proxy.
  4. header_parser :
    C’est ici que l’on inspecte ce qu’il y a dans les en-têtes de la requête.
    On développe un hook très rarement par ici, pour la simple et bonne raison qu’il est possible d’inspecter, utiliser et modifier les en-têtes à n’importe quelle phase, dans un autre hook.
    Le module mod_setenvif est le module standard qui utilise un header_parser pour initialiser certaines variables d’environnement en fonction des entêtes.
  5. access_checker :
    C’est ici qu’Apache détermine si l’accès à la ressource demandée est autorisé ou non. Un autre module peut venir s’ajouter ou remplacer le module standard mod_access (httpd 1.x et 2.0) ou mod_authz_host (httpd 2.2), qui implémente les directives Allow/Deny From.
  6. check_user_id :
    Si une méthode d’authentification a été activée, alors c’est maintenant qu’elle sera appliquée, et que le champ r->user sera initialisé en conséquence.
    Un module peut implémenter ici une méthode d’authentification.
  7. auth_checker :
    Ce hook vérifie si l’utilisateur authentifié a le droit d’effectuer l’opération demandée.
  8. type_checker :
    On applique les règles (quand on le peut) en fonction du type MIME et des informations demandées, et on détermine, si ce n’est pas encore fait, le générateur de contenu à utiliser. Les modules stantards qui implémentent ce hook sont mod_negociation et mod_mime (ce dernier définit le type MIME et les informations à partir du type qui a été déterminé, afin choisir le générateur de contenu, en fonction des directives de configuration standards telles que, par exemple, les extensions des noms de fichier)
  9. fixups :
    Ce hook très générique offre la possibilité de faire quelque chose après tout ce qui a été fait précédemment, et juste avant l’appel du générateur de contenu.
    C’est l’un des hook les plus implémentés par les développeurs de modules.
  10. handler :
    Le fameux et célèbre hook « générateur de contenu !
    Il est entièrement responsable de ce qui est renvoyé au client.
    S’il y a des données envoyées par le client, il est aussi responsable de la lecture et de leur exploitation.
    A l’inverse des autres hooks, durant lesquels plusieurs fonctions de plusieurs modules développés peuvent être appelées, ici, il ne peut y avoir qu’un seul et unique handler. Une seule fonction est appelée, d’un module donné, en fonction de tous les autres modules appelés précédemment.
  11. log_transaction :
    Ce hook enregistre dans des fichiers de suivi (des logs), la transaction qui vient de se terminer. Notez bien que c’est après avoir envoyé la réponse au client. Un module peut venir ajouter une fonction ici, ou remplacer la fonction hook standard d’Apache.

NB : tous ces hooks ont une définition de fonction correspondante. Absolument tous. La plupart se ressemblent, mais pas tous ! En général :

  • les hooks qui sont appelés pendant la phase de gestion d’une requête ressemblent à :
    static int my_func(request_rec* r) {
    ...
    }
  • les hooks qui sont appelés pendant les phases d’initialisation et de configuration ressemblent à :
    static int my_cfg(cmd_parms* cmd, void* cfg, /*args */) {
    ...
    }
  • les hooks qui sont appelés pendant les phases de pré-initialisation et de post-configuration ressemblent à :
    static int my_cfg(apr_pool_t* pool, apr_pool_t* plog,
    apr_pool_t* ptemp) {
    ...
    }
  • le hook qui est appelé pendant la phase de création d’un processus fils ressemble à :
    static void my_child_init(apr_pool_t* pool, server_rec* s) {
    ...
    }

    Sachant que le pool passé en argument est le pool du fils.

Notez qu’il est possible de « créer » ses propres hooks, mais c’est quelque chose de plus complexe et ici on est dans un résumé, pas un cours complet.