AMPHP est une collection de bibliothèques événementielles pour PHP conçues avec les fibres et la concurrence à l'esprit. Ce package fournit un serveur d'applications HTTP/1.1 et HTTP/2 simultané et non bloquant pour PHP basé sur Revolt. Plusieurs fonctionnalités sont fournies dans des packages distincts, tels que le composant WebSocket.
Ce package peut être installé en tant que dépendance de Composer.
composer require amphp/http-server
De plus, vous souhaiterez peut-être installer la bibliothèque nghttp2
pour profiter de FFI afin d'accélérer et de réduire l'utilisation de la mémoire.
Cette bibliothèque permet d'accéder à votre application via le protocole HTTP, en acceptant les demandes des clients et en transmettant ces demandes aux gestionnaires définis par votre application qui renverront une réponse.
Les requêtes entrantes sont représentées par des objets Request
. Une requête est fournie à un implémenteur de RequestHandler
, qui définit une méthode handleRequest()
renvoyant une instance de Response
.
public function handleRequest( Request $ request ): Response
Les gestionnaires de requêtes sont traités plus en détail dans la section RequestHandler
.
Ce serveur HTTP est construit sur la boucle d'événements Revolt et le cadre de concurrence non bloquant Amp. Ainsi, il hérite du support total de toutes leurs primitives et il est possible d'utiliser toutes les bibliothèques non bloquantes construites sur Revolt.
Remarque De manière générale, vous devez vous familiariser avec le concept
Future
, avec les coroutines, et connaître les différentes fonctions du combinateur pour vraiment réussir à utiliser le serveur HTTP.
Presque toutes les fonctions intégrées de PHP bloquent les E/S, ce qui signifie que le thread en cours d'exécution (essentiellement équivalent au processus dans le cas de PHP) sera effectivement arrêté jusqu'à ce que la réponse soit reçue. Quelques exemples de telles fonctions : mysqli_query
, file_get_contents
, usleep
et bien d'autres.
Une bonne règle de base est la suivante : chaque fonction PHP intégrée effectuant des E/S le fait de manière bloquante, à moins que vous ne soyez sûr que ce n'est pas le cas.
Il existe des bibliothèques fournissant des implémentations qui utilisent des E/S non bloquantes. Vous devez les utiliser à la place des fonctions intégrées.
Nous couvrons les besoins d'E/S les plus courants, tels que les sockets réseau, l'accès aux fichiers, les requêtes HTTP et les websockets, les clients de bases de données MySQL et Postgres et Redis. Si l'utilisation d'E/S bloquantes ou de longs calculs est nécessaire pour répondre à une requête, envisagez d'utiliser la bibliothèque Parallel pour exécuter ce code dans un processus ou un thread distinct.
Avertissement N'utilisez pas de fonctions d'E/S bloquantes dans le serveur HTTP.
// Here's a bad example, DO NOT do something like the following!
$ handler = new ClosureRequestHandler ( function () {
sleep ( 5 ); // Equivalent to a blocking I/O function with a 5 second timeout
return new Response ;
});
// Start a server with this handler and hit it twice.
// You'll have to wait until the 5 seconds are over until the second request is handled.
Votre application sera servie par une instance de HttpServer
. Cette bibliothèque fournit SocketHttpServer
, qui conviendra à la plupart des applications, construit sur des composants trouvés dans cette bibliothèque et dans amphp/socket
.
Pour créer une instance de SocketHttpServer
et écouter les requêtes, au minimum quatre éléments sont requis :
RequestHandler
pour répondre aux requêtes entrantes,ErrorHander
pour fournir des réponses aux requêtes invalides,PsrLogLoggerInterface
, et <?php
use Amp ByteStream ;
use Amp Http HttpStatus ;
use Amp Http Server DefaultErrorHandler ;
use Amp Http Server Request ;
use Amp Http Server RequestHandler ;
use Amp Http Server Response ;
use Amp Http Server SocketHttpServer ;
use Amp Log ConsoleFormatter ;
use Amp Log StreamHandler ;
use Monolog Logger ;
use Monolog Processor PsrLogMessageProcessor ;
require __DIR__ . ' /vendor/autoload.php ' ;
// Note any PSR-3 logger may be used, Monolog is only an example.
$ logHandler = new StreamHandler ( ByteStream getStdout ());
$ logHandler -> pushProcessor ( new PsrLogMessageProcessor ());
$ logHandler -> setFormatter ( new ConsoleFormatter ());
$ logger = new Logger ( ' server ' );
$ logger -> pushHandler ( $ logHandler );
$ requestHandler = new class () implements RequestHandler {
public function handleRequest ( Request $ request ) : Response
{
return new Response (
status: HttpStatus:: OK ,
headers: [ ' Content-Type ' => ' text/plain ' ],
body: ' Hello, world! ' ,
);
}
};
$ errorHandler = new DefaultErrorHandler ();
$ server = SocketHttpServer:: createForDirectAccess ( $ logger );
$ server -> expose ( ' 127.0.0.1:1337 ' );
$ server -> start ( $ requestHandler , $ errorHandler );
// Serve requests until SIGINT or SIGTERM is received by the process.
Amp trapSignal ([ SIGINT , SIGTERM ]);
$ server -> stop ();
L'exemple ci-dessus crée un serveur simple qui envoie une réponse en texte brut à chaque demande reçue.
SocketHttpServer
fournit deux constructeurs statiques pour les cas d'utilisation courants en plus du constructeur normal pour des utilisations plus avancées et personnalisées.
SocketHttpServer::createForDirectAccess()
: utilisé dans l'exemple ci-dessus, cela crée un serveur d'applications HTTP adapté à un accès direct au réseau. Des limites réglables sont imposées sur les connexions par IP, le nombre total de connexions et les demandes simultanées (10, 1 000 et 1 000 par défaut, respectivement). La compression des réponses peut être activée ou désactivée (activée par défaut) et les méthodes de requête sont limitées par défaut à un ensemble connu de verbes HTTP.SocketHttpServer::createForBehindProxy()
: crée un serveur approprié à utiliser derrière un service proxy tel que nginx. Ce constructeur statique nécessite une liste d'adresses IP proxy de confiance (avec des masques de sous-réseau facultatifs) et un cas d'énumération de ForwardedHeaderType
(correspondant à Forwarded
ou X-Forwarded-For
) pour analyser l'adresse IP du client d'origine à partir des en-têtes de requête. Aucune limite n'est imposée sur le nombre de connexions au serveur, cependant le nombre de requêtes simultanées est limité (1000 par défaut, réglable ou supprimable). La compression des réponses peut être activée ou désactivée (activée par défaut). Les méthodes de requête sont limitées par défaut à un ensemble connu de verbes HTTP. Si aucune de ces méthodes ne répond aux besoins de votre application, le constructeur SocketHttpServer
peut être utilisé directement. Cela offre une énorme flexibilité dans la façon dont les connexions client entrantes sont créées et gérées, mais nécessitera plus de code pour être créé. Le constructeur demande à l'utilisateur de transmettre une instance de SocketServerFactory
, utilisée pour créer des instances Socket
client (les deux composants de la bibliothèque amphp/socket
) et une instance de ClientFactory
, qui crée de manière appropriée des instances Client
qui sont attachées à chaque Request
effectuée par le client. .
RequestHandler
Les requêtes entrantes sont représentées par des objets Request
. Une requête est fournie à un implémenteur de RequestHandler
, qui définit une méthode handleRequest()
renvoyant une instance de Response
.
public function handleRequest( Request $ request ): Response
Chaque requête client (c'est-à-dire l'appel à RequestHandler::handleRequest()
) est exécutée dans une coroutine distincte afin que les requêtes soient automatiquement traitées de manière coopérative au sein du processus serveur. Lorsqu'un gestionnaire de requêtes attend des E/S non bloquantes, les autres requêtes client sont traitées dans des coroutines simultanées. Votre gestionnaire de requêtes peut lui-même créer d'autres coroutines à l'aide de Ampasync()
pour exécuter plusieurs tâches pour une seule requête.
Habituellement, un RequestHandler
génère directement une réponse, mais il peut également déléguer à un autre RequestHandler
. Un exemple d'un tel RequestHandler
déléguant est le Router
.
L'interface RequestHandler
est destinée à être implémentée par des classes personnalisées. Pour des cas d'utilisation très simples ou une moquerie rapide, vous pouvez utiliser CallableRequestHandler
, qui peut envelopper n'importe quel callable
et accepter un Request
et renvoyer un Response
.
Le middleware permet le pré-traitement des demandes et le post-traitement des réponses. En dehors de cela, un middleware peut également intercepter le traitement de la requête et renvoyer une réponse sans déléguer au gestionnaire de requête transmis. Les classes doivent implémenter l'interface Middleware
pour cela.
Remarque Middleware suit généralement d'autres mots comme soft- et hardware avec son pluriel. Cependant, nous utilisons le terme middleware pour désigner plusieurs objets implémentant l’interface
Middleware
.
public function handleRequest( Request $ request , RequestHandler $ next ): Response
handleRequest
est la seule méthode de l'interface Middleware
. Si le Middleware
ne gère pas la requête lui-même, il doit déléguer la création de la réponse au RequestHandler
reçu.
function stackMiddleware( RequestHandler $ handler , Middleware ... $ middleware ): RequestHandler
Plusieurs middlewares peuvent être empilés en utilisant AmpHttpServerMiddlewarestackMiddleware()
, qui accepte un RequestHandler
comme premier argument et un nombre variable d'instances Middleware
. Le RequestHandler
renvoyé appellera chaque middleware dans l’ordre fourni.
$ requestHandler = new class implements RequestHandler {
public function handleRequest ( Request $ request ): Response
{
return new Response (
status: HttpStatus:: OK ,
headers: [ " content-type " => " text/plain; charset=utf-8 " ],
body: " Hello, World! " ,
);
}
}
$ middleware = new class implements Middleware {
public function handleRequest ( Request $ request , RequestHandler $ next ): Response
{
$ requestTime = microtime ( true );
$ response = $ next -> handleRequest ( $ request );
$ response -> setHeader ( " x-request-time " , microtime ( true ) - $ requestTime );
return $ response ;
}
};
$ stackedHandler = Middleware stackMiddleware ( $ requestHandler , $ middleware );
$ errorHandler = new DefaultErrorHandler ();
// $logger is a PSR-3 logger instance.
$ server = SocketHttpServer:: createForDirectAccess ( $ logger );
$ server -> expose ( ' 127.0.0.1:1337 ' );
$ server -> start ( $ stackedHandler , $ errorHandler );
ErrorHandler
Un ErrorHander
est utilisé par le serveur HTTP lorsqu'une demande mal formée ou non valide est reçue. L'objet Request
est fourni s'il est construit à partir des données entrantes, mais il ne peut pas toujours être défini.
public function handleError(
int $ status ,
? string $ reason = null ,
? Request $ request = null ,
): Response
Cette bibliothèque fournit DefaultErrorHandler
qui renvoie une page HTML stylisée comme corps de réponse. Vous souhaiterez peut-être fournir une implémentation différente pour votre application, en utilisant potentiellement plusieurs en conjonction avec un routeur.
Request
Il est rare que vous ayez besoin de construire un objet Request
vous-même, car ils seront généralement fournis à RequestHandler::handleRequest()
par le serveur.
/**
* @param string $method The HTTP method verb.
* @param array<string>|array<string, array<string>> $headers An array of strings or an array of string arrays.
*/
public function __construct(
private readonly Client $ client ,
string $ method ,
Psr Http Message UriInterface $ uri ,
array $ headers = [],
Amp ByteStream ReadableStream | string $ body = '' ,
private string $ protocol = ' 1.1 ' ,
? Trailers $ trailers = null ,
)
public function getClient(): Client
Renvoie le Сlient
qui envoie la demande
public function getMethod(): string
Renvoie la méthode HTTP utilisée pour effectuer cette requête, par exemple "GET"
.
public function setMethod( string $ method ): void
Définit la méthode HTTP de la requête.
public function getUri(): Psr Http Message UriInterface
Renvoie l' URI
de la requête.
public function setUri( Psr Http Message UriInterface $ uri ): void
Définit un nouvel URI
pour la demande.
public function getProtocolVersion(): string
Renvoie la version du protocole HTTP sous forme de chaîne (par exemple "1.0", "1.1", "2").
public function setProtocolVersion( string $ protocol )
Définit un nouveau numéro de version de protocole pour la demande.
/** @return array<non-empty-string, list<string>> */
public function getHeaders(): array
Renvoie les en-têtes sous la forme d'un tableau de tableaux de chaînes indexé par chaîne ou d'un tableau vide si aucun en-tête n'a été défini.
public function hasHeader( string $ name ): bool
Vérifie si l'en-tête donné existe.
/** @return list<string> */
public function getHeaderArray( string $ name ): array
Renvoie le tableau de valeurs pour l'en-tête donné ou un tableau vide si l'en-tête n'existe pas.
public function getHeader( string $ name ): ? string
Renvoie la valeur de l'en-tête donné. Si plusieurs en-têtes sont présents pour l’en-tête nommé, seule la première valeur d’en-tête sera renvoyée. Utilisez getHeaderArray()
pour renvoyer un tableau de toutes les valeurs d'un en-tête particulier. Renvoie null
si l'en-tête n'existe pas.
public function setHeaders( array $ headers ): void
Définit les en-têtes du tableau donné.
/** @param array<string>|string $value */
public function setHeader( string $ name , array | string $ value ): void
Définit l'en-tête sur la ou les valeurs données. Toutes les lignes d'en-tête précédentes portant le nom donné seront remplacées.
/** @param array<string>|string $value */
public function addHeader( string $ name , array | string $ value ): void
Ajoute une ligne d'en-tête supplémentaire avec le nom donné.
public function removeHeader( string $ name ): void
Supprime l'en-tête donné s'il existe. S'il existe plusieurs lignes d'en-tête portant le même nom, elles sont toutes supprimées.
public function getBody(): RequestBody
Renvoie le corps de la requête. Le RequestBody
permet un accès en streaming et en tampon à un InputStream
.
public function setBody( ReadableStream | string $ body )
Définit le flux pour le corps du message
Remarque L'utilisation d'une chaîne définira automatiquement l'en-tête
Content-Length
sur la longueur de la chaîne donnée. La définition d’unReadableStream
supprimera l’en-têteContent-Length
. Si vous connaissez la longueur exacte du contenu de votre flux, vous pouvez ajouter un en-tête decontent-length
après avoir appelésetBody()
.
/** @return array<non-empty-string, RequestCookie> */
public function getCookies(): array
Renvoie tous les cookies dans la carte associative du nom du cookie à RequestCookie
.
public function getCookie( string $ name ): ? RequestCookie
Obtient une valeur de cookie par nom ou null
.
public function setCookie( RequestCookie $ cookie ): void
Ajoute un Cookie
à la requête.
public function removeCookie( string $ name ): void
Supprime un cookie de la requête.
public function getAttributes(): array
Renvoie un tableau de tous les attributs stockés dans le stockage local mutable de la requête.
public function removeAttributes(): array
Supprime tous les attributs de requête du stockage local mutable de la requête.
public function hasAttribute( string $ name ): bool
Vérifiez si un attribut portant le nom donné existe dans le stockage local mutable de la demande.
public function getAttribute( string $ name ): mixed
Récupérez une variable du stockage local mutable de la requête.
Remarque Le nom de l'attribut doit être entouré d'un espace de noms de fournisseur et de package, comme les classes.
public function setAttribute( string $ name , mixed $ value ): void
Attribuez une variable au stockage local mutable de la requête.
Remarque Le nom de l'attribut doit être entouré d'un espace de noms de fournisseur et de package, comme les classes.
public function removeAttribute( string $ name ): void
Supprime une variable du stockage local mutable de la requête.
public function getTrailers(): Trailers
Permet d'accéder aux Trailers
d'une requête.
public function setTrailers( Trailers $ trailers ): void
Attribue l'objet Trailers
à utiliser dans la requête.
Les détails liés au client sont regroupés dans les objets AmpHttpServerDriverClient
renvoyés par Request::getClient()
. L'interface Client
fournit des méthodes pour récupérer les adresses de socket distantes et locales et les informations TLS (le cas échéant).
Response
La classe Response
représente une réponse HTTP. Une Response
est renvoyée par les gestionnaires de requêtes et le middleware.
/**
* @param int $code The HTTP response status code.
* @param array<string>|array<string, array<string>> $headers An array of strings or an array of string arrays.
*/
public function __construct(
int $ code = HttpStatus:: OK ,
array $ headers = [],
Amp ByteStream ReadableStream | string $ body = '' ,
? Trailers $ trailers = null ,
)
Appelle les gestionnaires de suppression (c'est-à-dire les fonctions enregistrées via la méthode onDispose()
).
Remarque Les exceptions non interceptées provenant des gestionnaires de suppression seront transmises au gestionnaire d'erreurs de boucle d'événements.
public function getBody(): Amp ByteStream ReadableStream
Renvoie le flux du corps du message.
public function setBody( Amp ByteStream ReadableStream | string $ body )
Définit le flux pour le corps du message.
Remarque L'utilisation d'une chaîne définira automatiquement l'en-tête
Content-Length
sur la longueur de la chaîne donnée. La définition d’unReadableStream
supprimera l’en-têteContent-Length
. Si vous connaissez la longueur exacte du contenu de votre flux, vous pouvez ajouter un en-tête decontent-length
après avoir appelésetBody()
.
/** @return array<non-empty-string, list<string>> */
public function getHeaders(): array
Renvoie les en-têtes sous la forme d'un tableau de tableaux de chaînes indexé par chaîne ou d'un tableau vide si aucun en-tête n'a été défini.
public function hasHeader( string $ name ): bool
Vérifie si l'en-tête donné existe.
/** @return list<string> */
public function getHeaderArray( string $ name ): array
Renvoie le tableau de valeurs pour l'en-tête donné ou un tableau vide si l'en-tête n'existe pas.
public function getHeader( string $ name ): ? string
Renvoie la valeur de l'en-tête donné. Si plusieurs en-têtes sont présents pour l’en-tête nommé, seule la première valeur d’en-tête sera renvoyée. Utilisez getHeaderArray()
pour renvoyer un tableau de toutes les valeurs d'un en-tête particulier. Renvoie null
si l'en-tête n'existe pas.
public function setHeaders( array $ headers ): void
Définit les en-têtes du tableau donné.
/** @param array<string>|string $value */
public function setHeader( string $ name , array | string $ value ): void
Définit l'en-tête sur la ou les valeurs données. Toutes les lignes d'en-tête précédentes portant le nom donné seront remplacées.
/** @param array<string>|string $value */
public function addHeader( string $ name , array | string $ value ): void
Ajoute une ligne d'en-tête supplémentaire avec le nom donné.
public function removeHeader( string $ name ): void
Supprime l'en-tête donné s'il existe. S'il existe plusieurs lignes d'en-tête portant le même nom, elles sont toutes supprimées.
public function getStatus(): int
Renvoie le code d'état de la réponse.
public function getReason(): string
Renvoie la phrase de motif décrivant le code d'état.
public function setStatus( int $ code , string | null $ reason ): void
Définit le code d'état HTTP numérique (entre 100 et 599) et la phrase de raison. Utilisez null pour la phrase de motif afin d'utiliser la phrase par défaut associée au code d'état.
/** @return array<non-empty-string, ResponseCookie> */
public function getCookies(): array
Renvoie tous les cookies dans une carte associative du nom du cookie à ResponseCookie
.
public function getCookie( string $ name ): ? ResponseCookie
Obtient une valeur de cookie par nom ou null
si aucun cookie portant ce nom n'est présent.
public function setCookie( ResponseCookie $ cookie ): void
Ajoute un cookie à la réponse.
public function removeCookie( string $ name ): void
Supprime un cookie de la réponse.
/** @return array<string, Push> Map of URL strings to Push objects. */
public function getPushes(): array
Renvoie la liste des ressources Push dans une carte associative de chaînes d'URL vers des objets Push
.
/** @param array<string>|array<string, array<string>> $headers */
public function push( string $ url , array $ headers ): void
Indiquez les ressources dont un client a probablement besoin pour récupérer. (par exemple Link: preload
ou HTTP/2 Server Push).
public function isUpgraded(): bool
Renvoie true
si un rappel de détachement a été défini, false
s'il n'y en a pas.
/** @param Closure(DriverUpgradedSocket, Request, Response): void $upgrade */
public function upgrade( Closure $ upgrade ): void
Définit un rappel à appeler une fois que la réponse a été écrite sur le client et modifie l'état de la réponse en 101 Switching Protocols
. Le rappel reçoit une instance de DriverUpgradedSocket
, la Request
qui a initié la mise à niveau et cette Response
.
Le rappel peut être supprimé en modifiant le statut en autre chose que 101.
public function getUpgradeCallable(): ? Closure
Renvoie la fonction de mise à niveau si elle est présente.
/** @param Closure():void $onDispose */
public function onDispose( Closure $ onDispose ): void
Enregistre une fonction qui est invoquée lorsque la réponse est ignorée. Une réponse est rejetée soit une fois qu'elle a été écrite sur le client, soit si elle est remplacée dans une chaîne middleware.
public function getTrailers(): Trailers
Permet d'accéder aux Trailers
d'une réponse.
public function setTrailers( Trailers $ trailers ): void
Attribue l'objet Trailers
à utiliser dans la réponse. Les bandes-annonces sont envoyées une fois que l'intégralité du corps de la réponse a été définie pour le client.
RequestBody
, renvoyé par Request::getBody()
, fournit un accès tamponné et diffusé au corps de la requête. Utilisez l'accès en streaming pour gérer les messages volumineux, ce qui est particulièrement important si vous avez des limites de messages plus importantes (comme des dizaines de mégaoctets) et que vous ne souhaitez pas tout mettre en mémoire tampon. Si plusieurs personnes téléchargent des fichiers volumineux en même temps, la mémoire risque de s'épuiser rapidement.
Par conséquent, la gestion incrémentielle est importante, accessible via l'API read()
de AmpByteStreamReadableStream
.
Dans le cas où un client se déconnecte, read()
échoue avec une AmpHttpServerClientException
. Cette exception est levée pour les API read()
et buffer()
.
Remarque
ClientException
n'ont pas besoin d'être interceptées. Vous pouvez les attraper si vous souhaitez continuer, mais ce n'est pas obligatoire. Le serveur mettra fin silencieusement au cycle de requête et éliminera alors cette exception.
Au lieu de définir une limite de corps générique élevée, vous devriez envisager de l'augmenter uniquement lorsque cela est nécessaire, ce qui est possible de manière dynamique avec la méthode increaseSizeLimit()
sur RequestBody
.
Remarque
RequestBody
lui-même ne fournit pas d'analyse des données du formulaire. Vous pouvez utiliseramphp/http-server-form-parser
si vous en avez besoin.
Comme Request
, il est rare d'avoir besoin de construire une instance RequestBody
car elle sera fournie dans le cadre de Request
.
public function __construct(
ReadableStream | string $ stream ,
? Closure $ upgradeSize = null ,
)
public function increaseSizeLimit( int $ limit ): void
Augmente dynamiquement la limite de taille du corps pour permettre aux gestionnaires de requêtes individuels de gérer des corps de requête plus grands que ceux définis par défaut pour le serveur HTTP.
La classe Trailers
permet d'accéder aux trailers d'une requête HTTP, accessibles via Request::getTrailers()
. null
est renvoyé si aucune fin de séquence n'est attendue sur la demande. Trailers::await()
renvoie un Future
qui est résolu avec un objet HttpMessage
fournissant des méthodes pour accéder aux en-têtes de fin.
$ trailers = $ request -> getTrailers ();
$ message = $ trailers ?->await();
Le serveur HTTP ne sera pas le goulot d'étranglement. Une mauvaise configuration, l'utilisation d'E/S bloquantes ou des applications inefficaces le sont.
Le serveur est bien optimisé et peut gérer des dizaines de milliers de requêtes par seconde sur du matériel typique tout en maintenant un niveau élevé de concurrence pour des milliers de clients.
Mais ces performances diminueront considérablement avec des applications inefficaces. Le serveur a l'avantage que les classes et les gestionnaires sont toujours chargés, donc il n'y a pas de perte de temps avec la compilation et l'initialisation.
Un piège courant consiste à commencer à exploiter le Big Data avec de simples opérations sur les chaînes, ce qui nécessite de nombreuses copies volumineuses inefficaces. Au lieu de cela, le streaming doit être utilisé lorsque cela est possible pour des corps de requêtes et de réponses plus volumineux.
Le problème est vraiment le coût du processeur. Une gestion inefficace des E/S (tant qu'elle n'est pas bloquante !) ne fait que retarder les requêtes individuelles. Il est recommandé de distribuer simultanément et éventuellement de regrouper plusieurs requêtes d'E/S indépendantes via les combinateurs d'Amp, mais un gestionnaire lent ralentira également toutes les autres requêtes. Pendant qu'un gestionnaire calcule, tous les autres gestionnaires ne peuvent pas continuer. Il est donc impératif de réduire au minimum les temps de calcul des gestionnaires.
Plusieurs exemples peuvent être trouvés dans le répertoire ./examples
du référentiel. Ceux-ci peuvent être exécutés comme des scripts PHP normaux sur la ligne de commande.
php examples/hello-world.php
Vous pouvez ensuite accéder à l'exemple de serveur à http://localhost:1337/
dans votre navigateur.
Si vous découvrez des problèmes liés à la sécurité, veuillez utiliser le rapporteur de problèmes de sécurité privé au lieu d'utiliser le suivi des problèmes public.
La licence MIT (MIT). Veuillez consulter LICENCE pour plus d'informations.