Writing PSR-7 middleware
Within Slim 3’s Request object, there’s a method called getIp() which is determines the client’s IP address. However it’s rather simplistic and potentially risky as it checks the X-Forwarded-For header with no ability to ignore this header or whitelist whether we trust the final proxy in the chain.
Determining the client’s IP address is an ideal use-case for middleware as we can inspect the headers in the request and then set an attribute so that middleware further down the chain can use it. Following my rather unimaginative naming, I’ve called it rka-ip-address-middleware.
However, in this article, I want to look at how easy it is to write useful PSR-7 middleware.
Minimal valid middleware
Writing middleware is really easy, so lets look at the basics of how I built this. You simply need to provide a callable that has this function signature:
function(RequestInterface $request, ResponseInterface $response, callable $next) : ReponseInterface
We are given a request and response object along with the next middleware in the chain and must return a response. Our middleware function must also be a callable itself and this is easy to do with a class by using the __invoke() magic method.
A minimal class that acts as PSR-7 middleware therefore looks something like this:
namespace RKA\Middleware; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; class IpAddress { public function __invoke( ServerRequestInterface $request, ResponseInterface $response, callable $next = null ) { if ($next) { $response = $next($request, $response); } return $response; } }
This middleware doesn’t actually do anything by itself, but does do the minimum required:
- Call the $next middleware in the chain
- Return a $response object
Doing something interesting
For a piece of middleware to be useful, it has to do something. Generally, this means that we should look at the $request or $response objects that we have received and do something with them. For this example, we need to look at the request’s headers to determine the client’s IP address and then store it back into the request for use by subsequent middleware.
Obviously, if there is no subsequent middleware to call, then we don’t need to do this work anyway as there’s nobody to use it, so our code becomes:
public function __invoke( ServerRequestInterface $request, ResponseInterface $response, callable $next = null ) { if (!$next) { return $response; } $ipAddress = $this->determineClientIpAddress($request); return $next($request, $response); }
(The implementation details of determineClientIpAddress are irrelevant for this discussion.)
We have to store the $ipAddress back into the $request object. The way to do this is to use the server attributes which exists to provide messaging between the middleware in the chain.
We’ll call our new attribute “ip_address” and as PSR-7 messages are immutable, we use the withAttribute() method to create a new Request object:
$request = $request->withAttribute('ip_address', $ipAddress);
So our middleware handler is complete:
public function __invoke( ServerRequestInterface $request, ResponseInterface $response, callable $next = null ) { if (!$next) { return $response; } $ipAddress = $this->determineClientIpAddress($request); $request = $request->withAttribute('ip_address', $ipAddress); return $next($request, $response); }
Configuring middleware
One nice thing about using a class for your middleware is that it’s easy to configure. Let’s say that we want the user to be able to pick the name of the attribute to store the IP address into. We can do this quite easily by introducing a property and a constructor:
class IpAddress { protected $attribName = 'ip_address'; public function __construct($attribName = null) { if (!empty($attribName)) { $this->attribName = $attribName; } } public function __invoke( ServerRequestInterface $request, ResponseInterface $response, callable $next = null ) { if (!$next) { return $response; } $ipAddress = $this->determineClientIpAddress($request); $request = $request->withAttribute($this->attribName, $ipAddress); return $next($request, $response); } }
Adding middleware to your application
There are a number of PSR-7 middleware-aware frameworks, that have different method names for adding middleware:
- Slim 3: $app->add(new RKA\Middleware\IpAddress('client_ip'));
- Radar: $adr->middle(new RKA\Middleware\IpAddress('client_ip'));
- Expressive: $app->pipe(new RKA\Middleware\IpAddress('client_ip'));
To sum up
Middleware is a powerful way to inspect and act upon HTTP messages in your application. The PSR-7 standard is feature rich enough that with the attributes in ServerRequestInterface it provides a way to add additional information that can be used by subsequent middleware which is very useful and allows for a lot of flexibility in building your applications.
I like the last tip how to use it with different systems :-) .
Your example middleware allows $next to be optional/null, but most middleware handler implementations seem to have $next as a required argument of type callable (thus your signature should not default to null and the if (!$next) check isn't necessary).
Chris, "Most" is the key word in your sentence.
Stratigility's MiddlewareInterface's signature is:
Hmm, gotcha.
Hmm, why is this a perfect use-case for middleware?
As far as I can see, this is much better suited for a library that inspects PSR-7 requests… with something like this:
"`
$ip = MyLibrary::getIp($anyPsr7Request);
"`
The request attributes are a nice touch and useful for information like this, but they're another hidden contract that I wouldn't want to get started on…
What do you think?
Thanks for raising this point, because I didn't think of it this way before. But you are right, it would probably be better as a singleton.
I think it depends how you think about it.
I think of the IP address as a property of the Request, not as something to go and fetch. By using Middleware, any subsequent use of the request has this property, which I prefer.
Personally, I distrust Singletons as they look a lot like globals to me. e.g. if the list of valid proxies is configurable, then it would be MyLibrary::setValidProxies() which could be called from anywhere…
However, whatever approach works for you is fine :)
s/resposne/response
Good article!
Thanks Hannes. I've fixed. :)
Nice idea, however we don't currently have any standard PSR-7 middleware. The problem has already been highlighted by Chris Eskow with next being mandatory for some projects, but even worse some projects only have the request parameter (see php-http/httplug: http://php-http.readthedocs.org/en/latest/httplug/).
We're just doing what already happened with Symfony/HttpKernelIbterface middleware: redefining each our own middleware standard…
Maybe a wiser option would be to make a new PSR for message middleware?
> but even worse some projects only have the request parameter (see php-http/httplug: http://php-http.readthedocs.org/en/latest/httplug/).
Don't mix client and server side middleware please :), httplug has also a concept of middleware in its plugin client : https://github.com/php-http/plugins/blob/master/src/Plugin.php, but it's very specific to a client implementation (like the first callable which is mandatory to reboot the chain of execution with a redirect response)
IMO, different implementation is good for a start it allows to confront idea and not blindly follow a rule, PSR7 is still quite new and not need to rush for X standards. (but yeah having a good middleware standard is something needed for the long term)
Don't worry, I'm not mixing them ;) . My point is: whether you need a client or a server side middleware, you still have many ways to implement it.
Some requires you to pass the next middleware as an argument like here, some let you the choice (e.g. with httplug and HttpKernelInterface where we can inject the next middleware in the constructor as a class property).
Those two implementations have been around for a while, even outside of PHP. It's time to create a standard and stick to it, before we get too many "PSR-7 middleware" implementations.
What is the best way to add middleware to a certain route?
When I write $app->get(…)->add(new My\Middleware() ) I assume new will be called – the object will be instantiated – whether the route is matched or not. It looks like some overhead to me.
Register it with the DIC and then let Slim instantiate it.
However, if you don't need to inject any dependencies into the My\Middleware class, then you can skip the DIC registration as Slim will automatically instantiate it for you, so the minimum you actually need is:
Great read, was looking for some practical examples of how to use it.
btw, the CakePHP framework also supports middleware as of 3.4:
https://book.cakephp.org/3.0/en/controllers/middleware.html