Event-driven, streaming HTTP client and server implementation for ReactPHP

Overview

HTTP

CI status installs on Packagist

Event-driven, streaming HTTP client and server implementation for ReactPHP.

This HTTP library provides re-usable implementations for an HTTP client and server based on ReactPHP's Socket and EventLoop components. Its client component allows you to send any number of async HTTP/HTTPS requests concurrently. Its server component allows you to build plaintext HTTP and secure HTTPS servers that accept incoming HTTP requests from HTTP clients (such as web browsers). This library provides async, streaming means for all of this, so you can handle multiple concurrent HTTP requests without blocking.

Table of contents

Quickstart example

Once installed, you can use the following code to access an HTTP web server and send some simple HTTP GET requests:



require __DIR__ . '/vendor/autoload.php';

$client = new React\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 with Hello World! to every request.

listen($socket);">


require __DIR__ . '/vendor/autoload.php';

$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    return React\Http\Message\Response::plaintext(
        "Hello World!\n"
    );
});

$socket = new React\Socket\SocketServer('127.0.0.1:8080');
$http->listen($socket);

See also the examples.

Client Usage

Request methods

Most importantly, this project provides a Browser object that offers several methods that resemble the HTTP protocol methods:

$browser->get($url, array $headers = array());
$browser->head($url, array $headers = array());
$browser->post($url, array $headers = array(), string|ReadableStreamInterface $body = '');
$browser->delete($url, array $headers = array(), string|ReadableStreamInterface $body = '');
$browser->put($url, array $headers = array(), string|ReadableStreamInterface $body = '');
$browser->patch($url, array $headers = array(), string|ReadableStreamInterface $body = '');

Each of these methods requires a $url and some optional parameters to send an HTTP request. Each of these method names matches the respective HTTP request method, for example the get() method sends an HTTP GET request.

You can optionally pass an associative array of additional $headers that will be sent with this HTTP request. Additionally, each method will automatically add a matching Content-Length request header if an outgoing request body is given and its size is known and non-empty. For an empty request body, if will only include a Content-Length: 0 request header if the request method usually expects a request body (only applies to POST, PUT and PATCH HTTP request methods).

If you're using a streaming request body, it will default to using Transfer-Encoding: chunked unless you explicitly pass in a matching Content-Length request header. See also streaming request for more details.

By default, all of the above methods default to sending requests using the HTTP/1.1 protocol version. If you want to explicitly use the legacy HTTP/1.0 protocol version, you can use the withProtocolVersion() method. If you want to use any other or even custom HTTP request method, you can use the request() method.

Each of the above methods supports async operation and either fulfills with a PSR-7 ResponseInterface or rejects with an Exception. Please see the following chapter about promises for more details.

Promises

Sending requests is async (non-blocking), so you can actually send multiple requests in parallel. The Browser will respond to each request with a PSR-7 ResponseInterface message, the order is not guaranteed. Sending requests uses a Promise-based interface 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 traditional blocking API.

Keep in mind that resolving the Promise with the full response message means the whole 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 the streaming 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).

Cancellation

The returned Promise is implemented in such a way that it can be cancelled when it is still pending. Cancelling a pending promise will reject its value with an Exception and clean up any underlying resources.

$promise = $browser->get($url);

Loop::addTimer(2.0, function () use ($promise) {
    $promise->cancel();
});

Timeouts

This library uses a very efficient HTTP implementation, so most HTTP requests should usually be completed in mere milliseconds. However, when sending HTTP requests over an unreliable network (the internet), there are a number of things that can go wrong and may cause the request to fail after a time. As such, this library respects PHP's default_socket_timeout setting (default 60s) as a timeout for sending the outgoing HTTP request and waiting for a successful response and will 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 full response body and following any eventual redirects. See also redirects below to configure the number of redirects to follow (or disable following redirects altogether) and also streaming below to not take receiving large response bodies into account for this timeout.

You can use the withTimeout() method to pass a custom timeout value in seconds like this:

$browser = $browser->withTimeout(10.0);

$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
    // response received within 10 seconds maximum
    var_dump($response->getHeaders());
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

Similarly, you can use a bool false to not apply a timeout at all or use a bool true value to restore the default handling. See withTimeout() for more details.

If you're using a streaming response body, the time it takes 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 as when downloading a very large stream or when streaming data over a long-lived connection.

If you're using a streaming request body, the time it takes to send the request body stream will not be included in the timeout. This allows you to keep this outgoing stream open for a longer time, such as when uploading a very large stream.

Note that this timeout handling applies to the higher-level HTTP layer. Lower layers such as socket and DNS may also apply (different) timeout values. In particular, the underlying socket connection uses the same default_socket_timeout setting to establish the underlying transport connection. To control this connection timeout behavior, you can inject a custom Connector like this:

$browser = new React\Http\Browser(
    new React\Socket\Connector(
        array(
            'timeout' => 5
        )
    )
);

Authentication

This library supports HTTP Basic Authentication using the Authorization: Basic … request header or allows you to set an explicit Authorization request header.

By default, this library does not include an outgoing Authorization request header. If the server requires authentication, if may return a 401 (Unauthorized) status code which will reject the request by default (see also the withRejectErrorResponse() method below).

In order to pass authentication details, you can simply pass the username and password as part of the request URL like this:

$promise = $browser->get('https://user:[email protected]/api');

Note that special characters in the authentication details have to be percent-encoded, see also rawurlencode(). This example will automatically pass the base64-encoded authentication details using the outgoing Authorization: Basic … request header. If the HTTP endpoint you're talking to requires any other authentication scheme, you can also pass this header explicitly. This is common when using (RESTful) HTTP APIs that use OAuth access tokens or JSON Web Tokens (JWT):

$token = 'abc123';

$promise = $browser->get(
    'https://example.com/api',
    array(
        'Authorization' => 'Bearer ' . $token
    )
);

When following redirects, the Authorization request header will never be sent to any remote hosts by default. When following a redirect where the Location response header contains authentication details, these details will be sent for following requests. See also redirects below.

Redirects

By default, this library follows any redirects and obeys 3xx (Redirection) status codes using the Location 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 here
    var_dump($response->getHeaders());
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

Any redirected requests will follow the semantics of the original request and will include the same request headers as the original request except for those listed below. If the original request contained a request body, this request body will never be passed to the redirected request. Accordingly, each redirected request will remove any Content-Length and Content-Type request headers.

If the original request used HTTP authentication with an Authorization request header, this request header will only be passed as part of the redirected request if the redirected URL is using the same host. In other words, the Authorizaton request header will not be forwarded to other foreign hosts due to possible privacy/security concerns. When following a redirect where the Location response header contains authentication details, these details will be sent for following requests.

You can use the withFollowRedirects() method to control the maximum number of redirects to follow or to return any redirect responses 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 here
    var_dump($response->getHeaders());
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

See also withFollowRedirects() for more details.

Blocking

As stated above, this library provides you a powerful, async API by default.

If, however, you want to integrate this into your traditional, blocking environment, you should look into also using clue/reactphp-block.

The resulting blocking code could look something like this:

use Clue\React\Block;

$browser = new React\Http\Browser();

$promise = $browser->get('http://example.com/');

try {
    $response = Block\await($promise, Loop::get());
    // response successfully received
} catch (Exception $e) {
    // an error occured while performing the request
}

Similarly, you can also process multiple requests concurrently and await an array of Response objects:

$promises = array(
    $browser->get('http://example.com/'),
    $browser->get('http://www.example.org/'),
);

$responses = Block\awaitAll($promises, Loop::get());

Please refer to clue/reactphp-block 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 the streaming API.

Concurrency

As stated above, this library provides you a powerful, async API. Being able to send a large number of requests at once is one of the core features of this project. For instance, you can easily send 100 requests concurrently while processing SQL queries at the same time.

Remember, with great power comes great responsibility. Sending an excessive number of requests may either take up all resources on your side or it may even get you banned by the remote side if it sees an unreasonable number of requests from your side.

// watch out if array contains many elements
foreach ($urls as $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 sending side to a reasonable value. It's common to use a rather small limit, as doing more than a dozen of things at once may easily overwhelm the receiving side. You can use clue/reactphp-mq as a lightweight in-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 = new Clue\React\Mq\Queue(10, null, function ($url) use ($browser) {
    return $browser->get($url);
});

foreach ($urls as $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 be enqueued until one of the pending requests completes. This integrates nicely with the existing Promise-based API. Please refer to clue/reactphp-mq for more details.

This in-memory approach works reasonably well for some thousand outstanding requests. If you're processing a very large input list (think millions of rows in a CSV or NDJSON file), you may want to look into using a streaming approach instead. See clue/reactphp-flux for more details.

Streaming response

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 a streaming 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 the requestStreaming() method to send an arbitrary HTTP request and receive a streaming response. It uses the same HTTP message API, but does not buffer the response body in memory. It only processes the response body in small chunks as data is received and forwards this data through ReactPHP's Stream API. This works for (any number of) responses of arbitrary sizes.

This means it resolves with a normal PSR-7 ResponseInterface, which can be used to access the response message parameters as usual. You can access the message body as usual, however it now also implements ReactPHP's ReadableStreamInterface as well as parts of the PSR-7 StreamInterface.

$browser->requestStreaming('GET', $url)->then(function (Psr\Http\Message\ResponseInterface $response) {
    $body = $response->getBody();
    assert($body instanceof Psr\Http\Message\StreamInterface);
    assert($body instanceof React\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 the stream download benchmark example and the stream 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 = array());
$body->close();
$body->pause();
$body->resume();

Because the message body is in a streaming state, invoking the following methods doesn'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 how timeouts apply slightly differently when using streaming. In streaming mode, the timeout value covers creating the underlying transport connection, sending the HTTP request, receiving the HTTP response headers and following any eventual redirects. In particular, the timeout value does not take receiving (possibly large) response bodies into account.

If you want to integrate the streaming response into a higher level API, then working with Promise objects that resolve with Stream objects is often inconvenient. Consider looking into also using react/promise-stream. The resulting streaming code could look something like this:

use React\Promise\Stream;

function download(Browser $browser, string $url): React\Stream\ReadableStreamInterface {
    return Stream\unwrapReadable(
        $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 the requestStreaming() method for more details.

Streaming request

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 instance implementing ReactPHP's ReadableStreamInterface to the request methods like this:

$browser->post($url, array(), $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 will default to using Transfer-Encoding: chunked or you have to explicitly pass in a matching Content-Length request header like so:

post($url, array('Content-Length' => '11'), $body);">
$body = new React\Stream\ThroughStream();
Loop::addTimer(1.0, function () use ($body) {
    $body->end("hello world");
});

$browser->post($url, array('Content-Length' => '11'), $body);

If the streaming request body emits an error event or is explicitly closed without emitting a successful end event first, the request will automatically be closed and rejected.

HTTP proxy

You can also establish your outgoing connections through an HTTP CONNECT proxy server by adding a dependency to clue/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"), to conceal the origin address (anonymity) or to circumvent address blocking (geoblocking). While many (public) HTTP CONNECT proxy servers often limit this to HTTPS port 443 only, this can technically be used to tunnel any TCP/IP-based protocol, such as plain HTTP and TLS-encrypted HTTPS.

$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');

$connector = new React\Socket\Connector(array(
    'tcp' => $proxy,
    'dns' => false
));

$browser = new React\Http\Browser($connector);

See also the HTTP proxy example.

SOCKS proxy

You can also establish your outgoing connections through a SOCKS proxy server by adding a dependency to clue/reactphp-socks.

The SOCKS proxy protocol family (SOCKS5, SOCKS4 and SOCKS4a) is commonly used to tunnel HTTP(S) traffic through an intermediary ("proxy"), to conceal the origin address (anonymity) or to circumvent address blocking (geoblocking). While many (public) SOCKS proxy servers often limit this to HTTP(S) port 80 and 443 only, this can technically be used to tunnel any TCP/IP-based protocol.

$proxy = new Clue\React\Socks\Client('127.0.0.1:1080');

$connector = new React\Socket\Connector(array(
    'tcp' => $proxy,
    'dns' => false
));

$browser = new React\Http\Browser($connector);

See also the SOCKS proxy example.

SSH proxy

You can also establish your outgoing connections through an SSH server by adding a dependency to clue/reactphp-ssh-proxy.

Secure Shell (SSH) is a secure network protocol that is most commonly used to access a login shell on a remote server. Its architecture allows it to use multiple secure channels over a single connection. Among others, this can also be used to create an "SSH tunnel", which is commonly used to tunnel HTTP(S) traffic through an intermediary ("proxy"), to conceal 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 accessible from the outside (database behind firewall) and as such can also be used for plain HTTP and TLS-encrypted HTTPS.

$proxy = new Clue\React\SshProxy\SshSocksConnector('[email protected]');

$connector = new React\Socket\Connector(array(
    'tcp' => $proxy,
    'dns' => false
));

$browser = new React\Http\Browser($connector);

See also the SSH proxy example.

Unix domain sockets

By default, this library supports transport over plaintext TCP/IP and secure TLS connections for the http:// and https:// 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 to override the destination URL so that the hostname given in the request URL will no longer be used to establish the connection:

$connector = new React\Socket\FixedUriConnector(
    'unix:///var/run/docker.sock',
    new React\Socket\UnixConnector()
);

$browser = new React\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 the Unix Domain Sockets (UDS) example.

Server Usage

HttpServer

The React\Http\HttpServer class is responsible for handling incoming connections and then processing each incoming HTTP request.

When a complete HTTP request has been received, it will invoke the given request handler function. This request handler function needs to be passed to the constructor and will be invoked with the respective request object and expects a response object in return:

$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    return React\Http\Message\Response::plaintext(
        "Hello World!\n"
    );
});

Each incoming HTTP request message is always represented by the PSR-7 ServerRequestInterface, see also following request chapter for more details.

Each outgoing HTTP response message is always represented by the PSR-7 ResponseInterface, see also following response chapter for more details.

This class takes an optional LoopInterface|null $loop parameter that can be used to pass the event loop instance to use for this object. You can use a null value here in order to use the default loop. This value SHOULD NOT be given unless you're sure you want to explicitly use a given event loop instance.

In order to start listening for any incoming connections, the HttpServer needs to be attached to an instance of React\Socket\ServerInterface through the listen() method as described in the following chapter. In its most simple form, you can attach this to a React\Socket\SocketServer in order to start a plaintext HTTP server like this:

$http = new React\Http\HttpServer($handler);

$socket = new React\Socket\SocketServer('0.0.0.0:8080');
$http->listen($socket);

See also the listen() method and the hello world server example for more details.

By default, the HttpServer buffers and parses the complete incoming HTTP request in memory. It will invoke the given request handler function when the complete request headers and request body has been received. This means the request object passed to your request handler function will be fully compatible with PSR-7 (http-message). This provides sane defaults for 80% of the use cases and is the recommended way to use this library unless you're sure you know what you're doing.

On the other hand, buffering complete HTTP requests in memory until they can be processed by your request handler function means that this class has to employ a number of limits to avoid consuming too much memory. In order to take the more advanced configuration out your hand, it respects setting from your php.ini to apply its default settings. This is a list of PHP settings this class respects with their respective default values:

memory_limit 128M
post_max_size 8M // capped at 64K

enable_post_data_reading 1
max_input_nesting_level 64
max_input_vars 1000

file_uploads 1
upload_max_filesize 2M
max_file_uploads 20

In particular, the post_max_size setting limits how much memory a single HTTP request is allowed to consume while buffering its request body. This needs to be limited because the server can process a large number of requests concurrently, so the server may potentially consume a large amount of memory otherwise. To support higher concurrency by default, this value is capped at 64K. If you assign a higher value, it will only allow 64K by default. If a request exceeds this limit, its request body will be ignored and it will be processed like a request with no request body at all. See below for explicit configuration to override this setting.

By default, this class will try to avoid consuming more than half of your memory_limit for buffering multiple concurrent HTTP requests. As such, with the above default settings of 128M max, it will try to consume no more than 64M for buffering multiple concurrent HTTP requests. As a consequence, it will limit the concurrency to 1024 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 requests with a large HTTP request body (e.g. large file uploads). If you want to increase this buffer size, you will have to also increase the total memory limit to allow for more concurrent requests (set memory_limit 512M or more) or explicitly limit concurrency.

In order to override the above buffering defaults, you can configure the HttpServer explicitly. You can use the LimitConcurrentRequestsMiddleware and RequestBodyBufferMiddleware (see below) to explicitly configure the total number of requests that can be handled at once like this:

$http = new React\Http\HttpServer(
    new React\Http\Middleware\StreamingRequestMiddleware(),
    new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
    new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
    new React\Http\Middleware\RequestBodyParserMiddleware(),
    $handler
);

In this example, we allow processing up to 100 concurrent requests at once and each request can buffer up to 2M. This means you may have to keep a maximum of 200M of memory for incoming request body buffers. Accordingly, you need to adjust the memory_limit ini setting to allow for these buffers plus your actual application logic memory requirements (think 512M or more).

Internally, this class automatically assigns these middleware handlers automatically when no StreamingRequestMiddleware is given. Accordingly, you can use this example to override all default settings to implement custom limits.

As an alternative to buffering the complete request body in memory, you can also use a streaming approach where only small chunks of data have to be kept in memory:

$http = new React\Http\HttpServer(
    new React\Http\Middleware\StreamingRequestMiddleware(),
    $handler
);

In this case, it will invoke the request handler function once the HTTP request headers have been received, i.e. before receiving the potentially much larger HTTP request body. This means the request passed to your request handler function may not be fully compatible with PSR-7. This is specifically designed to help with more advanced use cases where you want to have full control over consuming the incoming HTTP request body and concurrency settings. See also streaming incoming request below for more details.

Changelog v1.5.0: This class has been renamed to HttpServer from the previous Server class in order to avoid any ambiguities. The previous name has been deprecated and should not be used anymore.

listen()

The listen(React\Socket\ServerInterface $socket): void method can be used to start listening for HTTP requests on the given socket server instance.

The given React\Socket\ServerInterface is responsible for emitting the underlying streaming connections. This HTTP server needs to be attached to it in order to process any connections and pase incoming streaming data as incoming HTTP request messages. In its most common form, you can attach this to a React\Socket\SocketServer in order to start a plaintext HTTP server like this:

$http = new React\Http\HttpServer($handler);

$socket = new React\Socket\SocketServer('0.0.0.0:8080');
$http->listen($socket);

See also hello world server example for more details.

This example will start listening for HTTP requests on the alternative HTTP port 8080 on all interfaces (publicly). As an alternative, it is very common to use a reverse proxy and let this HTTP server listen on the localhost (loopback) interface only by using the listen address 127.0.0.1:8080 instead. This way, you host your application(s) on the default HTTP port 80 and only route specific requests to this HTTP server.

Likewise, it's usually recommended to use a reverse proxy setup to accept secure HTTPS requests on default HTTPS port 443 (TLS termination) and only route plaintext requests to this HTTP server. As an alternative, you can also accept secure HTTPS requests with this HTTP server by attaching this to a React\Socket\SocketServer using a secure TLS listen address, a certificate file and optional passphrase like this:

$http = new React\Http\HttpServer($handler);

$socket = new React\Socket\SocketServer('tls://0.0.0.0:8443', array(
    'tls' => array(
        'local_cert' => __DIR__ . '/localhost.pem'
    )
));
$http->listen($socket);

See also hello world HTTPS example for more details.

Server Request

As seen above, the HttpServer class is responsible for handling incoming connections and then processing each incoming HTTP request.

The request object will be processed once the request has been received by the client. This request object implements the PSR-7 ServerRequestInterface which in turn extends the PSR-7 RequestInterface and will be passed to the callback function like this.

getMethod() . "\n"; $body .= "The requested path is: " . $request->getUri()->getPath() . "\n"; return React\Http\Message\Response::plaintext( $body ); });">
$http = new React\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";

   return React\Http\Message\Response::plaintext(
       $body
   );
});

For more details about the request object, also check out the documentation of PSR-7 ServerRequestInterface and PSR-7 RequestInterface.

Request parameters

The getServerParams(): mixed[] method can be used to get server-side parameters similar to the $_SERVER variable. The following parameters are currently available:

  • REMOTE_ADDR The IP address of the request sender
  • REMOTE_PORT Port of the request sender
  • SERVER_ADDR The IP address of the server
  • SERVER_PORT The port of the server
  • REQUEST_TIME Unix timestamp when the complete request header has been received, as integer similar to time()
  • REQUEST_TIME_FLOAT Unix timestamp when the complete request header has been received, as float similar to microtime(true)
  • HTTPS Set to 'on' if the request used HTTPS, otherwise it won't be set
getServerParams()['REMOTE_ADDR'] . "\n"; return React\Http\Message\Response::plaintext( $body ); });">
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    $body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR'] . "\n";

    return React\Http\Message\Response::plaintext(
        $body
    );
});

See also whatsmyip server example.

Advanced: Note that address parameters will not be set if you're listening on a Unix domain socket (UDS) path as this protocol lacks the concept of host/port.

Query parameters

The getQueryParams(): array method can be used to get the query parameters similiar to the $_GET variable.

$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    $queryParams = $request->getQueryParams();

    $body = 'The query parameter "foo" is not set. Click the following link ';
    $body .= 'to use query parameter in your request';

    if (isset($queryParams['foo'])) {
        $body = 'The value of "foo" is: ' . htmlspecialchars($queryParams['foo']);
    }

    return React\Http\Message\Response::html(
        $body
    );
});

The response in the above example will return a response body with a link. The URL contains the query parameter foo with the value bar. Use htmlentities like in this example to prevent Cross-Site Scripting (abbreviated as XSS).

See also server query parameters example.

Request body

By default, the Server will buffer and parse the full request body in memory. This means the given request object includes the parsed request body and any file uploads.

As an alternative to the default buffering logic, you can also use the StreamingRequestMiddleware. Jump to the next chapter to learn more about how to process a streaming incoming request.

As stated above, each incoming HTTP request is always represented by the PSR-7 ServerRequestInterface. This interface provides several methods that are useful when working with the incoming request body as described below.

The getParsedBody(): null|array|object method can be used to get the parsed request body, similar to PHP's $_POST variable. This method may return a (possibly nested) array structure with all body parameters or a null value if the request body could not be parsed. By default, this method will only return parsed data for requests using Content-Type: application/x-www-form-urlencoded or Content-Type: multipart/form-data request headers (commonly used for POST requests for HTML form submission data).

$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    $name = $request->getParsedBody()['name'] ?? 'anonymous';

    return React\Http\Message\Response::plaintext(
        "Hello $name!\n"
    );
});

See also form upload example for more details.

The getBody(): StreamInterface method can be used to get the raw data from this request body, similar to PHP's php://input stream. This method returns an instance of the request body represented by the PSR-7 StreamInterface. This is particularly useful when using a custom request body that will not otherwise be parsed by default, such as a JSON (Content-Type: application/json) or an XML (Content-Type: application/xml) request body (which is commonly used for POST, PUT or PATCH requests in JSON-based or RESTful/RESTish APIs).

$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    $data = json_decode((string)$request->getBody());
    $name = $data->name ?? 'anonymous';

    return React\Http\Message\Response::json(
        ['message' => "Hello $name!"]
    );
});

See also JSON API server example for more details.

The getUploadedFiles(): array method can be used to get the uploaded files in this request, similar to PHP's $_FILES variable. This method returns a (possibly nested) array structure with all file uploads, each represented by the PSR-7 UploadedFileInterface. This array will only be filled when using the Content-Type: multipart/form-data request header (commonly used for POST requests for HTML file uploads).

$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    $files = $request->getUploadedFiles();
    $name = isset($files['avatar']) ? $files['avatar']->getClientFilename() : 'nothing';

    return React\Http\Message\Response::plaintext(
        "Uploaded $name\n"
    );
});

See also form upload server example for more details.

The getSize(): ?int method can be used to get 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 number of bytes as defined by the message boundaries. This value may be 0 if the request message does not contain a request body (such as a simple GET request). This method operates on the buffered request body, i.e. the request body size is always known, even when the request does not specify a Content-Length request header or when using Transfer-Encoding: chunked for HTTP/1.1 requests.

Note: The HttpServer automatically takes care of handling requests with the additional Expect: 100-continue request header. When HTTP/1.1 clients want to send a bigger request body, they MAY send only the request headers with an additional Expect: 100-continue request header and wait before sending the actual (large) message body. In this case the server will automatically send an intermediary HTTP/1.1 100 Continue response to the client. This ensures you will receive the request body without a delay as expected.

Streaming incoming request

If you're using the advanced StreamingRequestMiddleware, 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 the previous chapter to learn more about how to process a buffered request body.

While this may be uncommon in the PHP ecosystem, this is actually a very powerful approach that gives you several advantages not otherwise possible:

  • React to requests before receiving a large request body, such as rejecting an unauthenticated request or one that exceeds allowed message lengths (file uploads).
  • Start processing parts of the request body before the remainder of the request body 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.

The getBody(): StreamInterface method can be used to access the request body stream. In the streaming mode, this method returns a stream instance that implements both the PSR-7 StreamInterface and the ReactPHP ReadableStreamInterface. However, most of the PSR-7 StreamInterface methods have been designed under the assumption of being in control of a synchronous request body. Given that this does not apply to this server, the following PSR-7 StreamInterface methods are not used and SHOULD NOT be called: tell(), eof(), seek(), rewind(), write() and read(). If this is an issue for your use case and/or you want to access uploaded files, it's highly recommended to use a buffered request body or use the RequestBodyBufferMiddleware instead. The ReactPHP ReadableStreamInterface gives you access to the incoming request body as the individual chunks arrive:

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)); }); }); } );">
$http = new React\Http\HttpServer(
    new React\Http\Middleware\StreamingRequestMiddleware(),
    function (Psr\Http\Message\ServerRequestInterface $request) {
        $body = $request->getBody();
        assert($body instanceof Psr\Http\Message\StreamInterface);
        assert($body instanceof React\Stream\ReadableStreamInterface);

        return new React\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 also streaming request server example for more details.

The data event will be emitted whenever new data is available on the request body stream. The server also automatically takes care of decoding any incoming requests using Transfer-Encoding: chunked and will only emit the actual payload as data.

The end event will be emitted when the request body stream terminates successfully, i.e. it was read until its expected end.

The error event will be emitted in case the request stream contains invalid data for Transfer-Encoding: chunked or when the connection closes before the complete request stream has been received. The server will automatically stop reading from the connection and discard all incoming data instead of closing it. A response message can still be sent (unless the connection is already closed).

A close event will be emitted after an error or end event.

For more details about the request body stream, check out the documentation of ReactPHP ReadableStreamInterface.

The getSize(): ?int method can be used to get 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 number of bytes as defined by the message boundaries. This value may be 0 if the request message does not contain a request body (such as a simple GET request). This method operates on the streaming request body, i.e. the request body size may be unknown (null) when using Transfer-Encoding: chunked for HTTP/1.1 requests.

withStatus(React\Http\Message\Response::STATUS_LENGTH_REQUIRED); } return React\Http\Message\Response::plaintext( "Request body size: " . $size . " bytes\n" ); } );">
$http = new React\Http\HttpServer(
    new React\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";

            return React\Http\Message\Response::plaintext(
                $body
            )->withStatus(React\Http\Message\Response::STATUS_LENGTH_REQUIRED);
        }

        return React\Http\Message\Response::plaintext(
            "Request body size: " . $size . " bytes\n"
        );
    }
);

Note: The HttpServer automatically takes care of handling requests with the additional Expect: 100-continue request header. When HTTP/1.1 clients want to send a bigger request body, they MAY send only the request headers with an additional Expect: 100-continue request header and wait before sending the actual (large) message body. In this case the server will automatically send an intermediary HTTP/1.1 100 Continue response to the client. This ensures you will receive the streaming request body without a delay as expected.

Request method

Note that the server supports any request method (including custom and non- standard ones) and all request-target formats defined in the HTTP specs for each respective method, including normal origin-form requests as well as proxy requests in absolute-form and authority-form. The getUri(): UriInterface method can be used to get the effective request URI which provides you access to individiual URI components. Note that (depending on the given request-target) certain URI components may or may not be present, for example the getPath(): string method will return an empty string for requests in asterisk-form or authority-form. Its getHost(): string method will return the host as determined by the effective request URI, which defaults to the local socket address if an HTTP/1.0 client did not specify one (i.e. no Host header). Its getScheme(): string method will return http or https depending on whether the request was made over a secure TLS connection to the target host.

The Host header value will be sanitized to match this host component plus the port component only if it is non-standard for this URI scheme.

You can use getMethod(): string and getRequestTarget(): string to check this is an accepted request and may want to reject other requests with an appropriate error code, such as 400 (Bad Request) or 405 (Method Not Allowed).

The CONNECT method is useful in a tunneling setup (HTTPS proxy) and not something most HTTP servers would want to care about. Note that if you want to handle this method, the client MAY send a different request-target than the Host header value (such as removing default ports) and the request-target MUST take precendence when forwarding.

Cookie parameters

The getCookieParams(): string[] method can be used to get all cookies sent with the current request.

getCookieParams()[$key] . "\n"; return React\Http\Message\Response::plaintext( $body ); } return React\Http\Message\Response::plaintext( "Your cookie has been set.\n" )->withHeader('Set-Cookie', urlencode($key) . '=' . urlencode('test;more')); });">
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    $key = 'react\php';

    if (isset($request->getCookieParams()[$key])) {
        $body = "Your cookie value is: " . $request->getCookieParams()[$key] . "\n";

        return React\Http\Message\Response::plaintext(
            $body
        );
    }

    return React\Http\Message\Response::plaintext(
        "Your cookie has been set.\n"
    )->withHeader('Set-Cookie', urlencode($key) . '=' . urlencode('test;more'));
});

The above example will try to set a cookie on first access and will try to print the cookie value on all subsequent tries. Note how the example uses the urlencode() function to encode non-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 also cookie server example for more details.

Invalid request

The HttpServer class supports both HTTP/1.1 and HTTP/1.0 request messages. If a client sends an invalid request message, uses an invalid HTTP protocol version or sends an invalid Transfer-Encoding request header value, the server will automatically send a 400 (Bad Request) HTTP error response to the client and close the connection. On top of this, it will emit an error event that can be used for logging purposes like this:

$http->on('error', function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

Note that the server will also emit an error event if you do not return a valid response object from your request handler function. See also invalid response for more details.

Server Response

The callback function passed to the constructor of the HttpServer is responsible for processing the request and returning a response, which will be delivered to the client.

This function MUST return an instance implementing PSR-7 ResponseInterface object or a ReactPHP Promise which resolves with a PSR-7 ResponseInterface object.

This projects ships a Response class which implements the PSR-7 ResponseInterface. In its most simple form, you can use it like this:

$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    return React\Http\Message\Response::plaintext(
        "Hello World!\n"
    );
});

We use this Response class throughout our project examples, but feel free to use any other implementation of the PSR-7 ResponseInterface. See also the Response class for more details.

Deferred response

The example above returns the response directly, because it needs no time to be processed. Using a database, the file system or long calculations (in fact every action that will take >=1ms) to create your response, will slow down the server. To prevent this you SHOULD use a ReactPHP Promise. This example shows how such a long-term action could look like:

$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    $promise = new Promise(function ($resolve, $reject) {
        Loop::addTimer(1.5, function() use ($resolve) {
            $resolve();
        });
    });

    return $promise->then(function () { 
        return React\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. The ReactPHP Promise will resolve in a Response object when the request body ends. If the client closes the connection while the promise is still pending, the promise will automatically be cancelled. The promise cancellation handler can be used to clean up any pending resources allocated in this case (if applicable). If a promise is resolved after the client closes, it will simply be ignored.

Streaming outgoing response

The Response class in this project supports to add an instance which implements the ReactPHP ReadableStreamInterface for the response body. So you are able stream data directly into the response body. Note that other implementations of the PSR-7 ResponseInterface may only support strings.

$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    $stream = new ThroughStream();

    $timer = Loop::addPeriodicTimer(0.5, function () use ($stream) {
        $stream->write(microtime(true) . PHP_EOL);
    });

    Loop::addTimer(5, function() use ($timer, $stream) {
        Loop::cancelTimer($timer);
        $stream->end();
    });

    return new React\Http\Message\Response(
        React\Http\Message\Response::STATUS_OK,
        array(
            'Content-Type' => 'text/plain'
        ),
        $stream
    );
});

The above example will emit every 0.5 seconds the current Unix timestamp with 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 chunks or 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, the response stream will automatically be closed. If a promise is resolved with a streaming body after the client closes, the response stream will automatically be closed. The close event can be used to clean up any pending resources allocated in this case (if applicable).

Note that special care has to be taken if you use a body stream instance that implements ReactPHP's DuplexStreamInterface (such as the ThroughStream in the above example).

For most cases, this will simply only consume its readable side and forward (send) any data that is emitted by the stream, thus entirely ignoring the writable side of the stream. If however this is either a 101 (Switching Protocols) response or a 2xx (Successful) response to a CONNECT method, it will also write data to the writable side of the stream. This can be avoided by either rejecting all requests with the CONNECT method (which is what most normal origin HTTP servers would likely do) or or ensuring that only ever an instance of ReactPHP's ReadableStreamInterface is used.

The 101 (Switching Protocols) response code is useful for the more advanced Upgrade requests, such as upgrading to the WebSocket protocol or implementing custom protocol logic that is out of scope of the HTTP specs and this HTTP library. If you want to handle the Upgrade: WebSocket header, you will likely want to look into using Ratchet instead. If you want to handle a custom protocol, you will likely want to look into the HTTP specs and also see examples #81 and #82 for more details. In particular, the 101 (Switching Protocols) response code MUST NOT be used unless you send an Upgrade response header value that is also present in the corresponding HTTP/1.1 Upgrade request header value. The server automatically takes care of sending a Connection: upgrade header value in this case, so you don't have to.

The CONNECT method is useful in a tunneling setup (HTTPS proxy) and not something most origin HTTP servers would want to care about. The HTTP specs define an opaque "tunneling mode" for this method and make no use of the message body. For consistency reasons, this library uses a DuplexStreamInterface in the response body for tunneled application data. This implies that that a 2xx (Successful) response to a CONNECT request can 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 piped through the writable stream for consumption. Note that while the HTTP specs make no use of the request body for CONNECT requests, one may still be present. Normal request body processing applies here and the connection will only turn to "tunneling mode" after the request body has been processed (which should be empty in most cases). See also HTTP CONNECT server example for more details.

Response length

If the response body size is known, a Content-Length response header will be added automatically. This is the most common use case, for example when using a string response body like this:

$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    return React\Http\Message\Response::plaintext(
        "Hello World!\n"
    );
});

If the response body size is unknown, a Content-Length response header can not be added automatically. When using a streaming outgoing response without an explicit Content-Length response header, outgoing HTTP/1.1 response messages will automatically use Transfer-Encoding: chunked while legacy HTTP/1.0 response messages will contain the plain response body. If you know the length of your streaming response body, you MAY want to specify it explicitly like this:

'13', 'Content-Type' => 'text/plain', ), $stream ); });">
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
    $stream = new ThroughStream();

    Loop::addTimer(2.0, function () use ($stream) {
        $stream->end("Hello World!\n");
    });

    return new React\Http\Message\Response(
        React\Http\Message\Response::STATUS_OK,
        array(
            'Content-Length' => '13',
            'Content-Type' => 'text/plain',
        ),
        $stream
    );
});

Any response to a HEAD request and any response with a 1xx (Informational), 204 (No Content) or 304 (Not Modified) status code will not include a message body as per the HTTP specs. This means that your callback does not have to take special care of this and any response body will simply be ignored.

Similarly, any 2xx (Successful) response to a CONNECT request, any response with a 1xx (Informational) or 204 (No Content) status code will not include a Content-Length or Transfer-Encoding header as these do not apply to these messages. Note that a response to a HEAD request and any response with a 304 (Not Modified) status code MAY include these headers even though the message does not contain a response body, because these header would apply to the message if the same request would have used an (unconditional) GET.

Invalid response

As stated above, each outgoing HTTP response is always represented by the PSR-7 ResponseInterface. If your request handler function returns an invalid value or throws an unhandled Exception or Throwable, the server will automatically send a 500 (Internal Server Error) HTTP error response to the client. On top of this, it will emit an error event that can be used for logging purposes 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 an error event if the client sends an invalid HTTP request that never reaches your request handler function. See also invalid request for more details. Additionally, a streaming incoming request body can also emit an error event on the request body.

The server will only send a very generic 500 (Interval Server Error) HTTP error response without any further details to the client if an unhandled error occurs. While we understand this might make initial debugging harder, it also means that the server does not leak any application details or stack traces to the outside by default. It is usually recommended to catch any Exception or Throwable within your request handler function or alternatively use a middleware to avoid this generic error handling and create your own HTTP response message instead.

Default response headers

When a response is returned from the request handler function, it will be processed by the HttpServer and then sent back to the client.

A Server: ReactPHP/1 response header will be added automatically. You can add a custom Server response header like this:

$http = new React\Http\HttpServer(function (ServerRequestInterface $request) {
    return new React\Http\Message\Response(
        React\Http\Message\Response::STATUS_OK,
        array(
            'Server' => 'PHP/3'
        )
    );
});

If you do not want to send this Sever response header at all (such as when you don't want to expose the underlying server software), you can use an empty string value like this:

$http = new React\Http\HttpServer(function (ServerRequestInterface $request) {
    return new React\Http\Message\Response(
        React\Http\Message\Response::STATUS_OK,
        array(
            'Server' => ''
        )
    );
});

A Date response header will be added automatically with the current system date and time if none is given. You can add a custom Date response header like this:

$http = new React\Http\HttpServer(function (ServerRequestInterface $request) {
    return new React\Http\Message\Response(
        React\Http\Message\Response::STATUS_OK,
        array(
            'Date' => gmdate('D, d M Y H:i:s \G\M\T')
        )
    );
});

If you do not want to send this Date response header at all (such as when you don't have an appropriate clock to rely on), you can use an empty string value like this:

$http = new React\Http\HttpServer(function (ServerRequestInterface $request) {
    return new React\Http\Message\Response(
        React\Http\Message\Response::STATUS_OK,
        array(
            'Date' => ''
        )
    );
});

The HttpServer 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 the HTTP/1.1 protocol version, the response message will also use the same protocol version, no matter what version is returned from the request handler function.

The server supports persistent connections. An appropriate Connection: keep-alive or Connection: close response header will be added automatically, respecting the matching request header value and HTTP default header values. The server is responsible for handling the Connection response header, so you SHOULD NOT pass this response header yourself, unless you explicitly want to override the user's choice with a Connection: close response header.

Middleware

As documented above, the HttpServer accepts a single request handler argument that is responsible for processing an incoming HTTP request and then creating and returning an outgoing HTTP response.

Many common use cases involve validating, processing, manipulating the incoming HTTP request before passing it to the final business logic request handler. As such, this project supports the concept of middleware request handlers.

Custom middleware

A middleware request handler is expected to adhere the following rules:

  • It is a valid callable.
  • It accepts an instance implementing PSR-7 ServerRequestInterface as first argument and an optional callable as second argument.
  • It returns either:
  • It calls $next($request) to continue processing the next middleware request handler or returns explicitly without calling $next to abort the chain.
    • The $next request handler (recursively) invokes the next request handler 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) to change 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 to implement custom "retry" logic etc.

Note that this very simple definition allows you to use either anonymous functions or any classes that use the magic __invoke() method. This allows you to easily create custom middleware request handlers on the fly or use a class based approach to ease using existing middleware implementations.

While this project does provide the means to use middleware implementations, it does not aim to define how middleware implementations should look like. We realize that there's a vivid ecosystem of middleware implementations and ongoing effort to standardize interfaces between these with PSR-15 (HTTP Server Request Handlers) and support this goal. As such, this project only bundles a few middleware implementations that are required to match PHP's request behavior (see below) and otherwise actively encourages Third-Party Middleware implementations.

In order to use middleware request handlers, simply pass a list of all callables as defined above to the HttpServer. The following example adds a middleware request handler that adds the current time to the request as a header (Request-Time) and a final request handler that always returns a 200 OK status code without a body:

$http = new React\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) {
        return new React\Http\Message\Response(React\Http\Message\Response::STATUS_OK);
    }
);

Note how the middleware request handler and the final request handler have a very simple (and similar) interface. The only difference is that the final request handler does not receive a $next handler.

Similarly, you can use the result of the $next middleware request handler function to modify the outgoing response. Note that as per the above documentation, the $next middleware request handler may return a PSR-7 ResponseInterface directly or one wrapped in a promise for deferred resolution. In order to simplify handling both paths, you can simply wrap this in a Promise\resolve() call like this:

$http = new React\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) {
        return new React\Http\Message\Response(React\Http\Message\Response::STATUS_OK);
    }
);

Note that the $next middleware request handler may also throw an Exception (or return a rejected promise) as described above. The previous example does not catch any exceptions and would thus signal an error condition to the HttpServer. Alternatively, you can also catch any Exception to implement custom error handling logic (or logging etc.) by wrapping this in a Promise like this:

withStatus(React\Http\Message\Response::STATUS_INTERNAL_SERVER_ERROR); }); }, function (Psr\Http\Message\ServerRequestInterface $request) { if (mt_rand(0, 1) === 1) { throw new RuntimeException('Database error'); } return new React\Http\Message\Response(React\Http\Message\Response::STATUS_OK); } );">
$http = new React\Http\HttpServer(
    function (Psr\Http\Message\ServerRequestInterface $request, callable $next) {
        $promise = new React\Promise\Promise(function ($resolve) use ($next, $request) {
            $resolve($next($request));
        });
        return $promise->then(null, function (Exception $e) {
            return React\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) {
            throw new RuntimeException('Database error');
        }
        return new React\Http\Message\Response(React\Http\Message\Response::STATUS_OK);
    }
);

Third-Party Middleware

While this project does provide the means to use middleware implementations (see above), it does not aim to define how middleware implementations should look like. We realize that there's a vivid ecosystem of middleware implementations and ongoing effort to standardize interfaces between these with PSR-15 (HTTP Server Request Handlers) and support this goal. As such, this project only bundles a few middleware implementations that are required to match PHP's request behavior (see middleware implementations) and otherwise actively encourages third-party middleware implementations.

While we would love to support PSR-15 directly in react/http, we understand that this interface does not specifically target async APIs and as such does not take advantage of promises for deferred responses. The gist of this is that where PSR-15 enforces a PSR-7 ResponseInterface return value, we also accept a PromiseInterface . As such, we suggest using the external PSR-15 middleware adapter that uses on the fly monkey patching of these return values which makes using most PSR-15 middleware possible with this package without any changes required.

Other than that, you can also use the above middleware definition to create custom middleware. A non-exhaustive list of third-party middleware can be found at the middleware wiki. If you build or know a custom middleware, make sure to let the world know and feel free to add it to this list.

API

Browser

The React\Http\Browser is responsible for sending HTTP requests to your HTTP server and keeps track of pending incoming HTTP responses.

$browser = new React\Http\Browser();

This class takes two optional arguments for more advanced usage:

// constructor signature as of v1.5.0
$browser = new React\Http\Browser(?ConnectorInterface $connector = null, ?LoopInterface $loop = null);

// legacy constructor signature before v1.5.0
$browser = new React\Http\Browser(?LoopInterface $loop = null, ?ConnectorInterface $connector = null);

If you need custom connector settings (DNS resolution, TLS parameters, timeouts, proxy servers etc.), you can explicitly pass a custom instance of the ConnectorInterface:

$connector = new React\Socket\Connector(array(
    'dns' => '127.0.0.1',
    'tcp' => array(
        'bindto' => '192.168.10.1:0'
    ),
    'tls' => array(
        'verify_peer' => false,
        'verify_peer_name' => false
    )
));

$browser = new React\Http\Browser($connector);

This class takes an optional LoopInterface|null $loop parameter that can be used to pass the event loop instance to use for this object. You can use a null value here in order to use the default loop. This value SHOULD NOT be given unless you're sure you want to explicitly use a given 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.

get()

The get(string $url, array $headers = array()): PromiseInterface method can be used to send 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 also GET request client example.

post()

The post(string $url, array $headers = array(), string|ReadableStreamInterface $body = ''): PromiseInterface method can be used to send 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 also POST 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 matching Content-Length request header if the outgoing request body is a string. If you're using a streaming request body (ReadableStreamInterface), it will default to using Transfer-Encoding: chunked or you have to explicitly pass in a matching Content-Length request header like so:

post($url, array('Content-Length' => '11'), $body);">
$body = new React\Stream\ThroughStream();
Loop::addTimer(1.0, function () use ($body) {
    $body->end("hello world");
});

$browser->post($url, array('Content-Length' => '11'), $body);

head()

The head(string $url, array $headers = array()): PromiseInterface method can be used to send 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;
});

patch()

The patch(string $url, array $headers = array(), string|ReadableStreamInterface $body = ''): PromiseInterface method can be used to send 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 matching Content-Length request header if the outgoing request body is a string. If you're using a streaming request body (ReadableStreamInterface), it will default to using Transfer-Encoding: chunked or you have to explicitly pass in a matching Content-Length request header like so:

patch($url, array('Content-Length' => '11'), $body);">
$body = new React\Stream\ThroughStream();
Loop::addTimer(1.0, function () use ($body) {
    $body->end("hello world");
});

$browser->patch($url, array('Content-Length' => '11'), $body);

put()

The put(string $url, array $headers = array(), string|ReadableStreamInterface $body = ''): PromiseInterface method can be used to send 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 also PUT XML client example.

This method will automatically add a matching Content-Length request header if the outgoing request body is a string. If you're using a streaming request body (ReadableStreamInterface), it will default to using Transfer-Encoding: chunked or you have to explicitly pass in a matching Content-Length request header like so:

put($url, array('Content-Length' => '11'), $body);">
$body = new React\Stream\ThroughStream();
Loop::addTimer(1.0, function () use ($body) {
    $body->end("hello world");
});

$browser->put($url, array('Content-Length' => '11'), $body);

delete()

The delete(string $url, array $headers = array(), string|ReadableStreamInterface $body = ''): PromiseInterface method can be used to send 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;
});

request()

The request(string $method, string $url, array $headers = array(), string|ReadableStreamInterface $body = ''): PromiseInterface method can be used to send an arbitrary HTTP request.

The preferred way to send an HTTP request is by using the above request methods, for example the get() method to send an HTTP GET request.

As an alternative, if you want to use a custom HTTP request method, you can 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 matching Content-Length request header if the size of the outgoing request body is known and non-empty. For an empty request body, if will only include a Content-Length: 0 request header if the request method usually expects a request body (only applies to POST, PUT and PATCH).

If you're using a streaming request body (ReadableStreamInterface), it will default to using Transfer-Encoding: chunked or you have to explicitly pass in a matching Content-Length request header like so:

request('POST', $url, array('Content-Length' => '11'), $body);">
$body = new React\Stream\ThroughStream();
Loop::addTimer(1.0, function () use ($body) {
    $body->end("hello world");
});

$browser->request('POST', $url, array('Content-Length' => '11'), $body);

requestStreaming()

The requestStreaming(string $method, string $url, array $headers = array(), string|ReadableStreamInterface $body = ''): PromiseInterface method can be used to send 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 above request methods, for example the get() method to send an HTTP GET request. Each of these methods will buffer the whole response body in memory by default. This is easy to get started and works reasonably well for smaller responses.

In some situations, it's a better idea to use a streaming approach, where only small chunks have to be kept in memory. You can use this method to send an arbitrary HTTP request and receive a streaming response. It uses the same HTTP message API, but does not buffer the response body in memory. It only processes the response body in small chunks as data is received and forwards this data through ReactPHP'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($body instanceof Psr\Http\Message\StreamInterface);
    assert($body instanceof React\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 ReactPHP's ReadableStreamInterface and the streaming response for more details, examples and possible use-cases.

This method will automatically add a matching Content-Length request header if the size of the outgoing request body is known and non-empty. For an empty request body, if will only include a Content-Length: 0 request header if the request method usually expects a request body (only applies to POST, PUT and PATCH).

If you're using a streaming request body (ReadableStreamInterface), it will default to using Transfer-Encoding: chunked or you have to explicitly pass in a matching Content-Length request header like so:

requestStreaming('POST', $url, array('Content-Length' => '11'), $body);">
$body = new React\Stream\ThroughStream();
Loop::addTimer(1.0, function () use ($body) {
    $body->end("hello world");
});

$browser->requestStreaming('POST', $url, array('Content-Length' => '11'), $body);

withTimeout()

The withTimeout(bool|number $timeout): Browser method can be used to change 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 bool false to disable any timeouts. In this case, requests can stay pending forever:

$browser = $browser->withTimeout(false);

You can pass in a bool true to re-enable default timeout handling. This will respects PHP's default_socket_timeout setting (default 60s):

$browser = $browser->withTimeout(true);

See also timeouts for more details about timeout handling.

Notice that the Browser is an immutable object, i.e. this method actually returns a new Browser instance with the given timeout value applied.

withFollowRedirects()

The withFollowRedirects(bool|int $followRedirects): Browser method can be used to change 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 redirects is exceeded. You can pass in a 0 to reject the request for any redirects encountered:

$browser = $browser->withFollowRedirects(0);

$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
    // only non-redirected responses will now end up here
    var_dump($response->getHeaders());
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

You can pass in a bool false to disable following any redirects. In this case, requests will resolve with the redirection response instead of following the Location response header:

$browser = $browser->withFollowRedirects(false);

$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
    // any redirects will now end up here
    var_dump($response->getHeaderLine('Location'));
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

You can pass in a bool true to re-enable default redirect handling. This defaults to following a maximum of 10 redirects:

$browser = $browser->withFollowRedirects(true);

See also redirects for more details about redirect handling.

Notice that the Browser is an immutable object, i.e. this method actually returns a new Browser instance with the given redirect setting applied.

withRejectErrorResponse()

The withRejectErrorResponse(bool $obeySuccessCode): Browser method can be used to change whether non-successful HTTP response status codes (4xx and 5xx) will be rejected.

You can pass in a bool false to disable rejecting incoming responses that use a 4xx or 5xx response status code. In this case, requests will resolve 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 here
    var_dump($response->getStatusCode(), $response->getReasonPhrase());
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

You can pass in a bool true to re-enable default status code handling. This defaults to rejecting any response status codes in the 4xx or 5xx range with a ResponseException:

$browser = $browser->withRejectErrorResponse(true);

$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
    // any successful HTTP response will now end up here
    var_dump($response->getStatusCode(), $response->getReasonPhrase());
}, function (Exception $e) {
    if ($e instanceof React\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 the Browser is an immutable object, i.e. this method actually returns a new Browser instance with the given setting applied.

withBase()

The withBase(string|null $baseUrl): Browser method can be used to change the base URL used to resolve relative URLs to.

If you configure a base URL, any requests to relative URLs will be processed by first resolving this relative to the given absolute base URL. 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 a null base URL to return a new instance that does not use a base URL:

$browser = $browser->withBase(null);

Accordingly, any requests using relative URLs to a browser that does not use a base URL can not be completed and will be rejected without sending a request.

This method will throw an InvalidArgumentException if the given $baseUrl argument is not a valid URL.

Notice that the Browser is an immutable object, i.e. the withBase() method actually returns a new Browser instance with the given base URL applied.

withProtocolVersion()

The withProtocolVersion(string $protocolVersion): Browser method can be used to change the HTTP protocol version that will be used for all subsequent requests.

All the above request methods default to sending requests as HTTP/1.1. This is the preferred HTTP protocol version which also provides decent backwards-compatibility with legacy HTTP/1.0 servers. As such, there should rarely be a need to explicitly change this protocol version.

If you want to explicitly use the legacy HTTP/1.0 protocol version, you can use this method:

$browser = $browser->withProtocolVersion('1.0');

$browser->get($url)->then(…);

Notice that the Browser is an immutable object, i.e. this method actually returns a new Browser instance with the new protocol version applied.

withResponseBuffer()

The withResponseBuffer(int $maximumSize): Browser method can be used to change the maximum size for buffering a response body.

The preferred way to send an HTTP request is by using the above request methods, for example the get() method to send an HTTP GET request. Each of these methods will buffer the whole response body in memory by default. This is easy to get started and works reasonably well for smaller responses.

By default, the response body buffer will be limited to 16 MiB. If the response 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 MiB
    var_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 each pending request until its transfer is completed and it will only be freed after a pending request is fulfilled. As such, increasing this maximum buffer size to allow larger response bodies is usually not recommended. Instead, you can use the requestStreaming() method to receive responses with arbitrary sizes without buffering. Accordingly, this maximum buffer size setting has no effect on streaming responses.

Notice that the Browser is an immutable object, i.e. this method actually returns a new Browser instance with the given setting applied.

React\Http\Message

Response

The React\Http\Message\Response class can be used to represent an outgoing server response message.

Hello world!\n" );">
$response = new React\Http\Message\Response(
    React\Http\Message\Response::STATUS_OK,
    array(
        'Content-Type' => 'text/html'
    ),
    "Hello world!\n"
);

This class implements the PSR-7 ResponseInterface which in turn extends the PSR-7 MessageInterface.

On top of this, this class implements the PSR-7 Message Util StatusCodeInterface which means that most common HTTP status codes are available as class constants with the STATUS_* prefix. For instance, the 200 OK and 404 Not Found status codes can used as Response::STATUS_OK and Response::STATUS_NOT_FOUND respectively.

Internally, this implementation builds on top of an existing incoming response message and only adds required streaming support. This base class is considered an implementation detail that may change in the future.

html()

The static html(string $html): Response method can be used to create an HTML response.

$html = <<


Hello wörld!


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 a 200 OK status code and the appropriate Content-Type response header for the given HTTP source string encoded in UTF-8 (Unicode). It's generally recommended to end the given plaintext string with a trailing newline.

If you want to use a different status code or custom HTTP response headers, you can manipulate the returned response object using the provided PSR-7 methods or directly instantiate a custom HTTP response object using the Response constructor:

Error

\n

Invalid user name given.

\n" )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);">
$response = React\Http\Message\Response::html(
    "

Error

\n

Invalid user name given.

\n"
)->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
json()

The static json(mixed $data): Response method can be used to create 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 a 200 OK status code and the appropriate Content-Type response header for the given structured data encoded as a JSON text.

The given structured data will be encoded as a JSON text. Any string values in the data must be encoded in UTF-8 (Unicode). If the encoding fails, this method will throw an InvalidArgumentException.

By default, the given structured data will be encoded with the flags as shown above. This includes pretty printing (PHP 5.4+) and preserving zero fractions for float values (PHP 5.6.6+) to ease debugging. It is assumed any additional data overhead is usually compensated by using HTTP response compression.

If you want to use a different status code or custom HTTP response headers, you can manipulate the returned response object using the provided PSR-7 methods or directly instantiate a custom HTTP response object using the Response constructor:

$response = React\Http\Message\Response::json(
    ['error' => 'Invalid user name given']
)->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
plaintext()

The static plaintext(string $text): Response method can be used to create 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 a 200 OK status code and the appropriate Content-Type response header for the given plaintext string encoded in UTF-8 (Unicode). It's generally recommended to end the given plaintext string with a trailing newline.

If you want to use a different status code or custom HTTP response headers, you can manipulate the returned response object using the provided PSR-7 methods or directly instantiate a custom HTTP response object using the Response constructor:

withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);">
$response = React\Http\Message\Response::plaintext(
    "Error: Invalid user name given.\n"
)->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
xml()

The static xml(string $xml): Response method can be used to create an XML response.

Hello wörld! XML; $response = React\Http\Message\Response::xml($xml);">
$xml = <<
   


    
   
    Hello wörld!
   


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 a 200 OK status code and the appropriate Content-Type response header for the given XML source string. It's generally recommended to use UTF-8 (Unicode) and specify this as part of the leading XML declaration and to end the given XML source string with a trailing newline.

If you want to use a different status code or custom HTTP response headers, you can manipulate the returned response object using the provided PSR-7 methods or directly instantiate a custom HTTP response object using the Response constructor:

Invalid user name given. \n" )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);">
$response = React\Http\Message\Response::xml(
    "
   
    
     Invalid user name given.
    
   \n"
)->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);

ServerRequest

The React\Http\Message\ServerRequest class can be used to respresent an incoming server request message.

This class implements the PSR-7 ServerRequestInterface which extends the PSR-7 RequestInterface which in turn extends the PSR-7 MessageInterface.

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 web application reacts to certain HTTP requests.

Internally, this implementation builds on top of an existing outgoing request message and only adds required server methods. This base class is considered an implementation detail that may change in the future.

ResponseException

The React\Http\Message\ResponseException is an Exception sub-class that will be used to reject a request promise if the remote server returns a non-success status code (anything but 2xx or 3xx). You can control this behavior via the withRejectErrorResponse() method.

The getCode(): int method can be used to return the HTTP response status code.

The getResponse(): ResponseInterface method can be used to access its underlying response object.

React\Http\Middleware

StreamingRequestMiddleware

The React\Http\Middleware\StreamingRequestMiddleware can be used to process incoming requests with a streaming request body (without buffering).

This allows you to process requests of any size without buffering the request body in memory. Instead, it will represent the request body as a ReadableStreamInterface that emit chunks of incoming data as it is received:

$http = new React\Http\HttpServer(
    new React\Http\Middleware\StreamingRequestMiddleware(),
    function (Psr\Http\Message\ServerRequestInterface $request) {
        $body = $request->getBody();
        assert($body instanceof Psr\Http\Message\StreamInterface);
        assert($body instanceof React\Stream\ReadableStreamInterface);

        return new React\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(new React\Http\Message\Response(
                    React\Http\Message\Response::STATUS_OK,
                    [],
                    "Received $bytes bytes\n"
                ));
            });
        });
    }
);

See also streaming incoming request for more details.

Additionally, this middleware can be used in combination with the LimitConcurrentRequestsMiddleware and RequestBodyBufferMiddleware (see below) to explicitly configure the total number of requests that can be handled at once:

$http = new React\Http\HttpServer(
    new React\Http\Middleware\StreamingRequestMiddleware(),
    new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
    new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
    new React\Http\Middleware\RequestBodyParserMiddleware(),
    $handler
);

Internally, this class is used as a "marker" to not trigger the default request buffering behavior in the HttpServer. It does not implement any logic on its own.

LimitConcurrentRequestsMiddleware

The React\Http\Middleware\LimitConcurrentRequestsMiddleware can be used to limit how many next handlers can be executed concurrently.

If this middleware is invoked, it will check if the number of pending handlers is below the allowed limit and then simply invoke the next handler and it will return whatever the next handler returns (or throws).

If the number of pending handlers exceeds the allowed limit, the request will be queued (and its streaming body will be paused) and it will return a pending promise. Once a pending handler returns (or throws), it will pick the oldest request from this queue and invokes the next handler (and its streaming body will be resumed).

The following example shows how this middleware can be used to ensure no more than 10 handlers will be invoked at once:

$http = new React\Http\HttpServer(
    new React\Http\Middleware\LimitConcurrentRequestsMiddleware(10),
    $handler
);

Similarly, this middleware is often used in combination with the RequestBodyBufferMiddleware (see below) to limit the total number of requests that can be buffered at once:

$http = new React\Http\HttpServer(
    new React\Http\Middleware\StreamingRequestMiddleware(),
    new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
    new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
    new React\Http\Middleware\RequestBodyParserMiddleware(),
    $handler
);

More sophisticated examples include limiting the total number of requests that can be buffered at once and then ensure the actual request handler only processes one request after another without any concurrency:

$http = new React\Http\HttpServer(
    new React\Http\Middleware\StreamingRequestMiddleware(),
    new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
    new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request
    new React\Http\Middleware\RequestBodyParserMiddleware(),
    new React\Http\Middleware\LimitConcurrentRequestsMiddleware(1), // only execute 1 handler (no concurrency)
    $handler
);

RequestBodyBufferMiddleware

One of the built-in middleware is the React\Http\Middleware\RequestBodyBufferMiddleware which can 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 handler and 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 use post_max_size (default 8 MiB) from PHP's configuration. (Note that the value from your matching SAPI will be used, which is the CLI configuration in most cases.)

Any incoming request that has a request body that exceeds this limit will be accepted, but its request body will be discarded (empty request body). This is done in order to avoid having to keep an incoming request with an excessive size (for example, think of a 2 GB file upload) in memory. This allows the next middleware handler to still handle this request, but it will see an empty request body. This is similar to PHP's default behavior, where the body will not be parsed if this limit is exceeded. However, unlike PHP's default behavior, the raw request body is not available via php://input.

The RequestBodyBufferMiddleware will buffer requests with bodies of known size (i.e. with Content-Length header specified) as well as requests with bodies of unknown size (i.e. with Transfer-Encoding: chunked header).

All requests will be buffered in memory until the request body end has been reached and then call the next middleware handler with the complete, buffered request. Similarly, this will immediately invoke the next middleware handler for requests that have an empty request body (such as a simple GET request) and requests that 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 concurrent requests, up to 2000 MiB may be allocated for these buffers alone. As such, it's highly recommended to use this along with the LimitConcurrentRequestsMiddleware (see above) to limit the total number of concurrent requests.

Usage:

$http = new React\Http\HttpServer(
    new React\Http\Middleware\StreamingRequestMiddleware(),
    new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
    new React\Http\Middleware\RequestBodyBufferMiddleware(16 * 1024 * 1024), // 16 MiB
    function (Psr\Http\Message\ServerRequestInterface $request) {
        // The body from $request->getBody() is now fully available without the need to stream it 
        return new React\Http\Message\Response(React\Http\Message\Response::STATUS_OK);
    },
);

RequestBodyParserMiddleware

The React\Http\Middleware\RequestBodyParserMiddleware takes a fully buffered request body (generally from RequestBodyBufferMiddleware), and parses the form values and file uploads from the incoming HTTP request body.

This middleware handler takes care of applying values from HTTP requests that use Content-Type: application/x-www-form-urlencoded or Content-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() methods as defined by PSR-7.

Accordingly, each file upload will be represented as instance implementing the PSR-7 UploadedFileInterface. Due to its blocking nature, the moveTo() method is not available and throws a RuntimeException instead. You can use $contents = (string)$file->getStream(); to access the file contents 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 ($avatar instanceof Psr\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';
    }

    return new React\Http\Message\Response(
        React\Http\Message\Response::STATUS_OK,
        array(
            'Content-Type' => 'text/plain'
        ),
        $name . ' uploaded ' . $uploaded
    );
};

$http = new React\Http\HttpServer(
    new React\Http\Middleware\StreamingRequestMiddleware(),
    new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers
    new React\Http\Middleware\RequestBodyBufferMiddleware(16 * 1024 * 1024), // 16 MiB
    new React\Http\Middleware\RequestBodyParserMiddleware(),
    $handler
);

See also form upload server example for more details.

By default, this middleware respects the upload_max_filesize (default 2M) ini setting. Files that exceed this limit will be rejected with an UPLOAD_ERR_INI_SIZE error. You can control the maximum filesize for each individual file upload by explicitly passing the maximum filesize in bytes as the first parameter to the constructor like this:

new React\Http\Middleware\RequestBodyParserMiddleware(8 * 1024 * 1024); // 8 MiB limit per file

By default, this middleware respects the file_uploads (default 1) and max_file_uploads (default 20) 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 ignored and the getUploadedFiles() 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 explicitly passing the second parameter to the constructor like this:

new React\Http\Middleware\RequestBodyParserMiddleware(10 * 1024, 100); // 100 files with 10 KiB each

Note that this middleware handler simply parses everything that is already buffered in the request body. It is imperative that the request body is buffered by a prior middleware handler as given in the example above. This previous middleware handler is also responsible for rejecting incoming requests that exceed allowed message sizes (such as big file uploads). The RequestBodyBufferMiddleware used above simply discards excessive request bodies, resulting in an empty body. If you use this middleware without buffering first, it will try to parse an empty (streaming) body and may thus assume an empty data structure. See also RequestBodyBufferMiddleware for more details.

PHP's MAX_FILE_SIZE hidden field is respected by this middleware. Files that exceed this limit will be rejected with an UPLOAD_ERR_FORM_SIZE error.

This middleware respects the max_input_vars (default 1000) and max_input_nesting_level (default 64) ini settings.

Note that this middleware ignores the enable_post_data_reading (default 1) ini setting because it makes little sense to respect here and is left up to higher-level implementations. If you want to respect this setting, you have to check its value and effectively avoid using this middleware entirely.

Install

The recommended way to install this library is through Composer. New to Composer?

This project follows SemVer. This will install the latest supported version:

$ composer require react/http:^1.6

See also the CHANGELOG for details about version upgrades.

This project aims to run on any platform and thus does not require any PHP extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM. It's highly recommended to use the latest supported PHP version for this project.

Tests

To run the test suite, you first need to clone this repo and then install all dependencies through 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 rely on 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

License

MIT, see LICENSE file.

Comments
  • Streaming request body parsing

    Streaming request body parsing

    This PR is the follow up for #13. It started out to make multipart streaming but ended up making all bodies streaming.

    The parsers emit a post event with the key and value of a post variable and file on uploaded files found in the request. On the request object getFiles is gone due to the streaming nature of the parsers. getPost is still there but it won't have everything until the entire request has been parsed.

    Todo:

    • [x] Normal body streaming
    • [x] Multipart body streaming
    • [x] Form URL Encoded body streaming
    • [x] File object for files in the request
    • [x] Make sure all parsers behave the same
    • [x] Make the form parsers optional
    • [X] Add buffered sink like helpers for request post fields and files
    • [X] Update readme with an example
    new feature 
    opened by WyriHaximus 56
  • Roadmap to stable v1.0.0

    Roadmap to stable v1.0.0

    Let's face it, this project is currently beta, but has been used in production for years :shipit:

    We're currently following a v0.X.Y release scheme (http://sentimentalversioning.org/).

    We should finally make this stable and fully adhere to SemVer and release a stable v1.0.0.

    To a large extend, a stable v1.0.0 helps making BC breaks more explicit and thus the whole project more reliable from a consumer perspective. This project is actively maintained and has received some major updates in the last weeks and has some major updates planned in the next weeks. Given our current versioning scheme, we'd like to ensure all anticipated BC breaks will be merged before the planned v1.0.0 release.

    As such, I've set up a roadmap that enlists only the major changes for each version among with planned release dates towards a stable v1.0.0 release:

    v0.4.4 :white_check_mark:

    • Released 2017-02-13
    • Headers PSR-7 and case-insensitive
    • Version constraints
    • Stream empty data events and back-pressure

    v0.5.0 :white_check_mark:

    • Released 2017-02-16
    • Request PSR-7 methods
    • Reduce public API
    • Socket component v0.5 and secure HTTPS

    v0.6.0 :white_check_mark:

    • Released 2017-03-09
    • Chunked transfer encoding and Content-Length
    • Stream data, end and close behavior
    • Stream v0.5 API
    • HTTP/1.1 and HTTP/1.0
    • No longer considered beta quality

    v0.7.0 :white_check_mark:

    • Released 2017-05-29
    • Streaming PSR-7 support
    • Request-In-Response-Out callbacks

    v0.8.0 :white_check_mark:

    • Released 2017-12-12
    • Full PSR-7 support
    • Form submissions, including POST fields and file uploads
    • Middleware request handler arrays

    v1.0.0 :white_check_mark:

    • Released 2020-07-11
    • New HTTP client
    • Clean up Server APIs
    • LTS

    This ticket aims to serve as a basic overview and does not contain every single change. Please also see the milestone links and the CHANGELOG for more details.

    Obviously, this roadmap is subject to change and I'll try to keep it updated as we progress. In order to avoid cluttering this, please keep discussion in this ticket to a minimum and consider reaching out to us through new tickets or Twitter etc.

    maintenance help wanted 
    opened by clue 27
  • Multipart handling

    Multipart handling

    Hello,

    I added basic support for multipart for files and POST requests

    I also wrote the unit tests for this part and improved the existing ones.

    One thing that could be done better is that now, it waits for the whole body before it parses it, we could implement a system where it feeds the content to the parser and the files are created on the fly and we don't use the whole memory for file uploads.

    new feature 
    opened by onigoetz 21
  • HTTP client: Preserve request method and body for `307 Temporary Redirect` and `308 Permanent Redirect`

    HTTP client: Preserve request method and body for `307 Temporary Redirect` and `308 Permanent Redirect`

    Should close #409 303 See Other should change the method to GET all other redirects should leave it unchanged (Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages)

    I think there are some specialties for 300 and 304 which I ignored for now, but I could look further into it if you wish.

    new feature HTTP/1.1 
    opened by dinooo13 20
  • RequestBodyBufferMiddleware: 411 No Content Length for GET requests?

    RequestBodyBufferMiddleware: 411 No Content Length for GET requests?

    I'm doing some testing with Guzzle as client and I'm running into unexpected behaviour- 411 instead of qualified response.

    In https://github.com/reactphp/http/blob/abdf36ee15e3e17e292943ffd4a85d13aa01284c/src/Middleware/RequestBodyBufferMiddleware.php#L35 an HTTP 411 is returned when Content-length is not set. According to https://stackoverflow.com/questions/8540931/what-is-the-correct-content-length-to-set-for-a-get-request this requirement should not be necessary.

    A message-body MUST NOT be included in a request if the specification of the request method (section 5.1.1) does not allow sending an entity-body in requests.

    IMHO that would mean that the 411 should not be returned for GET requests and instead 0 assumed.

    cc @WyriHaximus

    question help wanted 
    opened by andig 20
  • Chrome only: Emits additional `error` events for HTTPS only (Unable to complete SSL/TLS handshake)

    Chrome only: Emits additional `error` events for HTTPS only (Unable to complete SSL/TLS handshake)

    When i run the 02-hello-world-https.php example, it does work (seeing "hello world" in a browser).

    But I am also seeing this error:

    php examples/02-hello-world-https.php 
    Listening on https://0.0.0.0:8443
    exception 'UnexpectedValueException' with message 'Unable to complete SSL/TLS handshake: ' in /Users/*********/Projects/http/vendor/react/socket/src/StreamEncryption.php:112
    Stack trace:
    #0 /Users/*********/Projects/http/vendor/react/socket/src/StreamEncryption.php(78): React\Socket\StreamEncryption->toggleCrypto(Resource id #28, Object(React\Promise\Deferred), true)
    #1 [internal function]: React\Socket\StreamEncryption->React\Socket\{closure}(Resource id #28, Object(React\EventLoop\StreamSelectLoop))
    #2 /Users/*********/Projects/http/vendor/react/event-loop/src/StreamSelectLoop.php(232): call_user_func(Object(Closure), Resource id #28, Object(React\EventLoop\StreamSelectLoop))
    #3 /Users/*********/Projects/http/vendor/react/event-loop/src/StreamSelectLoop.php(201): React\EventLoop\StreamSelectLoop->waitForStreamActivity(NULL)
    #4 /Users/*********/Projects/http/examples/02-hello-world-https.php(29): React\EventLoop\StreamSelectLoop->run()
    #5 {main}exception 'UnexpectedValueException' with message 'Unable to complete SSL/TLS handshake: ' in /Users/*********/Projects/http/vendor/react/socket/src/StreamEncryption.php:112
    Stack trace:
    #0 /Users/*********/Projects/http/vendor/react/socket/src/StreamEncryption.php(78): React\Socket\StreamEncryption->toggleCrypto(Resource id #55, Object(React\Promise\Deferred), true)
    #1 [internal function]: React\Socket\StreamEncryption->React\Socket\{closure}(Resource id #55, Object(React\EventLoop\StreamSelectLoop))
    #2 /Users/*********/Projects/http/vendor/react/event-loop/src/StreamSelectLoop.php(232): call_user_func(Object(Closure), Resource id #55, Object(React\EventLoop\StreamSelectLoop))
    #3 /Users/*********/Projects/http/vendor/react/event-loop/src/StreamSelectLoop.php(201): React\EventLoop\StreamSelectLoop->waitForStreamActivity(NULL)
    #4 /Users/*********/Projects/http/examples/02-hello-world-https.php(29): React\EventLoop\StreamSelectLoop->run()
    #5 {main}
    

    Also I think the response time might be low side.

    I did have to include a cafile in the example code

    $loop = Factory::create();
    $socket = new Server(isset($argv[1]) ? $argv[1] : '0.0.0.0:8443', $loop);
    $socket = new SecureServer($socket, $loop, array(
        'local_cert' => $argv[2]. '/bin/combined.pem',
        'cafile' => $argv[2]. '/bin/intermediate.pem',
    ));
    
    question help wanted 
    opened by bassim 19
  • Server: Detect when response has been sent

    Server: Detect when response has been sent

    Currently, I can't seem to find a way to check when a response has been sent. Once a response is returned via the Handler Callback, there is no way of knowing what happened to it, or check when it has been sent completely.

    Use case: Checking that all requests have been handled and sent to the client before shutting down the server.

    Personally, I am trying to close the server once a request has been handled, much like in this StackOverflow question, however, the suggested solution here (closing the socket connection) does not seem to work properly, as it only closes the socket connection after another request has been received.

    question 
    opened by CupOfTea696 17
  • Add the ability to define a custom request header size if you need bigger headers

    Add the ability to define a custom request header size if you need bigger headers

    This PR is a follow up of the discussion in #214. This allows us to configure the server with a max_header_size and pass it down to the RequestHeaderParser.

    Question which I asked my self during implementation:

    • Do we want to fallback silently on invalid parameter usage?
    • Do we want to fail early on invalid parameter usage (i.e. fail on boot before the first request arrives)
    maintenance easy pick help wanted 
    opened by christoph-kluge 17
  • Change maxSize on RequestHeaderParser

    Change maxSize on RequestHeaderParser

    Hello! You make a wonderful project, which we use in production. But we have a moment that has to be constantly maintained and remembered about it. This is the maximum size of the buffer, when parsing the request. Now it's 4096 characters, which is not very convenient. Is it possible to make this parameter configurable or get rid of it? React\Http\RequestHeaderParser, line 18 on v0.7.4

    new feature easy pick help wanted 
    opened by RuslanHimikEvchev 16
  • Middleware

    Middleware

    During DPC @clue and I had a long chat about how to handle body parsers without loosing the current flexibility. We came to the conclusion that middlewares are the way to go. The following proposal is inspired by the WIP PSR-15 but doesn't implement it (I'll get back on that later in this PR).


    Suggested reading order

    This PR contains a lot of changes, how ever most of those changes are in examples and tests. Here is a recommended reading order:

    1. README.md
    2. MiddlewareInterface.php
    3. MiddlewareStackInterface.php
    4. MiddlewareStack.php
    5. Server.php
    6. Middleware/Callback.php
    7. Middleware/LimitHandlers.php
    8. Middleware/Buffer.php
    9. The rest

    Major changes

    Where 0.7 only requires you to pass a callable to handle incoming requests, this PR proposes to use middleware for that. (While still leaving the callable way intact by magically wrapping it.) One way to setup middleware by passing an array with middlewares implementing MiddlewareInterface:

    $server = new Server([
        new Buffer(),
        new Callback(function ($request) {
            return new Response(200);
        }),
    ]);
    

    Or by passing in a concrete implementation of MiddlewareStackInterface:

    $server = new Server(new MiddlewareStack([
        new Buffer(),
        new Callback(function (ServerRequestInterface $request) {
            return new Response(200);
        }),
    ]));
    

    The latter is done automatically when doing the former internally in the server. But you can create your own middleware stack implementation and use that instead.

    Included middlewares

    This PR includes three middlewares Buffer, Callback, and LimitHandlers.

    Buffer

    Buffers the request body until it is fully in or when it reach the given size:

    $middlewares = [
        new Buffer(),
    ];
    

    Callback

    Handles request just like 0.7 using a callback:

    $middlewares = [
        new Callback(function (ServerRequestInterface $request) {
            return new Response(200);
        }),
    ];
    

    LimitHandlers

    Limits the number of concurrent request being handled at the same time:

    $middlewares = [
        new LimitHandlers(10),
    ];
    

    Body parsing

    Currently one of our main issues with body parsing is the memory usage. But with the LimitHandlers we can pause the incoming request stream until we're ready handle it thus limiting the amount of memory needed to handle request. And with Buffer we can first stream the body in, parse it with for example BodyParser, and then hand it to the next middleware on the stack.

    PSR-15

    While the middleware implementation in this PR is inspired by the work done for the coming PSR-15 it doesn't implement it due to a small but major different. This implementation relies on promises where PSR-15 always assumes a response to be returned. An adapter for PSR-15 is beyond the scope of this PR but not for a Friends of ReactPHP packages and thus I've created for/http-middleware-psr15-adapter that utilizes RecoilPHP and on the fly PHP parsing and rewriting of PSR-15 middlewares.

    With that package you can wrap the adapter around a PSR-15 as follows:

    $middlewares = [
        new PSR15Middleware(
            $loop, 
            'Namespace\MiddlewareClassname', 
            [/** Constructor arguments */]
        ),
    ];
    

    *Note: I've added last section to show how easy it can be to add PSR-15 middlewares, with the rewriting there is no guarantee for success

    new feature BC break help wanted 
    opened by WyriHaximus 15
  • Reactphp http server benchmark

    Reactphp http server benchmark

    Hi all!

    Try to make benchmark for react http server.

    $loop = React\EventLoop\Factory::create();
    
    $server = new React\Http\Server(
        function (Psr\Http\Message\ServerRequestInterface $request) {
            return new React\Http\Response(
                200,
                array('Content-Type' => 'text/plain'),
                "test\n"
            );
        }
    );
    
    $socket = new React\Socket\Server(1337, $loop);
    $server->listen($socket);
    
    $loop->run();
    

    Then run apache benchmark test:

    ab -c 10 -n 1000000 http://site.ll:1337/
    This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
    
    Concurrency Level:      10
    Time taken for tests:   377.910 seconds
    Complete requests:      1000000
    Failed requests:        0
    Total transferred:      133000000 bytes
    HTML transferred:       5000000 bytes
    Requests per second:    2646.13 [#/sec] (mean)
    Time per request:       3.779 [ms] (mean)
    Time per request:       0.378 [ms] (mean, across all concurrent requests)
    Transfer rate:          343.69 [Kbytes/sec] received
    
    Connection Times (ms)
                  min  mean[+/-sd] median   max
    Connect:        0    0   0.0      0       5
    Processing:     0    4   0.4      4      18
    Waiting:        0    4   0.4      4      18
    Total:          1    4   0.4      4      18
    
    Percentage of the requests served within a certain time (ms)
      50%      4
      66%      4
      75%      4
      80%      4
      90%      4
      95%      4
      98%      5
      99%      5
     100%     18 (longest request)
    

    Wrk benchmark:

    wrk -t1 -c1000 -d60s http://127.0.0.1:1337/
    Running 1m test @ http://127.0.0.1:1337/
      1 threads and 1000 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency   162.45ms  246.37ms   1.61s    82.58%
        Req/Sec     1.46k     1.18k    3.08k    52.57%
      53882 requests in 1.00m, 7.81MB read
      Socket errors: connect 81, read 3, write 0, timeout 45
    Requests/sec:    896.56
    Transfer/sec:    133.08KB
    
    wrk -t10 -c1000 -d60s http://127.0.0.1:1337/
    Running 1m test @ http://127.0.0.1:1337/
      10 threads and 1000 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    13.56ms   32.66ms   1.67s    99.48%
        Req/Sec   294.90    205.06     1.68k    66.34%
      66713 requests in 1.00m, 9.67MB read
      Socket errors: connect 268, read 5, write 0, timeout 30
    Requests/sec:   1110.09
    Transfer/sec:    164.78KB
    

    What I doing wrong, why result so bad:

    Time taken for tests:   377.910 seconds
    Requests per second:    2646.13 [#/sec] (mean)
    
    opened by Shkarbatov 14
  • Fix not respecting unlimited `post_max_size`

    Fix not respecting unlimited `post_max_size`

    Currently when post_max_size is set to 0 it is not evaluated as "unlimited", but rather as literally zero, so not even a byte is allowed.

    In all modern versions of PHP it should mean "unlimited", see https://www.php.net/manual/en/ini.core.php#ini.post-max-size . I see that this library has dependency on 5.3.0 rather then the 5.3.2, where this was introduced, so it is technically debatable, but since this was introduced in PHP patch versions I think it should not be an issue (or the dependency can perhaps be raised by the two patch versions).

    Also I think this library already adopts this interpretation since when debugging this issue I have seen for example https://github.com/reactphp/http/pull/365. That is why this leads me to believe that it is indeed a bug and not intentional.

    This is currently not covered by any tests and since it reads a global ini value I do not think that without changing the interface of the constructor (at least meaning) it is possible to write any nice unit tests. But please let me know if you have an idea how to achieve that.

    bug 
    opened by VasekPurchart 4
  • Set up PHPStan on GitHub Actions

    Set up PHPStan on GitHub Actions

    This PR sets up PHPStan to run on GitHub Actions, as discussed in discussions#469.

    Overview

    • [x] Sets up PHPStan to run on GitHub Actions on PHP 8.1 only
    • [x] Configures PHPStan to run the analysis on the examples, src and tests folders
    • [x] Generates the baseline so that static analysis passes immediately

    Baseline

    Because this PR aims to set up PHPStan and not address the errors it reports, I've generated a baseline to make the pipeline succeed. We'll then be able to incrementally fix the problems in future PRs.

    opened by nhedger 1
  • Testsuite improvements

    Testsuite improvements

    The changes introduced in this PR simplify the tested classes' creation. This will pave the path for a significantly small change set for #425 and upcoming PR's that will add full HTTP 1.1, and 2 in the future that deal with connection reuse. And as such are bound to introduce changes to the affected internal classes.

    On it's own these changes might not look like they bring something new to the project, but they are aimed at our developer experience.

    maintenance easy pick 
    opened by WyriHaximus 2
  • HTTP client: Support persistent connections (aka HTTP/1.1 Keep-Alive)

    HTTP client: Support persistent connections (aka HTTP/1.1 Keep-Alive)

    We should support persistent connections (aka HTTP/1.1 Keep-Alive) also for the client side. This suggests major performance improvements, especially when sending a lot of requests to the same remote host via HTTPS. I've implemented keep-alive support for the server side via #405, so we should apply similar logic also for the client side:

    Once a complete response is received, we should put the now idle connection into a list of idle connections. If a new outgoing request is to be sent, check if one idle connection to this destination already exists. After a very short grace period (defaults to a millisecond or so, possibly configurable), close the connection if no new outgoing request is sent.

    This way, consumers of this library do not have to take care of this feature. Also, by using a very short grace period, the loop does not keep running noticeably longer than without this feature. This doesn't matter for long running scripts, but it's important for short running scripts to not appear to be "blocked".

    The implementation sounds easy enough, but unfortunately requires a major refactoring first to give the Sender class more control over reusing connections. I've started looking into this a while ago and have created some working prototypes, but full spec compliance turns out to be surprisingly complex. In particular, we may have to look into retrying requests over a new connection if the server closes the connection between requests.

    We welcome contributions, reach out if you want to support this project :+1:

    Originally posted by @clue in https://github.com/reactphp/http/issues/39#issuecomment-657278857

    new feature HTTP/1.1 help wanted HTTP/1.0 
    opened by clue 3
  • HTTP client: Support for handling cookies

    HTTP client: Support for handling cookies

    While I was trying to port some blocking libcURL code to ReactPHP I found there is no support for handling cookies when using the HTTP client. Inspired by other clients (especially cURL) I propose that the following features should be added:

    • compliance with HTTP State Management Mechanism (expiration, send the cookie only to the corresponding domain, etc.)
    • cookie support can be enabled/disabled
    • cookies can be added/removed manually
    • serialization/deserialization to/from a format like Netscape Cookie Jar (ideally this should be decoupled so other formats could be easily added)

    As suggested by @WyriHaximus, storing the cookie jar could be done with react/cache which I find totally ok. Saving and loading to/from files could be implemented by the user or we could build something on top of ArrayCache.

    After we settle the details of this, I would be happy to implement it :D

    new feature 
    opened by JustBeYou 3
Releases(v1.8.0)
  • v1.8.0(Sep 29, 2022)

    • Feature: Support for default request headers. (#461 by @51imyy)

      $browser = new React\Http\Browser();
      $browser = $browser->withHeader('User-Agent', 'ACME');
      
      $browser->get($url)->then(…);
      
    • Feature: Forward compatibility with upcoming Promise v3. (#460 by @clue)

    Source code(tar.gz)
    Source code(zip)
  • v1.7.0(Aug 23, 2022)

    This is a SECURITY and feature release for the 1.x series of ReactPHP's HTTP component.

    • Security fix: This release fixes a medium severity security issue in ReactPHP's HTTP server component that affects all versions between v0.7.0 and v1.6.0. All users are encouraged to upgrade immediately. Special thanks to Marco Squarcina (TU Wien) for reporting this and working with us to coordinate this release. (CVE-2022-36032 reported by @lavish and fixed by @clue)

    • Feature: Improve HTTP server performance by ~20%, reuse syscall values for clock time and socket addresses. (#457 and #467 by @clue)

    • Feature: Full PHP 8.2+ compatibility, refactor internal Transaction to avoid assigning dynamic properties. (#459 by @clue and #466 by @WyriHaximus)

    • Feature / Fix: Allow explicit Content-Length response header on HEAD requests. (#444 by @mrsimonbennett)

    • Minor documentation improvements. (#452 by @clue, #458 by @nhedger, #448 by @jorrit and #446 by @SimonFrings)

    • Improve test suite, update to use new reactphp/async package instead of clue/reactphp-block, skip memory tests when lowering memory limit fails and fix legacy HHVM build. (#464 and #440 by @clue and #450 by @SimonFrings)

    Source code(tar.gz)
    Source code(zip)
  • v1.6.0(Feb 3, 2022)

    • Feature: Add factory methods for common HTML/JSON/plaintext/XML response types. (#439 by @clue)

      $response = React\Http\Response\html("<h1>Hello wörld!</h1>\n");
      $response = React\Http\Response\json(['message' => 'Hello wörld!']);
      $response = React\Http\Response\plaintext("Hello wörld!\n");
      $response = React\Http\Response\xml("<message>Hello wörld!</message>\n");
      
    • Feature: Expose all status code constants via Response class. (#432 by @clue)

      $response = new React\Http\Message\Response(
          React\Http\Message\Response::STATUS_OK, // 200 OK
          …
      );
      $response = new React\Http\Message\Response(
          React\Http\Message\Response::STATUS_NOT_FOUND, // 404 Not Found
          …
      );
      
    • Feature: Full support for PHP 8.1 release. (#433 by @SimonFrings and #434 by @clue)

    • Feature / Fix: Improve protocol handling for HTTP responses with no body. (#429 and #430 by @clue)

    • Internal refactoring and internal improvements for handling requests and responses. (#422 by @WyriHaximus and #431 by @clue)

    • Improve documentation, update proxy examples, include error reporting in examples. (#420, #424, #426, and #427 by @clue)

    • Update test suite to use default loop. (#438 by @clue)

    Source code(tar.gz)
    Source code(zip)
  • v1.5.0(Aug 4, 2021)

    • Feature: Update Browser signature to take optional $connector as first argument and to match new Socket API without nullable loop arguments. (#418 and #419 by @clue)

      // unchanged
      $browser = new React\Http\Browser();
      
      // deprecated
      $browser = new React\Http\Browser(null, $connector);
      $browser = new React\Http\Browser($loop, $connector);
      
      // new
      $browser = new React\Http\Browser($connector);
      $browser = new React\Http\Browser($connector, $loop);
      
    • Feature: Rename Server to HttpServer to avoid class name collisions and to avoid any ambiguities with regards to the new SocketServer API. (#417 and #419 by @clue)

      // deprecated
      $server = new React\Http\Server($handler);
      $server->listen(new React\Socket\Server(8080));
      
      // new
      $http = new React\Http\HttpServer($handler);
      $http->listen(new React\Socket\SocketServer('127.0.0.1:8080'));
      
    Source code(tar.gz)
    Source code(zip)
  • v1.4.0(Jul 11, 2021)

    A major new feature release, see release announcement.

    • Feature: Simplify usage by supporting new default loop. (#410 by @clue)

      // old (still supported)
      $browser = new React\Http\Browser($loop);
      $server = new React\Http\Server($loop, $handler);
      
      // new (using default loop)
      $browser = new React\Http\Browser();
      $server = new React\Http\Server($handler);
      
    Source code(tar.gz)
    Source code(zip)
  • v1.3.0(Apr 11, 2021)

    • Feature: Support persistent connections (Connection: keep-alive). (#405 by @clue)

      This shows a noticeable performance improvement especially when benchmarking using persistent connections (which is the default pretty much everywhere). Together with other changes in this release, this improves benchmarking performance by around 100%.

    • Feature: Require Host request header for HTTP/1.1 requests. (#404 by @clue)

    • Minor documentation improvements. (#398 by @fritz-gerneth and #399 and #400 by @pavog)

    • Improve test suite, use GitHub actions for continuous integration (CI). (#402 by @SimonFrings)

    Source code(tar.gz)
    Source code(zip)
  • v1.2.0(Dec 4, 2020)

    • Feature: Keep request body in memory also after consuming request body. (#395 by @clue)

      This means consumers can now always access the complete request body as detailed in the documentation. This allows building custom parsers and more advanced processing models without having to mess with the default parsers.

    Source code(tar.gz)
    Source code(zip)
  • v1.1.0(Sep 11, 2020)

    • Feature: Support upcoming PHP 8 release, update to reactphp/socket v1.6 and adjust type checks for invalid chunk headers. (#391 by @clue)

    • Feature: Consistently resolve base URL according to HTTP specs. (#379 by @clue)

    • Feature / Fix: Expose Transfer-Encoding: chunked response header and fix chunked responses for HEAD requests. (#381 by @clue)

    • Internal refactoring to remove unneeded MessageFactory and Response classes. (#380 and #389 by @clue)

    • Minor documentation improvements and improve test suite, update to support PHPUnit 9.3. (#385 by @clue and #393 by @SimonFrings)

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0(Jul 11, 2020)

    A major new feature release, see release announcement.

    • First stable LTS release, now following SemVer. We'd like to emphasize that this component is production ready and battle-tested. We plan to support all long-term support (LTS) releases for at least 24 months, so you have a rock-solid foundation to build on top of.

    This update involves some major new features and a number of BC breaks due to some necessary API cleanup. We've tried hard to avoid BC breaks where possible and minimize impact otherwise. We expect that most consumers of this package will be affected by BC breaks, but updating should take no longer than a few minutes. See below for more details:

    • Feature: Add async HTTP client implementation. (#368 by @clue)

      $browser = new React\Http\Browser($loop);
      $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
          echo $response->getBody();
      });
      

      The code has been imported as-is from clue/reactphp-buzz v2.9.0, with only minor changes to the namespace and we otherwise leave all the existing APIs unchanged. Upgrading from clue/reactphp-buzz v2.9.0 to this release should be a matter of updating some namespace references only:

      // old
      $browser = new Clue\React\Buzz\Browser($loop);
      
      // new
      $browser = new React\Http\Browser($loop);
      
    • Feature / BC break: Add LoopInterface as required first constructor argument to Server and change Server to accept variadic middleware handlers instead of array. (#361 and #362 by @WyriHaximus)

      // old
      $server = new React\Http\Server($handler);
      $server = new React\Http\Server([$middleware, $handler]);
      
      // new
      $server = new React\Http\Server($loop, $handler);
      $server = new React\Http\Server($loop, $middleware, $handler);
      
    • Feature / BC break: Move Response class to React\Http\Message\Response and expose ServerRequest class to React\Http\Message\ServerRequest. (#370 by @clue)

      // old
      $response = new React\Http\Response(200, [], 'Hello!');
      
      // new
      $response = new React\Http\Message\Response(200, [], 'Hello!');
      
    • Feature / BC break: Add StreamingRequestMiddleware to stream incoming requests, mark StreamingServer as internal. (#367 by @clue)

      // old: advanced StreamingServer is now internal only
      $server = new React\Http\StreamingServer($handler);
      
      // new: use StreamingRequestMiddleware instead of StreamingServer
      $server = new React\Http\Server(
           $loop,
           new React\Http\Middleware\StreamingRequestMiddleware(),
           $handler
      );
      
    • Feature / BC break: Improve default concurrency to 1024 requests and cap default request buffer at 64K. (#371 by @clue)

      This improves default concurrency to 1024 requests and caps the default request buffer at 64K. The previous defaults resulted in just 4 concurrent requests with a request buffer of 8M. See Server for details on how to override these defaults.

    • Feature: Expose ReactPHP in User-Agent client-side request header and in Server server-side response header. (#374 by @clue)

    • Mark all classes as final to discourage inheriting from it. (#373 by @WyriHaximus)

    • Improve documentation and use fully-qualified class names throughout the documentation and add ReactPHP core team as authors to composer.json and license file. (#366 and #369 by @WyriHaximus and #375 by @clue)

    • Improve test suite and support skipping all online tests with --exclude-group internet. (#372 by @clue)

    Source code(tar.gz)
    Source code(zip)
  • v0.8.7(Jul 5, 2020)

    • Fix: Fix parsing multipart request body with quoted header parameters (dot net). (#363 by @ebimmel)

    • Fix: Fix calculating concurrency when post_max_size ini is unlimited. (#365 by @clue)

    • Improve test suite to run tests on PHPUnit 9 and clean up test suite. (#364 by @SimonFrings)

    Source code(tar.gz)
    Source code(zip)
  • v0.8.6(Jan 12, 2020)

    • Fix: Fix parsing Cookie request header with comma in its values. (#352 by @fiskie)

    • Fix: Avoid unneeded warning when decoding invalid data on PHP 7.4. (#357 by @WyriHaximus)

    • Add .gitattributes to exclude dev files from exports. (#353 by @reedy)

    Source code(tar.gz)
    Source code(zip)
  • v0.8.5(Oct 29, 2019)

    • Internal refactorings and optimizations to improve request parsing performance. Benchmarks suggest number of requests/s improved by ~30% for common GET requests. (#345, #346, #349 and #350 by @clue)

    • Add documentation and example for JSON/XML request body and improve documentation for concurrency and streaming requests and for error handling. (#341 and #342 by @clue)

    Source code(tar.gz)
    Source code(zip)
  • v0.8.4(Jan 16, 2019)

    • Improvement: Internal refactoring to simplify response header logic. (#321 by @clue)

    • Improvement: Assign Content-Length response header automatically only when size is known. (#329 by @clue)

    • Improvement: Import global functions for better performance. (#330 by @WyriHaximus)

    Source code(tar.gz)
    Source code(zip)
  • v0.8.3(Apr 11, 2018)

    • Feature: Do not pause connection stream to detect closed connections immediately. (#315 by @clue)

    • Feature: Keep incoming Transfer-Encoding: chunked request header. (#316 by @clue)

    • Feature: Reject invalid requests that contain both Content-Length and Transfer-Encoding request headers. (#318 by @clue)

    • Minor internal refactoring to simplify connection close logic after sending response. (#317 by @clue)

    Source code(tar.gz)
    Source code(zip)
  • v0.8.2(Apr 6, 2018)

    • Fix: Do not pass $next handler to final request handler. (#308 by @clue)

    • Fix: Fix awaiting queued handlers when cancelling a queued handler. (#313 by @clue)

    • Fix: Fix Server to skip SERVER_ADDR params for Unix domain sockets (UDS). (#307 by @clue)

    • Documentation for PSR-15 middleware and minor documentation improvements. (#314 by @clue and #297, #298 and #310 by @seregazhuk)

    • Minor code improvements and micro optimizations. (#301 by @seregazhuk and #305 by @kalessil)

    Source code(tar.gz)
    Source code(zip)
  • v0.8.1(Jan 5, 2018)

    • Major request handler performance improvement. Benchmarks suggest number of requests/s improved by more than 50% for common GET requests! We now avoid queuing, buffering and wrapping incoming requests in promises when we're below limits and instead can directly process common requests. (#291, #292, #293, #294 and #296 by @clue)

    • Fix: Fix concurrent invoking next middleware request handlers (#293 by @clue)

    • Small code improvements (#286 by @seregazhuk)

    • Improve test suite to be less fragile when using ext-event and fix test suite forward compatibility with upcoming EventLoop releases (#288 and #290 by @clue)

    Source code(tar.gz)
    Source code(zip)
  • v0.8.0(Dec 12, 2017)

    • Feature / BC break: Add new Server facade that buffers and parses incoming HTTP requests. This provides full PSR-7 compatibility, including support for form submissions with POST fields and file uploads. The old Server has been renamed to StreamingServer for advanced usage and is used internally. (#266, #271, #281, #282, #283 and #284 by @WyriHaximus and @clue)

      // old: handle incomplete/streaming requests
      $server = new Server($handler);
      
      // new: handle complete, buffered and parsed requests
      // new: full PSR-7 support, including POST fields and file uploads
      $server = new Server($handler);
      
      // new: handle incomplete/streaming requests
      $server = new StreamingServer($handler);
      

      While this is technically a small BC break, this should in fact not break most consuming code. If you rely on the old request streaming, you can explicitly use the advanced StreamingServer to restore old behavior.

    • Feature: Add support for middleware request handler arrays (#215, #228, #229, #236, #237, #238, #246, #247, #277, #279 and #285 by @WyriHaximus, @clue and @jsor)

      // new: middleware request handler arrays
      $server = new Server(array(
          function (ServerRequestInterface $request, callable $next) {
              $request = $request->withHeader('Processed', time());
              return $next($request);
          },
          function (ServerRequestInterface $request) {
              return new Response();
          }
      ));
      
    • Feature: Add support for limiting how many next request handlers can be executed concurrently (LimitConcurrentRequestsMiddleware) (#272 by @clue and @WyriHaximus)

      // new: explicitly limit concurrency
      $server = new Server(array(
          new LimitConcurrentRequestsMiddleware(10),
          $handler
      ));
      
    • Feature: Add support for buffering the incoming request body (RequestBodyBufferMiddleware). This feature mimics PHP's default behavior and respects its post_max_size ini setting by default and allows explicit configuration. (#216, #224, #263, #276 and #278 by @WyriHaximus and #235 by @andig)

      // new: buffer up to 10 requests with 8 MiB each
      $server = new StreamingServer(array(
          new LimitConcurrentRequestsMiddleware(10),
          new RequestBodyBufferMiddleware('8M'),
          $handler
      ));
      
    • Feature: Add support for parsing form submissions with POST fields and file uploads (RequestBodyParserMiddleware). This feature mimics PHP's default behavior and respects its ini settings and MAX_FILE_SIZE POST fields by default and allows explicit configuration. (#220, #226, #252, #261, #264, #265, #267, #268, #274 by @WyriHaximus and @clue)

      // new: buffer up to 10 requests with 8 MiB each
      // and limit to 4 uploads with 2 MiB each
      $server = new StreamingServer(array(
          new LimitConcurrentRequestsMiddleware(10),
          new RequestBodyBufferMiddleware('8M'),
          new RequestBodyParserMiddleware('2M', 4)
          $handler
      ));
      
    • Feature: Update Socket to work around sending secure HTTPS responses with PHP < 7.1.4 (#244 by @clue)

    • Feature: Support sending same response header multiple times (e.g. Set-Cookie) (#248 by @clue)

    • Feature: Raise maximum request header size to 8k to match common implementations (#253 by @clue)

    • Improve test suite by adding forward compatibility with PHPUnit 6, test against PHP 7.1 and PHP 7.2 and refactor and remove risky and duplicate tests. (#243, #269 and #270 by @carusogabriel and #249 by @clue)

    • Minor code refactoring to move internal classes to React\Http\Io namespace and clean up minor code and documentation issues (#251 by @clue, #227 by @kalessil, #240 by @christoph-kluge, #230 by @jsor and #280 by @andig)

    Source code(tar.gz)
    Source code(zip)
  • v0.7.4(Aug 16, 2017)

  • v0.7.3(Aug 14, 2017)

    • Feature: Support Throwable when setting previous exception from server callback (#155 by @jsor)

    • Fix: Fixed URI parsing for origin-form requests that contain scheme separator such as /path?param=http://example.com. (#209 by @aaronbonneau)

    • Improve test suite by locking Travis distro so new defaults will not break the build (#211 by @clue)

    Source code(tar.gz)
    Source code(zip)
  • v0.7.2(Jul 4, 2017)

    • Fix: Stricter check for invalid request-line in HTTP requests (#206 by @clue)

    • Refactor to use HTTP response reason phrases from response object (#205 by @clue)

    Source code(tar.gz)
    Source code(zip)
  • v0.7.1(Jun 17, 2017)

    • Fix: Fix parsing CONNECT request without Host header (#201 by @clue)

    • Internal preparation for future PSR-7 UploadedFileInterface (#199 by @WyriHaximus)

    Source code(tar.gz)
    Source code(zip)
  • v0.7.0(May 29, 2017)

    • Feature / BC break: Use PSR-7 (http-message) standard and Request-In-Response-Out-style request handler callback. Pass standard PSR-7 ServerRequestInterface and expect any standard PSR-7 ResponseInterface in return for the request handler callback. (#146 and #152 and #170 by @legionth)

      // old
      $app = function (Request $request, Response $response) {
          $response->writeHead(200, array('Content-Type' => 'text/plain'));
          $response->end("Hello world!\n");
      };
      
      // new
      $app = function (ServerRequestInterface $request) {
          return new Response(
              200,
              array('Content-Type' => 'text/plain'),
              "Hello world!\n"
          );
      };
      

      A Content-Length header will automatically be included if the size can be determined from the response body. (#164 by @maciejmrozinski)

      The request handler callback will automatically make sure that responses to HEAD requests and certain status codes, such as 204 (No Content), never contain a response body. (#156 by @clue)

      The intermediary 100 Continue response will automatically be sent if demanded by a HTTP/1.1 client. (#144 by @legionth)

      The request handler callback can now return a standard Promise if processing the request needs some time, such as when querying a database. Similarly, the request handler may return a streaming response if the response body comes from a ReadableStreamInterface or its size is unknown in advance.

      // old
      $app = function (Request $request, Response $response) use ($db) {
          $db->query()->then(function ($result) use ($response) {
              $response->writeHead(200, array('Content-Type' => 'text/plain'));
              $response->end($result);
          });
      };
      
      // new
      $app = function (ServerRequestInterface $request) use ($db) {
          return $db->query()->then(function ($result) {
              return new Response(
                  200,
                  array('Content-Type' => 'text/plain'),
                  $result
              );
          });
      };
      

      Pending promies and response streams will automatically be canceled once the client connection closes. (#187 and #188 by @clue)

      The ServerRequestInterface contains the full effective request URI, server-side parameters, query parameters and parsed cookies values as defined in PSR-7. (#167 by @clue and #174, #175 and #180 by @legionth)

      $app = function (ServerRequestInterface $request) {
          return new Response(
              200,
              array('Content-Type' => 'text/plain'),
              $request->getUri()->getScheme()
          );
      };
      

      Advanced: Support duplex stream response for Upgrade requests such as Upgrade: WebSocket or custom protocols and CONNECT requests (#189 and #190 by @clue)

      Note that the request body will currently not be buffered and parsed by default, which depending on your particilar use-case, may limit interoperability with the PSR-7 (http-message) ecosystem. The provided streaming request body interfaces allow you to perform buffering and parsing as needed in the request handler callback. See also the README and examples for more details.

    • Feature / BC break: Replace request listener with callback function and use listen() method to support multiple listening sockets (#97 by @legionth and #193 by @clue)

      // old
      $server = new Server($socket);
      $server->on('request', $app);
      
      // new
      $server = new Server($app);
      $server->listen($socket);
      
    • Feature: Support the more advanced HTTP requests, such as OPTIONS * HTTP/1.1 (OPTIONS method in asterisk-form), GET http://example.com/path HTTP/1.1 (plain proxy requests in absolute-form), CONNECT example.com:443 HTTP/1.1 (CONNECT proxy requests in authority-form) and sanitize Host header value across all requests. (#157, #158, #161, #165, #169 and #173 by @clue)

    • Feature: Forward compatibility with Socket v1.0, v0.8, v0.7 and v0.6 and forward compatibility with Stream v1.0 and v0.7 (#154, #163, #183, #184 and #191 by @clue)

    • Feature: Simplify examples to ease getting started and add benchmarking example (#151 and #162 by @clue)

    • Improve test suite by adding tests for case insensitive chunked transfer encoding and ignoring HHVM test failures until Travis tests work again. (#150 by @legionth and #185 by @clue)

    Source code(tar.gz)
    Source code(zip)
  • v0.6.0(Mar 9, 2017)

    • Feature / BC break: The Request and Response objects now follow strict stream semantics and their respective methods and events. (#116, #129, #133, #135, #136, #137, #138, #140, #141 by @legionth and #122, #123, #130, #131, #132, #142 by @clue)

      This implies that the Server now supports proper detection of the request message body stream, such as supporting decoding chunked transfer encoding, delimiting requests with an explicit Content-Length header and those with an empty request message body.

      These streaming semantics are compatible with previous Stream v0.5, future compatible with v0.5 and upcoming v0.6 versions and can be used like this:

      $http->on('request', function (Request $request, Response $response) {
          $contentLength = 0;
          $request->on('data', function ($data) use (&$contentLength) {
              $contentLength += strlen($data);
          });
      
          $request->on('end', function () use ($response, &$contentLength){
              $response->writeHead(200, array('Content-Type' => 'text/plain'));
              $response->end("The length of the submitted request body is: " . $contentLength);
          });
      
          // an error occured
          // e.g. on invalid chunked encoded data or an unexpected 'end' event 
          $request->on('error', function (\Exception $exception) use ($response, &$contentLength) {
              $response->writeHead(400, array('Content-Type' => 'text/plain'));
              $response->end("An error occured while reading at length: " . $contentLength);
          });
      });
      

      Similarly, the Request and Response now strictly follow the close() method and close event semantics. Closing the Request does not interrupt the underlying TCP/IP in order to allow still sending back a valid response message. Closing the Response does terminate the underlying TCP/IP connection in order to clean up resources.

      You should make sure to always attach a request event listener like above. The Server will not respond to an incoming HTTP request otherwise and keep the TCP/IP connection pending until the other side chooses to close the connection.

    • Feature: Support HTTP/1.1 and HTTP/1.0 for Request and Response. (#124, #125, #126, #127, #128 by @clue and #139 by @legionth)

      The outgoing Response will automatically use the same HTTP version as the incoming Request message and will only apply HTTP/1.1 semantics if applicable. This includes that the Response will automatically attach a Date and Connection: close header if applicable.

      This implies that the Server now automatically responds with HTTP error messages for invalid requests (status 400) and those exceeding internal request header limits (status 431).

    Source code(tar.gz)
    Source code(zip)
  • v0.5.0(Feb 16, 2017)

    • Feature / BC break: Change Request methods to be in line with PSR-7 (#117 by @clue)

      • Rename getQuery() to getQueryParams()
      • Rename getHttpVersion() to getProtocolVersion()
      • Change getHeaders() to always return an array of string values for each header
    • Feature / BC break: Update Socket component to v0.5 and add secure HTTPS server support (#90 and #119 by @clue)

      // old plaintext HTTP server
      $socket = new React\Socket\Server($loop);
      $socket->listen(8080, '127.0.0.1');
      $http = new React\Http\Server($socket);
      
      // new plaintext HTTP server
      $socket = new React\Socket\Server('127.0.0.1:8080', $loop);
      $http = new React\Http\Server($socket);
      
      // new secure HTTPS server
      $socket = new React\Socket\Server('127.0.0.1:8080', $loop);
      $socket = new React\Socket\SecureServer($socket, $loop, array(
          'local_cert' => __DIR__ . '/localhost.pem'
      ));
      $http = new React\Http\Server($socket);
      
    • BC break: Mark internal APIs as internal or private and remove unneeded ServerInterface (#118 by @clue, #95 by @legionth)

    Source code(tar.gz)
    Source code(zip)
  • v0.4.4(Feb 13, 2017)

    • Feature: Add request header accessors (à la PSR-7) (#103 by @clue)

      // get value of host header
      $host = $request->getHeaderLine('Host');
      
      // get list of all cookie headers
      $cookies = $request->getHeader('Cookie');
      
    • Feature: Forward pause() and resume() from Request to underlying connection (#110 by @clue)

      // support back-pressure when piping request into slower destination
      $request->pipe($dest);
      
      // manually pause/resume request
      $request->pause();
      $request->resume();
      
    • Fix: Fix 100-continue to be handled case-insensitive and ignore it for HTTP/1.0. Similarly, outgoing response headers are now handled case-insensitive, e.g we no longer apply chunked transfer encoding with mixed-case Content-Length. (#107 by @clue)

      // now handled case-insensitive
      $request->expectsContinue();
      
      // now works just like properly-cased header
      $response->writeHead($status, array('content-length' => 0));
      
    • Fix: Do not emit empty data events and ignore empty writes in order to not mess up chunked transfer encoding (#108 and #112 by @clue)

    • Lock and test minimum required dependency versions and support PHPUnit v5 (#113, #115 and #114 by @andig)

    Source code(tar.gz)
    Source code(zip)
  • v0.4.3(Feb 10, 2017)

    • Fix: Do not take start of body into account when checking maximum header size (#88 by @nopolabs)
    • Fix: Remove data listener if HeaderParser emits an error (#83 by @nick4fake)
    • First class support for PHP 5.3 through PHP 7 and HHVM (#101 and #102 by @clue, #66 by @WyriHaximus)
    • Improve test suite by adding PHPUnit to require-dev, improving forward compatibility with newer PHPUnit versions and replacing unneeded test stubs (#92 and #93 by @nopolabs, #100 by @legionth)
    Source code(tar.gz)
    Source code(zip)
  • v0.4.2(Nov 9, 2016)

    • Remove all listeners after emitting error in RequestHeaderParser #68 @WyriHaximus
    • Catch Guzzle parse request errors #65 @WyriHaximus
    • Remove branch-alias definition as per reactphp/react#343 #58 @WyriHaximus
    • Add functional example to ease getting started #64 by @clue
    • Naming, immutable array manipulation #37 @cboden
    Source code(tar.gz)
    Source code(zip)
  • v0.4.1(May 21, 2015)

  • v0.4.0(Mar 5, 2017)

    • BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
    • BC break: Update to React/Promise 2.0
    • BC break: Update to Evenement 2.0
    • Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
    • Bump React dependencies to v0.4
    Source code(tar.gz)
    Source code(zip)
  • v0.3.0(Mar 5, 2017)

Owner
ReactPHP
Event-driven, non-blocking I/O with PHP.
ReactPHP
LittleProxy is a high performance HTTP proxy written in Java atop Trustin Lee's excellent Netty event-based networking library

LittleProxy is a high performance HTTP proxy written in Java atop Trustin Lee's excellent Netty event-based networking library

null 1.9k Dec 26, 2022
Spike is a fast reverse proxy built on top of ReactPHP that helps to expose your local services to the internet.

Spike is a fast reverse proxy built on top of ReactPHP that helps to expose your local services to the internet. 简体中文 Installation Install via compose

Tao 649 Dec 26, 2022
ReactPHP HttpClient Adapter for Guzzle6, for Guzzle5 check ReactGuzzleRing

react-guzzle-psr7 ReactPHP HttpClient Adapter for Guzzle6, for Guzzle5 check ReactGuzzleRing Installation To install via Composer, use the command bel

Cees-Jan Kiewiet 69 Dec 8, 2022
Declarative HTTP Clients using Guzzle HTTP Library and PHP 8 Attributes

Waffler How to install? $ composer require waffler/waffler This package requires PHP 8 or above. How to test? $ composer phpunit Quick start For our e

Waffler 3 Aug 26, 2022
PSR-7 HTTP Message implementation

zend-diactoros Repository abandoned 2019-12-31 This repository has moved to laminas/laminas-diactoros. Master: Develop: Diactoros (pronunciation: /dɪʌ

Zend Framework 1.6k Dec 9, 2022
Guzzle, an extensible PHP HTTP client

Guzzle, PHP HTTP client Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services. Simple interf

Guzzle 22.3k Jan 2, 2023
A Chainable, REST Friendly, PHP HTTP Client. A sane alternative to cURL.

Httpful Httpful is a simple Http Client library for PHP 7.2+. There is an emphasis of readability, simplicity, and flexibility – basically provide the

Nate Good 1.7k Dec 21, 2022
PHP's lightweight HTTP client

Buzz - Scripted HTTP browser Buzz is a lightweight (<1000 lines of code) PHP 7.1 library for issuing HTTP requests. The library includes three clients

Kris Wallsmith 1.9k Jan 4, 2023
HTTPlug, the HTTP client abstraction for PHP

HTTPlug HTTPlug, the HTTP client abstraction for PHP. Intro HTTP client standard built on PSR-7 HTTP messages. The HTTPlug client interface is compati

The PHP HTTP group 2.4k Dec 30, 2022
Unirest in PHP: Simplified, lightweight HTTP client library.

Unirest for PHP Unirest is a set of lightweight HTTP libraries available in multiple languages, built and maintained by Mashape, who also maintain the

Kong 1.3k Dec 28, 2022
A PHP proxy to solve client browser HTTP CORS(cross-origin) restrictions.

cors-bypass-proxy A PHP proxy to solve client browser HTTP CORS(cross-origin) restrictions. A simple way to solve CORS issue when you have no access t

Gracious Emmanuel 15 Nov 17, 2022
Zenscrape package is a simple PHP HTTP client-provider that makes it easy to parsing site-pages

Zenscrape package is a simple PHP HTTP client-provider that makes it easy to parsing site-pages

Andrei 3 Jan 17, 2022
Async HTTP/1.1+2 client for PHP based on Amp.

This package provides an asynchronous HTTP client for PHP based on Amp. Its API simplifies standards-compliant HTTP resource traversal and RESTful web

AMPHP 641 Dec 19, 2022
Simple HTTP cURL client for PHP 7.1+ based on PSR-18

Simple HTTP cURL client for PHP 7.1+ based on PSR-18 Installation composer require sunrise/http-client-curl QuickStart composer require sunrise/http-f

Sunrise // PHP 15 Sep 5, 2022
Pushpin is a publish-subscribe server, supporting HTTP and WebSocket connections.

Pushpin and 1 million connections Pushpin is a publish-subscribe server, supporting HTTP and WebSocket connections. This repository contains instructi

Fanout 14 Jul 15, 2022
Retrofit implementation in PHP. A REST client for PHP.

Retrofit PHP Retrofit is a type-safe REST client. It is blatantly stolen from square/retrofit and implemented in PHP. ❗ UPGRADE NOTICE ❗ Version 3 int

null 153 Dec 21, 2022
Express.php is a new HTTP - Server especially made for RESTful APIs written in PHP.

express.php Express.php is a new HTTP - Server especially made for RESTful APIs written in PHP. Features Fast The Library is handles requests fast and

null 5 Aug 19, 2022
Requests for PHP is a humble HTTP request library. It simplifies how you interact with other sites and takes away all your worries.

Requests for PHP Requests is a HTTP library written in PHP, for human beings. It is roughly based on the API from the excellent Requests Python librar

null 3.5k Dec 31, 2022
Record your test suite's HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests.

This is a port of the VCR Ruby library to PHP. Record your test suite's HTTP interactions and replay them during future test runs for fast, determinis

php-vcr 1.1k Dec 23, 2022