AMPHP は、ファイバーと同時実行性を念頭に置いて設計された PHP 用のイベント駆動型ライブラリのコレクションです。このパッケージは、Revolt に基づいた PHP 用のノンブロッキング、同時 HTTP/1.1 および HTTP/2 アプリケーション サーバーを提供します。 WebSocket コンポーネントなど、いくつかの機能は個別のパッケージで提供されます。
このパッケージは Composer の依存関係としてインストールできます。
composer require amphp/http-server
さらに、 nghttp2
ライブラリをインストールして FFI を利用し、メモリ使用量を高速化し、削減することもできます。
このライブラリは、HTTP プロトコルを介してアプリケーションへのアクセスを提供し、クライアント要求を受け入れ、それらの要求をアプリケーションによって定義されたハンドラーに転送し、応答が返されます。
受信リクエストはRequest
オブジェクトによって表されます。リクエストは、 Response
のインスタンスを返すhandleRequest()
メソッドを定義するRequestHandler
の実装者に提供されます。
public function handleRequest( Request $ request ): Response
リクエスト ハンドラーについては、 RequestHandler
セクションで詳しく説明します。
この HTTP サーバーは、Revolt イベント ループとノンブロッキング同時実行フレームワーク Amp の上に構築されています。したがって、すべてのプリミティブの完全なサポートを継承し、Revolt 上に構築されたすべてのノンブロッキング ライブラリを使用することができます。
注一般に、HTTP サーバーを実際に使用するためには、コルーチンを使用した
Future
概念に慣れ、いくつかのコンビネータ関数を認識する必要があります。
PHP のほぼすべての組み込み関数はブロッキング I/O を実行します。つまり、実行中のスレッド (PHP の場合はプロセスにほぼ相当) は、応答が受信されるまで実質的に停止されます。このような関数の例としては、 mysqli_query
、 file_get_contents
、 usleep
などがあります。
良い経験則は次のとおりです。I/O を実行するすべての組み込み PHP 関数は、ブロッキングではないことが確実にわかっている場合を除き、ブロッキング方法でそれを実行します。
ノンブロッキング I/O を使用する実装を提供するライブラリがあります。組み込み関数の代わりにこれらを使用する必要があります。
ネットワーク ソケット、ファイル アクセス、HTTP リクエストと Web ソケット、MySQL および Postgres データベース クライアント、Redis などの最も一般的な I/O ニーズをカバーします。リクエストを満たすためにブロッキング I/O の使用や長時間の計算が必要な場合は、並列ライブラリを使用してそのコードを別のプロセスまたはスレッドで実行することを検討してください。
警告HTTP サーバーではブロッキング I/O 関数を使用しないでください。
// 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.
アプリケーションはHttpServer
のインスタンスによって提供されます。このライブラリは、このライブラリとamphp/socket
にあるコンポーネントに基づいて構築された、ほとんどのアプリケーションに適したSocketHttpServer
を提供します。
SocketHttpServer
のインスタンスを作成してリクエストをリッスンするには、少なくとも 4 つのものが必要です。
RequestHandler
のインスタンス、ErrorHander
のインスタンス、PsrLogLoggerInterface
のインスタンス、および <?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 ();
上記の例では、受信したすべてのリクエストに対してプレーンテキストの応答を送信する単純なサーバーを作成します。
SocketHttpServer
より高度なカスタム用途向けの通常のコンストラクターに加えて、一般的なユースケース向けの 2 つの静的コンストラクターを提供します。
SocketHttpServer::createForDirectAccess()
: 上記の例で使用され、これは直接ネットワーク アクセスに適した HTTP アプリケーション サーバーを作成します。調整可能な制限は、IP ごとの接続、総接続数、および同時リクエストに課されます (デフォルトでは、それぞれ 10、1000、および 1000)。応答の圧縮はオンまたはオフ (デフォルトではオン) に切り替えることができ、リクエスト メソッドはデフォルトで既知の HTTP 動詞のセットに制限されます。SocketHttpServer::createForBehindProxy()
: nginx などのプロキシ サービスの背後にある場合に使用するのに適したサーバーを作成します。この静的コンストラクターには、要求ヘッダーから元のクライアント IP を解析するために、信頼できるプロキシ IP のリスト (オプションのサブネット マスク付き) とForwardedHeaderType
の列挙型ケース ( Forwarded
またはX-Forwarded-For
に対応) が必要です。サーバーへの接続数に制限はありませんが、同時リクエストの数は制限されています (デフォルトでは 1000、調整または削除可能)。応答圧縮はオンまたはオフに切り替えることができます (デフォルトではオン)。デフォルトでは、リクエスト メソッドは既知の HTTP 動詞のセットに限定されます。これらのメソッドのいずれもアプリケーションのニーズを満たさない場合は、 SocketHttpServer
コンストラクターを直接使用できます。これにより、受信接続のクライアント接続の作成および処理方法に非常に高い柔軟性が提供されますが、作成するにはより多くのコードが必要になります。コンストラクターでは、ユーザーが、クライアントSocket
インスタンス ( amphp/socket
ライブラリの両方のコンポーネント) の作成に使用されるSocketServerFactory
のインスタンスと、クライアントによって行われた各Request
にアタッチされるClient
インスタンスを適切に作成するClientFactory
のインスタンスを渡す必要があります。 。
RequestHandler
受信リクエストはRequest
オブジェクトによって表されます。リクエストは、 Response
のインスタンスを返すhandleRequest()
メソッドを定義するRequestHandler
の実装者に提供されます。
public function handleRequest( Request $ request ): Response
各クライアント要求 (つまり、 RequestHandler::handleRequest()
の呼び出し) は別個のコルーチン内で実行されるため、要求はサーバー プロセス内で自動的に連携して処理されます。リクエスト ハンドラーがノンブロッキング I/O で待機している場合、他のクライアント リクエストは同時コルーチンで処理されます。リクエスト ハンドラー自体がAmpasync()
使用して他のコルーチンを作成し、1 つのリクエストに対して複数のタスクを実行する場合があります。
通常、 RequestHandler
応答を直接生成しますが、別のRequestHandler
に委任する場合もあります。このような委任RequestHandler
の例はRouter
です。
RequestHandler
インターフェイスは、カスタム クラスによって実装されることを目的としています。非常に単純なユースケースや簡単なモックの場合は、 CallableRequestHandler
を使用できます。これは、 callable
オブジェクトをラップし、 Request
受け入れてResponse
返すことができます。
ミドルウェアを使用すると、リクエストの前処理と応答の後処理が可能になります。それとは別に、ミドルウェアはリクエスト処理をインターセプトし、渡されたリクエスト ハンドラーに委任せずに応答を返すこともできます。クラスはそのためにMiddleware
インターフェイスを実装する必要があります。
注ミドルウェアは通常、ソフトウェアやハードウェアなどの複数形の単語の後に続きます。ただし、ミドルウェアという用語は、
Middleware
インターフェイスを実装する複数のオブジェクトを指すために使用されます。
public function handleRequest( Request $ request , RequestHandler $ next ): Response
handleRequest
、 Middleware
インターフェイスの唯一のメソッドです。 Middleware
リクエスト自体を処理しない場合は、受信したRequestHandler
にレスポンスの作成を委任する必要があります。
function stackMiddleware( RequestHandler $ handler , Middleware ... $ middleware ): RequestHandler
AmpHttpServerMiddlewarestackMiddleware()
を使用すると、複数のミドルウェアをスタックできます。これは、最初の引数としてRequestHandler
と可変数のMiddleware
インスタンスを受け取ります。返されたRequestHandler
は、指定された順序で各ミドルウェアを呼び出します。
$ 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
ErrorHander
、不正な形式または無効なリクエストを受信したときに HTTP サーバーによって使用されます。 Request
オブジェクトは、受信データから構築された場合に提供されますが、常に設定されるとは限りません。
public function handleError(
int $ status ,
? string $ reason = null ,
? Request $ request = null ,
): Response
このライブラリは、様式化された HTML ページを応答本文として返すDefaultErrorHandler
を提供します。アプリケーションに別の実装を提供し、場合によってはルーターと組み合わせて複数を使用することもできます。
Request
Request オブジェクトは通常サーバーによってRequestHandler::handleRequest()
に提供されるため、自分でRequest
オブジェクトを構築する必要があることはほとんどありません。
/**
* @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
リクエストを送信したСlient
を返します
public function getMethod(): string
このリクエストの作成に使用された HTTP メソッド (例: "GET"
を返します。
public function setMethod( string $ method ): void
リクエストのHTTPメソッドを設定します。
public function getUri(): Psr Http Message UriInterface
リクエストURI
返します。
public function setUri( Psr Http Message UriInterface $ uri ): void
リクエストの新しいURI
を設定します。
public function getProtocolVersion(): string
HTTP プロトコルのバージョンを文字列として返します (例: "1.0"、"1.1"、"2")。
public function setProtocolVersion( string $ protocol )
リクエストの新しいプロトコルのバージョン番号を設定します。
/** @return array<non-empty-string, list<string>> */
public function getHeaders(): array
ヘッダーを文字列配列の文字列インデックス付き配列として返すか、ヘッダーが設定されていない場合は空の配列として返します。
public function hasHeader( string $ name ): bool
指定されたヘッダーが存在するかどうかを確認します。
/** @return list<string> */
public function getHeaderArray( string $ name ): array
指定されたヘッダーの値の配列を返すか、ヘッダーが存在しない場合は空の配列を返します。
public function getHeader( string $ name ): ? string
指定されたヘッダーの値を返します。名前付きヘッダーに複数のヘッダーが存在する場合、最初のヘッダー値のみが返されます。 getHeaderArray()
を使用して、特定のヘッダーのすべての値の配列を返します。ヘッダーが存在しない場合はnull
を返します。
public function setHeaders( array $ headers ): void
指定された配列からヘッダーを設定します。
/** @param array<string>|string $value */
public function setHeader( string $ name , array | string $ value ): void
ヘッダーを指定された値に設定します。指定された名前を持つ以前のヘッダー行はすべて置き換えられます。
/** @param array<string>|string $value */
public function addHeader( string $ name , array | string $ value ): void
指定された名前のヘッダー行を追加します。
public function removeHeader( string $ name ): void
指定されたヘッダーが存在する場合はそれを削除します。同じ名前のヘッダー行が複数存在する場合、それらはすべて削除されます。
public function getBody(): RequestBody
リクエストボディを返します。 RequestBody
InputStream
へのストリーミングおよびバッファリングされたアクセスが可能になります。
public function setBody( ReadableStream | string $ body )
メッセージ本文のストリームを設定します
注文字列を使用すると、
Content-Length
ヘッダーが指定された文字列の長さに自動的に設定されます。ReadableStream
設定すると、Content-Length
ヘッダーが削除されます。ストリームの正確なコンテンツ長がわかっている場合は、setBody()
呼び出した後にcontent-length
ヘッダーを追加できます。
/** @return array<non-empty-string, RequestCookie> */
public function getCookies(): array
Cookie 名の連想マップ内のすべての Cookie をRequestCookie
に返します。
public function getCookie( string $ name ): ? RequestCookie
Cookie 値を名前またはnull
で取得します。
public function setCookie( RequestCookie $ cookie ): void
リクエストにCookie
追加します。
public function removeCookie( string $ name ): void
リクエストから Cookie を削除します。
public function getAttributes(): array
リクエストの変更可能なローカル ストレージに保存されているすべての属性の配列を返します。
public function removeAttributes(): array
リクエストの変更可能なローカル ストレージからすべてのリクエスト属性を削除します。
public function hasAttribute( string $ name ): bool
指定された名前の属性がリクエストの変更可能なローカル ストレージに存在するかどうかを確認します。
public function getAttribute( string $ name ): mixed
リクエストの可変ローカル ストレージから変数を取得します。
注属性の名前は、クラスと同様に、ベンダーおよびパッケージの名前空間で指定する必要があります。
public function setAttribute( string $ name , mixed $ value ): void
変数をリクエストの可変ローカル ストレージに割り当てます。
注属性の名前は、クラスと同様に、ベンダーおよびパッケージの名前空間で指定する必要があります。
public function removeAttribute( string $ name ): void
リクエストの可変ローカルストレージから変数を削除します。
public function getTrailers(): Trailers
リクエストのTrailers
へのアクセスを許可します。
public function setTrailers( Trailers $ trailers ): void
リクエストで使用されるTrailers
オブジェクトを割り当てます。
クライアント関連の詳細は、 Request::getClient()
から返されるAmpHttpServerDriverClient
オブジェクトにバンドルされます。 Client
インターフェイスは、リモートおよびローカルのソケット アドレスと TLS 情報 (該当する場合) を取得するメソッドを提供します。
Response
Response
クラスは HTTP 応答を表します。 Response
リクエスト ハンドラーとミドルウェアによって返されます。
/**
* @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 ,
)
破棄ハンドラー (つまり、 onDispose()
メソッド経由で登録された関数) を呼び出します。
注破棄ハンドラーからのキャッチされなかった例外は、イベント ループ エラー ハンドラーに転送されます。
public function getBody(): Amp ByteStream ReadableStream
メッセージ本文のストリームを返します。
public function setBody( Amp ByteStream ReadableStream | string $ body )
メッセージ本文のストリームを設定します。
注文字列を使用すると、
Content-Length
ヘッダーが指定された文字列の長さに自動的に設定されます。ReadableStream
設定すると、Content-Length
ヘッダーが削除されます。ストリームの正確なコンテンツ長がわかっている場合は、setBody()
呼び出した後にcontent-length
ヘッダーを追加できます。
/** @return array<non-empty-string, list<string>> */
public function getHeaders(): array
ヘッダーを文字列配列の文字列インデックス付き配列として返すか、ヘッダーが設定されていない場合は空の配列として返します。
public function hasHeader( string $ name ): bool
指定されたヘッダーが存在するかどうかを確認します。
/** @return list<string> */
public function getHeaderArray( string $ name ): array
指定されたヘッダーの値の配列を返すか、ヘッダーが存在しない場合は空の配列を返します。
public function getHeader( string $ name ): ? string
指定されたヘッダーの値を返します。名前付きヘッダーに複数のヘッダーが存在する場合、最初のヘッダー値のみが返されます。 getHeaderArray()
を使用して、特定のヘッダーのすべての値の配列を返します。ヘッダーが存在しない場合はnull
を返します。
public function setHeaders( array $ headers ): void
指定された配列からヘッダーを設定します。
/** @param array<string>|string $value */
public function setHeader( string $ name , array | string $ value ): void
ヘッダーを指定された値に設定します。指定された名前を持つ以前のヘッダー行はすべて置き換えられます。
/** @param array<string>|string $value */
public function addHeader( string $ name , array | string $ value ): void
指定された名前のヘッダー行を追加します。
public function removeHeader( string $ name ): void
指定されたヘッダーが存在する場合はそれを削除します。同じ名前のヘッダー行が複数存在する場合、それらはすべて削除されます。
public function getStatus(): int
レスポンスのステータスコードを返します。
public function getReason(): string
ステータス コードを説明する理由フレーズを返します。
public function setStatus( int $ code , string | null $ reason ): void
数値の HTTP ステータス コード (100 ~ 599) と理由フレーズを設定します。ステータス コードに関連付けられたデフォルトのフレーズを使用するには、理由フレーズに null を使用します。
/** @return array<non-empty-string, ResponseCookie> */
public function getCookies(): array
Cookie 名の連想マップ内のすべての Cookie をResponseCookie
に返します。
public function getCookie( string $ name ): ? ResponseCookie
Cookie 値を名前で取得するか、その名前の Cookie が存在しない場合はnull
取得します。
public function setCookie( ResponseCookie $ cookie ): void
応答に Cookie を追加します。
public function removeCookie( string $ name ): void
応答から Cookie を削除します。
/** @return array<string, Push> Map of URL strings to Push objects. */
public function getPushes(): array
URL 文字列の連想マップ内のプッシュ リソースのリストをPush
オブジェクトに返します。
/** @param array<string>|array<string, array<string>> $headers */
public function push( string $ url , array $ headers ): void
クライアントがフェッチする必要があると思われるリソースを示します。 (例Link: preload
または HTTP/2 サーバー プッシュ)。
public function isUpgraded(): bool
デタッチ コールバックが設定されている場合はtrue
、設定されていない場合はfalse
を返します。
/** @param Closure(DriverUpgradedSocket, Request, Response): void $upgrade */
public function upgrade( Closure $ upgrade ): void
応答がクライアントに書き込まれた後に呼び出されるコールバックを設定し、応答のステータスを101 Switching Protocols
に変更します。コールバックは、 DriverUpgradedSocket
のインスタンス、アップグレードを開始したRequest
、およびこのResponse
を受け取ります。
ステータスを 101 以外に変更すると、コールバックを削除できる場合があります。
public function getUpgradeCallable(): ? Closure
アップグレード関数が存在する場合はそれを返します。
/** @param Closure():void $onDispose */
public function onDispose( Closure $ onDispose ): void
レスポンスが破棄されたときに呼び出される関数を登録します。応答は、クライアントに書き込まれた後、またはミドルウェア チェーンで置き換えられた場合に破棄されます。
public function getTrailers(): Trailers
応答のTrailers
へのアクセスを許可します。
public function setTrailers( Trailers $ trailers ): void
応答で使用されるTrailers
オブジェクトを割り当てます。応答本文全体がクライアントに設定されると、トレーラーが送信されます。
Request::getBody()
から返されるRequestBody
、リクエスト本文へのバッファリングおよびストリーミングされたアクセスを提供します。ストリーミング アクセスを使用して大きなメッセージを処理します。これは、メッセージの制限が大きく (数十メガバイトなど)、メッセージをすべてメモリにバッファリングしたくない場合に特に重要です。複数のユーザーが大きなボディを同時にアップロードすると、メモリがすぐに枯渇する可能性があります。
したがって、増分処理が重要であり、 AmpByteStreamReadableStream
のread()
API を介してアクセスできます。
クライアントが切断された場合、 read()
AmpHttpServerClientException
で失敗します。この例外は、 read()
とbuffer()
API の両方でスローされます。
注:
ClientException
をキャッチする必要はありません。続行したい場合は捕まえることもできますが、そうする必要はありません。サーバーは要求サイクルをサイレントに終了し、その例外を破棄します。
一般的な本文制限を高く設定する代わりに、必要な場合にのみ本文制限を増やすことを検討する必要があります。これは、 RequestBody
のincreaseSizeLimit()
メソッドを使用して動的に可能です。
注
RequestBody
自体はフォーム データの解析を提供しません。必要に応じて、amphp/http-server-form-parser
使用できます。
Request
と同様に、 RequestBody
インスタンスはRequest
の一部として提供されるため、そのインスタンスを構築する必要があることはほとんどありません。
public function __construct(
ReadableStream | string $ stream ,
? Closure $ upgradeSize = null ,
)
public function increaseSizeLimit( int $ limit ): void
本文サイズの制限を動的に増やして、個々のリクエスト ハンドラーが HTTP サーバーに設定されているデフォルトよりも大きなリクエスト本文を処理できるようにします。
Trailers
クラスを使用すると、 Request::getTrailers()
経由でアクセスできる HTTP リクエストのトレーラーにアクセスできます。リクエストでトレーラーが予期されていない場合は、 null
が返されます。 Trailers::await()
トレーラー ヘッダーにアクセスするメソッドを提供するHttpMessage
オブジェクトで解決されるFuture
を返します。
$ trailers = $ request -> getTrailers ();
$ message = $ trailers ?->await();
HTTP サーバーがボトルネックになることはありません。構成ミス、ブロッキング I/O の使用、または非効率的なアプリケーションが該当します。
このサーバーは適切に最適化されており、数千のクライアントの高レベルの同時実行性を維持しながら、一般的なハードウェアで 1 秒あたり数万のリクエストを処理できます。
ただし、非効率なアプリケーションではパフォーマンスが大幅に低下します。サーバーには、クラスとハンドラーが常にロードされるという優れた利点があるため、コンパイルと初期化で時間のロスがありません。
よくある落とし穴は、単純な文字列操作でビッグ データの操作を開始し、非効率な大きなコピーが多数必要になることです。代わりに、より大きなリクエストおよびレスポンスボディの場合は、可能な限りストリーミングを使用する必要があります。
問題は実際には CPU コストです。非効率的な I/O 管理 (非ブロッキングである限り) は、個々のリクエストを遅らせるだけです。同時にディスパッチし、最終的には Amp のコンビネータを介して複数の独立した I/O リクエストをバンドルすることが推奨されますが、ハンドラーが遅いと他のすべてのリクエストも遅くなります。 1 つのハンドラーが計算を行っている間は、他のすべてのハンドラーは処理を続行できません。したがって、ハンドラーの計算時間を最小限に抑えることが不可欠です。
いくつかの例は、リポジトリの./examples
ディレクトリにあります。これらは、コマンド ラインで通常の PHP スクリプトとして実行できます。
php examples/hello-world.php
その後、ブラウザでhttp://localhost:1337/
にあるサンプル サーバーにアクセスできます。
セキュリティ関連の問題を発見した場合は、公開の問題トラッカーを使用する代わりに、非公開のセキュリティ問題報告者を使用してください。
MIT ライセンス (MIT)。詳細については、「ライセンス」を参照してください。