- Notifications
You must be signed in to change notification settings - Fork148
Event-driven, streaming HTTP client and server implementation for ReactPHP.
License
reactphp/http
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Event-driven, streaming HTTP client and server implementation forReactPHP.
Development version: This branch contains the code for the upcoming v3release. For the code of the current stable v1 release, check out the
1.x
branch.The upcoming v3 release will be the way forward for this package. However,we will still actively support v1 for those not yet on the latest version.See alsoinstallation instructions for more details.
This HTTP library provides re-usable implementations for an HTTP client andserver based on ReactPHP'sSocket
andEventLoop
components.Its client component allows you to send any number of async HTTP/HTTPS requestsconcurrently.Its server component allows you to build plaintext HTTP and secure HTTPS serversthat accept incoming HTTP requests from HTTP clients (such as web browsers).This library provides async, streaming means for all of this, so you can handlemultiple concurrent HTTP requests without blocking.
Table of contents
- Quickstart example
- Client Usage
- Server Usage
- API
- Install
- Tests
- License
Onceinstalled, you can use the following code to access anHTTP web server and send some simple HTTP GET requests:
<?phprequire__DIR__ .'/vendor/autoload.php';$client =newReact\Http\Browser();$client->get('http://www.google.com/')->then(function (Psr\Http\Message\ResponseInterface$response) {var_dump($response->getHeaders(), (string)$response->getBody());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
This is an HTTP server which responds withHello World!
to every request.
<?phprequire__DIR__ .'/vendor/autoload.php';$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request) {returnReact\Http\Message\Response::plaintext("Hello World!\n" );});$socket =newReact\Socket\SocketServer('127.0.0.1:8080');$http->listen($socket);
See also theexamples.
Most importantly, this project provides aBrowser
object thatoffers several methods that resemble the HTTP protocol methods:
$browser->get($url, array$headers = []);$browser->head($url, array$headers = []);$browser->post($url, array$headers = [], string|ReadableStreamInterface$body ='');$browser->delete($url, array$headers = [], string|ReadableStreamInterface$body ='');$browser->put($url, array$headers = [], string|ReadableStreamInterface$body ='');$browser->patch($url, array$headers = [], string|ReadableStreamInterface$body ='');
Each of these methods requires a$url
and some optional parameters to send anHTTP request. Each of these method names matches the respective HTTP requestmethod, for example theget()
method sends an HTTPGET
request.
You can optionally pass an associative array of additional$headers
that will besent with this HTTP request. Additionally, each method will automatically add amatchingContent-Length
request header if an outgoing request body is given and itssize is known and non-empty. For an empty request body, if will only include aContent-Length: 0
request header if the request method usually expects a requestbody (only applies toPOST
,PUT
andPATCH
HTTP request methods).
If you're using astreaming request body, it will defaultto usingTransfer-Encoding: chunked
unless you explicitly pass in a matchingContent-Length
request header. See alsostreaming request for more details.
By default, all of the above methods default to sending requests using theHTTP/1.1 protocol version. If you want to explicitly use the legacy HTTP/1.0protocol version, you can use thewithProtocolVersion()
method. If you want to use any other or even custom HTTP request method, you canuse therequest()
method.
Each of the above methods supports async operation and eitherfulfills with aPSR-7ResponseInterface
orrejects with anException
.Please see the following chapter aboutpromises for more details.
Sending requests is async (non-blocking), so you can actually send multiplerequests in parallel.TheBrowser
will respond to each request with aPSR-7ResponseInterface
message, the order is not guaranteed.Sending requests uses aPromise-basedinterface that makes it easy to react to when an HTTP request is completed(i.e. either successfully fulfilled or rejected with an error):
$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface$response) {var_dump('Response received',$response); },function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL; });
If this looks strange to you, you can also use the more traditionalblocking API.
Keep in mind that resolving the Promise with the full response message means thewhole response body has to be kept in memory.This is easy to get started and works reasonably well for smaller responses(such as common HTML pages or RESTful or JSON API requests).
You may also want to look into thestreaming API:
- If you're dealing with lots of concurrent requests (100+) or
- If you want to process individual data chunks as they happen (without having to wait for the full response body) or
- If you're expecting a big response body size (1 MiB or more, for example when downloading binary files) or
- If you're unsure about the response body size (better be safe than sorry when accessing arbitrary remote HTTP endpoints and the response body size is unknown in advance).
The returned Promise is implemented in such a way that it can be cancelledwhen it is still pending.Cancelling a pending promise will reject its value with an Exception andclean up any underlying resources.
$promise =$browser->get($url);Loop::addTimer(2.0,function ()use ($promise) {$promise->cancel();});
This library uses a very efficient HTTP implementation, so most HTTP requestsshould usually be completed in mere milliseconds. However, when sending HTTPrequests over an unreliable network (the internet), there are a number of thingsthat can go wrong and may cause the request to fail after a time. As such, thislibrary respects PHP'sdefault_socket_timeout
setting (default 60s) as a timeoutfor sending the outgoing HTTP request and waiting for a successful response andwill otherwise cancel the pending request and reject its value with an Exception.
Note that this timeout value covers creating the underlying transport connection,sending the HTTP request, receiving the HTTP response headers and its fullresponse body and following any eventualredirects. See alsoredirects below to configure the number of redirects to follow (ordisable following redirects altogether) and alsostreamingbelow to not take receiving large response bodies into account for this timeout.
You can use thewithTimeout()
method to pass a custom timeoutvalue in seconds like this:
$browser =$browser->withTimeout(10.0);$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface$response) {// response received within 10 seconds maximumvar_dump($response->getHeaders());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
Similarly, you can use a boolfalse
to not apply a timeout at allor use a booltrue
value to restore the default handling.SeewithTimeout()
for more details.
If you're using astreaming response body, the time ittakes to receive the response body stream will not be included in the timeout.This allows you to keep this incoming stream open for a longer time, such aswhen downloading a very large stream or when streaming data over a long-livedconnection.
If you're using astreaming request body, the time ittakes to send the request body stream will not be included in the timeout. Thisallows you to keep this outgoing stream open for a longer time, such as whenuploading a very large stream.
Note that this timeout handling applies to the higher-level HTTP layer. Lowerlayers such as socket and DNS may also apply (different) timeout values. Inparticular, the underlying socket connection uses the samedefault_socket_timeout
setting to establish the underlying transport connection. To control thisconnection timeout behavior, you caninject a customConnector
like this:
$browser =newReact\Http\Browser(newReact\Socket\Connector( ['timeout' =>5 ] ));
This library supportsHTTP Basic Authenticationusing theAuthorization: Basic …
request header or allows you to set an explicitAuthorization
request header.
By default, this library does not include an outgoingAuthorization
requestheader. If the server requires authentication, if may return a401
(Unauthorized)status code which will reject the request by default (see also thewithRejectErrorResponse()
method below).
In order to pass authentication details, you can simply pass the username andpassword as part of the request URL like this:
$promise =$browser->get('https://user:pass@example.com/api');
Note that special characters in the authentication details have to bepercent-encoded, see alsorawurlencode()
.This example will automatically pass the base64-encoded authentication detailsusing the outgoingAuthorization: Basic …
request header. If the HTTP endpointyou're talking to requires any other authentication scheme, you can also passthis header explicitly. This is common when using (RESTful) HTTP APIs that useOAuth access tokens or JSON Web Tokens (JWT):
$token ='abc123';$promise =$browser->get('https://example.com/api', ['Authorization' =>'Bearer' .$token ]);
When following redirects, theAuthorization
request header will never be sentto any remote hosts by default. When following a redirect where theLocation
response header contains authentication details, these details will be sent forfollowing requests. See alsoredirects below.
By default, this library follows any redirects and obeys3xx
(Redirection)status codes using theLocation
response header from the remote server.The promise will be fulfilled with the last response from the chain of redirects.
$browser->get($url,$headers)->then(function (Psr\Http\Message\ResponseInterface$response) {// the final response will end up herevar_dump($response->getHeaders());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
Any redirected requests will follow the semantics of the original request andwill include the same request headers as the original request except for thoselisted below.If the original request is a temporary (307) or a permanent (308) redirect, requestbody and headers will be passed to the redirected request. Otherwise, the requestbody will never be passed to the redirected request. Accordingly, each redirectedrequest will remove anyContent-Length
andContent-Type
request headers.
If the original request used HTTP authentication with anAuthorization
requestheader, this request header will only be passed as part of the redirectedrequest if the redirected URL is using the same host. In other words, theAuthorizaton
request header will not be forwarded to other foreign hosts due topossible privacy/security concerns. When following a redirect where theLocation
response header contains authentication details, these details will be sent forfollowing requests.
You can use thewithFollowRedirects()
method tocontrol the maximum number of redirects to follow or to return any redirectresponses as-is and apply custom redirection logic like this:
$browser =$browser->withFollowRedirects(false);$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface$response) {// any redirects will now end up herevar_dump($response->getHeaders());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
See alsowithFollowRedirects()
for more details.
As stated above, this library provides you a powerful, async API by default.
You can also integrate this into your traditional, blocking environment by usingreactphp/async. This allows you to simplyawait async HTTP requests like this:
usefunctionReact\Async\await;$browser =newReact\Http\Browser();$promise =$browser->get('http://example.com/');try {$response =await($promise);// response successfully received}catch (Exception$e) {// an error occurred while performing the request}
Similarly, you can also process multiple requests concurrently and await an array ofResponse
objects:
usefunctionReact\Async\await;usefunctionReact\Promise\all;$promises = [$browser->get('http://example.com/'),$browser->get('http://www.example.org/'),];$responses =await(all($promises));
This is made possible thanks to fibers available in PHP 8.1+ and ourcompatibility API that also works on all supported PHP versions.Please refer toreactphp/async for more details.
Keep in mind the above remark about buffering the whole response message in memory.As an alternative, you may also see one of the following chapters for thestreaming API.
As stated above, this library provides you a powerful, async API. Being able tosend a large number of requests at once is one of the core features of thisproject. For instance, you can easily send 100 requests concurrently whileprocessing SQL queries at the same time.
Remember, with great power comes great responsibility. Sending an excessivenumber of requests may either take up all resources on your side or it may evenget you banned by the remote side if it sees an unreasonable number of requestsfrom your side.
// watch out if array contains many elementsforeach ($urlsas$url) {$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface$response) {var_dump($response->getHeaders()); },function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL; });}
As a consequence, it's usually recommended to limit concurrency on the sendingside to a reasonable value. It's common to use a rather small limit, as doingmore than a dozen of things at once may easily overwhelm the receiving side. Youcan useclue/reactphp-mq as a lightweightin-memory queue to concurrently do many (but not too many) things at once:
// wraps Browser in a Queue object that executes no more than 10 operations at once$q =newClue\React\Mq\Queue(10,null,function ($url)use ($browser) {return$browser->get($url);});foreach ($urlsas$url) {$q($url)->then(function (Psr\Http\Message\ResponseInterface$response) {var_dump($response->getHeaders()); },function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL; });}
Additional requests that exceed the concurrency limit will automatically beenqueued until one of the pending requests completes. This integrates nicelywith the existingPromise-based API. Please refer toclue/reactphp-mq for more details.
This in-memory approach works reasonably well for some thousand outstandingrequests. If you're processing a very large input list (think millions of rowsin a CSV or NDJSON file), you may want to look into using a streaming approachinstead. Seeclue/reactphp-flux formore details.
All of the above examples assume you want to store the whole response body in memory.This is easy to get started and works reasonably well for smaller responses.
However, there are several situations where it's usually a better idea to use astreaming approach, where only small chunks have to be kept in memory:
- If you're dealing with lots of concurrent requests (100+) or
- If you want to process individual data chunks as they happen (without having to wait for the full response body) or
- If you're expecting a big response body size (1 MiB or more, for example when downloading binary files) or
- If you're unsure about the response body size (better be safe than sorry when accessing arbitrary remote HTTP endpoints and the response body size is unknown in advance).
You can use therequestStreaming()
method to send anarbitrary HTTP request and receive a streaming response. It uses the same HTTPmessage API, but does not buffer the response body in memory. It only processesthe response body in small chunks as data is received and forwards this datathroughReactPHP's Stream API. This worksfor (any number of) responses of arbitrary sizes.
This means it resolves with a normalPSR-7ResponseInterface
,which can be used to access the response message parameters as usual.You can access the message body as usual, however it now alsoimplementsReactPHP'sReadableStreamInterface
as well as parts of thePSR-7StreamInterface
.
$browser->requestStreaming('GET',$url)->then(function (Psr\Http\Message\ResponseInterface$response) {$body =$response->getBody();assert($bodyinstanceofPsr\Http\Message\StreamInterface);assert($bodyinstanceofReact\Stream\ReadableStreamInterface);$body->on('data',function ($chunk) {echo$chunk; });$body->on('error',function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL; });$body->on('close',function () {echo'[DONE]' .PHP_EOL; });},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
See also thestream download benchmark example andthestream forwarding example.
You can invoke the following methods on the message body:
$body->on($event,$callback);$body->eof();$body->isReadable();$body->pipe(React\Stream\WritableStreamInterface$dest, array$options = []);$body->close();$body->pause();$body->resume();
Because the message body is in a streaming state, invoking the following methodsdoesn't make much sense:
$body->__toString();// ''$body->detach();// throws BadMethodCallException$body->getSize();// null$body->tell();// throws BadMethodCallException$body->isSeekable();// false$body->seek();// throws BadMethodCallException$body->rewind();// throws BadMethodCallException$body->isWritable();// false$body->write();// throws BadMethodCallException$body->read();// throws BadMethodCallException$body->getContents();// throws BadMethodCallException
Note howtimeouts apply slightly differently when using streaming.In streaming mode, the timeout value covers creating the underlying transportconnection, sending the HTTP request, receiving the HTTP response headers andfollowing any eventualredirects. In particular, the timeout valuedoes not take receiving (possibly large) response bodies into account.
If you want to integrate the streaming response into a higher level API, thenworking with Promise objects that resolve with Stream objects is often inconvenient.Consider looking into also usingreact/promise-stream.The resulting streaming code could look something like this:
usefunctionReact\Promise\Stream\unwrapReadable;functiondownload(Browser$browser,string$url):React\Stream\ReadableStreamInterface {returnunwrapReadable($browser->requestStreaming('GET',$url)->then(function (Psr\Http\Message\ResponseInterface$response) {return$response->getBody(); }) );}$stream =download($browser,$url);$stream->on('data',function ($data) {echo$data;});$stream->on('error',function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
See also therequestStreaming()
method for more details.
Besides streaming the response body, you can also stream the request body.This can be useful if you want to send big POST requests (uploading files etc.)or process many outgoing streams at once.Instead of passing the body as a string, you can simply pass an instanceimplementingReactPHP'sReadableStreamInterface
to therequest methods like this:
$browser->post($url, [],$stream)->then(function (Psr\Http\Message\ResponseInterface$response) {echo'Successfully sent.';},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
If you're using a streaming request body (React\Stream\ReadableStreamInterface
), it willdefault to usingTransfer-Encoding: chunked
or you have to explicitly pass in amatchingContent-Length
request header like so:
$body =newReact\Stream\ThroughStream();Loop::addTimer(1.0,function ()use ($body) {$body->end("hello world");});$browser->post($url, ['Content-Length' =>'11'],$body);
If the streaming request body emits anerror
event or is explicitly closedwithout emitting a successfulend
event first, the request will automaticallybe closed and rejected.
You can also establish your outgoing connections through an HTTP CONNECT proxy serverby adding a dependency toclue/reactphp-http-proxy.
HTTP CONNECT proxy servers (also commonly known as "HTTPS proxy" or "SSL proxy")are commonly used to tunnel HTTPS traffic through an intermediary ("proxy"), toconceal the origin address (anonymity) or to circumvent address blocking(geoblocking). While many (public) HTTP CONNECT proxy servers often limit thisto HTTPS port443
only, this can technically be used to tunnel any TCP/IP-basedprotocol, such as plain HTTP and TLS-encrypted HTTPS.
$proxy =newClue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');$connector =newReact\Socket\Connector(['tcp' =>$proxy,'dns' =>false]);$browser =newReact\Http\Browser($connector);
See also theHTTP proxy example.
You can also establish your outgoing connections through a SOCKS proxy serverby adding a dependency toclue/reactphp-socks.
The SOCKS proxy protocol family (SOCKS5, SOCKS4 and SOCKS4a) is commonly used totunnel HTTP(S) traffic through an intermediary ("proxy"), to conceal the originaddress (anonymity) or to circumvent address blocking (geoblocking). While many(public) SOCKS proxy servers often limit this to HTTP(S) port80
and443
only, this can technically be used to tunnel any TCP/IP-based protocol.
$proxy =newClue\React\Socks\Client('127.0.0.1:1080');$connector =newReact\Socket\Connector(['tcp' =>$proxy,'dns' =>false]);$browser =newReact\Http\Browser($connector);
See also theSOCKS proxy example.
You can also establish your outgoing connections through an SSH serverby adding a dependency toclue/reactphp-ssh-proxy.
Secure Shell (SSH) is a securenetwork protocol that is most commonly used to access a login shell on a remoteserver. Its architecture allows it to use multiple secure channels over a singleconnection. Among others, this can also be used to create an "SSH tunnel", whichis commonly used to tunnel HTTP(S) traffic through an intermediary ("proxy"), toconceal the origin address (anonymity) or to circumvent address blocking(geoblocking). This can be used to tunnel any TCP/IP-based protocol (HTTP, SMTP,IMAP etc.), allows you to access local services that are otherwise not accessiblefrom the outside (database behind firewall) and as such can also be used forplain HTTP and TLS-encrypted HTTPS.
$proxy =newClue\React\SshProxy\SshSocksConnector('alice@example.com');$connector =newReact\Socket\Connector(['tcp' =>$proxy,'dns' =>false]);$browser =newReact\Http\Browser($connector);
See also theSSH proxy example.
By default, this library supports transport over plaintext TCP/IP and secureTLS connections for thehttp://
andhttps://
URL schemes respectively.This library also supports Unix domain sockets (UDS) when explicitly configured.
In order to use a UDS path, you have to explicitly configure the connector tooverride the destination URL so that the hostname given in the request URL willno longer be used to establish the connection:
$connector =newReact\Socket\FixedUriConnector('unix:///var/run/docker.sock',newReact\Socket\UnixConnector());$browser =newReact\Http\Browser($connector);$client->get('http://localhost/info')->then(function (Psr\Http\Message\ResponseInterface$response) {var_dump($response->getHeaders(), (string)$response->getBody());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
See also theUnix Domain Sockets (UDS) example.
TheReact\Http\HttpServer
class is responsible for handling incoming connections and thenprocessing each incoming HTTP request.
When a complete HTTP request has been received, it will invoke the givenrequest handler function. This request handler function needs to be passed tothe constructor and will be invoked with the respectiverequestobject and expects aresponse object in return:
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request) {returnReact\Http\Message\Response::plaintext("Hello World!\n" );});
Each incoming HTTP request message is always represented by thePSR-7ServerRequestInterface
,see also followingrequest chapter for more details.
Each outgoing HTTP response message is always represented by thePSR-7ResponseInterface
,see also followingresponse chapter for more details.
This class takes an optionalLoopInterface|null $loop
parameter that can be used topass the event loop instance to use for this object. You can use anull
valuehere in order to use thedefault loop.This value SHOULD NOT be given unless you're sure you want to explicitly use agiven event loop instance.
In order to start listening for any incoming connections, theHttpServer
needsto be attached to an instance ofReact\Socket\ServerInterface
through thelisten()
method as described in the followingchapter. In its most simple form, you can attach this to aReact\Socket\SocketServer
in order to start a plaintext HTTP server like this:
$http =newReact\Http\HttpServer($handler);$socket =newReact\Socket\SocketServer('0.0.0.0:8080');$http->listen($socket);
See also thelisten()
method and thehello world server examplefor more details.
By default, theHttpServer
buffers and parses the complete incoming HTTPrequest in memory. It will invoke the given request handler function when thecomplete request headers and request body has been received. This means therequest object passed to your request handler function will befully compatible with PSR-7 (http-message). This provides sane defaults for80% of the use cases and is the recommended way to use this library unlessyou're sure you know what you're doing.
On the other hand, buffering complete HTTP requests in memory until they canbe processed by your request handler function means that this class has toemploy a number of limits to avoid consuming too much memory. In order totake the more advanced configuration out your hand, it respects setting fromyourphp.ini
to apply itsdefault settings. This is a list of PHP settings this class respects withtheir respective default values:
memory_limit 128Mpost_max_size 8M // capped at 64Kenable_post_data_reading 1max_input_nesting_level 64max_input_vars 1000file_uploads 1upload_max_filesize 2Mmax_file_uploads 20
In particular, thepost_max_size
setting limits how much memory a singleHTTP request is allowed to consume while buffering its request body. Thisneeds to be limited because the server can process a large number of requestsconcurrently, so the server may potentially consume a large amount of memoryotherwise. To support higher concurrency by default, this value is cappedat64K
. If you assign a higher value, it will only allow64K
by default.If a request exceeds this limit, its request body will be ignored and it willbe processed like a request with no request body at all. See below forexplicit configuration to override this setting.
By default, this class will try to avoid consuming more than half of yourmemory_limit
for buffering multiple concurrent HTTP requests. As such, withthe above default settings of128M
max, it will try to consume no more than64M
for buffering multiple concurrent HTTP requests. As a consequence, itwill limit the concurrency to1024
HTTP requests with the above defaults.
It is imperative that you assign reasonable values to your PHP ini settings.It is usually recommended to not support buffering incoming HTTP requestswith a large HTTP request body (e.g. large file uploads). If you want toincrease this buffer size, you will have to also increase the total memorylimit to allow for more concurrent requests (setmemory_limit 512M
or more)or explicitly limit concurrency.
In order to override the above buffering defaults, you can configure theHttpServer
explicitly. You can use theLimitConcurrentRequestsMiddleware
andRequestBodyBufferMiddleware
(see below)to explicitly configure the total number of requests that can be handled atonce like this:
$http =newReact\Http\HttpServer(newReact\Http\Middleware\StreamingRequestMiddleware(),newReact\Http\Middleware\LimitConcurrentRequestsMiddleware(100),// 100 concurrent buffering handlersnewReact\Http\Middleware\RequestBodyBufferMiddleware(2 *1024 *1024),// 2 MiB per requestnewReact\Http\Middleware\RequestBodyParserMiddleware(),$handler);
In this example, we allow processing up to 100 concurrent requests at onceand each request can buffer up to2M
. This means you may have to keep amaximum of200M
of memory for incoming request body buffers. Accordingly,you need to adjust thememory_limit
ini setting to allow for these buffersplus your actual application logic memory requirements (think512M
or more).
Internally, this class automatically assigns these middleware handlersautomatically when no
StreamingRequestMiddleware
is given. Accordingly, you can use this example to override all defaultsettings to implement custom limits.
As an alternative to buffering the complete request body in memory, you canalso use a streaming approach where only small chunks of data have to be keptin memory:
$http =newReact\Http\HttpServer(newReact\Http\Middleware\StreamingRequestMiddleware(),$handler);
In this case, it will invoke the request handler function once the HTTPrequest headers have been received, i.e. before receiving the potentiallymuch larger HTTP request body. This means therequest passed toyour request handler function may not be fully compatible with PSR-7. This isspecifically designed to help with more advanced use cases where you want tohave full control over consuming the incoming HTTP request body andconcurrency settings. See alsostreaming incoming requestbelow for more details.
Thelisten(React\Socket\ServerInterface $socket): void
method can be used tostart listening for HTTP requests on the given socket server instance.
The givenReact\Socket\ServerInterface
is responsible for emitting the underlying streaming connections. ThisHTTP server needs to be attached to it in order to process anyconnections and pase incoming streaming data as incoming HTTP requestmessages. In its most common form, you can attach this to aReact\Socket\SocketServer
in order to start a plaintext HTTP server like this:
$http =newReact\Http\HttpServer($handler);$socket =newReact\Socket\SocketServer('0.0.0.0:8080');$http->listen($socket);
See alsohello world server examplefor more details.
This example will start listening for HTTP requests on the alternativeHTTP port8080
on all interfaces (publicly). As an alternative, it isvery common to use a reverse proxy and let this HTTP server listen on thelocalhost (loopback) interface only by using the listen address127.0.0.1:8080
instead. This way, you host your application(s) on thedefault HTTP port80
and only route specific requests to this HTTPserver.
Likewise, it's usually recommended to use a reverse proxy setup to acceptsecure HTTPS requests on default HTTPS port443
(TLS termination) andonly route plaintext requests to this HTTP server. As an alternative, youcan also accept secure HTTPS requests with this HTTP server by attachingthis to aReact\Socket\SocketServer
using a secure TLS listen address, a certificate file and optionalpassphrase
like this:
$http =newReact\Http\HttpServer($handler);$socket =newReact\Socket\SocketServer('tls://0.0.0.0:8443', ['tls' => ['local_cert' =>__DIR__ .'/localhost.pem' ]]);$http->listen($socket);
See alsohello world HTTPS examplefor more details.
As seen above, theHttpServer
class is responsible for handlingincoming connections and then processing each incoming HTTP request.
The request object will be processed once the request hasbeen received by the client.This request object implements thePSR-7ServerRequestInterface
which in turn extends thePSR-7RequestInterface
and will be passed to the callback function like this.
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request) {$body ="The method of the request is:" .$request->getMethod() ."\n";$body .="The requested path is:" .$request->getUri()->getPath() ."\n";returnReact\Http\Message\Response::plaintext($body );});
For more details about the request object, also check out the documentation ofPSR-7ServerRequestInterface
andPSR-7RequestInterface
.
ThegetServerParams(): mixed[]
method can be used toget server-side parameters similar to the$_SERVER
variable.The following parameters are currently available:
REMOTE_ADDR
The IP address of the request senderREMOTE_PORT
Port of the request senderSERVER_ADDR
The IP address of the serverSERVER_PORT
The port of the serverREQUEST_TIME
Unix timestamp when the complete request header has been received,as integer similar totime()
REQUEST_TIME_FLOAT
Unix timestamp when the complete request header has been received,as float similar tomicrotime(true)
HTTPS
Set to 'on' if the request used HTTPS, otherwise it won't be set
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request) {$body ="Your IP is:" .$request->getServerParams()['REMOTE_ADDR'] ."\n";returnReact\Http\Message\Response::plaintext($body );});
See alsowhatsmyip server example.
Advanced: Note that address parameters will not be set if you're listening ona Unix domain socket (UDS) path as this protocol lacks the concept ofhost/port.
ThegetQueryParams(): array
method can be used to get the query parameterssimiliar to the$_GET
variable.
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request) {$queryParams =$request->getQueryParams();$body ='The query parameter "foo" is not set. Click the following link';$body .='<a href="/?foo=bar">to use query parameter in your request</a>';if (isset($queryParams['foo'])) {$body ='The value of "foo" is:' .htmlspecialchars($queryParams['foo']); }returnReact\Http\Message\Response::html($body );});
The response in the above example will return a response body with a link.The URL contains the query parameterfoo
with the valuebar
.Usehtmlentities
like in this example to preventCross-Site Scripting (abbreviated as XSS).
See alsoserver query parameters example.
By default, theServer
will buffer and parse the full request bodyin memory. This means the given request object includes the parsed request bodyand any file uploads.
As an alternative to the default buffering logic, you can also use the
StreamingRequestMiddleware
. Jump to the nextchapter to learn more about how to process astreaming incoming request.
As stated above, each incoming HTTP request is always represented by thePSR-7ServerRequestInterface
.This interface provides several methods that are useful when working with theincoming request body as described below.
ThegetParsedBody(): null|array|object
method can be used toget the parsed request body, similar toPHP's$_POST
variable.This method may return a (possibly nested) array structure with all bodyparameters or anull
value if the request body could not be parsed.By default, this method will only return parsed data for requests usingContent-Type: application/x-www-form-urlencoded
orContent-Type: multipart/form-data
request headers (commonly used forPOST
requests for HTML form submission data).
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request) {$name =$request->getParsedBody()['name'] ??'anonymous';returnReact\Http\Message\Response::plaintext("Hello$name!\n" );});
See alsoform upload example for more details.
ThegetBody(): StreamInterface
method can be used toget the raw data from this request body, similar toPHP'sphp://input
stream.This method returns an instance of the request body represented by thePSR-7StreamInterface
.This is particularly useful when using a custom request body that will nototherwise be parsed by default, such as a JSON (Content-Type: application/json
) oran XML (Content-Type: application/xml
) request body (which is commonly used forPOST
,PUT
orPATCH
requests in JSON-based or RESTful/RESTish APIs).
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request) {$data =json_decode((string)$request->getBody());$name =$data->name ??'anonymous';returnReact\Http\Message\Response::json( ['message' =>"Hello$name!"] );});
See alsoJSON API server example for more details.
ThegetUploadedFiles(): array
method can be used toget the uploaded files in this request, similar toPHP's$_FILES
variable.This method returns a (possibly nested) array structure with all file uploads, each represented by thePSR-7UploadedFileInterface
.This array will only be filled when using theContent-Type: multipart/form-data
request header (commonly used forPOST
requests for HTML file uploads).
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request) {$files =$request->getUploadedFiles();$name =isset($files['avatar']) ?$files['avatar']->getClientFilename() :'nothing';returnReact\Http\Message\Response::plaintext("Uploaded$name\n" );});
See alsoform upload server example for more details.
ThegetSize(): ?int
method can be used toget the size of the request body, similar to PHP's$_SERVER['CONTENT_LENGTH']
variable.This method returns the complete size of the request body measured in numberof bytes as defined by the message boundaries.This value may be0
if the request message does not contain a request body(such as a simpleGET
request).This method operates on the buffered request body, i.e. the request body sizeis always known, even when the request does not specify aContent-Length
requestheader or when usingTransfer-Encoding: chunked
for HTTP/1.1 requests.
Note: The
HttpServer
automatically takes care of handling requests with theadditionalExpect: 100-continue
request header. When HTTP/1.1 clients want tosend a bigger request body, they MAY send only the request headers with anadditionalExpect: 100-continue
request header and wait before sending the actual(large) message body. In this case the server will automatically send anintermediaryHTTP/1.1 100 Continue
response to the client. This ensures youwill receive the request body without a delay as expected.
If you're using the advancedStreamingRequestMiddleware
,the request object will be processed once the request headers have been received.This means that this happens irrespective of (i.e.before) receiving the(potentially much larger) request body.
Note that this is non-standard behavior considered advanced usage. Jump to theprevious chapter to learn more about how to process a bufferedrequest body.
While this may be uncommon in the PHP ecosystem, this is actually a very powerfulapproach that gives you several advantages not otherwise possible:
- React to requestsbefore receiving a large request body,such as rejecting an unauthenticated request or one that exceeds allowedmessage lengths (file uploads).
- Start processing parts of the request body before the remainder of the requestbody arrives or if the sender is slowly streaming data.
- Process a large request body without having to buffer anything in memory,such as accepting a huge file upload or possibly unlimited request body stream.
ThegetBody(): StreamInterface
method can be used toaccess the request body stream.In the streaming mode, this method returns a stream instance that implements both thePSR-7StreamInterface
and theReactPHPReadableStreamInterface
.However, most of thePSR-7StreamInterface
methods have been designed under the assumption of being in control of asynchronous request body.Given that this does not apply to this server, the followingPSR-7StreamInterface
methods are not used and SHOULD NOT be called:tell()
,eof()
,seek()
,rewind()
,write()
andread()
.If this is an issue for your use case and/or you want to access uploaded files,it's highly recommended to use a bufferedrequest body or use theRequestBodyBufferMiddleware
instead.TheReactPHPReadableStreamInterface
gives you access to the incoming request body as the individual chunks arrive:
$http =newReact\Http\HttpServer(newReact\Http\Middleware\StreamingRequestMiddleware(),function (Psr\Http\Message\ServerRequestInterface$request) {$body =$request->getBody();assert($bodyinstanceofPsr\Http\Message\StreamInterface);assert($bodyinstanceofReact\Stream\ReadableStreamInterface);returnnewReact\Promise\Promise(function ($resolve,$reject)use ($body) {$bytes =0;$body->on('data',function ($data)use (&$bytes) {$bytes +=strlen($data); });$body->on('end',function ()use ($resolve, &$bytes){$resolve(React\Http\Message\Response::plaintext("Received$bytes bytes\n" )); });// an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event$body->on('error',function (Exception$e)use ($resolve, &$bytes) {$resolve(React\Http\Message\Response::plaintext("Encountered error after$bytes bytes:{$e->getMessage()}\n" )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST)); }); }); });
The above example simply counts the number of bytes received in the request body.This can be used as a skeleton for buffering or processing the request body.
See alsostreaming request server example for more details.
Thedata
event will be emitted whenever new data is available on the requestbody stream.The server also automatically takes care of decoding any incoming requests usingTransfer-Encoding: chunked
and will only emit the actual payload as data.
Theend
event will be emitted when the request body stream terminatessuccessfully, i.e. it was read until its expected end.
Theerror
event will be emitted in case the request stream contains invaliddata forTransfer-Encoding: chunked
or when the connection closes beforethe complete request stream has been received.The server will automatically stop reading from the connection and discard allincoming data instead of closing it.A response message can still be sent (unless the connection is already closed).
Aclose
event will be emitted after anerror
orend
event.
For more details about the request body stream, check out the documentation ofReactPHPReadableStreamInterface
.
ThegetSize(): ?int
method can be used toget the size of the request body, similar to PHP's$_SERVER['CONTENT_LENGTH']
variable.This method returns the complete size of the request body measured in numberof bytes as defined by the message boundaries.This value may be0
if the request message does not contain a request body(such as a simpleGET
request).This method operates on the streaming request body, i.e. the request body sizemay be unknown (null
) when usingTransfer-Encoding: chunked
for HTTP/1.1 requests.
$http =newReact\Http\HttpServer(newReact\Http\Middleware\StreamingRequestMiddleware(),function (Psr\Http\Message\ServerRequestInterface$request) {$size =$request->getBody()->getSize();if ($size ===null) {$body ="The request does not contain an explicit length.";$body .="This example does not accept chunked transfer encoding.\n";returnReact\Http\Message\Response::plaintext($body )->withStatus(React\Http\Message\Response::STATUS_LENGTH_REQUIRED); }returnReact\Http\Message\Response::plaintext("Request body size:" .$size ." bytes\n" ); });
Note: The
HttpServer
automatically takes care of handling requests with theadditionalExpect: 100-continue
request header. When HTTP/1.1 clients want tosend a bigger request body, they MAY send only the request headers with anadditionalExpect: 100-continue
request header and wait before sending the actual(large) message body. In this case the server will automatically send anintermediaryHTTP/1.1 100 Continue
response to the client. This ensures youwill receive the streaming request body without a delay as expected.
Note that the server supportsany request method (including custom and non-standard ones) and all request-target formats defined in the HTTP specs for eachrespective method, includingnormalorigin-form
requests as well asproxy requests inabsolute-form
andauthority-form
.ThegetUri(): UriInterface
method can be used to get the effective requestURI which provides you access to individiual URI components.Note that (depending on the givenrequest-target
) certain URI components mayor may not be present, for example thegetPath(): string
method will returnan empty string for requests inasterisk-form
orauthority-form
.ItsgetHost(): string
method will return the host as determined by theeffective request URI, which defaults to the local socket address if an HTTP/1.0client did not specify one (i.e. noHost
header).ItsgetScheme(): string
method will returnhttp
orhttps
dependingon whether the request was made over a secure TLS connection to the target host.
TheHost
header value will be sanitized to match this host component plus theport component only if it is non-standard for this URI scheme.
You can usegetMethod(): string
andgetRequestTarget(): string
tocheck this is an accepted request and may want to reject other requests withan appropriate error code, such as400
(Bad Request) or405
(Method NotAllowed).
The
CONNECT
method is useful in a tunneling setup (HTTPS proxy) and notsomething most HTTP servers would want to care about.Note that if you want to handle this method, the client MAY send a differentrequest-target than theHost
header value (such as removing default ports)and the request-target MUST take precendence when forwarding.
ThegetCookieParams(): string[]
method can be used toget all cookies sent with the current request.
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request) {$key ='greeting';if (isset($request->getCookieParams()[$key])) {$body ="Your cookie value is:" .$request->getCookieParams()[$key] ."\n";returnReact\Http\Message\Response::plaintext($body ); }returnReact\Http\Message\Response::plaintext("Your cookie has been set.\n" )->withHeader('Set-Cookie',$key .'=' .urlencode('Hello world!'));});
The above example will try to set a cookie on first access andwill try to print the cookie value on all subsequent tries.Note how the example uses theurlencode()
function to encodenon-alphanumeric characters.This encoding is also used internally when decoding the name and value of cookies(which is in line with other implementations, such as PHP's cookie functions).
See alsocookie server example for more details.
TheHttpServer
class supports both HTTP/1.1 and HTTP/1.0 request messages.If a client sends an invalid request message, uses an invalid HTTPprotocol version or sends an invalidTransfer-Encoding
request header value,the server will automatically send a400
(Bad Request) HTTP error responseto the client and close the connection.On top of this, it will emit anerror
event that can be used for loggingpurposes like this:
$http->on('error',function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
Note that the server will also emit anerror
event if you do not return avalid response object from your request handler function. See alsoinvalid response for more details.
The callback function passed to the constructor of theHttpServer
isresponsible for processing the request and returning a response, which will bedelivered to the client.
This function MUST return an instance implementingPSR-7ResponseInterface
object or aReactPHP Promisewhich resolves with aPSR-7ResponseInterface
object.
This projects ships aResponse
class which implements thePSR-7ResponseInterface
.In its most simple form, you can use it like this:
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request) {returnReact\Http\Message\Response::plaintext("Hello World!\n" );});
We use thisResponse
class throughout our project examples, butfeel free to use any other implementation of thePSR-7ResponseInterface
.See also theResponse
class for more details.
The example above returns the response directly, because it needsno time to be processed.Using a database, the file system or long calculations(in fact every action that will take >=1ms) to create yourresponse, will slow down the server.To prevent this you SHOULD use aReactPHP Promise.This example shows how such a long-term action could look like:
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request) {$promise =newPromise(function ($resolve,$reject) { Loop::addTimer(1.5,function()use ($resolve) {$resolve(); }); });return$promise->then(function () {returnReact\Http\Message\Response::plaintext("Hello World!" ); });});
The above example will create a response after 1.5 second.This example shows that you need a promise,if your response needs time to created.TheReactPHP Promise
will resolve in aResponse
object when the requestbody ends.If the client closes the connection while the promise is still pending, thepromise will automatically be cancelled.The promise cancellation handler can be used to clean up any pending resourcesallocated in this case (if applicable).If a promise is resolved after the client closes, it will simply be ignored.
TheResponse
class in this project supports to add an instance which implements theReactPHPReadableStreamInterface
for the response body.So you are able stream data directly into the response body.Note that other implementations of thePSR-7ResponseInterface
may only support strings.
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request) {$stream =newThroughStream();// send some data every once in a while with periodic timer$timer = Loop::addPeriodicTimer(0.5,function ()use ($stream) {$stream->write(microtime(true) .PHP_EOL); });// end stream after a few seconds$timeout = Loop::addTimer(5.0,function()use ($stream,$timer) { Loop::cancelTimer($timer);$stream->end(); });// stop timer if stream is closed (such as when connection is closed)$stream->on('close',function ()use ($timer,$timeout) { Loop::cancelTimer($timer); Loop::cancelTimer($timeout); });returnnewReact\Http\Message\Response(React\Http\Message\Response::STATUS_OK, ['Content-Type' =>'text/plain' ],$stream );});
The above example will emit every 0.5 seconds the current Unix timestampwith microseconds as float to the client and will end after 5 seconds.This is just a example you could use of the streaming,you could also send a big amount of data via little chunksor use it for body data that needs to calculated.
If the request handler resolves with a response stream that is already closed,it will simply send an empty response body.If the client closes the connection while the stream is still open, theresponse stream will automatically be closed.If a promise is resolved with a streaming body after the client closes, theresponse stream will automatically be closed.Theclose
event can be used to clean up any pending resources allocatedin this case (if applicable).
Note that special care has to be taken if you use a body stream instance thatimplements ReactPHP's
DuplexStreamInterface
(such as theThroughStream
in the above example).Formost cases, this will simply only consume its readable side and forward(send) any data that is emitted by the stream, thus entirely ignoring thewritable side of the stream.If however this is either a
101
(Switching Protocols) response or a2xx
(Successful) response to aCONNECT
method, it will alsowrite data to thewritable side of the stream.This can be avoided by either rejecting all requests with theCONNECT
method (which is what mostnormal origin HTTP servers would likely do) oror ensuring that only ever an instance ofReactPHP'sReadableStreamInterface
is used.The
101
(Switching Protocols) response code is useful for the more advancedUpgrade
requests, such as upgrading to the WebSocket protocol orimplementing custom protocol logic that is out of scope of the HTTP specs andthis HTTP library.If you want to handle theUpgrade: WebSocket
header, you will likely wantto look into usingRatchet instead.If you want to handle a custom protocol, you will likely want to look into theHTTP specs and also seeexamples #81 and #82 for more details.In particular, the101
(Switching Protocols) response code MUST NOT be usedunless you send anUpgrade
response header value that is also present inthe corresponding HTTP/1.1Upgrade
request header value.The server automatically takes care of sending aConnection: upgrade
header value in this case, so you don't have to.The
CONNECT
method is useful in a tunneling setup (HTTPS proxy) and notsomething most origin HTTP servers would want to care about.The HTTP specs define an opaque "tunneling mode" for this method and make nouse of the message body.For consistency reasons, this library uses aDuplexStreamInterface
in theresponse body for tunneled application data.This implies that that a2xx
(Successful) response to aCONNECT
requestcan in fact use a streaming response body for the tunneled application data,so that any raw data the client sends over the connection will be pipedthrough the writable stream for consumption.Note that while the HTTP specs make no use of the request body forCONNECT
requests, one may still be present. Normal request body processing applieshere and the connection will only turn to "tunneling mode" after the requestbody has been processed (which should be empty in most cases).See alsoHTTP CONNECT server example for more details.
If the response body size is known, aContent-Length
response header will beadded automatically. This is the most common use case, for example when usingastring
response body like this:
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request) {returnReact\Http\Message\Response::plaintext("Hello World!\n" );});
If the response body size is unknown, aContent-Length
response header can notbe added automatically. When using astreaming outgoing responsewithout an explicitContent-Length
response header, outgoing HTTP/1.1 responsemessages will automatically useTransfer-Encoding: chunked
while legacy HTTP/1.0response messages will contain the plain response body. If you know the lengthof your streaming response body, you MAY want to specify it explicitly like this:
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request) {$stream =newThroughStream(); Loop::addTimer(2.0,function ()use ($stream) {$stream->end("Hello World!\n"); });returnnewReact\Http\Message\Response(React\Http\Message\Response::STATUS_OK, ['Content-Length' =>'13','Content-Type' =>'text/plain', ],$stream );});
Any response to aHEAD
request and any response with a1xx
(Informational),204
(No Content) or304
(Not Modified) status code willnot include amessage body as per the HTTP specs.This means that your callback does not have to take special care of this and anyresponse body will simply be ignored.
Similarly, any2xx
(Successful) response to aCONNECT
request, any responsewith a1xx
(Informational) or204
(No Content) status code willnotinclude aContent-Length
orTransfer-Encoding
header as these do not applyto these messages.Note that a response to aHEAD
request and any response with a304
(NotModified) status code MAY include these headers even thoughthe message does not contain a response body, because these header would applyto the message if the same request would have used an (unconditional)GET
.
As stated above, each outgoing HTTP response is always represented by thePSR-7ResponseInterface
.If your request handler function returns an invalid value or throws anunhandledException
orThrowable
, the server will automatically send a500
(Internal Server Error) HTTP error response to the client.On top of this, it will emit anerror
event that can be used for loggingpurposes like this:
$http->on('error',function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;if ($e->getPrevious() !==null) {echo'Previous:' .$e->getPrevious()->getMessage() .PHP_EOL; }});
Note that the server will also emit anerror
event if the client sends aninvalid HTTP request that never reaches your request handler function. Seealsoinvalid request for more details.Additionally, astreaming incoming request bodycan also emit anerror
event on the request body.
The server will only send a very generic500
(Interval Server Error) HTTPerror response without any further details to the client if an unhandlederror occurs. While we understand this might make initial debugging harder,it also means that the server does not leak any application details or stacktraces to the outside by default. It is usually recommended to catch anyException
orThrowable
within your request handler function or alternativelyuse amiddleware
to avoid this generic error handling andcreate your own HTTP response message instead.
When a response is returned from the request handler function, it will beprocessed by theHttpServer
and then sent back to the client.
AServer: ReactPHP/1
response header will be added automatically. You can adda customServer
response header like this:
$http =newReact\Http\HttpServer(function (ServerRequestInterface$request) {returnnewReact\Http\Message\Response(React\Http\Message\Response::STATUS_OK, ['Server' =>'PHP/3' ] );});
If you do not want to send thisSever
response header at all (such as when youdon't want to expose the underlying server software), you can use an emptystring value like this:
$http =newReact\Http\HttpServer(function (ServerRequestInterface$request) {returnnewReact\Http\Message\Response(React\Http\Message\Response::STATUS_OK, ['Server' =>'' ] );});
ADate
response header will be added automatically with the current systemdate and time if none is given. You can add a customDate
response headerlike this:
$http =newReact\Http\HttpServer(function (ServerRequestInterface$request) {returnnewReact\Http\Message\Response(React\Http\Message\Response::STATUS_OK, ['Date' =>gmdate('D, d M Y H:i:s \G\M\T') ] );});
If you do not want to send thisDate
response header at all (such as when youdon't have an appropriate clock to rely on), you can use an empty string valuelike this:
$http =newReact\Http\HttpServer(function (ServerRequestInterface$request) {returnnewReact\Http\Message\Response(React\Http\Message\Response::STATUS_OK, ['Date' =>'' ] );});
TheHttpServer
class will automatically add the protocol version of the request,so you don't have to. For instance, if the client sends the request using theHTTP/1.1 protocol version, the response message will also use the same protocolversion, no matter what version is returned from the request handler function.
The server supports persistent connections. An appropriateConnection: keep-alive
orConnection: close
response header will be added automatically, respecting thematching request header value and HTTP default header values. The server isresponsible for handling theConnection
response header, so you SHOULD NOT passthis response header yourself, unless you explicitly want to override the user'schoice with aConnection: close
response header.
As documented above, theHttpServer
accepts a single request handlerargument that is responsible for processing an incoming HTTP request and thencreating and returning an outgoing HTTP response.
Many common use cases involve validating, processing, manipulating the incomingHTTP request before passing it to the final business logic request handler.As such, this project supports the concept of middleware request handlers.
A middleware request handler is expected to adhere the following rules:
- It is a valid
callable
. - It accepts an instance implementingPSR-7
ServerRequestInterface
as first argument and an optionalcallable
as second argument. - It returns either:
- An instance implementingPSR-7
ResponseInterface
for direct consumption. - Any promise which can be consumed by
Promise\resolve()
resolving to aPSR-7ResponseInterface
for deferred consumption. - It MAY throw an
Exception
(or return a rejected promise) in order tosignal an error condition and abort the chain.
- An instance implementingPSR-7
- It calls
$next($request)
to continue processing the next middlewarerequest handler or returns explicitly without calling$next
toabort the chain.- The
$next
request handler (recursively) invokes the next requesthandler from the chain with the same logic as above and returns (or throws)as above. - The
$request
may be modified prior to calling$next($request)
tochange the incoming request the next middleware operates on. - The
$next
return value may be consumed to modify the outgoing response. - The
$next
request handler MAY be called more than once if you want toimplement custom "retry" logic etc.
- The
Note that this very simple definition allows you to use either anonymousfunctions or any classes that use the magic__invoke()
method.This allows you to easily create custom middleware request handlers on the flyor use a class based approach to ease using existing middleware implementations.
While this project does provide the means touse middleware implementations,it does not aim todefine how middleware implementations should look like.We realize that there's a vivid ecosystem of middleware implementations andongoing effort to standardize interfaces between these withPSR-15 (HTTP Server Request Handlers)and support this goal.As such, this project only bundles a few middleware implementations that arerequired to match PHP's request behavior (see below) and otherwise activelyencouragesThird-Party Middleware implementations.
In order to use middleware request handlers, simply pass a list of allcallables as defined above to theHttpServer
.The following example adds a middleware request handler that adds the current time to the request as aheader (Request-Time
) and a final request handler that always returns a200 OK
status code without a body:
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request,callable$next) {$request =$request->withHeader('Request-Time',time());return$next($request); },function (Psr\Http\Message\ServerRequestInterface$request) {returnnewReact\Http\Message\Response(React\Http\Message\Response::STATUS_OK); });
Note how the middleware request handler and the final request handler have avery simple (and similar) interface. The only difference is that the finalrequest handler does not receive a
$next
handler.
Similarly, you can use the result of the$next
middleware request handlerfunction to modify the outgoing response.Note that as per the above documentation, the$next
middleware request handler may return aPSR-7ResponseInterface
directly or one wrapped in a promise for deferred resolution.In order to simplify handling both paths, you can simply wrap this in aPromise\resolve()
call like this:
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request,callable$next) {$promise =React\Promise\resolve($next($request));return$promise->then(function (ResponseInterface$response) {return$response->withHeader('Content-Type','text/html'); }); },function (Psr\Http\Message\ServerRequestInterface$request) {returnnewReact\Http\Message\Response(React\Http\Message\Response::STATUS_OK); });
Note that the$next
middleware request handler may also throw anException
(or return a rejected promise) as described above.The previous example does not catch any exceptions and would thus signal anerror condition to theHttpServer
.Alternatively, you can also catch anyException
to implement custom errorhandling logic (or logging etc.) by wrapping this in aPromise
like this:
$http =newReact\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface$request,callable$next) {$promise =newReact\Promise\Promise(function ($resolve)use ($next,$request) {$resolve($next($request)); });return$promise->then(null,function (Exception$e) {returnReact\Http\Message\Response::plaintext('Internal error:' .$e->getMessage() ."\n" )->withStatus(React\Http\Message\Response::STATUS_INTERNAL_SERVER_ERROR); }); },function (Psr\Http\Message\ServerRequestInterface$request) {if (mt_rand(0,1) ===1) {thrownewRuntimeException('Database error'); }returnnewReact\Http\Message\Response(React\Http\Message\Response::STATUS_OK); });
While this project does provide the means touse middleware implementations(see above), it does not aim todefine how middleware implementations shouldlook like. We realize that there's a vivid ecosystem of middlewareimplementations and ongoing effort to standardize interfaces between these withPSR-15 (HTTP Server Request Handlers)and support this goal.As such, this project only bundles a few middleware implementations that arerequired to match PHP's request behavior (seemiddleware implementations) and otherwise activelyencourages third-party middleware implementations.
While we would love to support PSR-15 directly inreact/http
, we understandthat this interface does not specifically target async APIs and as such doesnot take advantage of promises fordeferred responses.The gist of this is that where PSR-15 enforces aPSR-7ResponseInterface
return value, we also accept aPromiseInterface<ResponseInterface>
.As such, we suggest using the externalPSR-15 middleware adapterthat uses on the fly monkey patching of these return values which makes usingmost PSR-15 middleware possible with this package without any changes required.
Other than that, you can also use the abovemiddleware definitionto create custom middleware. A non-exhaustive list of third-party middleware canbe found at themiddleware wiki.If you build or know a custom middleware, make sure to let the world know andfeel free to add it to this list.
TheReact\Http\Browser
is responsible for sending HTTP requests to your HTTP serverand keeps track of pending incoming HTTP responses.
$browser =newReact\Http\Browser();
This class takes two optional arguments for more advanced usage:
$browser =newReact\Http\Browser(?ConnectorInterface$connector =null, ?LoopInterface$loop =null);
If you need custom connector settings (DNS resolution, TLS parameters, timeouts,proxy servers etc.), you can explicitly pass a custom instance of theConnectorInterface
:
$connector =newReact\Socket\Connector(['dns' =>'127.0.0.1','tcp' => ['bindto' =>'192.168.10.1:0' ],'tls' => ['verify_peer' =>false,'verify_peer_name' =>false ]]);$browser =newReact\Http\Browser($connector);
This class takes an optionalLoopInterface|null $loop
parameter that can be used topass the event loop instance to use for this object. You can use anull
valuehere in order to use thedefault loop.This value SHOULD NOT be given unless you're sure you want to explicitly use agiven event loop instance.
Note that the browser class is final and shouldn't be extended, it is likely to be marked final in a future release.
Theget(string $url, array $headers = []): PromiseInterface<ResponseInterface>
method can be used tosend an HTTP GET request.
$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface$response) {var_dump((string)$response->getBody());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
See alsoGET request client example.
Thepost(string $url, array $headers = [], string|ReadableStreamInterface $body = ''): PromiseInterface<ResponseInterface>
method can be used tosend an HTTP POST request.
$browser->post($url, ['Content-Type' =>'application/json' ],json_encode($data))->then(function (Psr\Http\Message\ResponseInterface$response) {var_dump(json_decode((string)$response->getBody()));},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
See alsoPOST JSON client example.
This method is also commonly used to submit HTML form data:
$data = ['user' =>'Alice','password' =>'secret'];$browser->post($url, ['Content-Type' =>'application/x-www-form-urlencoded' ],http_build_query($data));
This method will automatically add a matchingContent-Length
requestheader if the outgoing request body is astring
. If you're using astreaming request body (ReadableStreamInterface
), it will default tousingTransfer-Encoding: chunked
or you have to explicitly pass in amatchingContent-Length
request header like so:
$body =newReact\Stream\ThroughStream();Loop::addTimer(1.0,function ()use ($body) {$body->end("hello world");});$browser->post($url, ['Content-Length' =>'11'),$body);
Thehead(string $url, array $headers = []): PromiseInterface<ResponseInterface>
method can be used tosend an HTTP HEAD request.
$browser->head($url)->then(function (Psr\Http\Message\ResponseInterface$response) {var_dump($response->getHeaders());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
Thepatch(string $url, array $headers = [], string|ReadableStreamInterface $body = ''): PromiseInterface<ResponseInterface>
method can be used tosend an HTTP PATCH request.
$browser->patch($url, ['Content-Type' =>'application/json' ],json_encode($data))->then(function (Psr\Http\Message\ResponseInterface$response) {var_dump(json_decode((string)$response->getBody()));},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
This method will automatically add a matchingContent-Length
requestheader if the outgoing request body is astring
. If you're using astreaming request body (ReadableStreamInterface
), it will default tousingTransfer-Encoding: chunked
or you have to explicitly pass in amatchingContent-Length
request header like so:
$body =newReact\Stream\ThroughStream();Loop::addTimer(1.0,function ()use ($body) {$body->end("hello world");});$browser->patch($url, ['Content-Length' =>'11'],$body);
Theput(string $url, array $headers = [], string|ReadableStreamInterface $body = ''): PromiseInterface<ResponseInterface>
method can be used tosend an HTTP PUT request.
$browser->put($url, ['Content-Type' =>'text/xml' ],$xml->asXML())->then(function (Psr\Http\Message\ResponseInterface$response) {var_dump((string)$response->getBody());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
See alsoPUT XML client example.
This method will automatically add a matchingContent-Length
requestheader if the outgoing request body is astring
. If you're using astreaming request body (ReadableStreamInterface
), it will default tousingTransfer-Encoding: chunked
or you have to explicitly pass in amatchingContent-Length
request header like so:
$body =newReact\Stream\ThroughStream();Loop::addTimer(1.0,function ()use ($body) {$body->end("hello world");});$browser->put($url, ['Content-Length' =>'11'],$body);
Thedelete(string $url, array $headers = [], string|ReadableStreamInterface $body = ''): PromiseInterface<ResponseInterface>
method can be used tosend an HTTP DELETE request.
$browser->delete($url)->then(function (Psr\Http\Message\ResponseInterface$response) {var_dump((string)$response->getBody());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
Therequest(string $method, string $url, array $headers = [], string|ReadableStreamInterface $body = ''): PromiseInterface<ResponseInterface>
method can be used tosend an arbitrary HTTP request.
The preferred way to send an HTTP request is by using the aboverequest methods, for example theget()
method to send an HTTPGET
request.
As an alternative, if you want to use a custom HTTP request method, youcan use this method:
$browser->request('OPTIONS',$url)->then(function (Psr\Http\Message\ResponseInterface$response) {var_dump((string)$response->getBody());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
This method will automatically add a matchingContent-Length
requestheader if the size of the outgoing request body is known and non-empty.For an empty request body, if will only include aContent-Length: 0
request header if the request method usually expects a request body (onlyapplies toPOST
,PUT
andPATCH
).
If you're using a streaming request body (ReadableStreamInterface
), itwill default to usingTransfer-Encoding: chunked
or you have toexplicitly pass in a matchingContent-Length
request header like so:
$body =newReact\Stream\ThroughStream();Loop::addTimer(1.0,function ()use ($body) {$body->end("hello world");});$browser->request('POST',$url, ['Content-Length' =>'11'],$body);
TherequestStreaming(string $method, string $url, array $headers = [], string|ReadableStreamInterface $body = ''): PromiseInterface<ResponseInterface>
method can be used tosend an arbitrary HTTP request and receive a streaming response without buffering the response body.
The preferred way to send an HTTP request is by using the aboverequest methods, for example theget()
method to send an HTTPGET
request. Each of these methods will bufferthe whole response body in memory by default. This is easy to get startedand works reasonably well for smaller responses.
In some situations, it's a better idea to use a streaming approach, whereonly small chunks have to be kept in memory. You can use this method tosend an arbitrary HTTP request and receive a streaming response. It usesthe same HTTP message API, but does not buffer the response body inmemory. It only processes the response body in small chunks as data isreceived and forwards this data throughReactPHP's Stream API.This works for (any number of) responses of arbitrary sizes.
$browser->requestStreaming('GET',$url)->then(function (Psr\Http\Message\ResponseInterface$response) {$body =$response->getBody();assert($bodyinstanceofPsr\Http\Message\StreamInterface);assert($bodyinstanceofReact\Stream\ReadableStreamInterface);$body->on('data',function ($chunk) {echo$chunk; });$body->on('error',function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL; });$body->on('close',function () {echo'[DONE]' .PHP_EOL; });},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
See alsoReactPHP'sReadableStreamInterface
and thestreaming response for more details,examples and possible use-cases.
This method will automatically add a matchingContent-Length
requestheader if the size of the outgoing request body is known and non-empty.For an empty request body, if will only include aContent-Length: 0
request header if the request method usually expects a request body (onlyapplies toPOST
,PUT
andPATCH
).
If you're using a streaming request body (ReadableStreamInterface
), itwill default to usingTransfer-Encoding: chunked
or you have toexplicitly pass in a matchingContent-Length
request header like so:
$body =newReact\Stream\ThroughStream();Loop::addTimer(1.0,function ()use ($body) {$body->end("hello world");});$browser->requestStreaming('POST',$url, ['Content-Length' =>'11'],$body);
ThewithTimeout(bool|number $timeout): Browser
method can be used tochange the maximum timeout used for waiting for pending requests.
You can pass in the number of seconds to use as a new timeout value:
$browser =$browser->withTimeout(10.0);
You can pass in a boolfalse
to disable any timeouts. In this case,requests can stay pending forever:
$browser =$browser->withTimeout(false);
You can pass in a booltrue
to re-enable default timeout handling. Thiswill respects PHP'sdefault_socket_timeout
setting (default 60s):
$browser =$browser->withTimeout(true);
See alsotimeouts for more details about timeout handling.
Notice that theBrowser
is an immutable object, i.e. thismethod actually returns anewBrowser
instance with thegiven timeout value applied.
ThewithFollowRedirects(bool|int $followRedirects): Browser
method can be used tochange how HTTP redirects will be followed.
You can pass in the maximum number of redirects to follow:
$browser =$browser->withFollowRedirects(5);
The request will automatically be rejected when the number of redirectsis exceeded. You can pass in a0
to reject the request for anyredirects encountered:
$browser =$browser->withFollowRedirects(0);$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface$response) {// only non-redirected responses will now end up herevar_dump($response->getHeaders());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
You can pass in a boolfalse
to disable following any redirects. Inthis case, requests will resolve with the redirection response insteadof following theLocation
response header:
$browser =$browser->withFollowRedirects(false);$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface$response) {// any redirects will now end up herevar_dump($response->getHeaderLine('Location'));},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
You can pass in a booltrue
to re-enable default redirect handling.This defaults to following a maximum of 10 redirects:
$browser =$browser->withFollowRedirects(true);
See alsoredirects for more details about redirect handling.
Notice that theBrowser
is an immutable object, i.e. thismethod actually returns anewBrowser
instance with thegiven redirect setting applied.
ThewithRejectErrorResponse(bool $obeySuccessCode): Browser
method can be used tochange whether non-successful HTTP response status codes (4xx and 5xx) will be rejected.
You can pass in a boolfalse
to disable rejecting incoming responsesthat use a 4xx or 5xx response status code. In this case, requests willresolve with the response message indicating an error condition:
$browser =$browser->withRejectErrorResponse(false);$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface$response) {// any HTTP response will now end up herevar_dump($response->getStatusCode(),$response->getReasonPhrase());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
You can pass in a booltrue
to re-enable default status code handling.This defaults to rejecting any response status codes in the 4xx or 5xxrange with aResponseException
:
$browser =$browser->withRejectErrorResponse(true);$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface$response) {// any successful HTTP response will now end up herevar_dump($response->getStatusCode(),$response->getReasonPhrase());},function (Exception$e) {if ($einstanceofReact\Http\Message\ResponseException) {// any HTTP response error message will now end up here$response =$e->getResponse();var_dump($response->getStatusCode(),$response->getReasonPhrase()); }else {echo'Error:' .$e->getMessage() .PHP_EOL; }});
Notice that theBrowser
is an immutable object, i.e. thismethod actually returns anewBrowser
instance with thegiven setting applied.
ThewithBase(string|null $baseUrl): Browser
method can be used tochange the base URL used to resolve relative URLs to.
If you configure a base URL, any requests to relative URLs will beprocessed by first resolving this relative to the given absolute baseURL. This supports resolving relative path references (like../
etc.).This is particularly useful for (RESTful) API calls where all endpoints(URLs) are located under a common base URL.
$browser =$browser->withBase('http://api.example.com/v3/');// will request http://api.example.com/v3/users$browser->get('users')->then(…);
You can pass in anull
base URL to return a new instance that does notuse a base URL:
$browser =$browser->withBase(null);
Accordingly, any requests using relative URLs to a browser that does notuse a base URL can not be completed and will be rejected without sendinga request.
This method will throw anInvalidArgumentException
if the given$baseUrl
argument is not a valid URL.
Notice that theBrowser
is an immutable object, i.e. thewithBase()
methodactually returns anewBrowser
instance with the given base URL applied.
ThewithProtocolVersion(string $protocolVersion): Browser
method can be used tochange the HTTP protocol version that will be used for all subsequent requests.
All the aboverequest methods default to sendingrequests as HTTP/1.1. This is the preferred HTTP protocol version whichalso provides decent backwards-compatibility with legacy HTTP/1.0servers. As such, there should rarely be a need to explicitly change thisprotocol version.
If you want to explicitly use the legacy HTTP/1.0 protocol version, youcan use this method:
$browser =$browser->withProtocolVersion('1.0');$browser->get($url)->then(…);
Notice that theBrowser
is an immutable object, i.e. thismethod actually returns anewBrowser
instance with thenew protocol version applied.
ThewithResponseBuffer(int $maximumSize): Browser
method can be used tochange the maximum size for buffering a response body.
The preferred way to send an HTTP request is by using the aboverequest methods, for example theget()
method to send an HTTPGET
request. Each of these methods will bufferthe whole response body in memory by default. This is easy to get startedand works reasonably well for smaller responses.
By default, the response body buffer will be limited to 16 MiB. If theresponse body exceeds this maximum size, the request will be rejected.
You can pass in the maximum number of bytes to buffer:
$browser =$browser->withResponseBuffer(1024 *1024);$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface$response) {// response body will not exceed 1 MiBvar_dump($response->getHeaders(), (string)$response->getBody());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
Note that the response body buffer has to be kept in memory for eachpending request until its transfer is completed and it will only be freedafter a pending request is fulfilled. As such, increasing this maximumbuffer size to allow larger response bodies is usually not recommended.Instead, you can use therequestStreaming()
methodto receive responses with arbitrary sizes without buffering. Accordingly,this maximum buffer size setting has no effect on streaming responses.
Notice that theBrowser
is an immutable object, i.e. thismethod actually returns anewBrowser
instance with thegiven setting applied.
ThewithHeader(string $header, string $value): Browser
method can be used toadd a request header for all following requests.
$browser =$browser->withHeader('User-Agent','ACME');$browser->get($url)->then(…);
Note that the new header will overwrite any headers previously set withthe same name (case-insensitive). Following requests will use these headersby default unless they are explicitly set for any requests.
ThewithoutHeader(string $header): Browser
method can be used toremove any default request headers previously set viathewithHeader()
method.
$browser =$browser->withoutHeader('User-Agent');$browser->get($url)->then(…);
Note that this method only affects the headers which were set with themethodwithHeader(string $header, string $value): Browser
TheReact\Http\Message\Response
class can be used torepresent an outgoing server response message.
$response =newReact\Http\Message\Response(React\Http\Message\Response::STATUS_OK, ['Content-Type' =>'text/html' ],"<html>Hello world!</html>\n");
This class implements thePSR-7ResponseInterface
which in turn extends thePSR-7MessageInterface
.
On top of this, this class implements thePSR-7 Message UtilStatusCodeInterface
which means that most common HTTP status codes are available as classconstants with theSTATUS_*
prefix. For instance, the200 OK
and404 Not Found
status codes can used asResponse::STATUS_OK
andResponse::STATUS_NOT_FOUND
respectively.
Internally, this implementation builds on top of a base class which isconsidered an implementation detail that may change in the future.
The statichtml(string $html): Response
method can be used tocreate an HTML response.
$html =<<<HTML<!doctype html><html><body>Hello wörld!</body></html>HTML;$response =React\Http\Message\Response::html($html);
This is a convenient shortcut method that returns the equivalent of this:
$response = new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Content-Type' => 'text/html; charset=utf-8' ], $html);
This method always returns a response with a200 OK
status code andthe appropriateContent-Type
response header for the given HTTP sourcestring encoded in UTF-8 (Unicode). It's generally recommended to end thegiven plaintext string with a trailing newline.
If you want to use a different status code or custom HTTP responseheaders, you can manipulate the returned response object using theprovided PSR-7 methods or directly instantiate a custom HTTP responseobject using theResponse
constructor:
$response =React\Http\Message\Response::html("<h1>Error</h1>\n<p>Invalid user name given.</p>\n")->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
The staticjson(mixed $data): Response
method can be used tocreate a JSON response.
$response =React\Http\Message\Response::json(['name' =>'Alice']);
This is a convenient shortcut method that returns the equivalent of this:
$response = new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Content-Type' => 'application/json' ], json_encode( ['name' => 'Alice'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION ) . "\n");
This method always returns a response with a200 OK
status code andthe appropriateContent-Type
response header for the given structureddata encoded as a JSON text.
The given structured data will be encoded as a JSON text. Anystring
values in the data must be encoded in UTF-8 (Unicode). If the encodingfails, this method will throw anInvalidArgumentException
.
By default, the given structured data will be encoded with the flags asshown above. This includes pretty printing and preserving zero fractionsforfloat
values to ease debugging. It is assumed any additional dataoverhead is usually compensated by using HTTP response compression.
If you want to use a different status code or custom HTTP responseheaders, you can manipulate the returned response object using theprovided PSR-7 methods or directly instantiate a custom HTTP responseobject using theResponse
constructor:
$response =React\Http\Message\Response::json( ['error' =>'Invalid user name given'])->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
The staticplaintext(string $text): Response
method can be used tocreate a plaintext response.
$response =React\Http\Message\Response::plaintext("Hello wörld!\n");
This is a convenient shortcut method that returns the equivalent of this:
$response = new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Content-Type' => 'text/plain; charset=utf-8' ], "Hello wörld!\n");
This method always returns a response with a200 OK
status code andthe appropriateContent-Type
response header for the given plaintextstring encoded in UTF-8 (Unicode). It's generally recommended to end thegiven plaintext string with a trailing newline.
If you want to use a different status code or custom HTTP responseheaders, you can manipulate the returned response object using theprovided PSR-7 methods or directly instantiate a custom HTTP responseobject using theResponse
constructor:
$response =React\Http\Message\Response::plaintext("Error: Invalid user name given.\n")->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
The staticxml(string $xml): Response
method can be used tocreate an XML response.
$xml =<<<XML<?xml version="1.0" encoding="utf-8"?><body> <greeting>Hello wörld!</greeting></body>XML;$response =React\Http\Message\Response::xml($xml);
This is a convenient shortcut method that returns the equivalent of this:
$response = new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Content-Type' => 'application/xml' ], $xml);
This method always returns a response with a200 OK
status code andthe appropriateContent-Type
response header for the given XML sourcestring. It's generally recommended to use UTF-8 (Unicode) and specifythis as part of the leading XML declaration and to end the given XMLsource string with a trailing newline.
If you want to use a different status code or custom HTTP responseheaders, you can manipulate the returned response object using theprovided PSR-7 methods or directly instantiate a custom HTTP responseobject using theResponse
constructor:
$response =React\Http\Message\Response::xml("<error><message>Invalid user name given.</message></error>\n")->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
TheReact\Http\Message\Request
class can be used torespresent an outgoing HTTP request message.
This class implements thePSR-7RequestInterface
which extends thePSR-7MessageInterface
.
This is mostly used internally to represent each outgoing HTTP requestmessage for the HTTP client implementation. Likewise, you can also use thisclass with other HTTP client implementations and for tests.
Internally, this implementation builds on top of a base class which isconsidered an implementation detail that may change in the future.
TheReact\Http\Message\ServerRequest
class can be used torespresent an incoming server request message.
This class implements thePSR-7ServerRequestInterface
which extends thePSR-7RequestInterface
which in turn extends thePSR-7MessageInterface
.
This is mostly used internally to represent each incoming request message.Likewise, you can also use this class in test cases to test how your webapplication reacts to certain HTTP requests.
Internally, this implementation builds on top of a base class which isconsidered an implementation detail that may change in the future.
TheReact\Http\Message\Uri
class can be used torespresent a URI (or URL).
This class implements thePSR-7UriInterface
.
This is mostly used internally to represent the URI of each HTTP requestmessage for our HTTP client and server implementations. Likewise, you mayalso use this class with other HTTP implementations and for tests.
TheReact\Http\Message\ResponseException
is anException
sub-class that will be used to rejecta request promise if the remote server returns a non-success status code(anything but 2xx or 3xx).You can control this behavior via thewithRejectErrorResponse()
method.
ThegetCode(): int
method can be used toreturn the HTTP response status code.
ThegetResponse(): ResponseInterface
method can be used toaccess its underlying response object.
TheReact\Http\Middleware\StreamingRequestMiddleware
can be used toprocess incoming requests with a streaming request body (without buffering).
This allows you to process requests of any size without buffering the requestbody in memory. Instead, it will represent the request body as aReadableStreamInterface
that emit chunks of incoming data as it is received:
$http =newReact\Http\HttpServer(newReact\Http\Middleware\StreamingRequestMiddleware(),function (Psr\Http\Message\ServerRequestInterface$request) {$body =$request->getBody();assert($bodyinstanceofPsr\Http\Message\StreamInterface);assert($bodyinstanceofReact\Stream\ReadableStreamInterface);returnnewReact\Promise\Promise(function ($resolve)use ($body) {$bytes =0;$body->on('data',function ($chunk)use (&$bytes) {$bytes +=\count($chunk); });$body->on('close',function ()use (&$bytes,$resolve) {$resolve(newReact\Http\Message\Response(React\Http\Message\Response::STATUS_OK, [],"Received$bytes bytes\n" )); }); }); });
See alsostreaming incoming requestfor more details.
Additionally, this middleware can be used in combination with theLimitConcurrentRequestsMiddleware
andRequestBodyBufferMiddleware
(see below)to explicitly configure the total number of requests that can be handled atonce:
$http =newReact\Http\HttpServer(newReact\Http\Middleware\StreamingRequestMiddleware(),newReact\Http\Middleware\LimitConcurrentRequestsMiddleware(100),// 100 concurrent buffering handlersnewReact\Http\Middleware\RequestBodyBufferMiddleware(2 *1024 *1024),// 2 MiB per requestnewReact\Http\Middleware\RequestBodyParserMiddleware(),$handler);
Internally, this class is used as a "marker" to not trigger the defaultrequest buffering behavior in the
HttpServer
. It does not implement any logicon its own.
TheReact\Http\Middleware\LimitConcurrentRequestsMiddleware
can be used tolimit how many next handlers can be executed concurrently.
If this middleware is invoked, it will check if the number of pendinghandlers is below the allowed limit and then simply invoke the next handlerand it will return whatever the next handler returns (or throws).
If the number of pending handlers exceeds the allowed limit, the request willbe queued (and its streaming body will be paused) and it will return a pendingpromise.Once a pending handler returns (or throws), it will pick the oldest requestfrom this queue and invokes the next handler (and its streaming body will beresumed).
The following example shows how this middleware can be used to ensure no morethan 10 handlers will be invoked at once:
$http =newReact\Http\HttpServer(newReact\Http\Middleware\LimitConcurrentRequestsMiddleware(10),$handler);
Similarly, this middleware is often used in combination with theRequestBodyBufferMiddleware
(see below)to limit the total number of requests that can be buffered at once:
$http =newReact\Http\HttpServer(newReact\Http\Middleware\StreamingRequestMiddleware(),newReact\Http\Middleware\LimitConcurrentRequestsMiddleware(100),// 100 concurrent buffering handlersnewReact\Http\Middleware\RequestBodyBufferMiddleware(2 *1024 *1024),// 2 MiB per requestnewReact\Http\Middleware\RequestBodyParserMiddleware(),$handler);
More sophisticated examples include limiting the total number of requeststhat can be buffered at once and then ensure the actual request handler onlyprocesses one request after another without any concurrency:
$http =newReact\Http\HttpServer(newReact\Http\Middleware\StreamingRequestMiddleware(),newReact\Http\Middleware\LimitConcurrentRequestsMiddleware(100),// 100 concurrent buffering handlersnewReact\Http\Middleware\RequestBodyBufferMiddleware(2 *1024 *1024),// 2 MiB per requestnewReact\Http\Middleware\RequestBodyParserMiddleware(),newReact\Http\Middleware\LimitConcurrentRequestsMiddleware(1),// only execute 1 handler (no concurrency)$handler);
One of the built-in middleware is theReact\Http\Middleware\RequestBodyBufferMiddleware
whichcan be used to buffer the whole incoming request body in memory.This can be useful if full PSR-7 compatibility is needed for the request handlerand the default streaming request body handling is not needed.The constructor accepts one optional argument, the maximum request body size.When one isn't provided it will usepost_max_size
(default 8 MiB) from PHP'sconfiguration.(Note that the value from your matching SAPI will be used, which is the CLIconfiguration in most cases.)
Any incoming request that has a request body that exceeds this limit will beaccepted, but its request body will be discarded (empty request body).This is done in order to avoid having to keep an incoming request with anexcessive size (for example, think of a 2 GB file upload) in memory.This allows the next middleware handler to still handle this request, but itwill see an empty request body.This is similar to PHP's default behavior, where the body will not be parsedif this limit is exceeded. However, unlike PHP's default behavior, the rawrequest body is not available viaphp://input
.
TheRequestBodyBufferMiddleware
will buffer requests with bodies of known size(i.e. withContent-Length
header specified) as well as requests with bodies ofunknown size (i.e. withTransfer-Encoding: chunked
header).
All requests will be buffered in memory until the request body end hasbeen reached and then call the next middleware handler with the complete,buffered request.Similarly, this will immediately invoke the next middleware handler for requeststhat have an empty request body (such as a simpleGET
request) and requeststhat are already buffered (such as due to another middleware).
Note that the given buffer size limit is applied to each request individually.This means that if you allow a 2 MiB limit and then receive 1000 concurrentrequests, up to 2000 MiB may be allocated for these buffers alone.As such, it's highly recommended to use this along with theLimitConcurrentRequestsMiddleware
(see above) to limitthe total number of concurrent requests.
Usage:
$http =newReact\Http\HttpServer(newReact\Http\Middleware\StreamingRequestMiddleware(),newReact\Http\Middleware\LimitConcurrentRequestsMiddleware(100),// 100 concurrent buffering handlersnewReact\Http\Middleware\RequestBodyBufferMiddleware(16 *1024 *1024),// 16 MiBfunction (Psr\Http\Message\ServerRequestInterface$request) {// The body from $request->getBody() is now fully available without the need to stream itreturnnewReact\Http\Message\Response(React\Http\Message\Response::STATUS_OK); },);
TheReact\Http\Middleware\RequestBodyParserMiddleware
takes a fully buffered request body(generally fromRequestBodyBufferMiddleware
),and parses the form values and file uploads from the incoming HTTP request body.
This middleware handler takes care of applying values from HTTPrequests that useContent-Type: application/x-www-form-urlencoded
orContent-Type: multipart/form-data
to resemble PHP's default superglobals$_POST
and$_FILES
.Instead of relying on these superglobals, you can use the$request->getParsedBody()
and$request->getUploadedFiles()
methodsas defined by PSR-7.
Accordingly, each file upload will be represented as instance implementing thePSR-7UploadedFileInterface
.Due to its blocking nature, themoveTo()
method is not available and throwsaRuntimeException
instead.You can use$contents = (string)$file->getStream();
to access the filecontents and persist this to your favorite data store.
$handler =function (Psr\Http\Message\ServerRequestInterface$request) {// If any, parsed form fields are now available from $request->getParsedBody()$body =$request->getParsedBody();$name =isset($body['name']) ?$body['name'] :'unnamed';$files =$request->getUploadedFiles();$avatar =isset($files['avatar']) ?$files['avatar'] :null;if ($avatarinstanceofPsr\Http\Message\UploadedFileInterface) {if ($avatar->getError() ===UPLOAD_ERR_OK) {$uploaded =$avatar->getSize() .' bytes'; }elseif ($avatar->getError() ===UPLOAD_ERR_INI_SIZE) {$uploaded ='file too large'; }else {$uploaded ='with error'; } }else {$uploaded ='nothing'; }returnnewReact\Http\Message\Response(React\Http\Message\Response::STATUS_OK, ['Content-Type' =>'text/plain' ],$name .' uploaded' .$uploaded );};$http =newReact\Http\HttpServer(newReact\Http\Middleware\StreamingRequestMiddleware(),newReact\Http\Middleware\LimitConcurrentRequestsMiddleware(100),// 100 concurrent buffering handlersnewReact\Http\Middleware\RequestBodyBufferMiddleware(16 *1024 *1024),// 16 MiBnewReact\Http\Middleware\RequestBodyParserMiddleware(),$handler);
See alsoform upload server example for more details.
By default, this middleware respects theupload_max_filesize
(default2M
) ini setting.Files that exceed this limit will be rejected with anUPLOAD_ERR_INI_SIZE
error.You can control the maximum filesize for each individual file upload byexplicitly passing the maximum filesize in bytes as the first parameter to theconstructor like this:
newReact\Http\Middleware\RequestBodyParserMiddleware(8 *1024 *1024);// 8 MiB limit per file
By default, this middleware respects thefile_uploads
(default1
) andmax_file_uploads
(default20
) ini settings.These settings control if any and how many files can be uploaded in a single request.If you upload more files in a single request, additional files will be ignoredand thegetUploadedFiles()
method returns a truncated array.Note that upload fields left blank on submission do not count towards this limit.You can control the maximum number of file uploads per request by explicitlypassing the second parameter to the constructor like this:
newReact\Http\Middleware\RequestBodyParserMiddleware(10 *1024,100);// 100 files with 10 KiB each
Note that this middleware handler simply parses everything that is alreadybuffered in the request body.It is imperative that the request body is buffered by a prior middlewarehandler as given in the example above.This previous middleware handler is also responsible for rejecting incomingrequests that exceed allowed message sizes (such as big file uploads).The
RequestBodyBufferMiddleware
used abovesimply discards excessive request bodies, resulting in an empty body.If you use this middleware without buffering first, it will try to parse anempty (streaming) body and may thus assume an empty data structure.See alsoRequestBodyBufferMiddleware
formore details.
PHP's
MAX_FILE_SIZE
hidden field is respected by this middleware.Files that exceed this limit will be rejected with anUPLOAD_ERR_FORM_SIZE
error.
This middleware respects the
max_input_vars
(default1000
) andmax_input_nesting_level
(default64
) ini settings.
Note that this middleware ignores the
enable_post_data_reading
(default1
) ini setting because it makes little sense to respect here andis left up to higher-level implementations.If you want to respect this setting, you have to check its value andeffectively avoid using this middleware entirely.
The recommended way to install this library isthrough Composer.New to Composer?
Once released, this project will followSemVer.At the moment, this will install the latest development version:
composer require react/http:^3@dev
See also theCHANGELOG for details about version upgrades.
This project aims to run on any platform and thus does not require any PHPextensions and supports running on PHP 7.1 through current PHP 8+.It'shighly recommended to use the latest supported PHP version for this project.
To run the test suite, you first need to clone this repo and then install alldependenciesthrough Composer:
composer install
To run the test suite, go to the project root and run:
vendor/bin/phpunit
The test suite also contains a number of functional integration tests that relyon a stable internet connection.If you do not want to run these, they can simply be skipped like this:
vendor/bin/phpunit --exclude-group internet
MIT, seeLICENSE file.
About
Event-driven, streaming HTTP client and server implementation for ReactPHP.