From HTTP Messages to PSR-7: What’s It All About?

Share this article

From HTTP Messages to PSR-7: What’s It All About?

This article was peer reviewed by Andrew Carter. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

The PHP Framework Interoperability Group (PHP-FIG) has relatively recently approved another proposal, the PSR-7: HTTP Messages Interface. The document crystallizes HTTP messages into 7 interfaces which a PHP library should implement if they subscribe to the specification. In PSR-7 By Example, Matthew Weier O’Phinney, editor of the PSR, gives an interesting overview of the specification. So what is it?

Image of digital question mark, indicating confusion and pending explanation

If you type bbc.co.uk in your browser, you go straight to the homepage of the BBC, but a number of things might have taken place between the time the browser sent an HTTP request to the server and it getting a response back.

Here’s a sample raw request.

GET / HTTP/1.1
Host: bbc.co.uk
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Accept: */*
Referer:

It’s always made up of a request line (GET / HTTP/1.1), a number of header field lines listed as <key>: value, a single empty line and an optional body for any data payload (for example query parameters or post data).

The blank line after the zero or more header lines must be a CRLF by itself. This means 2 characters – an ASCII 13 (Carriage Return), followed by ASCII 10 (Line Feed) or \r\n.

Let’s send this request from the command line via curl and see the response:

curl -i -H "Host: bbc.co.uk" -H "User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)" -H "Accept: */*" -X GET http://bbc.co.uk

HTTP/1.1 301 Moved Permanently
Content-Type: text/html
Date: Sun, 02 Oct 2016 20:49:42 GMT
Location: http://www.bbc.co.uk/
Connection: Keep-Alive
Content-Length: 0

Moved? There was a redirect. Then, let’s follow the trail and make a request to http://www.bbc.co.uk/ instead:

curl -i -H "Host: www.bbc.co.uk" -H "User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)" -H "Accept: */*" -X GET http://www.bbc.co.uk | less

HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html; charset=utf-8
ETag: W/"29c44-MXo+6t7MoPRad358MSWqwA"
X-Frame-Options: SAMEORIGIN
x-origin-route: xrt-ext
Content-Length: 171076
Date: Sun, 02 Oct 2016 20:54:27 GMT
Connection: keep-alive
Set-Cookie: BBC-UID=15c73fe11704a0731344da5ec13869204c1a22a0c7b444d60a708762e631ac0c0Mozilla/5.0%20(compatible%3b%20MSIE%209.0%3b%20Windows%20NT%206.1%3b%20Trident/5.0); expires=Thu, 01-Oct-20 20:54:27 GMT; path=/; domain=.bbc.co.uk
X-Cache-Action: HIT
X-Cache-Hits: 1223
X-Cache-Age: 55
Cache-Control: private, max-age=0, must-revalidate
Vary: Accept-Encoding, X-CDN, X-BBC-Edge-Scheme

<!DOCTYPE html>
<!--[if lte IE 9]>
  <html lang="en-GB" class="no-js no-flexbox no-flexboxlegacy">
<![endif]-->
<!--[if gt IE 9]><!-->
  <html lang="en-GB" class="no-js">
<!--<![endif]-->
<head>

That’s more like it. The first line, HTTP/1.1 200 OK, is the status line. Then we have headers, in a similar pattern to requests – <key>: value, an empty line, and the response body. Note: We passed the output through less so that we can see the first part of the response.

Requests and responses can be broken down into a message line, a number of header lines and body lines. The commonalities can be abstracted in an interface (MessageInterface) which the request (RequestInterface) and response (ResponseInterface) can extend with their distinct flavor of HTTP message.

PHP doesn’t only run within a web environment, and web requests might have originated from APIs. The ServerRequestInterface was designed to take care of other types of HTTP requests.

The other three interfaces are a further abstraction of specific aspects in messages. Looking back at the request message line:

GET / HTTP/1.1

This comprises:

  • METHOD: Although RFC 2616 defines safe and idempotent types of methods, for general applications, it’s sufficient to identify them by name – GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD and TRACE. CONNECT is reserved for use with a proxy that can dynamically switch to being a tunnel e.g. SSL tunneling.

  • TARGET: This is the URI or our request target, and things get a bit interesting here as we can have:

    • origin-form – path and query string of the URI. The query string may or may not be present.
    • absolute-form – an absolute URI.
    • authority-form – the authority part of a URI, made up of a maximum of 3 parts – user-info (optional), host and port (optional). The user-info may require a password too – user:password. We end up with a pattern of user:password@host:port. The user-info may also have require an
    • asterisk-form – just the string, *

    We end up with scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]. This part of the request message was abstracted to the UriInterface.

  • VERSION: There’s limited choice here as HTTP/1.1 is the current version. Prior to that, we had HTTP/1.0, and the next draft version is HTTP/2.0

Also, file uploads required special attention. In non-SAPI environments, the $_FILES environment variable is empty and in some situations such as non-POST requests, $_FILES isn’t populated at all. The UploadedFileInterface was designed to allow for a more consistent interaction with files.

The message (request or response) needs to be able to handle large data efficiently for both client and server. PHP has had streams built in since 4.3.0. The StreamInterface part of PSR-7 provides a wrapper around common operations and serialization of an entire stream to a string.

Challenges

The path to PSR-7 was paved with strong debate and differing opinions at every step of the way.

  • Immutable objects – The immutability or mutability of objects was one of the most hotly-debated points, and PSR-7 eventually settled for this:

    The proposal models messages and URIs as value objects.

    Messages are values where the identity is the aggregate of all parts of the message; a change to any aspect of the message is essentially a new message. This is the very definition of a value object. The practice by which changes result in a new instance is termed immutability, and is a feature designed to ensure the integrity of a given value.

    What this means is that each time you make any change to a message object, you end up with a new copy. The complexity of URI, headers and streams require an assurance that a full adoption of immutability by all collaborators offered to the designers of the interfaces.

    With immutability, any state change you make requires you to assign the result.

     $request = $request->setHeader('Cache-Control', 'public');
    

    Any method that changes the state of the current message returns an instance of it with the changes made. As long as a result assignment is made, you can chain any number of changes in a fluent-like syntax.

    $request = $request
            ->setHeader('Cache-Control', 'public')
            ->addHeader('Cache-Control', 'max-age=18600')
            ->setStatus(200);
    

    The methods having the prefix “with” must conform to the immutability requirement. A concern is that supporting mutability in one of the message interfaces means enforcing it across all the other interfaces.

    On the other hand, it has been highlighted that PSR-7 Objects Are Not Immutable as they are generally thought to be. It’s worthy of note that similar HTTP object implementations in Ruby and Node are mutable by design. So, PHP is in good company.

  • Nomenclature – These objects are designed as interfaces. Isn’t calling it MessageInterface superfluous? A method signature taking a request and response ends up being too long. Compare the following:

    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) : ResponseInterface
    {
    }
    
    public function __invoke(ServerRequest $request, Response $response, callable $next) : Response
    {
    }
    

    Aliasing is the suggested solution for those who prefer to drop the Interface suffixes.

  • Middleware – We’ve got the message interfaces which are like the ingredients for making a cake, perfectly measured out. I want a cake to consume but I don’t know how to bake. How about a recipe? PSR-7 only prescribes a standard for the definition of a request and response. How do we move from request to response? That “middleman” that does the leg work between the request and response is called middleware.

    The next step would be a unification of how to plumb PSR-7 interfaces so that applications and frameworks that conform to them can be completely swappable. This part of the debate which borders on providing a template for an interoperable implementation of PSR-7, has been moved to the separate PSR-15: HTTP Middlewares.

Usage

A number of libraries and frameworks have added support for PSR-7 in different ways.

  1. Symfony – The HttpFoundation Component is one of the most popular OOP abstractions of the HTTP specification prior to PHP-FIG. With the emergence of PSR-7, Symfony opted for a PSR-7 Bridge which converts HttpFoundation objects from and to objects which implement PSR-7 message interfaces.

  2. Zend Framework (ZF) – They came up with a Composer package, zendframework/zend-diactoros, of implementations of the HTTP message interfaces, not surprising as the editor of PSR-7 is the ZF Project Lead. Diactoros goes a step further by including a server that is similar to the one provided by http.Server in NodeJS.

  3. Slim – PSR 7 and Value Objects describes a flexible approach that makes Slim accept any implementation of PSR-7. That is, if the one provided by Slim doesn’t suffice.

  4. Guzzle – Being an HTTP client library, PSR-7 is of great relevance to this library. They built the guzzlehttp/psr7 Composer package, a message implementation of PSR-7, which they rely on. Guzzle and PSR-7 gives an excellent overview of their take on the specification.

  5. Aura – They’ve included Aura.Router, a web router implementation of PSR-7 in their collection of independent library packages. All router objects are managed by a RouterContainer from which you retrieve an instance of a Map object. Each method of this Map object may accept an HTTP message interface as one of its arguments.

  6. HTTPlug – A package of interfaces that allow library and application developers to create HTTP clients that are fully compatible with PSR-7. The HttpClient sends a PSR-7 Request and returns a PSR-7 response. See Breaking Free from Guzzle5 with PHP-HTTP and HTTPlug for a practical way of using this package.

Packagist has a list of PSR-7 implementations with a wide range of popularity or acceptance. However, a developer still has the following usage options:

  1. Direct – As the specification states, while Psr\Http\Message\MessageInterface MAY be implemented directly, implementors SHOULD implement Psr\Http\Message\RequestInterface and Psr\Http\Message\ResponseInterface. The simplest way is to install the package with Composer.

    composer require psr/http-message
    
  2. Indirect – The interfaces are used indirectly through an adapter. For example, PSR-7 support in Symfony is through the PSR HTTP Message Bridge, a library designed to convert Symfony Request and Response objects into objects that are fully compatible with PSR-7, and from PSR-7 back to Symfony objects.

  3. Partial – You might be interested in the more generic interfaces such as StreamInterface, UriInterface and UploadedFileInterface. Nothing stops you from implementing them in a non-HTTP messages context. The package is available on Packagist and Composer is your friend.

Conclusion

The PHP community should be applauded for coming together on the fundamental principle of how we interact with and manage HTTP requests and responses. PSR-15 transcends this and the intensity of the debate surrounding middlewares will not go away very soon, neither should the draft be expected to be accepted quickly. In the meantime, PSR-7 is there for all to embrace.

What do you think about PSR-7? Do you use it and subscribe to it, or do you feel like it’s just a layer of complication? We’d like to hear from you!

Frequently Asked Questions about HTTP Messages and PSR-7

What is the significance of PSR-7 in HTTP messaging?

PSR-7, also known as HTTP message interfaces, is a set of common interfaces defined by the PHP Framework Interop Group. It provides a standardized way to represent HTTP messages in PHP, making it easier for developers to create and work with HTTP messages. PSR-7 is significant because it allows for interoperability between different PHP libraries and frameworks that work with HTTP messages. This means that if a library or framework follows the PSR-7 standard, it can be used with any other library or framework that also follows this standard.

How does PSR-7 differ from traditional HTTP messaging?

Traditional HTTP messaging in PHP often involves working with global variables and functions provided by PHP, such as $_GET, $_POST, and $_SERVER. This can lead to code that is difficult to test and maintain. PSR-7, on the other hand, provides an object-oriented approach to HTTP messaging. It defines interfaces for HTTP messages, and these interfaces can be implemented by any class, providing a more structured and testable way to work with HTTP messages.

What are the main components of PSR-7?

PSR-7 consists of several interfaces that represent different aspects of an HTTP message. These include the RequestInterface, ResponseInterface, ServerRequestInterface, and UploadedFileInterface, among others. Each of these interfaces defines methods for working with a specific part of an HTTP message. For example, the RequestInterface includes methods for getting and setting the HTTP method, the request target, and the headers.

How do I create a PSR-7 message?

To create a PSR-7 message, you need to use a class that implements one of the PSR-7 interfaces. There are several libraries available that provide such classes, including Guzzle, Slim, and Zend Diactoros. Once you have chosen a library, you can create a new instance of a PSR-7 message class and use its methods to set the properties of the message.

How do I send a PSR-7 message?

Sending a PSR-7 message involves passing it to a function or method that knows how to handle it. This could be a function provided by a PSR-7 library, or it could be a method in your own code. The important thing is that the function or method needs to be able to work with the PSR-7 interfaces.

Can I use PSR-7 with my existing PHP code?

Yes, you can use PSR-7 with your existing PHP code. However, you may need to make some changes to your code to make it compatible with the PSR-7 interfaces. This could involve replacing uses of PHP’s global variables and functions with calls to PSR-7 methods, or it could involve adding type hints to your functions and methods to indicate that they accept PSR-7 messages.

What are the benefits of using PSR-7?

Using PSR-7 can make your code more structured, testable, and maintainable. It can also make it easier to integrate your code with other libraries and frameworks that use PSR-7. Additionally, because PSR-7 is a standard, using it can make your code more understandable to other developers who are familiar with the standard.

Are there any drawbacks to using PSR-7?

One potential drawback to using PSR-7 is that it can be more complex than traditional PHP HTTP messaging. This is because PSR-7 involves working with objects and interfaces, rather than simple variables and functions. However, this complexity can also be seen as a benefit, as it can lead to more robust and flexible code.

How do I handle file uploads with PSR-7?

PSR-7 includes the UploadedFileInterface, which provides methods for working with uploaded files. To handle a file upload, you would create an instance of a class that implements this interface, and then use its methods to access the uploaded file.

Can I use PSR-7 with non-HTTP protocols?

PSR-7 is specifically designed for HTTP messaging, so it may not be suitable for use with other protocols. However, the principles behind PSR-7, such as the use of interfaces and object-oriented programming, can be applied to other areas of PHP development.

Deji AkalaDeji Akala
View Author

Deji, a Zend Certified PHP Engineer, works as a Software Engineer with the British Council in London. He's passionate about Open Source, contributes to Drupal and speaks at Drupal Camps. He has worked as a dentist, teacher of English as a Foreign Language and radio journalist. Oh yes, he's four times a dad and supports Arsenal FC.

BrunoSOOPHPPHPphp-figpsrpsr-7standards
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week