Aplus Framework Docs

HTTP

Aplus Framework HTTP Library

Aplus Framework HTTP (HyperText Transfer Protocol) Library.

Installation

The installation of this library can be done with Composer:

composer require aplus/http

Request

With the Request class you can get Object Oriented information about the requested Protocol, URL, Headers and Body.

Create a PHP file (users.php) with the following contents:

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

use Framework\HTTP\Request;

$request = new Request();

Testing:

Add the following lines to test get Request information:

var_dump([
    'method' => $request->getMethod(),
    'host' => $request->getHeader('Host'),
    'isSecure' => $request->isSecure(),
    'isAjax' => $request->isAjax(),
    'url' => $request->getUrl()->toString(),
    'userAgent' => $request->getUserAgent()->toString(),
]);

Now, lets try a call with Curl:

curl http://localhost:8080/users.php?page=1

Curl will connect to the server with an HTTP Message like this:

GET /users.php?page=1 HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.68.0
Accept: */*

In the PHP script, the Request class will be instantiated and the array will be set and dumped.

The Curl response body will be like this:

array(6) {
  ["method"]=>
  string(3) "GET"
  ["host"]=>
  string(14) "localhost:8080"
  ["isSecure"]=>
  bool(false)
  ["isAjax"]=>
  bool(false)
  ["url"]=>
  string(38) "http://localhost:8080/users.php?page=1"
  ["userAgent"]=>
  string(11) "curl/7.68.0"
}

Secure Request

To make sure that the application will only respond to secure requests, you can force the request to be over HTTPS and also that it is responding only on allowed hosts.

Force HTTPS

If the request is not secure, we can force a redirect using HTTPS:

$request->forceHttps(); // void

This method checks if the request scheme is HTTPS.

And only if is not, it set headers and status to redirect to the HTTPS version of the URL and terminate the script.

Allowed Hosts

If, for some unknown reason, the virtual host is incorrectly configured on the server, it is possible to prevent unwanted access by whitelisting the allowed hosts.

See this example using nginx:

root /var/www/app/public;
server_name domain.tld api.domain.tld other.tld;

A Company requires only domain.tld and api.domain.tld to work, but one added the other.tld to the list of servernames. Nginx will respond to this host accessing the application public folder.

To prevent that, whitelist the allowed hosts. Set it on the Request constructor:

$allowedHosts = ['domain.tld', 'domain.tld:8088', 'api.domain.tld'];
$request = new Request($allowedHosts);

When a request for an unwanted host is done, an UnexpectedValueException will thrown, with the message "Invalid Host: other.tld".

With the throwable is possible, for example, to catch the Exception message and log it.

If the $allowedHosts argument is not set, the Request will accept any host.

Content Negotiation

It is also in the request that information is acquired for Content Negotiation.
Knowing what the HTTP Client accepts, and prioritizes, it is possible to generate a more complete and featured Response for each user.

The Request class has methods for negotiating content.

In them it is possible to pass the values available by the application.

Let's look at an example negotiating the value of the headers, Content-Type and Content-Language, which can be used in the Response:

$availableTypes = ['text/html', 'application/xml'];
$negotiatedType = $request->negotiateAccept($availableTypes);

$availableLanguages = ['en', 'es', 'pt-br'];
$negotiatedLanguage = $request->negotiateLanguage($availableLanguages); // string

The negotiation takes the Quality Values from the header in order of priority and returns the first one in the list of
those accepted by the application.
If none of the Quality Values are available in the application, the value returned is the first of the array of available.

Anyway, it is now possible to set the headers negotiated in the Response:

$response->setHeader('Content-Type', $negotiatedType); // static
$response->setHeader('Content-Language', $negotiatedLanguage); // static

Request with JSON

When working with JSON, has a method to check if the Content-Type is of JSON type.

And also, a method to get the JSON data from the Request body:

if ($request->isJson()) {
    $data = $request->getJson(); // object or false
}

Request with Uploads

When the request is done via the POST method and has multipart/form-data as Content-Type, it characterizes the upload of files.

The Request class has methods to work with uploaded files.

The getFile method returns an UploadedFile instance or null.

$file = $request->getFile('fieldName'); // UploadedFile or null
if ($file && $file->isValid()) {
    $filename = 'rand0m' . $file->getExtension();
    $filepath = '/var/www/app/uploads/' . $filename;
    $moved = $file->move($filepath); // bool
}

Request with Authorization

To identify the type of authorization received, you can use the getAuthType method. Which will return null if there is none, Basic, Bearer or Digest:

$request->getAuthType(); // string or null

Using the getBasicAuth method, we obtain an array containing two keys, username and password or null:

$request->getBasicAuth(); // array or null

Using the getBearerAuth method, we obtain an array containing one key, token or null:

$request->getBearerAuth(); // array or null

Using the getDigestAuth method, an array with 9 keys is obtained: username, realm, nonce, uri, response, opaque, qop, nc and cnonce.

$request->getDigestAuth(); // array or null

Request working with REST

The Request class has methods that work very well with REST APIs.

With the getMethod method, we get the HTTP method used:

$request->getMethod(); // string

With the getGet method, query parts from the current URL:

$request->getGet(); // array

The getPost method get data from POST requests.

$request->getPost(); // array

With the getJson method, the request body data is parsed in JSON, being an object, array or false if there are syntax errors.

$request->getJson(); // object, array or false

With the getBody method, the body string of the request is obtained.

$request->getBody(); // string

And with the getParsedBody method you get parsed body parts, used when the HTTP method is neither GET nor POST.

$request->getParsedBody(); // array

Request working with Arrays

The getGet, getPost, getPut, getPatch, getFile, getParsedBody, getEnv and getServer methods can get data from arrays via strings with square brackets.

For example, let's say $_POST equals the array below:

$_POST = [
    'user' => [
        'name' => 'John Doe',
        'address' => [
            'country' => 'Brazil',
            'city' => 'Sapiranga',
        ],
    ],
];

User data can be obtained as follows:

$name = $request->getPost('user[name]'); // John Doe
$city = $request->getPost('user[address][city]'); // Sapiranga

Response

The HTTP response send the message status, headers and body to a client.

To use the Response class, just instantiate it:

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

use Framework\HTTP\Request;
use Framework\HTTP\Response;

$request = new Request();
$response = new Response($request);

Response Status

Response status can be set with the setStatusCode, setStatusReason or setStatus methods using the status number or the value of a constant of the Status class:

use Framework\HTTP\Status;

$response->setStatus(401); // static
$response->setStatus(Status::UNAUTHORIZED); // static

Response Headers

Headers can be set using the setHeader method, using a string in the first parameter with the name of the header and in the second the value.

Also, you can use constants from the ResponseHeader class:

use Framework\HTTP\ResponseHeader;

$response->setHeader('Content-Type', 'text/xml'); // static
$response->setHeader(ResponseHeader::CONTENT_TYPE, 'text/xml'); // static
$response->setContentType('text/xml'); // static

Response Body

Each time you call the getBody method the buffer will be appended to the body. This is so that, for example, echo can be used.

Also, you can use the setBody method.

Let's see an example manipulating the body of the Response:

echo 'Oi!';
$body = $response->getBody(); // Oi!
$response->setBody('Hi!');
$body = $response->getBody(); // Hi!
echo ' What is your name';
$body = $response->getBody(); // Hi! What is your name
$response->appendBody('???');
$body = $response->getBody(); // Hi! What is your name???
$response->setBody(['name' => 'A Framework']);
$body = $response->getBody(); // name=A+Framework

Response with JSON

A response containing JSON can be set as:

$users = [
    [
        'id' => 1,
        'name' => 'Adam',
    ],
    [
        'id' => 2,
        'name' => 'Eve',
    ],
];

$response->setHeader('Content-Type', 'application/json'); // static
$response->setBody(json_encode($users)); // static

or simply:

$response->setJson($users); // static

Response with HTML

HTML, and any other Content-Type, can be set with the Response::{set,append,prepend}Body() methods.

$contents = '<h1>Hello, Aplus!</h1>';
$response->setBody($contents);
$contents = '<p>I am so happy to meet you.</p>';
$response->appendBody($contents);

If the Content-Type header is not set, it is automatically set to text/html; charset=UTF-8 when the Response is sent.

Response with Download

To send a file as a download in the response, you can call:

$response->setDownload('filepath.pdf'); // static

With the second parameter set to true the content disposition is inline, causing the browser to open the file in the window.

$response->setDownload('filepath.pdf', inline: true); // static

The third parameter makes it possible to continue downloads or start downloading a video at a certain time.

$response->setDownload('filepath.pdf', true, acceptRanges: true); // static

Sending the Response

Now that you've seen how to set the Response status, headers and body, it's time to see how to send the response to the User-Agent:

$response->send(); // void

The send method must be called only once, otherwise it will throw an exception. Calling the send method is the last step of the HTTP response. After that, nothing else should come out to the PHP Output Buffer. But, your script can continue to run normally if necessary.

Response Cache

Response has methods to simplify the caching process in the browser.

You can use the Cache-Control header to enable the cache for a certain time or negotiate the response through the ETag header:

Cache-Control

To set the Cache-Control header, you can use the setCache method passing the number of seconds in the first parameter and in the second pass true for it to be public or false to be private, which is the default .

$response->setCache(60); // static

To not save anything in the cache, use the setNoCache method:

$response->setNoCache(); // static

ETag

Through the ETag header it is possible to make the User-Agent cache the contents of the response body by an identifier. This will cause the response to contain a 304 Not Modified status and the message body to be empty, saving data to be transferred.

Likewise, it will avoid mid-air collisions on requests other than the GET or HEAD method:

$response->setAutoEtag(); // static

URL

The library has a class for working with URLs. See how it works:

use Framework\HTTP\URL;

$url = new URL('http://domain.tld:8080/slug?page=1#heading');
echo $url->getScheme(); // http
echo $url->getHost(); // domain.tld:8080
echo $url->getHostname(); // domain.tld
echo $url->getPort(); // 8080
$url->setHostname('foo-bar.com');
echo $url->getHost(); // foo-bar.com:8080
$url->setPort(80);
echo $url->getHost(); // foo-bar.com
echo $url->getPath(); // /slug
echo $url->getQuery(); // page=1
echo $url->getFragment(); // heading

AntiCSRF

The HTTP library has a class to prevent Cross-Site Request Forgery (CSRF) using the Synchronizer Token Pattern.

We will see below how AntiCSRF works.

We have a file called header.php that will be required by other files because it loads the autoload file, starts the session and instantiates the Request and AntiCSRF objects:

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

use Framework\HTTP\AntiCSRF;
use Framework\HTTP\Request;

session_start();
$request = new Request();
$antiCsrf = new AntiCSRF($request);

In this example, we have a form in the form.php file. In which there is a call to the $antiCsrf variable that returns the field with the token saved in the session.

The form action takes you to the save.php file using the POST method:

<?php
require __DIR__ . '/header.php';
?>
<form action="save.php" method="post">
    <?= $antiCsrf->input() ?>
    <label>Name</label>
    <input name="name"/>
    <label>Message</label>
    <textarea name="message"></textarea>
    <button>Send</button>
</form>

Finally, we have the save.php file, where the verification of the token received in the form is performed. If it is invalid, the script will terminate showing that the request is invalid. If valid, it will show that the form message can be saved.

<?php
require __DIR__ . '/header.php';

if (!$request->isPost() || !$antiCsrf->verify()) {
    exit ('Request is invalid.');
}
echo 'OK. Anti-CSRF is valid. You can save the message!';

Note that in this example we validated only the field with the anti-CSRF token and did not validate the other fields. Which can be validated using the Validation Library.

Content Security Policy

The Content-Security-Policy HTTP response header helps you reduce XSS risks on modern browsers by declaring which dynamic resources are allowed to load.

You can get more information on these pages:

CSP classes can be instantiated as in the following example.

Several directives can be passed in object construction or through the setDirectives method:

use Framework\HTTP\CSP;

$directives = [
    'default-src' => [
        'self',
    ],
    'style-src' => [
        'self',
        'cdn.foo.tld',
    ],
];
$csp = new CSP($directives);
$csp->setDirectives($directives);

Values for a single directive can be passed with the setDirective method:

$csp->setDirective(CSP::defaultSrc, [
    'self',
]);

Methods that start with set override directive values. To just add new values, use the addValue method:

$csp->addValue(CSP::styleSrc, [
    'self',
    'cdn.foo.tld',
]);

CSP Nonces

By default, when the value is self, the contents of the style and script tags do not execute.

To make them run, it is possible to add the nonce attribute to the tags, which have a unique value generated at each page load.

Let's see the following examples:

The dynamic code below written with PHP:

<script<?= $csp->getScriptNonceAttr() ?>>
    // ...
</script>

Will render HTML similar to the following example:

<script nonce="2aca99c7ee6e0884">
    // ...
</script>

So, it is also possible to add the nonce attribute in the style tag:

<style<?= $csp->getStyleNonceAttr() ?>>
    // ...
</style>

Which renders similar to the example below:

<style nonce="ccd8147d8a8e275c">
    // ...
</style>

Using the nonce attribute is a very practical way, however, as the nonce values are unique per request, it is impossible to cache the page in browsers.

CSP Hashes

To cache the page in browsers, such as by ETag, we can use hashes of the contents of the script and style tags.

Let's look at the following HTML page:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CSP Test</title>
    <style>
        body {
            background: cyan;
        }
    </style>
</head>
<body>
<script>
    console.log('Hello!');
</script>
<script>
    console.log('Bye.');
    // it is a comment
</script>
</body>
</html>

In order to get all the hashes of the style tags we can pass the content of the HTML page in the getStyleHashes method and it will return an array with all the hashes.

And then we can add them in the style-src directive via the addValues method:

$styleHashes = CSP::getStyleHashes($html);
$csp->addValue(CSP::styleSrc, $styleHashes);

The CSP header will look like the following:

Content-Security-Policy: style-src 'sha256-CvbCUHrSwRhSRk6O3h7eTuSY9r3oKFudXNGTM/oLBI8=';

Similarly, we can get the hashes of the script tags and add them via the addValues method:

$scriptHashes = CSP::getScriptHashes($html);
$csp->addValue(CSP::scriptSrc, $scriptHashes);

The CSP header will look similar to the following example:

Content-Security-Policy: style-src 'sha256-CvbCUHrSwRhSRk6O3h7eTuSY9r3oKFudXNGTM/oLBI8='; script-src 'sha256-IfEVrz7Me6SW7O7OHy04/VaUhErMLxjWHdJd8MYN5b0=' 'sha256-0TppQmjw9at2nEl3givShY5l6nABmQ84qrh1dRgvMJ0=';

CSP in Response

An object of the CSP class can be set to an object of the Framework\HTTP\Response class and then it will be sent with the response via the send method:

$csp = new CSP([
    CSP::defaultSrc => [
        'self',
    ],
    CSP::styleSrc => [
        'self',
        'cdn.foo.tld',
    ],
    CSP::scriptSrc => [
        'self',
        'cdn.foo.tld',
    ],
]);

$response = new Framework\HTTP\Response;
$response->setCsp($csp);

Only if you're sure the page doesn't have any malicious scripts, get the hashes from the response body and add them to the CSP object:

$scriptHashes = CSP::getScriptHashes($response->getBody());
$csp->addValue(CSP::scriptSrc, $scriptHashes);

Then the response can be sent:

$response->send();

CSP in a Meta Tag

Another way to define the Content-Security-Policy is through an HTML meta tag. See the example below:

<meta http-equiv="Content-Security-Policy" content="<?= $csp ?>">

Conclusion

Aplus HTTP Library is an easy-to-use tool for, beginners and experienced, PHP developers.
It is perfect for building, simple and full-featured, HTTP interactions.
The more you use it, the more you will learn.

Did you find something wrong?
Be sure to let us know about it with an issue.
Thank you!

Search results