HTTP Middleware in Laravel 4.1
As of Laravel 4.1, the framework now uses StackPHP's implementation of Symfony's HttpKernel. This means we can add our own middleware to our HTTP layer!
What is middleware good for? Well, lots of things - any logic you might stick between the request/response life cycle that's not necessarily part of your application logic. For example:
- Adding session support
- Parsing query strings
- Implementing rate-limiting
- Sniffing for bot traffic
- Adding logging
- Parsing request JSON
- Anything else related to the request/response lifecycle
Laravel's Implementation
The first thing to know is that to add Middleware to our request/response cycle, the Decorator Pattern is in use.
Whether you understand the design pattern or not, this basically just means that each middleware (decorator) we use must implement HttpKernelInterface
.
Next, we need to know how to add middleware to our app. Luckily, the Foundation/Application
has this functionality built-in for us:
/**
* Add a HttpKernel middleware onto the stack.
*
* @param string $class
* @param array $parameters
* @return \Illuminate\Foundation\Application
*/
public function middleware($class, array $parameters = array())
{
$this->middlewares[] = compact('class', 'parameters');
return $this;
}
Let's create some middleware of our own.
Simple Rate Limiting Example
For the sake of a quick and dirty example (this is both), let's rate limit our site by IP address (you hopefully will come up with a much better or more interesting idea on your own).
First, create the rate-limiting class implementing HttpKernelInterface
. This will be our middleware. I created this example one within my application library code under the namespace Fideloper\Http
:
<?php namespace Fideloper\Http;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
class RateLimiter implements HttpKernelInterface {
/**
* The wrapped kernel implementation.
*
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
protected $app;
/**
* Create a new RateLimiter instance.
*
* @param \Symfony\Component\HttpKernel\HttpKernelInterface $app
* @return void
*/
public function __construct(HttpKernelInterface $app)
{
$this->app = $app;
}
/**
* Handle the given request and get the response.
*
* @implements HttpKernelInterface::handle
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @param int $type
* @param bool $catch
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle(SymfonyRequest $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
{
// Handle on passed down request
$response = $this->app->handle($request, $type, $catch);
$requestsPerHour = 60;
// Rate limit by IP address
$key = sprintf('api:%s', $request->getClientIp());
// Add if doesn't exist
// Remember for 1 hour
\Cache::add($key, 0, 60);
// Add to count
$count = \Cache::increment($key);
if( $count > $requestsPerHour )
{
// Short-circuit response - we're ignoring
$response->setContent('Rate limit exceeded');
$response->setStatusCode(403);
}
$response->headers->set('X-Ratelimit-Limit', $requestsPerHour, false);
$response->headers->set('X-Ratelimit-Remaining', $requestsPerHour-(int)$count, false);
return $response;
}
}
Because we implement HttpKernelInterface
, we need to have a handle
method in our class.
The handle
method will first call the handle
method on the previous middleware to get a response object. Understanding why we do this will come from understanding the Decorator Pattern. For now, if that's confusing just know that we get a response which we can act on by calling $this->app->handle()
in our handle
method (non-negotiable).
Then we can do some logic around rate limiting. I rate limit based on the IP address, creating a unique cache key based on user IP address. This key will expire after one hour (60 minutes), so our rate count will be valid for that long. After the hour, the count will reset back to zero.
If we reach the rate limit, we set the content and status code of the response and return that for the next middleware (or the app) to finally use.
Service Provider
Once we have a middleware in place, we need to use it. For that, I rely on the trusty old service providers. I chose to create this in the same namespace:
<?php namespace Fideloper\Http;
use Illuminate\Support\ServiceProvider;
class HttpServiceProvider extends ServiceProvider {
/**
* Register the binding
*
* @return void
*/
public function register()
{
$this->app->middleware( new RateLimiter($this->app) );
}
}
Note that I call the middleware
method on the Application
object. I add in the RateLimiter class and pass it an instance of the application, as required by the class.
That's it! We've added some HTTP middleware to our application!