Servlet filters. Straightforward, right? A request comes in, goes through each filter in the chain, hits the servlet and then the response goes the other way. Sort of. What could possibly go wrong?
Not much…unless you throw error handling into the mix. The trouble is, the servlet specification doesn’t lay down many rules on how error pages are invoked. My expectation was for the servlet container to transparently offload the request to the error page and then execute the filters in reverse. In other words, the filters shouldn’t be or need to be aware that the request has gone via the error page. The request should behave like any other request.
Tomcat seriously disabused me. It takes a different approach in which the main request almost completes (all the filters execute in reverse) before the request goes to the error page. The following diagram should make it clear what I mean:
Having read the servlet specification, it does make a bit of sense. One of the few requirements is that the servlet container should pass the original, unwrapped request and response to the error page, unless a filter is configured to execute for the ERROR dispatcher. Unfortunately, if you add something to the request in your filter, or attach a variable to the thread, it won’t be available to the error page if your filter also removes that data.
That may all be a bit too abstract, so how about a concrete example? Some of the filters in Apache Shiro bind an object called the security manager to the request’s thread. Any security code can then easily grab it on demand. When the request has finished, the filter removes (unbinds) the security manager from the thread. That’s just good practice. But what happens if the error page wants to access the security manager?
Hopefully it’s obvious from the discussion so far that you need to add Shiro’s filter to the ERROR dispatcher as well as the REQUEST one. This should work. When the servlet container triggers the error page, it first executes the filter. But that’s not what happened when I tried it. Instead, the error page threw an exception saying that no security manager was bound to the current thread.
What had gone wrong? After some debugging, I discovered that a super class of the filter, OncePerRequestFilter, was skipping execution of the filter if it detected that it had already been processed. How did it do that? By checking whether a particular request attribute was set. In this case, the attribute was still there from the main request, but the security manager had been unbound from the request thread.
This problem didn’t exhibit in Jetty at all, which is kind of annoying because you would hope that code that runs on one container would also run on another. Fortunately, there is a simple moral to this story: filters should always clean up after themselves when they finish and never leave stuff in the request, session, or thread.