Tutorial: Error Handlers

Error Handlers are code that should execute if an exception is raised somewhere in the web application. Eynnyd provides you with a way to associate an Exception type with a function to execute if that exception is raised.

An error handling function takes two forms: pre_response and post_response. More on this below.

This tutorial builds on the prior one on response interceptors, so if there is a piece you don’t understand here it was likely covered there.

First we will show you the code and then we will explain the relevant parts (AKA the parts not in the prior tutorials).

# hello_world_app.py
import logging

from eynnyd import RoutesBuilder
from eynnyd import EynnydWebappBuilder
from eynnyd import ResponseBuilder
from eynnyd import ErrorHandlersBuilder
from http import HTTPStatus

LOG = logging.getLogger("hello_world_app")

def hello_world(request):
    return ResponseBuilder() \
        .set_status(HTTPStatus.OK) \
        .set_utf8_body("Hello World")\
        .build()

def log_request(request):
    LOG.info("Got Request: {r}".format(r=request))
    return request

def log_response(request, response):
    LOG.info("Built Response: {s} for Request: {r}".format(s=response, r=request))
    return response

class UnAuthorizedAccessAttemptException(Exception):
    pass

def raise_unauth_for_missing_header(request):
    if "AUTH" not in request.headers:
        raise UnAuthorizedAccessAttemptException("Must have an auth header to be granted access.")
    return request

def handle_unauthorized_error(error_thrown, request):
    LOG.warn("Unauthorized attempt on url: {u}".format(u=request.request_uri))
    return ResponseBuilder() \
        .set_status(HTTPStatus.UNAUTHORIZED) \
        .set_utf8_body("Authorization failed with error: {e}".format(e=str(error_thrown))) \
        .build()

def build_application():
    routes = \
        RoutesBuilder() \
            .add_request_interceptor("/", raise_unauth_for_missing_header) \
            .add_request_interceptor("/hello", log_request) \
            .add_handler("GET", "/hello", hello_world) \
            .add_response_interceptor("/hello", log_response)\
            .build()

    error_handlers = \
        ErrorHandlersBuilder() \
            .add_pre_response_error_handler(UnAuthorizedAccessAttemptException, handle_unauthorized_error) \
            .build()

    return EynnydWebappBuilder() \
            .set_routes(routes) \
            .set_error_handlers(error_handlers) \
            .build()

application = build_application()

It should be noted that, so far in our tutorials, we have been using stand alone functions for all of our code. There functions can of course be encapsulated into objects so that a function like def log_request(request) could instead be def log_request(self, request) on a class and we would then use it at our callsites as logging_interceptors.log_request.

Building a Python Named Exception

The first relevant new piece to this tutorial is a custom named exception. Named Exceptions are superior to built in exceptions for a variety of reasons, mainly readability and allowing for explicit handling. That being said, you can use the built in python exceptions for this same purpose.

We haven’t done anything special with this named exception so it should look like your typical usage of python:

class UnAuthorizedAccessAttemptException(Exception):
    pass

Here we have defined an exception to be used when access is attempted which should be denied as unauthorized.

Raising an Exception

Now that we have an exception we need somewhere to raise it. For this tutorial we are going to do that in a new request Interceptor.

def raise_unauth_for_missing_header(request):
    if "AUTH" not in request.headers:
        raise UnAuthorizedAccessAttemptException("Must have an auth header to be granted access.")
    return request

Our new request Interceptor checks if there is a header keyed on “auth”. If not it raises our named exception. Of course we probably want to do more validation on this header to confirm that even if it is present it is valid, but we can leave that to other Interceptors (and out of this tutorial for simplicity).

The other thing we need to do, as expected is to register this request Interceptor into our Routes:

routes = \
    RoutesBuilder() \
        .add_request_interceptor("/", raise_unauth_for_missing_header) \
        .add_request_interceptor("/hello", log_request) \
        .add_handler("GET", "/hello", hello_world) \
        .add_response_interceptor("/hello", log_response)\
        .build()

As you can see, this Interceptor should run for all requests by using the root path “/”.

Writing a Error Handling Method

Next we need code that we want to run if this error is thrown. That looks like:

def handle_unauthorized_error(error_thrown, request):
    LOG.warn("Unauthorized attempt on url: {u}".format(u=request.request_uri))
    return ResponseBuilder() \
        .set_status(HTTPStatus.UNAUTHORIZED) \
        .set_utf8_body("Authorization failed with error: {e}".format(e=str(error_thrown))) \
        .build()

This function is built to handle errors thrown prior to having a response object (which is why it only takes parameters for the error_thrown and the request. If we threw our error from a Handler this code would look exactly the same. However, if we threw an error from a response Interceptor then this code would be different (the function would take a third parameter for the response).

Error Handlers return responses. In this case the response we are going to return is an UNAUTHORIZED status with a body of text describing the errors message.

Associating an Error Type with An Error Handler

Next we need to associate our named exception with the code we just wrote to handle that exception being thrown. We do this using the Eynnyd ErrorHandlersBuilder class:

error_handlers = \
    ErrorHandlersBuilder() \
        .add_pre_response_error_handler(UnAuthorizedAccessAttemptException, handle_unauthorized_error) \
        .build()

You can see we are associating our new handler handle_unauthorized_error to the named exception UnAuthorizedAccessAttemptException by calling add_pre_response_error_handler. It should be obvious that this method only works for errors raised from a pre response location (request Interceptors and handlers). Once there is a response (in response Interceptors) you would want to associate your exception with the code to call using a similar method called: add_post_response_error_handler.

Adding Error Handlers To The Web App

Finally we can add our Error Handlers to the Eynnyd webapp using the EynnydWebappBuilder:

return EynnydWebappBuilder() \
        .set_routes(routes) \
        .set_error_handlers(error_handlers) \
        .build()

Very similar to setting our routing object from earlier tutorials.