Using peel for HTTP Exceptions
Middleware is a powerful concept that modern frameworks use to bridge between separate parts of a stack - namely, a HTTP layer and application. A request comes in, is validated/manipulated by middleware, triggers behavior in the application, and then the response goes back out through the same middleware for further validation/manipulation. Modern web frameworks use this technique for authentication, parameter mappings, header attachments, among other things, all of which keep the application more focused on business logic and less on HTTP interface.
What happens when things go wrong? This can be especially important for middleware that checks on authentication or validation. There is a standard interface for middleware that allows early exit of the response in these cases.
function someMiddleware($request, $response, $cb) {
if (bad things) {
$response = $response->withStatus(500);
return $response;
}
return $cb($request, $response);
}
In this example, if 'bad things' happen, then the middleware performs an early exit with a 500 status code. Otherwise, the next piece of middleware (callback $cb) is executed. This is a great way to skip out of unnecessary processing along the stack for bad requests, especially if that extra processing is something that the visitor should not be allowed to perform do to 'bad things', but it does lead to a high amount of uncertainty with interface. If the app needs to return consistent errors than you have to ensure all of the middleware is doing so. This can get really annoying if you are rolling dependencies as middleware.
There's another way to skip out of a stack early. Exceptions. Throw an exception, and the program will bail early. Node has a great library for this, boom, where you can specify the type of error (like, boom.notFound), pass in some metadata as arguments, and then have a centralized error handler that looks for boom-flavored errors (if err.isBoom()) that can pull the metadata and status code appropriately.
PHP doesn't have a good library for something like this. Yes, it does have good SPL exceptions built in, though they don't map natively to HTTP errors. One could piggy-back off of the 'code' property of an exception, though that isn't strictly enforced and may cause problems with the app. The 'code' property is used to categorize exceptions according to your business needs, which should not be a 1:1 map with status codes. If you ignore this, though, you could use vanilla exceptions like this.
function someMiddleware($request, $response, $cb) {
if (bad things) {
throw new Exception('Not Found', 404);
}
return $cb($request, $response);
}
function errorHandler($request, $response, $e) {
$response = $response->withStatus($e->getCode());
$response->getBody()->write($e->getMessage());
return $response;
}
An alternative to this slightly hacky approach is a homegrown library of exception that are tailored to the HTTP interface. So I wrote one - peel. Using it is as simple as throwing an exception, and then catching it in the error handler and checking interface.
function someMiddleware($request, $response, $cb) {
if (bad things) {
throw new HttpError\BadRequest('Invalid JSON');
}
return $cb($request, $response);
}
function errorHandler($request, $response, $e) {
if ($e instanceof HttpErrorInterface) {
$response = $response->withStatus($e->getStatusCode());
$response->getBody()->write($e->getStatusMessage());
return $response;
}
// else this is a vanilla exceptions
$response = $response->withStatus(500);
$response->getBody()->write('Internal Server Error');
return $response;
}
The error handler is fairly generic, now that there is an interface to follow, so I also wrote up a standardized version called crash-pad. I borrowed heavily from the standard error response that node uses here, as I couldn't find any other good examples out there. Maybe JSON-API? Not sure. In the meantime, crash-pad includes the status code, status message, and the actual error message from the Exception on all outgoing error messaging.
All of the PHP middleware I'm using now leans on peel for expressive error handling. This allows the middleware to throw specific types of errors and break out of the stack appropriately while allowing the error handler to determine response format. I may eventually tweak peel to allow additional metadata, depending on the error, through for now I'm pretty happy with the standard status information and an optional message.
Comments (0)