PSR-7 Standard – Part 2 – Request and URI

PSR-7 Standard – Part 2 – Request and URI

This post is part of series:


In the last blog post we described the history of PSR-7. The standard contains only interfaces. Today we start with the first two interfaces. The RequestInterface and the UriInterface.

What is a HTTP Request?

To start we create a little server simulation script with this content:

<?php
print_r($_REQUEST);

After the creation we start the server script with PHP’s internal server by running this in our command line:

php -S 127.0.0.1:8080 server.php

This will start a server on our local machine listening on port 8080. Now, we have a little server to test some example requests.

Example GET Request

GET Requests are really simple. We can call the URL /mypath?foo=bar&baz=zoz with this simple text snippet.

GET /mypath?foo=bar&baz=zoz HTTP/1.1
Host: example.com

To simulate a call we can make use of the popular command line tool “curl”. If it is not installed on your machine, you should install it.

curl 'http://127.0.0.1:8080/mypath?foo=bar&baz=zoz'

If our server is running, we should see the response in the command line:

Array
(
    [foo] => bar
    [baz] => zoz
)

Curl command to simulate:

curl -X POST -d foo=bar -d baz=zoz 'http://127.0.0.1:8080/mypath'

Example POST Request

We can also post data. This is mostly done in HTML forms. The parameters are now part of a body. We need to tell the server the length of the data like in this snippet:

POST /mypath HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 15

foo=bar&baz=zoz´

As you can see, sending requests to a HTTP server is quite simple.

Sending Requests in PHP

In PHP we have several possibilities to send data to a remote server. The simplest way is to use one of the build-in functions like fopen, file_get_contents.

Let’s create a file named client.php and this content:

<?php
echo file_get_contents('http://127.0.0.1:8080/mypath?foo=bar&baz=zoz');

The output of the server should be the same as in our previous curl example. That was easy, but the usage of this functions has one drawback. The function was build to make calls to a local filesystem. It is possible to prohibit calls to remote servers by disabling it with the php.ini directive allow_url_fopen.

Another popular way is the CURL PHP Module which adds additional PHP functions.

<?php

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'http://127.0.0.1:8080/mypath?foo=bar&baz=zoz');
$result = curl_exec($ch);
print_r($result);

curl_close($ch);

But what if the module is not installed?

To deal with this issues there are a lot of userland PHP clients in the wild. Most of the clients have an own abstraction of the request. That’s where the PSR-7 standards can help us in several ways.

  1. Harmonize the way how a request is built.
  2. Re-use code across applications.
  3. Dealing with the local environment (available PHP-Modules).

RequestInterface

The following code snippet shows the RequestInterface. The RequestInterface allows us to describe a RFC 7230 HTTP message.

<?php

namespace Psr\Http\Message;

interface RequestInterface extends MessageInterface
{
    /**
     * Retrieves the message's request target.
     * @return string
     */
    public function getRequestTarget();

    /**
     * Return an instance with the specific request-target.
     *
     * @param mixed $requestTarget
     * @return static
     */
    public function withRequestTarget($requestTarget);

    /**
     * Retrieves the HTTP method of the request.
     *
     * @return string Returns the request method.
     */
    public function getMethod();

    /**
     * Return an instance with the provided HTTP method.
     *
     * @param string $method Case-sensitive method.
     * @return static
     * @throws \InvalidArgumentException for invalid HTTP methods.
     */
    public function withMethod($method);

    /**
     * Retrieves the URI instance.
     * 
     * @return UriInterface Returns a UriInterface instance
     *     representing the URI of the request.
     */
    public function getUri();

    /**
     * Returns an instance with the provided URI.
     *
     * @param UriInterface $uri New request URI to use.
     * @param bool $preserveHost Preserve the original state of the Host header.
     * @return static
     */
    public function withUri(UriInterface $uri, $preserveHost = false);
}

Every library that creates an implementation of this interfaces should be used to create a HTTP request for every HTTP (1.1) server on the planet.

To find an existing implementation we can use packagist.org. There is a virtual package with the name “psr/http-message-implementation” available. Feel free to use one of the implementations. As PHP developers we do not like to reinvent the wheel. https://packagist.org/providers/psr/http-message-implementation

In our example at the end of the article we use the Guzzle Guzzle library. It is also possible to only install the PSR-7 implementation of the library (package: guzzlehttp/psr7) which implements the virtual package. If you are not familiar what a virtual package is, think of it like an interface for Composer packages. There is a good article which describes virtual packages.

UriInterface

The UriInterface describes the URI which should be called by our Request.

<?php

namespace Psr\Http\Message;

/**
 * Value object representing a URI.
 */
interface UriInterface
{
    /**
     * Retrieve the scheme component of the URI.
     *
     * @return string The URI scheme.
     */
    public function getScheme();

    /**
     * Retrieve the authority component of the URI.
     *
     * @return string The URI authority, in "[user-info@]host[:port]" format.
     */
    public function getAuthority();

    /**
     * Retrieve the user information component of the URI.
     *
     * @return string The URI user information, in "username[:password]" format.
     */
    public function getUserInfo();

    /**
     * Retrieve the host component of the URI.
     *
     * @return string The URI host.
     */
    public function getHost();

    /**
     * Retrieve the port component of the URI.
     *
     * @return null|int The URI port.
     */
    public function getPort();

    /**
     * Retrieve the path component of the URI.
     *
     * @return string The URI path.
     */
    public function getPath();

    /**
     * Retrieve the query string of the URI.
     *
     * @return string The URI query string.
     */
    public function getQuery();

    /**
     * Retrieve the fragment component of the URI.
     *
     * @return string The URI fragment.
     */
    public function getFragment();

    /**
     * Return an instance with the specified scheme.
     *
     * @param string $scheme The scheme to use with the new instance.
     * @return static A new instance with the specified scheme.
     * @throws \InvalidArgumentException for invalid schemes.
     * @throws \InvalidArgumentException for unsupported schemes.
     */
    public function withScheme($scheme);

    /**
     * Return an instance with the specified user information.
     *
     * @param string $user The user name to use for authority.
     * @param null|string $password The password associated with $user.
     * @return static A new instance with the specified user information.
     */
    public function withUserInfo($user, $password = null);

    /**
     * Return an instance with the specified host.
     *
     * @param string $host The hostname to use with the new instance.
     * @return static A new instance with the specified host.
     * @throws \InvalidArgumentException for invalid hostnames.
     */
    public function withHost($host);

    /**
     * Return an instance with the specified port.
     *
     * @param null|int $port The port to use with the new instance; a null value
     *     removes the port information.
     * @return static A new instance with the specified port.
     * @throws \InvalidArgumentException for invalid ports.
     */
    public function withPort($port);

    /**
     * Return an instance with the specified path.
     *
     * @param string $path The path to use with the new instance.
     * @return static A new instance with the specified path.
     * @throws \InvalidArgumentException for invalid paths.
     */
    public function withPath($path);

    /**
     * Return an instance with the specified query string.
     *
     * @param string $query The query string to use with the new instance.
     * @return static A new instance with the specified query string.
     * @throws \InvalidArgumentException for invalid query strings.
     */
    public function withQuery($query);

    /**
     * Return an instance with the specified URI fragment.
     *
     * @param string $fragment The fragment to use with the new instance.
     * @return static A new instance with the specified fragment.
     */
    public function withFragment($fragment);

    /**
     * Return the string representation as a URI reference.
     *
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
     * @return string
     */
    public function __toString();
}

One important thing you should know is that the implementation of the URI is always immutable. This means that every method returns a new instance of the object instead of a reference to the existing object.

Wrong script:

<?php
$uri = new Uri();
$uri->withHost(‘127.0.0.1’);
$uri->withPort(8080);
echo $uri; // empty output

Correct script:

<?php
$uri = new Uri();
$uri = $uri->withHost(‘127.0.0.1’);
$uri = $uri->withPort(8080);
echo $uri; // output: http://127.0.0.1:8080

// or by using fluent interface

$uri = (new Uri())->withHost('127.0.0.1')->withPort(8080);

Using Guzzle

As last part of this article we make use of the popular HTTP client library Guzzle. The library offers all the stuff HTTP provides. In our example we explicitly make use of the Guzzle PSR-7 implementation of the RequestInterface. The class we use is \GuzzleHttp\Psr7\Request.

If you like to test the script, please feel free to install Guzzle using composer in your local directory (where all the other files mentioned above are) :

composer.phar require guzzlehttp/guzzle:~6.0

The code we use is this:

<?php

require_once 'vendor/autoload.php';

$request = new \GuzzleHttp\Psr7\Request('GET', 'http://127.0.0.1:8080/mypath?foo=bar&baz=zoz');

$client = new \GuzzleHttp\Client();
$response = $client->send($request);
echo $response->getBody();

The Response of the script should be the same as before. What are the advantages of this approach?

  1. We create a PSR-7 compatible Request with Guzzle.
  2. The Guzzle HTTP Client follows the standard. So the Request must not be created by the Guzzle library. Use whatever you want.

The next part of the series describes the HTTP Response.

4 thoughts on “PSR-7 Standard – Part 2 – Request and URI

Leave a Reply

Your email address will not be published. Required fields are marked *