AMPHP é uma coleção de bibliotecas orientadas a eventos para PHP projetadas com fibras e simultaneidade em mente. Este pacote fornece um servidor de aplicativos HTTP/1.1 e HTTP/2 simultâneo e sem bloqueio para PHP baseado em Revolt. Vários recursos são fornecidos em pacotes separados, como o componente WebSocket.
Este pacote pode ser instalado como uma dependência do Composer.
composer require amphp/http-server
Além disso, você pode querer instalar a biblioteca nghttp2
para aproveitar as vantagens do FFI para acelerar e reduzir o uso de memória.
Esta biblioteca fornece acesso à sua aplicação através do protocolo HTTP, aceitando solicitações do cliente e encaminhando essas solicitações para manipuladores definidos pela sua aplicação que retornarão uma resposta.
As solicitações recebidas são representadas por objetos Request
. Uma solicitação é fornecida a um implementador de RequestHandler
, que define um método handleRequest()
retornando uma instância de Response
.
public function handleRequest( Request $ request ): Response
Os manipuladores de solicitações são abordados com mais detalhes na seção RequestHandler
.
Este servidor HTTP é construído sobre o loop de eventos Revolt e a estrutura de simultaneidade sem bloqueio Amp. Assim, ele herda suporte total de todas as suas primitivas e é possível usar todas as bibliotecas sem bloqueio construídas em cima do Revolt.
Nota Em geral, você deve se familiarizar com o conceito
Future
, com corrotinas, e estar ciente das diversas funções do combinador para realmente ter sucesso no uso do servidor HTTP.
Quase todas as funções integradas do PHP bloqueiam E/S, o que significa que o thread de execução (principalmente equivalente ao processo no caso do PHP) será efetivamente interrompido até que a resposta seja recebida. Alguns exemplos de tais funções: mysqli_query
, file_get_contents
, usleep
e muito mais.
Uma boa regra prática é: toda função interna do PHP que faz E/S está fazendo isso de forma bloqueadora, a menos que você tenha certeza de que isso não acontece.
Existem bibliotecas que fornecem implementações que usam E/S sem bloqueio. Você deve usá-los em vez das funções integradas.
Cobrimos as necessidades de E/S mais comuns, como soquetes de rede, acesso a arquivos, solicitações HTTP e websockets, clientes de banco de dados MySQL e Postgres e Redis. Se for necessário usar E/S de bloqueio ou cálculos longos para atender a uma solicitação, considere usar a biblioteca Parallel para executar esse código em um processo ou thread separado.
Aviso Não use nenhuma função de bloqueio de E/S no servidor 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.
Seu aplicativo será atendido por uma instância do HttpServer
. Esta biblioteca fornece SocketHttpServer
, que será adequado para a maioria dos aplicativos, construído em componentes encontrados nesta biblioteca e em amphp/socket
.
Para criar uma instância de SocketHttpServer
e escutar solicitações, são necessárias no mínimo quatro coisas:
RequestHandler
para responder às solicitações recebidas,ErrorHander
para fornecer respostas a solicitações inválidas,PsrLogLoggerInterface
e <?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 ();
O exemplo acima cria um servidor simples que envia uma resposta em texto simples para cada solicitação recebida.
SocketHttpServer
fornece dois construtores estáticos para casos de uso comuns, além do construtor normal para usos mais avançados e personalizados.
SocketHttpServer::createForDirectAccess()
: Usado no exemplo acima, cria um servidor de aplicativos HTTP adequado para acesso direto à rede. Limites ajustáveis são impostos às conexões por IP, total de conexões e solicitações simultâneas (10, 1.000 e 1.000 por padrão, respectivamente). A compactação de resposta pode ser ativada ou desativada (ativada por padrão) e os métodos de solicitação são limitados a um conjunto conhecido de verbos HTTP por padrão.SocketHttpServer::createForBehindProxy()
: Cria um servidor apropriado para uso quando atrás de um serviço de proxy como o nginx. Este construtor estático requer uma lista de IPs de proxy confiáveis (com máscaras de sub-rede opcionais) e um caso enum de ForwardedHeaderType
(correspondente a Forwarded
ou X-Forwarded-For
) para analisar o IP do cliente original dos cabeçalhos de solicitação. Não são impostos limites ao número de conexões ao servidor, porém o número de solicitações simultâneas é limitado (1000 por padrão, ajustável ou pode ser removido). A compactação de resposta pode ser ativada ou desativada (ativada por padrão). Os métodos de solicitação são limitados a um conjunto conhecido de verbos HTTP por padrão. Se nenhum desses métodos atender às necessidades do seu aplicativo, o construtor SocketHttpServer
poderá ser usado diretamente. Isso fornece uma enorme flexibilidade na forma como as conexões de entrada do cliente são criadas e tratadas, mas exigirá mais código para serem criadas. O construtor requer que o usuário passe uma instância de SocketServerFactory
, usada para criar instâncias Socket
do cliente (ambos componentes da biblioteca amphp/socket
), e uma instância de ClientFactory
, que cria apropriadamente instâncias Client
que são anexadas a cada Request
feita pelo cliente .
RequestHandler
As solicitações recebidas são representadas por objetos Request
. Uma solicitação é fornecida a um implementador de RequestHandler
, que define um método handleRequest()
retornando uma instância de Response
.
public function handleRequest( Request $ request ): Response
Cada solicitação do cliente (ou seja, chamada para RequestHandler::handleRequest()
) é executada em uma corrotina separada para que as solicitações sejam tratadas automaticamente de forma cooperativa dentro do processo do servidor. Quando um manipulador de solicitações aguarda E/S sem bloqueio, outras solicitações do cliente são processadas em corrotinas simultâneas. Seu manipulador de solicitação pode criar outras corrotinas usando Ampasync()
para executar várias tarefas para uma única solicitação.
Normalmente, um RequestHandler
gera diretamente uma resposta, mas também pode delegar para outro RequestHandler
. Um exemplo de tal RequestHandler
de delegação é o Router
.
A interface RequestHandler
deve ser implementada por classes customizadas. Para casos de uso muito simples ou simulação rápida, você pode usar CallableRequestHandler
, que pode agrupar qualquer callable
e aceitar um Request
e retornar um Response
.
O middleware permite o pré-processamento de solicitações e o pós-processamento de respostas. Além disso, um middleware também pode interceptar o processamento da solicitação e retornar uma resposta sem delegar ao manipulador de solicitação passado. As classes precisam implementar a interface Middleware
para isso.
Nota Middleware geralmente segue outras palavras como software e hardware com seu plural. No entanto, usamos o termo middlewares para nos referirmos a vários objetos que implementam a interface
Middleware
.
public function handleRequest( Request $ request , RequestHandler $ next ): Response
handleRequest
é o único método da interface Middleware
. Se o Middleware
não tratar a solicitação sozinho, ele deverá delegar a criação da resposta ao RequestHandler
recebido.
function stackMiddleware( RequestHandler $ handler , Middleware ... $ middleware ): RequestHandler
Vários middlewares podem ser empilhados usando AmpHttpServerMiddlewarestackMiddleware()
, que aceita um RequestHandler
como primeiro argumento e um número variável de instâncias Middleware
. O RequestHandler
retornado invocará cada middleware na ordem fornecida.
$ 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
Um ErrorHander
é usado pelo servidor HTTP quando uma solicitação malformada ou inválida é recebida. O objeto Request
é fornecido se for construído a partir dos dados recebidos, mas nem sempre pode ser definido.
public function handleError(
int $ status ,
? string $ reason = null ,
? Request $ request = null ,
): Response
Esta biblioteca fornece DefaultErrorHandler
que retorna uma página HTML estilizada como corpo da resposta. Você pode desejar fornecer uma implementação diferente para seu aplicativo, potencialmente usando múltiplas em conjunto com um roteador.
Request
É raro que você mesmo precise construir um objeto Request
, pois eles normalmente serão fornecidos a RequestHandler::handleRequest()
pelo servidor.
/**
* @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
Retorna o Сlient
enviando a solicitação
public function getMethod(): string
Retorna o método HTTP usado para fazer esta solicitação, por exemplo, "GET"
.
public function setMethod( string $ method ): void
Define o método HTTP de solicitação.
public function getUri(): Psr Http Message UriInterface
Retorna o URI
da solicitação.
public function setUri( Psr Http Message UriInterface $ uri ): void
Define um novo URI
para a solicitação.
public function getProtocolVersion(): string
Retorna a versão do protocolo HTTP como uma string (por exemplo, "1.0", "1.1", "2").
public function setProtocolVersion( string $ protocol )
Define um novo número de versão do protocolo para a solicitação.
/** @return array<non-empty-string, list<string>> */
public function getHeaders(): array
Retorna os cabeçalhos como uma matriz indexada por strings de matrizes de strings ou uma matriz vazia se nenhum cabeçalho tiver sido definido.
public function hasHeader( string $ name ): bool
Verifica se determinado cabeçalho existe.
/** @return list<string> */
public function getHeaderArray( string $ name ): array
Retorna a matriz de valores para o cabeçalho fornecido ou uma matriz vazia se o cabeçalho não existir.
public function getHeader( string $ name ): ? string
Retorna o valor do cabeçalho fornecido. Se vários cabeçalhos estiverem presentes para o cabeçalho nomeado, somente o primeiro valor do cabeçalho será retornado. Use getHeaderArray()
para retornar uma matriz de todos os valores para um cabeçalho específico. Retorna null
se o cabeçalho não existir.
public function setHeaders( array $ headers ): void
Define os cabeçalhos da matriz fornecida.
/** @param array<string>|string $value */
public function setHeader( string $ name , array | string $ value ): void
Define o cabeçalho para os valores fornecidos. Todas as linhas de cabeçalho anteriores com o nome fornecido serão substituídas.
/** @param array<string>|string $value */
public function addHeader( string $ name , array | string $ value ): void
Adiciona uma linha de cabeçalho adicional com o nome fornecido.
public function removeHeader( string $ name ): void
Remove o cabeçalho fornecido, se existir. Se existirem várias linhas de cabeçalho com o mesmo nome, todas elas serão removidas.
public function getBody(): RequestBody
Retorna o corpo da solicitação. O RequestBody
permite acesso transmitido e em buffer a um InputStream
.
public function setBody( ReadableStream | string $ body )
Define o fluxo do corpo da mensagem
Nota O uso de uma string definirá automaticamente o cabeçalho
Content-Length
para o comprimento da string fornecida. Definir umReadableStream
removerá o cabeçalhoContent-Length
. Se você souber o comprimento exato do conteúdo do seu stream, poderá adicionar um cabeçalhocontent-length
após chamarsetBody()
.
/** @return array<non-empty-string, RequestCookie> */
public function getCookies(): array
Retorna todos os cookies no mapa associativo do nome do cookie para RequestCookie
.
public function getCookie( string $ name ): ? RequestCookie
Obtém um valor de cookie por name ou null
.
public function setCookie( RequestCookie $ cookie ): void
Adiciona um Cookie
à solicitação.
public function removeCookie( string $ name ): void
Remove um cookie da solicitação.
public function getAttributes(): array
Retorna uma matriz de todos os atributos armazenados no armazenamento local mutável da solicitação.
public function removeAttributes(): array
Remove todos os atributos de solicitação do armazenamento local mutável da solicitação.
public function hasAttribute( string $ name ): bool
Verifique se existe um atributo com o nome fornecido no armazenamento local mutável da solicitação.
public function getAttribute( string $ name ): mixed
Recuperar uma variável do armazenamento local mutável da solicitação.
Nota O nome do atributo deve ter um namespace com um namespace de fornecedor e pacote, como classes.
public function setAttribute( string $ name , mixed $ value ): void
Atribua uma variável ao armazenamento local mutável da solicitação.
Nota O nome do atributo deve ter um namespace com um namespace de fornecedor e pacote, como classes.
public function removeAttribute( string $ name ): void
Remove uma variável do armazenamento local mutável da solicitação.
public function getTrailers(): Trailers
Permite acesso aos Trailers
de uma solicitação.
public function setTrailers( Trailers $ trailers ): void
Atribui o objeto Trailers
a ser utilizado na solicitação.
Os detalhes relacionados ao cliente são agrupados em objetos AmpHttpServerDriverClient
retornados de Request::getClient()
. A interface Client
fornece métodos para recuperar os endereços de soquete remoto e local e informações de TLS (se aplicável).
Response
A classe Response
representa uma resposta HTTP. Uma Response
é retornada por manipuladores de solicitação e 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 ,
)
Invoca manipuladores de descarte (ou seja, funções registradas por meio do método onDispose()
).
Nota Exceções não detectadas dos manipuladores de descarte serão encaminhadas para o manipulador de erros do loop de eventos.
public function getBody(): Amp ByteStream ReadableStream
Retorna o fluxo do corpo da mensagem.
public function setBody( Amp ByteStream ReadableStream | string $ body )
Define o fluxo do corpo da mensagem.
Nota O uso de uma string definirá automaticamente o cabeçalho
Content-Length
para o comprimento da string fornecida. Definir umReadableStream
removerá o cabeçalhoContent-Length
. Se você souber o comprimento exato do conteúdo do seu stream, poderá adicionar um cabeçalhocontent-length
após chamarsetBody()
.
/** @return array<non-empty-string, list<string>> */
public function getHeaders(): array
Retorna os cabeçalhos como uma matriz indexada por strings de matrizes de strings ou uma matriz vazia se nenhum cabeçalho tiver sido definido.
public function hasHeader( string $ name ): bool
Verifica se determinado cabeçalho existe.
/** @return list<string> */
public function getHeaderArray( string $ name ): array
Retorna a matriz de valores para o cabeçalho fornecido ou uma matriz vazia se o cabeçalho não existir.
public function getHeader( string $ name ): ? string
Retorna o valor do cabeçalho fornecido. Se vários cabeçalhos estiverem presentes para o cabeçalho nomeado, somente o primeiro valor do cabeçalho será retornado. Use getHeaderArray()
para retornar uma matriz de todos os valores para um cabeçalho específico. Retorna null
se o cabeçalho não existir.
public function setHeaders( array $ headers ): void
Define os cabeçalhos da matriz fornecida.
/** @param array<string>|string $value */
public function setHeader( string $ name , array | string $ value ): void
Define o cabeçalho para os valores fornecidos. Todas as linhas de cabeçalho anteriores com o nome fornecido serão substituídas.
/** @param array<string>|string $value */
public function addHeader( string $ name , array | string $ value ): void
Adiciona uma linha de cabeçalho adicional com o nome fornecido.
public function removeHeader( string $ name ): void
Remove o cabeçalho fornecido, se existir. Se existirem várias linhas de cabeçalho com o mesmo nome, todas elas serão removidas.
public function getStatus(): int
Retorna o código de status da resposta.
public function getReason(): string
Retorna a frase de motivo que descreve o código de status.
public function setStatus( int $ code , string | null $ reason ): void
Define o código de status HTTP numérico (entre 100 e 599) e a frase de motivo. Use null como frase de motivo para usar a frase padrão associada ao código de status.
/** @return array<non-empty-string, ResponseCookie> */
public function getCookies(): array
Retorna todos os cookies em um mapa associativo de nome de cookie para ResponseCookie
.
public function getCookie( string $ name ): ? ResponseCookie
Obtém um valor de cookie por nome ou null
se nenhum cookie com esse nome estiver presente.
public function setCookie( ResponseCookie $ cookie ): void
Adiciona um cookie à resposta.
public function removeCookie( string $ name ): void
Remove um cookie da resposta.
/** @return array<string, Push> Map of URL strings to Push objects. */
public function getPushes(): array
Retorna uma lista de recursos push em um mapa associativo de strings de URL para objetos Push
.
/** @param array<string>|array<string, array<string>> $headers */
public function push( string $ url , array $ headers ): void
Indique os recursos que um cliente provavelmente precisará buscar. (por exemplo Link: preload
ou HTTP/2 Server Push).
public function isUpgraded(): bool
Retorna true
se um retorno de chamada de desanexação tiver sido definido, false
se nenhum.
/** @param Closure(DriverUpgradedSocket, Request, Response): void $upgrade */
public function upgrade( Closure $ upgrade ): void
Define um retorno de chamada a ser invocado assim que a resposta for gravada no cliente e altera o status da resposta para 101 Switching Protocols
. O retorno de chamada recebe uma instância de DriverUpgradedSocket
, o Request
que iniciou a atualização e este Response
.
O retorno de chamada pode ser removido alterando o status para algo diferente de 101.
public function getUpgradeCallable(): ? Closure
Retorna a função de atualização, se presente.
/** @param Closure():void $onDispose */
public function onDispose( Closure $ onDispose ): void
Registra uma função que é invocada quando a Resposta é descartada. Uma resposta é descartada depois de escrita no cliente ou se for substituída em uma cadeia de middleware.
public function getTrailers(): Trailers
Permite acesso aos Trailers
de uma resposta.
public function setTrailers( Trailers $ trailers ): void
Atribui o objeto Trailers
a ser usado na resposta. Os trailers são enviados assim que todo o corpo da resposta for definido para o cliente.
RequestBody
, retornado de Request::getBody()
, fornece acesso em buffer e streaming ao corpo da solicitação. Use o acesso transmitido para lidar com mensagens grandes, o que é particularmente importante se você tiver limites de mensagens maiores (como dezenas de megabytes) e não quiser armazenar tudo em buffer na memória. Se várias pessoas estiverem enviando corpos grandes ao mesmo tempo, a memória pode se esgotar rapidamente.
Conseqüentemente, o tratamento incremental é importante, acessível por meio da API read()
de AmpByteStreamReadableStream
.
Caso um cliente se desconecte, read()
falha com AmpHttpServerClientException
. Esta exceção é lançada para a API read()
e buffer()
.
Nota
ClientException
s não precisam ser capturados. Você pode pegá-los se quiser continuar, mas não é necessário. O Servidor encerrará silenciosamente o ciclo de solicitação e descartará essa exceção.
Em vez de definir um limite de corpo genérico alto, considere aumentar o limite de corpo apenas quando necessário, o que é dinamicamente possível com o método increaseSizeLimit()
em RequestBody
.
Nota O próprio
RequestBody
não fornece análise de dados de formulário. Você pode usaramphp/http-server-form-parser
se precisar.
Assim como Request
, é raro precisar construir uma instância RequestBody
, pois uma será fornecida como parte de Request
.
public function __construct(
ReadableStream | string $ stream ,
? Closure $ upgradeSize = null ,
)
public function increaseSizeLimit( int $ limit ): void
Aumenta o limite de tamanho do corpo dinamicamente para permitir que manipuladores de solicitações individuais manipulem corpos de solicitações maiores do que o padrão definido para o servidor HTTP.
A classe Trailers
permite acesso aos trailers de uma solicitação HTTP, acessível via Request::getTrailers()
. null
será retornado se trailers não forem esperados na solicitação. Trailers::await()
retorna um Future
que é resolvido com um objeto HttpMessage
que fornece métodos para acessar os cabeçalhos do trailer.
$ trailers = $ request -> getTrailers ();
$ message = $ trailers ?->await();
O servidor HTTP não será o gargalo. Configuração incorreta, uso de bloqueio de E/S ou aplicativos ineficientes são.
O servidor é bem otimizado e pode lidar com dezenas de milhares de solicitações por segundo em hardware típico, mantendo um alto nível de simultaneidade de milhares de clientes.
Mas esse desempenho diminuirá drasticamente com aplicações ineficientes. O servidor tem a boa vantagem de classes e manipuladores estarem sempre carregados, portanto não há perda de tempo com compilação e inicialização.
Uma armadilha comum é começar a operar em big data com operações simples de string, exigindo muitas cópias grandes e ineficientes. Em vez disso, o streaming deve ser usado sempre que possível para órgãos maiores de solicitação e resposta.
O problema realmente é o custo da CPU. O gerenciamento ineficiente de E/S (desde que não bloqueie!) está apenas atrasando solicitações individuais. Recomenda-se despachar simultaneamente e, eventualmente, agrupar várias solicitações de E/S independentes por meio dos combinadores do Amp, mas um manipulador lento também desacelerará todas as outras solicitações. Enquanto um manipulador estiver computando, todos os outros manipuladores não poderão continuar. Portanto, é imperativo reduzir ao mínimo os tempos de computação dos manipuladores.
Vários exemplos podem ser encontrados no diretório ./examples
do repositório. Eles podem ser executados como scripts PHP normais na linha de comando.
php examples/hello-world.php
Você pode então acessar o servidor de exemplo em http://localhost:1337/
no seu navegador.
Se você descobrir algum problema relacionado à segurança, use o relator de problemas de segurança privada em vez de usar o rastreador de problemas público.
A licença MIT (MIT). Consulte LICENÇA para obter mais informações.