Fonctionnement Général du SAPI
Dans le code source de PHP, toutes les interactions spécifiques à un serveur sont encapsulées dans la répertoire SAPI. Chaque implémentation correspondante définit une structure sapi_module_struct. Bien que les mécanismes internes varient selon qu'il s'agisse d'une ligne de commande ou d'un serveur HTTP comme Apache, l'interface adopte un patron de conception similaire au modèle de méthode abstraite.
Lorsque PHP doit initialiser son contexte serveur, il invoque une fonction pointeur contenue dans cette structure. Par exemple :
// Initialisation via l'interface standardisée
php_interface_context.startup(¤t_interface_ref);
// Exemple pour un contexte linéaire
cli_server_data.init_function(&cli_server_data);
Cette approche garantit qu'une grande partie du moteur Zend reste indépendante du serveur hôte, délégant uniquement les tâches spécifiques à l'implémentation du SAPI concernée.
Intégration avec Apache Web Server
Lorsque PHP est déployé sous forme de module partagé (DSO) pour Apache, il fonctionne généralement via mod_php. Le chargement de ce module se fait typiquement au démarrage du serveur ou dynamiquement durant l'exécution sans nécessiter de recompilation.
Pour activer le module, une directive spécifique est ajoutée au fichier de configuration principal :
LoadModule php_module modules/libphp.so
Apache analyse ensuite ce fichier pour identifier les structures de type module présentes dans le binaire chargé. Voici une représentation simplifiée de la structure d'enregistrement d'un module étendu, où nous avons renommé les identifiants pour illustrer la personnalisation :
const apache_module_definition my_php_handler = {
// Métadonnées standards gérées par macro
AP_MODULE_MAGIC_TAG,
// Fonctions de configuration par dossier
create_per_directory_config,
merge_per_directory_configs,
NULL, /* Configuration serveur globale */
NULL,
// Commandes directives acceptées
php_directive_table,
// Enregistrement des points d'accroche (hooks)
register_integration_hooks
};
Afin de garantir la compatibilité avec la version 2.0 d'Apache, la macro AP_MODULE_MAGIC_TAG remplit automatiquement les champs relatifs à la version et à l'index du module. La table de commandes définit des directives personnalisées accessibles dans la configuration Apache :
const command_descriptor php_specific_commands[] = {
COMMAND_TAKE2("php_value", handler_write_value, OR_OPTIONS, "Modification de valeur"),
COMMAND_TAKE1("ini_dir_path", handler_set_ini_location, RSRC_CONF, "Chemin INI"),
{NULL}
};
Le processus d'intégration utilise également des hooks spécifiques pour orchestrer le cycle de vie :
void setup_php_hooks(apr_pool_t *pool) {
ap_register_hook_pre_config(pre_configuration_stage, NULL, APR_HOOK_MIDDLE);
ap_register_hook_post_config(post_configuration_stage, NULL, APR_HOOK_MIDDLE);
ap_register_hook_handler(request_processing_stage, NULL, APR_HOOK_MIDDLE);
}
Lors du déclenchement du hook post_config, le module PHP lance son propre moteur SAPI via sapi_startup, suivie de l'initialisation de l'environnement Zend.
Le Protocole FastCGI
Contrairement au CGI classique qui crée un nouveau processus pour chaque requête (modèle fork-and-execute), FastCGI introduit un modèle persistent. Un gestionnaire de processus FastCGI maintient en mémoire plusieurs interprètes prêts à traiter les connexions entrantes, réduisant ainsi la latence liée à la création de processus.
Le flux de travail idéal suit ces étapes :
- Initialisation du gestionnaire FastCGI au démarrage du serveur Web.
- Démarrage multiple de processus
php-fpmou équivalents en attente de connexions. - Attribution d'une requête client à un processus disponible.
- Transfert des données d'environnement et du script via le canal TCP/UDP.
- Renvoi des résultats HTTP par la même connexion persistante.
Implémentation Bas Niveau dans PHP (CGI)
L'extension CGI de PHP gère la réception des requêtes directement via des sockets réseaux. Le point d'entrée se situe dans la fonction principale du binaire, qui orchestre la boucle d'événements réseau. Au lieu d'utiliser des appels système bruts dispersés, PHP encapsule ces opérations pour faciliter la portabilité.
La séquence d'établissement de la connexion serveur inclut la création du socket, le collage (bind) sur une adresse locale, et l'écoute active :
int establish_server_socket(const char *socket_path) {
int listen_fd = -1;
struct sockaddr_storage address_info;
// Création du flux TCP
listen_fd = create_network_stream();
if (listen_fd != ERROR) {
bind_local_address(listen_fd, &address_info);
enable_listening_mode(listen_fd, QUEUE_LIMIT);
}
return listen_fd;
}
Une fois le socket prêt, le processus principle entre dans une boucle de traitement. Il utilise fork() pour isoler la gestion des demandes :
while (process_active) {
pid_t worker_pid = fork_process();
switch (worker_pid) {
case 0: // Processus enfant gérant la requête
ignore_termination_signals();
handle_incoming_client_request();
exit(STATUS_SUCCESS);
default: // Processus parent continue à surveiller
count_active_workers++;
break;
}
}
À l'intérieur de la fonction de gession de requête, le blocage sur l'acceptation de socket se produit. Dès qu'une connexion est établie, les en-têtes et le corps de la requête sont lus séquentiellement :
void fetch_client_payload(fd_descriptor file_handle, void *buffer, size_t size) {
size_t bytes_read = 0;
while (bytes_read < size) {
ssize_t chunk = read_from_fd(file_handle, buffer + bytes_read, size - bytes_read);
if (chunk <= 0) break;
bytes_read += chunk;
}
}
Après l'exécution du script PHP et la génération de la réponse, la routine de nettoyage est appelée pour finaliser la transaction avant de retourner à l'état d'attente de la prochaine connexion. Cette architecture permet à PHP de fonctionner efficacement tant en mode CGI simple qu'en environnement modulaire complexe.