Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

An advanced async HTTP server library for PHP, perfect for real-time apps and APIs with high concurrency demands.

License

NotificationsYou must be signed in to change notification settings

amphp/http-server

Repository files navigation

AMPHP is a collection of event-driven libraries for PHP designed with fibers and concurrency in mind.This package provides a non-blocking, concurrent HTTP/1.1 and HTTP/2 application server for PHP based onRevolt.Several features are provided in separate packages, such as theWebSocket component.

Features

Requirements

  • PHP 8.1+

Installation

This package can be installed as aComposer dependency.

composer require amphp/http-server

Additionally, you might want to install thenghttp2 library to take advantage of FFI to speed up and reduce the memory usage.

Usage

This library provides access to your application through the HTTP protocol, accepting client requests and forwarding those requests to handlers defined by your application which will return a response.

Incoming requests are represented byRequest objects. A request is provided to an implementor ofRequestHandler, which defines ahandleRequest() method returning an instance ofResponse.

publicfunction handleRequest(Request$request):Response

Request handlers are covered in greater detail in theRequestHandler section.

This HTTP server is built on top ofthe Revolt event-loop andthe non-blocking concurrency framework Amp.Thus it inherits full support of all their primitives and it is possible to use all the non-blocking libraries built on top of Revolt.

NoteIn general, you should make yourself familiar withtheFutureconcept, withcoroutines, and be aware of the severalcombinator functions to really succeed at using the HTTP server.

Blocking I/O

Nearly every built-in function of PHP is doing blocking I/O, that means, the executing thread (mostly equivalent to the process in the case of PHP) will effectively be halted until the response is received.A few examples of such functions:mysqli_query,file_get_contents,usleep and many more.

A good rule of thumb is: Every built-in PHP function doing I/O is doing it in a blocking way, unless you know for sure it doesn't.

There arelibraries providing implementations that use non-blocking I/O. You should use these instead of the built-in functions.

We cover the most common I/O needs, such asnetwork sockets,file access,HTTP requests andwebsockets,MySQL andPostgres database clients, andRedis. If using blocking I/O or long computations are necessary to fulfill a request, consider using theParallel library to run that code in a separate process or thread.

WarningDo not use any blocking I/O functions in the HTTP server.

// Here's a bad example, DO NOT do something like the following!$handler =newClosureRequestHandler(function () {sleep(5);// Equivalent to a blocking I/O function with a 5 second timeoutreturnnewResponse;});// 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.

Creating an HTTP Server

Your application will be served by an instance ofHttpServer. This library providesSocketHttpServer, which will be suitable for most applications, built on components found in this library and inamphp/socket.

To create an instance ofSocketHttpServer and listen for requests, minimally four things are required:

  • an instance ofRequestHandler to respond to incoming requests,
  • an instance ofErrorHander to provide responses to invalid requests,
  • an instance ofPsr\Log\LoggerInterface, and
  • at least one host+port on which to listen for connections.
<?phpuseAmp\ByteStream;useAmp\Http\HttpStatus;useAmp\Http\Server\DefaultErrorHandler;useAmp\Http\Server\Request;useAmp\Http\Server\RequestHandler;useAmp\Http\Server\Response;useAmp\Http\Server\SocketHttpServer;useAmp\Log\ConsoleFormatter;useAmp\Log\StreamHandler;useMonolog\Logger;useMonolog\Processor\PsrLogMessageProcessor;require__DIR__.'/vendor/autoload.php';// Note any PSR-3 logger may be used, Monolog is only an example.$logHandler =newStreamHandler(ByteStream\getStdout());$logHandler->pushProcessor(newPsrLogMessageProcessor());$logHandler->setFormatter(newConsoleFormatter());$logger =newLogger('server');$logger->pushHandler($logHandler);$requestHandler =newclass()implements RequestHandler {publicfunctionhandleRequest(Request$request) :Response    {returnnewResponse(            status: HttpStatus::OK,            headers: ['Content-Type' =>'text/plain'],            body:'Hello, world!',        );    }};$errorHandler =newDefaultErrorHandler();$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();

The above example creates a simple server which sends a plain-text response to every request received.

NoteNagle's algorithm can negatively affect benchmarks on some systems. It can be disabled usingAmp\Socket\BindContext::withTcpNoDelay(), passing the returned instance as the second argument toSocketHttpServer::expose().

SocketHttpServer provides two static constructors for common use-cases in addition to the normal constructor for more advanced and custom uses.

  • SocketHttpServer::createForDirectAccess(): Used in the example above, this creates an HTTP application server suitable for direct network access. Adjustable limits are imposed on connections per IP, total connections, and concurrent requests (10, 1000, and 1000 by default, respectively). Response compression may be toggled on or off (on by default), and request methods are limited to a known set of HTTP verbs by default.
  • SocketHttpServer::createForBehindProxy(): Creates a server appropriate for use when behind a proxy service such as nginx. This static constructor requires a list of trusted proxy IPs (with optional subnet masks) and an enum case ofForwardedHeaderType (corresponding to eitherForwarded orX-Forwarded-For) to parse the original client IP from request headers. No limits are imposed on the number of connections to the server, however the number of concurrent requests is limited (1000 by default, adjustable or can be removed). Response compression may be toggled on or off (on by default). Request methods are limited to a known set of HTTP verbs by default.

If neither of these methods serves your application needs, theSocketHttpServer constructor may be used directly. This provides an enormous amount of flexibility in how incoming client connections are created and handled, but will require more code to create. The constructor requires the user to pass an instance ofSocketServerFactory, used to create clientSocket instances (both components of theamphp/socket library), and an instance ofClientFactory, which appropriately createsClient instances which are attached to eachRequest made by the client.

RequestHandler

Incoming requests are represented byRequest objects. A request is provided to an implementor ofRequestHandler, which defines ahandleRequest() method returning an instance ofResponse.

publicfunction handleRequest(Request$request):Response

Each client request (i.e., call toRequestHandler::handleRequest()) is executed within a separatecoroutine so requests are automatically handled cooperatively within the server process. When a request handler waits onnon-blocking I/O, other client requests are processed in concurrent coroutines. Your request handler may itself create other coroutines usingAmp\async() to execute multiple tasks for a single request.

Usually aRequestHandler directly generates a response, but it might also delegate to anotherRequestHandler.An example for such a delegatingRequestHandler is theRouter.

TheRequestHandler interface is meant to be implemented by custom classes.For very simple use cases or quick mocking, you can useCallableRequestHandler, which can wrap anycallable and accepting aRequest and returning aResponse.

Middleware

Middleware allows pre-processing of requests and post-processing of responses.Apart from that, a middleware can also intercept the request processing and return a response without delegating to the passed request handler.Classes have to implement theMiddleware interface for that.

NoteMiddleware generally follows other words like soft- and hardware with its plural.However, we use the termmiddlewares to refer to multiple objects implementing theMiddleware interface.

publicfunction handleRequest(Request$request,RequestHandler$next):Response

handleRequest is the only method of theMiddleware interface. If theMiddleware doesn't handle the request itself, it should delegate the response creation to the receivedRequestHandler.

function stackMiddleware(RequestHandler$handler,Middleware ...$middleware):RequestHandler

Multiple middlewares can be stacked by usingAmp\Http\Server\Middleware\stackMiddleware(), which accepts aRequestHandler as first argument and a variable number ofMiddleware instances. The returnedRequestHandler will invoke each middleware in the provided order.

$requestHandler =newclassimplements RequestHandler {publicfunctionhandleRequest(Request$request):Response    {returnnewResponse(            status: HttpStatus::OK,            headers: ["content-type" =>"text/plain; charset=utf-8"],            body:"Hello, World!",        );    }}$middleware =newclassimplements Middleware {publicfunctionhandleRequest(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 =newDefaultErrorHandler();// $logger is a PSR-3 logger instance.$server = SocketHttpServer::createForDirectAccess($logger);$server->expose('127.0.0.1:1337');$server->start($stackedHandler,$errorHandler);

ErrorHandler

AnErrorHander is used by the HTTP server when a malformed or otherwise invalid request is received. TheRequest object is provided if one constructed from the incoming data, but may not always be set.

publicfunction handleError(int$status,    ?string$reason =null,    ?Request$request =null,):Response

This library providesDefaultErrorHandler which returns a stylized HTML page as the response body. You may wish to provide a different implementation for your application, potentially using multiple in conjunction with arouter.

Request

Constructor

It is rare you will need to construct aRequest object yourself, as they will typically be provided toRequestHandler::handleRequest() by the server.

/** * @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. */publicfunction __construct(privatereadonlyClient$client,string$method,Psr\Http\Message\UriInterface$uri,array$headers = [],Amp\ByteStream\ReadableStream|string$body ='',privatestring$protocol ='1.1',    ?Trailers$trailers =null,)

Methods

publicfunction getClient():Client

Returns theСlient sending the request

publicfunction getMethod():string

Returns the HTTP method used to make this request, e.g."GET".

publicfunction setMethod(string$method):void

Sets the request HTTP method.

publicfunction getUri():Psr\Http\Message\UriInterface

Returns the requestURI.

publicfunction setUri(Psr\Http\Message\UriInterface$uri):void

Sets a newURI for the request.

publicfunction getProtocolVersion():string

Returns the HTTP protocol version as a string (e.g. "1.0", "1.1", "2").

publicfunction setProtocolVersion(string$protocol)

Sets a new protocol version number for the request.

/** @return array<non-empty-string, list<string>> */publicfunction getHeaders():array

Returns the headers as a string-indexed array of arrays of strings or an empty array if no headers have been set.

publicfunction hasHeader(string$name):bool

Checks if given header exists.

/** @return list<string> */publicfunction getHeaderArray(string$name):array

Returns the array of values for the given header or an empty array if the header does not exist.

publicfunction getHeader(string$name): ?string

Returns the value of the given header.If multiple headers are present for the named header, only the first header value will be returned.UsegetHeaderArray() to return an array of all values for the particular header.Returnsnull if the header does not exist.

publicfunction setHeaders(array$headers):void

Sets the headers from the given array.

/** @param array<string>|string $value */publicfunction setHeader(string$name,array|string$value):void

Sets the header to the given value(s).All previous header lines with the given name will be replaced.

/** @param array<string>|string $value */publicfunction addHeader(string$name,array|string$value):void

Adds an additional header line with the given name.

publicfunction removeHeader(string$name):void

Removes the given header if it exists.If multiple header lines with the same name exist, all of them are removed.

publicfunction getBody():RequestBody

Returns the request body. TheRequestBody allows streamed and buffered access to anInputStream.

publicfunction setBody(ReadableStream|string$body)

Sets the stream for the message body

NoteUsing a string will automatically set theContent-Length header to the length of the given string.Setting anReadableStream will remove theContent-Length header.If you know the exact content length of your stream, you can add acontent-length headerafter callingsetBody().

/** @return array<non-empty-string, RequestCookie> */publicfunction getCookies():array

Returns allcookies in associative map of cookie name toRequestCookie.

publicfunction getCookie(string$name): ?RequestCookie

Gets acookie value by name ornull.

publicfunction setCookie(RequestCookie$cookie):void

Adds aCookie to the request.

publicfunction removeCookie(string$name):void

Removes a cookie from the request.

publicfunction getAttributes():array

Returns an array of all the attributes stored in the request's mutable local storage.

publicfunction removeAttributes():array

Removes all request attributes from the request's mutable local storage.

publicfunction hasAttribute(string$name):bool

Check whether an attribute with the given name exists in the request's mutable local storage.

publicfunction getAttribute(string$name):mixed

Retrieve a variable from the request's mutable local storage.

NoteName of the attribute should be namespaced with a vendor and package namespace, like classes.

publicfunction setAttribute(string$name,mixed$value):void

Assign a variable to the request's mutable local storage.

NoteName of the attribute should be namespaced with a vendor and package namespace, like classes.

publicfunction removeAttribute(string$name):void

Removes a variable from the request's mutable local storage.

publicfunction getTrailers():Trailers

Allows access to theTrailers of a request.

publicfunction setTrailers(Trailers$trailers):void

Assigns theTrailers object to be used in the request.

Request Clients

Client-related details are bundled intoAmp\Http\Server\Driver\Client objects returned fromRequest::getClient(). TheClient interface provides methods to retrieve the remote and local socket addresses and TLS info (if applicable).

Response

TheResponse class represents an HTTP response. AResponse is returned byrequest handlers andmiddleware.

Constructor

/** * @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. */publicfunction __construct(int$code = HttpStatus::OK,array$headers = [],Amp\ByteStream\ReadableStream|string$body ='',    ?Trailers$trailers =null,)

Destructor

Invokes dispose handlers (i.e. functions that registered viaonDispose() method).

NoteUncaught exceptions from the dispose handlers will be forwarded to theevent loop error handler.

Methods

publicfunction getBody():Amp\ByteStream\ReadableStream

Returns thestream for the message body.

publicfunction setBody(Amp\ByteStream\ReadableStream|string$body)

Sets thestream for the message body.

NoteUsing a string will automatically set theContent-Length header to the length of the given string.Setting anReadableStream will remove theContent-Length header.If you know the exact content length of your stream, you can add acontent-length headerafter callingsetBody().

/** @return array<non-empty-string, list<string>> */publicfunction getHeaders():array

Returns the headers as a string-indexed array of arrays of strings or an empty array if no headers have been set.

publicfunction hasHeader(string$name):bool

Checks if given header exists.

/** @return list<string> */publicfunction getHeaderArray(string$name):array

Returns the array of values for the given header or an empty array if the header does not exist.

publicfunction getHeader(string$name): ?string

Returns the value of the given header.If multiple headers are present for the named header, only the first header value will be returned.UsegetHeaderArray() to return an array of all values for the particular header.Returnsnull if the header does not exist.

publicfunction setHeaders(array$headers):void

Sets the headers from the given array.

/** @param array<string>|string $value */publicfunction setHeader(string$name,array|string$value):void

Sets the header to the given value(s).All previous header lines with the given name will be replaced.

/** @param array<string>|string $value */publicfunction addHeader(string$name,array|string$value):void

Adds an additional header line with the given name.

publicfunction removeHeader(string$name):void

Removes the given header if it exists.If multiple header lines with the same name exist, all of them are removed.

publicfunction getStatus():int

Returns the response status code.

publicfunction getReason():string

Returns the reason phrase describing the status code.

publicfunction setStatus(int$code,string |null$reason):void

Sets the numeric HTTP status code (between 100 and 599) and reason phrase. Use null for the reason phrase to use the default phrase associated with the status code.

/** @return array<non-empty-string, ResponseCookie> */publicfunction getCookies():array

Returns allcookies in an associative map of cookie name toResponseCookie.

publicfunction getCookie(string$name): ?ResponseCookie

Gets acookie value by name ornull if no cookie with that name is present.

publicfunction setCookie(ResponseCookie$cookie):void

Adds acookie to the response.

publicfunction removeCookie(string$name):void

Removes acookie from the response.

/** @return array<string, Push> Map of URL strings to Push objects. */publicfunction getPushes():array

Returns list of push resources in an associative map of URL strings toPush objects.

/** @param array<string>|array<string, array<string>> $headers */publicfunction push(string$url,array$headers):void

Indicate resources which a client likely needs to fetch. (e.g.Link: preload or HTTP/2 Server Push).

publicfunction isUpgraded():bool

Returnstrue if a detach callback has been set,false if none.

/** @param Closure(Driver\UpgradedSocket, Request, Response): void $upgrade */publicfunction upgrade(Closure$upgrade):void

Sets a callback to be invoked once the response has been written to the client and changes the status of the response to101 Switching Protocols. The callback receives an instance ofDriver\UpgradedSocket, theRequest which initiated the upgrade, and thisResponse.

The callback may be removed by changing the status to something other than 101.

publicfunction getUpgradeCallable(): ?Closure

Returns the upgrade function if present.

/** @param Closure():void $onDispose */publicfunction onDispose(Closure$onDispose):void

Registers a function that is invoked when the Response is discarded. A response is discarded either once it has been written to the client or if it gets replaced in a middleware chain.

publicfunction getTrailers():Trailers

Allows access to theTrailers of a response.

publicfunction setTrailers(Trailers$trailers):void

Assigns theTrailers object to be used in the response. Trailers are sent once the entire response body has been set to the client.

Body

RequestBody, returned fromRequest::getBody(), provides buffered and streamed access to the request body.Use the streamed access to handle large messages, which is particularly important if you have larger message limits (like tens of megabytes) and don't want to buffer it all in memory.If multiple people are uploading large bodies concurrently, the memory might quickly get exhausted.

Hence, incremental handling is important, accessible viatheread() API ofAmp\ByteStream\ReadableStream.

In case a client disconnects, theread() fails with anAmp\Http\Server\ClientException.This exception is thrown for both theread() andbuffer() API.

NoteClientExceptions do notneed to be caught. You may catch them if you want to continue, but don't have to. The Server will silently end the request cycle and discard that exception then.

Instead of setting the generic body limit high, you should consider increasing the body limit only where needed, which is dynamically possible with theincreaseSizeLimit() method onRequestBody.

NoteRequestBody itself doesn't provide parsing of form data. You can useamphp/http-server-form-parser if you need it.

Constructor

LikeRequest, it is rare to need to construct aRequestBody instance as one will be provided as part of theRequest.

publicfunction __construct(ReadableStream|string$stream,    ?Closure$upgradeSize =null,)

Methods

publicfunction increaseSizeLimit(int$limit):void

Increases the body size limit dynamically to allow individual request handlers to handle larger request bodies than the default set for the HTTP server.

Trailers

TheTrailers class allows access to the trailers of an HTTP request, accessible viaRequest::getTrailers().null is returned if trailers are not expected on the request.Trailers::await() returns aFuture which is resolved with anHttpMessage object providing methods to access the trailer headers.

$trailers =$request->getTrailers();$message =$trailers?->await();

Bottlenecks

The HTTP server won't be the bottleneck. Misconfiguration, use of blocking I/O, or inefficient applications are.

The server is well-optimized and can handle tens of thousands of requests per second on typical hardware while maintaining a high level of concurrency of thousands of clients.

But that performance will decrease drastically with inefficient applications.The server has the nice advantage of classes and handlers being always loaded, so there's no time lost with compilation and initialization.

A common trap is to begin operating on big data with simple string operations, requiring many inefficient big copies.Instead, streaming should be used where possible for larger request and response bodies.

The problem really is CPU cost.Inefficient I/O management (as long as it is non-blocking!) is just delaying individual requests.It is recommended to dispatch simultaneously and eventually bundle multiple independent I/O requests via Amp's combinators, but a slow handler will slow down every other request too.While one handler is computing, all the other handlers can't continue.Thus it is imperative to reduce computation times of the handlers to a minimum.

Examples

Several examples can be found in the./examples directory of therepository.These can be executed as normal PHP scripts on the command line.

php examples/hello-world.php

You can then access the example server athttp://localhost:1337/ in your browser.

Security

If you discover any security related issues, please use the private security issue reporter instead of using the public issue tracker.

License

The MIT License (MIT). Please seeLICENSE for more information.

About

An advanced async HTTP server library for PHP, perfect for real-time apps and APIs with high concurrency demands.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Contributors44


[8]ページ先頭

©2009-2025 Movatter.jp