Implementing something like Django’s exception middleware in Play Framework

One thing that I’ve found to reduce boilerplate is being able to throw exceptions from helper methods and have those be converted to responses. For instance, I often want to see if an object exists, and if not, return a 404.

This is easy (and obvious) in Django - I can just create a middleware class and override the exception handling method. In Play it’s also easy, but it took a while to figure out where to put the logic. I think that I was looking for something as comprehensive as Django’s middleware - something that handles requests, responses, and exceptions, but that doesn’t exist in Play - those are all scattered around. To do something equivalent, you’d have to create a mixture of filters, actions, and creating your own ErrorHandler.

Looking at the exception handling alone, I find Django’s system to be much nicer out of the box. In Django, there is a list of middleware classes that it runs through, executing like handle_exception(request, exception) for each one. Each middleware class can be really focused and do one thing. In Play, it seems like there is only one class that can be the ErrorHandler (you set it in application.conf, which I’m sure leads to really bloated error handler classes unless people take the time to architect it otherwise. On the other hand, I guess you could argue that it’s weird for Django’s middleware classes to be concerned with requests, responses, and exceptions. Oh well, it works and it’s pretty simple for now, so I’m happy.

What didn’t work #

Here are some of the things I tried that didn’t work:

public class ImmediateHttpResponseAction extends Action.Simple {

    @Override
    public CompletionStage<Result> call(Http.Context ctx) {

        try {
            return delegate.call(ctx);
        } catch (ImmediateHttpResponse e) {
            return CompletableFuture.supplyAsync(() -> status(e.status, e.message));
        }
    }
}

What actually worked #

The solution just up being to just override the error handler. Nice and simple. So, to do this:

package utils;


public class ImmediateHttpResponse extends Exception {

    public int status;
    public String message;

    public ImmediateHttpResponse(int status, String message) {
        this.status = status;
        this.message = message;
    }

    public ImmediateHttpResponse(int status) {
        this.status = status;
        this.message = "";
    }

}
public class ErrorHandler extends DefaultHttpErrorHandler {

    @Inject
    public ErrorHandler(Configuration configuration, Environment environment, OptionalSourceMapper sourceMapper, Provider<Router> routes) {
        super(configuration, environment, sourceMapper, routes);
    }

    @Override
    public CompletionStage<play.mvc.Result> onServerError(Http.RequestHeader request, Throwable exception) {
        if (exception.getClass() == ImmediateHttpResponse.class) {
            ImmediateHttpResponse ex = (ImmediateHttpResponse) exception;
            return CompletableFuture.supplyAsync(() -> Results.status(ex.status, ex.message));
        } else {
            return super.onServerError(request, exception);
        }
    }
}

And then use it in your controller:

public class FooController extends Controller {
    private Foo getOr404(Long id) throws ImmediateHttpResponse {
        Foo foo = Foo.repository.byId(id);
        if (foo == null) {
            throw new ImmediateHttpResponse(404);
        }

        return contact;
    }

    public Result detail(Long id) throws Exception {
        Foo goo = getOr404(id);
        Form<Foo> form = formFactory.form(Foo.class);
        form = form.fill(foo);
        return ok(views.html.foo.detail.render(foo, form));
    }
}

And you’re in!

 
0
Kudos
 
0
Kudos

Now read this

Getting SQL Server to work with Play Framework

I’m using the jTDS driver with Play 2.5.8 (which uses HikariCP) and kept getting exceptions like the following when trying to create connection pools on startup. [info] application - Creating Pool for datasource 'default' [error]... Continue →