This was a tricky one. I'd removed other Jersey filters to eliminate them from the problem, but missed a plain servlet filter hiding at the bottom of web.xml
:
<filter>
<filter-name>myFilter</filter-name>
<filter-class>com.application.my.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Removing this filter fixed the issue - form params showed up in the Jersey filter. But why? I dug deeper, narrowing down the problem to a single statement in MyFilter
:
request.getParameter("some_param")
I tried to simplify the problem even more by removing MyFilter
and making the same call in the Jersey filter (by injecting HttpServletRequest
) - but the form parameters still showed up. The issue appears to happen specifically when calling getParameter
on the org.apache.catalina.connector.RequestFacade
instance that gets passed into javax.servlet.Filter.doFilter
. So is this in fact a Tomcat bug?
The documentation of ServletRequest.getParameter
says:
If the parameter data was sent in the request body, such as occurs with an HTTP POST request, then reading the body directly via
getInputStream()
orgetReader()
can interfere with the execution of this method.
So maybe the reverse is true too - that calling getParameter
might be allowed to interfere with the entity input stream? It's unclear to me whether the method's contract allows for this behavior, and whether it indicates a bug in Tomcat, Jersey, or neither.
Anyway, that old filter wasn't actually needed so my issue is solved but just removing it.
Here's a full reproduction of the problem (Tomcat 7.0):
web.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>test</display-name>
<servlet>
<servlet-name>jersey</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.application.my</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
<param-value>com.application.my.TestFilterFactory</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.feature.Trace</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jersey</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>servletFilter</filter-name>
<filter-class>com.application.my.TestServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>servletFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
TestServletFilter.java
:
package com.application.my;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public final class TestServletFilter implements Filter {
@Override
public void init(FilterConfig config) { }
@Override
public void doFilter(
final ServletRequest request,
final ServletResponse response,
final FilterChain chain
) throws IOException, ServletException {
System.out.println("calling getParameter on " + request.getClass().getName());
request.getParameter("blah");
chain.doFilter(request, response);
}
@Override
public void destroy() { }
}
TestFilterFactory.java
:
package com.application.my;
import java.util.Collections;
import java.util.List;
import com.sun.jersey.api.model.AbstractMethod;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import com.sun.jersey.spi.container.ResourceFilter;
import com.sun.jersey.spi.container.ResourceFilterFactory;
public final class TestFilterFactory implements ResourceFilterFactory {
@Override
public List<ResourceFilter> create(final AbstractMethod method) {
return Collections.<ResourceFilter>singletonList(new ResourceFilter() {
@Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
@Override
public ContainerRequest filter(final ContainerRequest request) {
System.out.println("form: " + request.getFormParameters());
return request;
}
};
}
@Override
public ContainerResponseFilter getResponseFilter() {
return null;
}
});
}
}
TestResource.java
:
package com.application.my;
import java.net.URI;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path("/post-stuff")
@Produces(MediaType.APPLICATION_JSON)
public final class TestResource {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response execute(
@FormParam("form_param_1") final String formParam1,
@FormParam("form_param_2") final String formParam2
) {
System.out.println("form param_1: " + formParam1);
System.out.println("form param_2: " + formParam2);
return Response.created(URI.create("/")).entity("{}").build();
}
}