Response¶
Whenever the client sends an HTTP request to our application, we need to send back an HTTP response message.
We love standards and want to make using X as simple as possible. That's why we build on top of the established PSR-7 standard (HTTP message interfaces). This standard defines common interfaces for HTTP request and response objects.
If you've ever used PSR-7 before, you should immediately feel at home when using X. If you're new to PSR-7, don't worry. Here's everything you need to know to get started.
ℹ️ A note about other PSR-7 implementations
All of the examples in this documentation use the
React\Http\Message\Response
class because this comes bundled as part of our dependencies. If you have more specific requirements or want to integrate this with an existing piece of code, you can use any response implementation as long as it implements thePsr\Http\Message\ResponseInterface
.
JSON¶
You can send JSON data as an HTTP response body like this:
<?php
// …
$app->get('/user', function () {
$data = [
[
'name' => 'Alice'
],
[
'name' => 'Bob'
]
];
return React\Http\Message\Response::json(
$data
);
});
This example returns a simple JSON response from some static data. In real-world applications, you may want to load this from a database. For common API usage, you may also want to receive a JSON request.
You can try out this example by sending an HTTP request like this:
$ curl http://localhost:8080/user
[
{
"name": "Alice"
},
{
"name": "Bob"
}
]
By default, the response will use the
200 OK
status code and will automatically include an appropriateContent-Type: application/json
response header, see also status codes and response headers below for more details.If you want more control over the response such as using custom JSON flags, you can also manually create a
React\Http\Message\Response
object like this:public/index.php<?php // … $app->get('/user', function () { $data = []; return new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Content-Type' => 'application/json' ], json_encode( $data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) . "\n" ); });
HTML¶
You can send HTML data as an HTTP response body like this:
<?php
// …
$app->get('/user', function () {
$html = <<<HTML
<h1>Hello Alice</h1>
HTML;
return React\Http\Message\Response::html(
$html
);
});
This example returns a simple HTML response from some static data. In real-world applications, you may want to load this from a database and perhaps use templates to render your HTML.
You can try out this example by sending an HTTP request like this:
$ curl http://localhost:8080/user
<h1>Hello Alice</h1>
By default, the response will use the
200 OK
status code and will automatically include an appropriateContent-Type: text/html; charset=utf-8
response header, see also status codes and response headers below for more details.If you want more control over this behavior, you can also manually create a
React\Http\Message\Response
object like this:public/index.php<?php // … $app->get('/user', function () { $html = "Hello Wörld!\n"; return new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Content-Type' => 'text/html; charset=utf-8' ], $html ); });
Status Codes¶
The json()
and html()
methods used above automatically use
a 200 OK
status code by default. You can assign status codes like this:
<?php
// …
$app->get('/user/{id}', function (Psr\Http\Message\ServerRequestInterface $request) {
$id = $request->getAttribute('id');
if ($id === 'admin') {
return React\Http\Message\Response::html(
"Forbidden\n"
)->withStatus(React\Http\Message\Response::STATUS_FORBIDDEN);
}
return React\Http\Message\Response::html(
"Hello $id\n"
);
});
You can try out this example by sending an HTTP request like this:
$ curl -I http://localhost:8080/user/Alice
HTTP/1.1 200 OK
…
$ curl -I http://localhost:8080/user/admin
HTTP/1.1 403 Forbidden
…
Each HTTP response message contains a status code that describes whether the HTTP request has been successfully completed. Here's a list with some of the most common HTTP status codes:
200 OK
301 Moved Permanently
302 Found
(previously302 Temporary Redirect
)304 Not Modified
(see HTTP caching below)403 Forbidden
404 Not Found
500 Internal Server Error
- …
See list of HTTP status codes for more details. Each status code can be referenced by its matching status code constant name such as
React\Http\Message\Response::STATUS_OK
orReact\Http\Message\Response::STATUS_NOT_FOUND
or by its status code number.
Headers¶
The json()
and html()
methods used above automatically use
an appropriate Content-Type
response header by default. You can assign HTTP
response headers like this:
<?php
// …
$app->get('/user', function () {
return React\Http\Message\Response::html(
"Hello wörld!\n"
)->withHeader('Cache-Control', 'public');
});
You can try out this example by sending an HTTP request like this:
$ curl -I http://localhost:8080/user
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: public
Hello wörld!
Each HTTP response message can contain an arbitrary number of response headers. Additionally, the application will automatically include default headers required by the HTTP protocol. It's not recommended to mess with these default headers unless you're sure you know what you're doing.
If you want more control over this behavior, you can also manually create a
React\Http\Message\Response
object like this:public/index.php<?php // … $app->get('/user', function () { return new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Content-Type' => 'text/html; charset=utf-8', 'Cache-Control' => 'public' ], "Hello wörld!\n" ); });
HTTP Redirects¶
To redirect incoming HTTP requests to a new location you can define your HTTP response like this:
<?php
// …
$app->get('/blog.html', function () {
return new React\Http\Message\Response(
React\Http\Message\Response::STATUS_FOUND,
[
'Location' => '/blog'
]
);
});
Redirect responses have a status code in the 3xx
range.
Here's a list with some of the most common HTTP redirect status codes:
301 Moved Permanently
302 Found
(previously302 Temporary Redirect
)303 See Other
307 Temporary Redirect
308 Permanent Redirect
Each status code can be referenced by its matching status code
constant name such as React\Http\Message\Response::STATUS_MOVED_PERMANENTLY
or React\Http\Message\Response::STATUS_FOUND
or by its status code number.
The Location
response header holds the URL to redirect to. When a Browser or a search engine
crawler receives a redirect, they will automatically follow the new URL provided
in the Location
header.
You can also use the redirect helper method for simpler use cases:
<?php
// …
$app->redirect('/promo/reactphp', 'https://reactphp.org/');
HTTP caching¶
HTTP caching can be used to significantly improve the performance of web applications by reusing previously fetched resources. HTTP caching is a whole topic on its own, so this section only aims to give a basic overview of how you can leverage HTTP caches with X. For a more in-depth overview, we highly recommend MDN.
HTTP supports caching for certain requests by default. In any but the most basic use cases, it's often a good idea to explicitly specify HTTP caching headers as part of the HTTP response to have more control over the freshness lifetime and revalidation behavior.
Cache-Control¶
The Cache-Control
response header can be used to control caching of responses by browsers and
shared caches such as proxies and CDNs. In its most basic form, you can use this
response header to control the lifetime of a cached response like this:
<?php
// …
$app->get('/user', function () {
$html = <<<HTML
<h1>Hello Alice</h1>
HTML;
return new React\Http\Message\Response::html(
$html
)->withHeader('Cache-Control', 'max-age=3600');
});
You can try out this example by sending an HTTP request like this:
$ curl -I http://localhost:8080/user
HTTP/1.1 200 OK
Cache-Control: max-age=3600
…
ETag¶
The ETag
response header can be used for conditional requests. This ensures the response
body only needs to be transferred when it actually changes. For instance, you
can build a hash (or some other arbitrary identifier) for your contents and
check if it matches the incoming
If-None-Match
request header for subsequent requests. If both values match, you can send a
304 Not Modified
response and omit the response body like this:
<?php
// …
$app->get('/user', function (Psr\Http\Message\ServerRequestInterface $request) {
// example body, would usually come from some kind of database
$html = <<<HTML
<h1>Hello Alice</h1>
HTML;
$etag = '"' . sha1($html) . '"';
if ($request->getHeaderLine('If-None-Match') === $etag) {
return new React\Http\Message\Response(
React\Http\Message\Response::STATUS_NOT_MODIFIED,
[
'Cache-Control' => 'max-age=0, must-revalidate',
'ETag' => $etag
]
);
}
return new React\Http\Message\Response(
React\Http\Message\Response::STATUS_OK,
[
'Content-Type' => 'text/html; charset=utf-8',
'Cache-Control' => 'max-age=0, must-revalidate',
'ETag' => $etag
],
$html
);
});
You can try out this example by sending an HTTP request like this:
$ curl http://localhost:8080/user
<h1>Hello Alice</h1>
$ curl -I http://localhost:8080/user -H 'If-None-Match: "87637b595ed5b32934c011dc6b33afb43f598865"'
HTTP/1.1 304 Not Modified
Cache-Control: max-age=0, must-revalidate
ETag: "87637b595ed5b32934c011dc6b33afb43f598865"
…
Last-Modified¶
The Last-Modified
response header can be used to signal when a response was last modified. Among
others, this can be used to ensure the response body only needs to be
transferred when it changes. For instance, you can store a timestamp or datetime
for your contents and check if it matches the incoming
If-Modified-Since
request header for subsequent requests. If both values match, you can send a
304 Not Modified
response and omit the response body like this:
<?php
// …
$app->get('/user', function (Psr\Http\Message\ServerRequestInterface $request) {
// example date and body, would usually come from some kind of database
$date = new DateTimeImmutable(
'2021-11-06 13:31:04',
new DateTimeZone('Europe/Berlin')
);
$html = <<<HTML
<h1>Hello Alice</h1>
HTML;
$modified = $date->setTimezone(new DateTimeZone('UTC'))->format(DATE_RFC7231);
if ($request->getHeaderLine('If-Modified-Since') === $modified) {
return new React\Http\Message\Response(
React\Http\Message\Response::STATUS_NOT_MODIFIED,
[
'Cache-Control' => 'max-age=0, must-revalidate',
'Last-Modified' => $modified
]
);
}
return new React\Http\Message\Response(
React\Http\Message\Response::STATUS_OK,
[
'Content-Type' => 'text/html; charset=utf-8',
'Cache-Control' => 'max-age=0, must-revalidate',
'Last-Modified' => $modified
],
$html
);
});
You can try out this example by sending an HTTP request like this:
$ curl http://localhost:8080/user
<h1>Hello Alice</h1>
$ curl -I http://localhost:8080/user -H 'If-Modified-Since: Sat, 06 Nov 2021 12:31:04 GMT'
HTTP/1.1 304 Not Modified
Cache-Control: max-age=0, must-revalidate
Last-Modified: Sat, 06 Nov 2021 12:31:04 GMT
…
ℹ️ Working with dates
For use in HTTP, you may format any date in the GMT/UTC timezone using the
DATE_RFC7231
(PHP 7.1.5+) constant as given above. If you don't know the modification date for your response or don't want to expose this information, you may want to useETag
headers from the previous section instead.
Output buffering¶
PHP provides a number of functions that write directly to the output buffer instead of returning values:
echo
,print
,printf()
,vprintf()
, etc.var_dump()
,var_export()
,print_r()
, etc.readfile()
,fpassthru()
,passthru()
, etc.- …
These functions can also be used in X, but do require some special care because
we want to redirect this output to be part of an HTTP response instead. You can
start a temporary output buffer using ob_start()
to catch any output and return it as a response body like this:
<?php
// …
$app->get('/dump', function () {
ob_start();
echo "Hello\n";
var_dump(42);
$body = ob_get_clean();
return React\Http\Message\Response::plaintext(
$body
);
});
You can try out this example by sending an HTTP request like this:
$ curl http://localhost:8080/dump
Hello
int(42)
ℹ️ A word of caution
Special care should be taken if the code in question is deeply nested with multiple return conditions or may throw an
Exception
.As a rule of thumb, output buffering should only be used as a last resort and directly working with
string
values is usually preferable. For instance,print_r()
,var_export()
and others accept optional boolean flags to return the value instead of printing to the output buffer. In many other cases, PHP also provides alternative functions that directly returnstring
values instead of writing to the output buffer. For instance, instead of usingprintf()
, you may want to usesprintf()
.
Internal Server Error¶
Each controller function needs to return a response object in order to send
an HTTP response message. If the controller function throws an Exception
(or
Throwable
) or returns any invalid type, the HTTP request will automatically be
rejected with a 500 Internal Server Error
HTTP error response:
<?php
// …
$app->get('/user', function () {
throw new BadMethodCallException();
});
You can try out this example by sending an HTTP request like this:
$ curl -I http://localhost:8080/user
HTTP/1.1 500 Internal Server Error
…
This default error handling can be configured through the App
.
See error handling for more details.