Repository navigation
Conversation
Jersey was registered as a filter mapped to /* when the handler was initialized, and it ran the resource before passing the request on. Filters that an application added afterwards, through onStartup() or getServletContext().addFilter(), therefore ran only after the resource had executed and written its response, so a filter that ended the request without calling chain.doFilter() could not stop it. Jersey now runs as the servlet at the end of the filter chain, the way DispatcherServlet does in the Spring module. Every filter registered with the servlet context runs first, in registration order, whether it was added before or after initialize(). JerseyHandlerFilter no longer continues the chain once Jersey has written the response. Calling initialize() a second time no longer throws a NullPointerException.
| @@ -229,4 +235,45 @@ public InjectionManager getInjectionManager() { | |||
| public Servlet getServlet() { | |||
There was a problem hiding this comment.
Now that Jersey runs as a real servlet, should this return jerseyServlet instead of null? It's public, and returning the actual terminal servlet would match what the handler routes to. If keeping null is intentional (e.g. to avoid exposing the private inner class), a short comment explaining that would help.
| throws IOException, ServletException { | ||
| // Jersey produces the response, so it ends the chain instead of passing the request on. Anything after it | ||
| // would only run once the resource had already executed. | ||
| handle(servletRequest, servletResponse); |
There was a problem hiding this comment.
With this change JerseyHandlerFilter is no longer registered as a filter, and doFilter() ignores the chain, so it's effectively just a holder for handle(). Keeping implements Filter for compatibility makes sense since the class is public, but could we update the class Javadoc (it still describes it as a "Servlet filter class") and consider marking doFilter() @Deprecated so nobody relies on it as a chain-continuing filter?
Issue #, if available:
Description of changes:
Servlet filters registered on a Jersey handler now run before the JAX-RS resource, the way they run in front of a servlet in a servlet container.
The problem
JerseyLambdaContainerHandler.initialize()registered Jersey as a filter mapped to/*, andJerseyHandlerFilter.doFilter()ran the resource before callingfilterChain.doFilter(). The default factory methods (getAwsProxyHandler,getHttpApiV2ProxyHandler) callinitialize()before returning, so any filter an application added afterwards, throughonStartup()orgetServletContext().addFilter(), was placed after Jersey. It ran only once the resource had executed and written its response. A filter that ended the request without callingchain.doFilter()could change the status code, but the resource had already run and its body was returned.Building the handler with the constructor, registering filters, and then calling
initialize()avoided this, but nothing documented that the order mattered.The change
JerseyServlet, mapped to/*) passed todoFilter()as the terminal step of the chain. This is the same mechanism the Spring module uses forDispatcherServlet:FilterChainManageralready appends the servlet after every matching filter, so filters run first and in registration order, however and whenever they were registered.JerseyHandlerFilter.doFilter()no longer callsfilterChain.doFilter()after Jersey has written the response. The Jersey call moved to a package-privatehandle()that the servlet uses.initialize()because the filter chain reads its name from the servlet config when it builds the chain. The base class'sinitialize()isn't reused for this because it throws a checked exception that Jersey's publicinitialize()doesn't declare.initialize()a second time used to throw aNullPointerException, because the duplicate filter registration returned null. It now works.Reordering the calls inside
JerseyHandlerFilter.doFilter()was not enough: when a filter ends the request, control just returns to the Jersey filter, which can't tell and runs the resource anyway.Behavior changes for applications
chain.doFilter()now stops the request before Jersey runs, which is standard servlet behavior. In applications whose filters forgot to continue the chain, those requests no longer reach Jersey.getServletContext().getFilterRegistration("JerseyFilter")returns null. Jersey is registered as the servletJerseyServletinstead.No changes outside the Jersey module.
Testing
JerseyFilterChainTest, 96 cases across API Gateway, ALB and HTTP API events, all sent throughproxyStream. Without the change, 48 fail and 3 error:onStartup()after a factory method that ends the request must stop it before the resource runs/filter-chain/admin/*must leave other paths served, and must apply to the other spellings of a protected path that Jersey routes to it (doubled slashes, matrix parameters, trailing slash, dot segments after the prefix, encoded?). Spellings that Jersey answers with 404 stay in the list as guards.initialize()run in registration orderinitialize()called twiceinitialize()registration order, forward dispatch from a filter registered beforeinitialize(), a filter that sets a response header before continuing, and a handler with no filters.gha_build.sh jersey true true: the Jersey archetype project builds with Maven and Gradle against this change and its tests pass, and the Jersey pet-store sample builds with Maven and Gradle.serverName_albHostHeader_returnsHostHeader,getMimeType_mimeTypeOfJavascript_expectApplicationJavascript).Related
The same production change applies to the other two maintained lines. The diffs to the main source files are identical across all three apart from
javaximports on 1.x. Only the new test's scaffolding differs: on both backports Jackson 2'sreadValuethrows a checkedIOException, and on 1.x the test filters implementinitanddestroybecause the Servlet 3.1Filterinterface has no default methods.By submitting this pull request