HTTP Requests

Basics

Requests are HTTP messages sent by clients to servers. They contain the following methods:

Note: The properties dictionary is a useful place to store metadata about a request, eg route variables.

Creating Requests

Creating a request is easy:

use Aphiria\Net\Http\Request;
use Aphiria\Net\Uri;

$request = new Request('GET', new Uri('https://example.com'));

You can set HTTP headers by calling

$request->getHeaders()->add('Foo', 'bar');

You can either set the body via the constructor or via Request::setBody():

use Aphiria\Net\Http\StringBody;

// Via constructor:
$body = new StringBody('foo');
$request = new Request('POST', new Uri('https://example.com'), null, $body);

// Or via setBody():
$request->setBody($body);

Creating Requests From Superglobals

PHP has superglobal arrays that store information about the requests. They're a mess, architecturally-speaking. Aphiria attempts to insulate developers from the nastiness of superglobals by giving you a simple method to create requests and responses. To create a request, use RequestFactory:

use Aphiria\Net\Http\RequestFactory;

$request = (new RequestFactory)->createRequestFromSuperglobals($_SERVER);

Aphiria reads all the information it needs from the $_SERVER superglobal - it doesn't need the others.

Trusted Proxies

If you're using a load balancer or some sort of proxy server, you'll need to add it to the list of trusted proxies. You can also use your proxy to set custom, trusted headers. You may specify them in the factory constructor:

// The client IP will be read from the "X-My-Proxy-Ip" header when using a trusted proxy
$factory = new RequestFactory(['192.168.1.1'], ['HTTP_CLIENT_IP' => 'X-My-Proxy-Ip']);
$request = $factory->createRequestFromSuperglobals($_SERVER);

Headers

Headers provide metadata about the HTTP message. In Aphiria, they're implemented by Aphiria\Net\Http\HttpHeaders, which extends Aphiria\Collections\HashTable. On top of the methods provided by HashTable, they also provide the following methods:

Note: Header names that are passed into the methods in HttpHeaders are automatically normalized to Train-Case. In other words, foo_bar will become Foo-Bar.

Bodies

HTTP bodies contain data associated with the HTTP message, and are optional. They're represented by Aphiria\Net\Http\IHttpBody. They provide a few methods to read and write their contents to streams and to strings:

String Bodies

HTTP bodies are most commonly represented as strings. Aphiria makes it easy to create a string body via StringBody:

use Aphiria\Net\Http\StringBody;

$body = new StringBody('foo');

Stream Bodies

Sometimes, bodies might be too big to hold entirely in memory. This is where StreamBody comes in handy:

use Aphiria\IO\Streams\Stream;
use Aphiria\Net\Http\StreamBody;

$stream = new Stream(fopen('foo.txt', 'r+b'));
$body = new StreamBody($stream);

URIs

A URI identifies a resource, typically over a network. They contain such information as the scheme, host, port, path, query string, and fragment. Aphiria represents them in Aphiria\Net\Uri, and they include the following methods:

To create an instance of Uri, pass the raw URI string into the constructor:

use Aphiria\Net\Uri;

$uri = new Uri('https://example.com/foo?bar=baz#blah');

Getting POST Data

In vanilla PHP, you can read URL-encoded form data via the $_POST superglobal. Aphiria gives you a helper to parse the body of form requests into a dictionary.

use Aphiria\Net\Http\Formatting\RequestParser;

// Let's assume the raw body is "email=foo%40bar.com"
$formInput = (new RequestParser)->readAsFormInput($request);
echo $formInput->get('email'); // "foo@bar.com"

Getting Query String Data

In vanilla PHP, query string data is read from the $_GET superglobal. In Aphiria, it's stored in the request's URI. Uri::getQueryString() returns the raw query string - to return it as an immutable dictionary, use RequestParser:

use Aphiria\Net\Http\Formatting\RequestParser;

// Assume the query string was "?foo=bar"
$queryStringParams = (new RequestParser)->parseQueryString($request);
echo $queryStringParams->get('foo'); // "bar"

JSON Requests

To check if a request is a JSON request, call

use Aphiria\Net\Http\Formatting\RequestParser;

$isJson = (new RequestParser)->isJson($request);

Rather than having to parse a JSON body yourself, you can use RequestParser to do it for you:

use Aphiria\Net\Http\Formatting\RequestParser;

$json = (new RequestParser)->readAsJson($request);

Getting Cookies

Aphiria has a helper to grab cookies from request headers as an immutable dictionary:

use Aphiria\Net\Http\Formatting\RequestParser;

$cookies = (new RequestParser)->parseCookies($request);
$cookies->get('userid');

Getting Client IP Address

If you use the RequestFactory to create your request, the client IP address will be added to the request property CLIENT_IP_ADDRESS. To make it easier to grab this value, you can use RequestParser to retrieve it:

use Aphiria\Net\Http\Formatting\RequestParser;

$clientIPAddress = (new RequestParser)->getClientIPAddress($request);

Note: This will take into consideration any trusted proxy header values when determining the original client IP address.

Header Parameters

Some header values are semicolon delimited, eg Content-Type: text/html; charset=utf-8. It's sometimes convenient to grab those key => value pairs:

$contentTypeValues = $requestParser->parseParameters($request, 'Content-Type');
// Keys without values will return null:
echo $contentTypeValues->get('text/html'); // null
echo $contentTypeValues->get('charset'); // "utf-8"

Serializing Requests

You can serialize a request per RFC 7230 by casting it to a string:

echo (string)$request;

By default, this will use origin-form for the request target, but you can override the request type via the constructor:

use Aphiria\Net\Http\RequestTargetTypes;

$request = new Request(
    'GET',
    new Uri('https://example.com/foo?bar'),
    null,
    null,
    null,
    '1.1',
    RequestTargetTypes::AUTHORITY_FORM
);

The following request target types may be used:

Multipart Requests

Multipart requests contain multiple bodies, each with headers. That's actually how file uploads work - each file gets a body with headers indicating the name, type, and size of the file. Aphiria can parse these multipart bodies into a MultipartBody, which extends StreamBody. It contains additional methods to get the boundary and the list of MultipartBodyPart objects that make up the body:

You can check if a request is a multipart request:

use Aphiria\Net\Http\Formatting\RequestParser;

$isMultipart = (new RequestParser)->isMultipart($request);

To parse a request body as a multipart body, call

use Aphiria\Net\Http\Formatting\RequestParser;

$multipartBody = (new RequestParser)->readAsMultipart($request);

Each MultipartBodyPart contains the following methods:

Saving Uploaded Files

To save a multipart body's parts to files in a memory-efficient manner, read each part as a stream and copy it to the destination path:

foreach ($multipartBody->getParts() as $multipartBodyPart) {
    $bodyStream = $multipartBodyPart->getBody()->readAsStream();
    $bodyStream->rewind();
    $bodyStream->copyToStream(new Stream(fopen('path/to/copy/to/' . uniqid(), 'wb')));
}

Getting the MIME Type of the Body

To grab the MIME type of an HTTP body, call

(new RequestParser)->getMimeType($multipartBodyPart);

Creating Multipart Requests

The Net library makes it straightforward to create a multipart request manually. The following example creates a request to upload two images:

use Aphiria\Net\Http\HttpHeaders;
use Aphiria\Net\Http\MultipartBody;
use Aphiria\Net\Http\MultipartBodyPart;
use Aphiria\Net\Http\Request;
use Aphiria\Net\Http\StreamBody;
use Aphiria\Net\Uri;

// Build the first image's headers and body
$image1Headers = new HttpHeaders();
$image1Headers->add('Content-Disposition', 'form-data; name="image1"; filename="foo.png"');
$image1Headers->add('Content-Type', 'image/png');
$image1Body = new StreamBody(fopen('path/to/foo.png', 'rb'));

// Build the second image's headers and body
$image2Headers = new HttpHeaders();
$image2Headers->add('Content-Disposition', 'form-data; name="image2"; filename="bar.png"');
$image2Headers->add('Content-Type', 'image/png');
$image2Body = new StreamBody(fopen('path/to/bar.png', 'rb'));

// Build the request's headers and body
$body = new MultipartBody([
    new MultipartBodyPart($image1Headers, $image1Body),
    new MultipartBodyPart($image2Headers, $image2Body)
]);
$headers = new HttpHeaders();
$headers->add('Content-Type', "multipart/form-data; boundary={$body->getBoundary()}");

// Build the request
$request = new Request(
    'POST',
    new Uri('https://example.com'),
    $headers,
    $body
);