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を提供します。アプリケーションに別の実装を提供し、場合によってはルーターと組み合わせて複数を使用することもできます。
RequestRequest オブジェクトは通常サーバーによって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(): stringHTTP プロトコルのバージョンを文字列として返します (例: "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)。詳細については、「ライセンス」を参照してください。