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

Deji Akala

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 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
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Accept: */*

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:" -H "User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)" -H "Accept: */*" -X GET

HTTP/1.1 301 Moved Permanently
Content-Type: text/html
Date: Sun, 02 Oct 2016 20:49:42 GMT
Connection: Keep-Alive
Content-Length: 0

Moved? There was a redirect. Then, let’s follow the trail and make a request to instead:

curl -i -H "Host:" -H "User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)" -H "Accept: */*" -X GET | 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=/;
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">
<!--[if gt IE 9]><!-->
  <html lang="en-GB" class="no-js">

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.


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')

    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.


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.


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!