I'm trying to get the form parameters of a request in a request filter:
@Override
public ContainerRequest filter(final ContainerRequest request) {
final Form formParameters = request.getFormParameters();
//logic
return request;
}
However, the form always seems to be empty. The HttpRequestContext.getFormParameters()
documentation says:
Get the form parameters of the request entity.
This method will ensure that the request entity is buffered such that it may be consumed by the applicaton.
Returns:
the form parameters, if there is a request entity and the content type is "application/x-www-form-urlencoded", otherwise an instance containing no parameters will be returned.
My resource is annotated with @Consumes("application/x-www-form-urlencoded")
, although it won't have been matched until after the request filter - is that why this isn't working?
I tried doing some research but couldn't find any conclusive evidence of whether this is possible. There was this 4-year old discussion, in which Paul Sandoz says:
If you are working in Jersey filters or with the HttpRequestContext
you can get the form parameters as follows: [broken link to Jersey 1.1.1 HttpRequestContext.getFormParameters
]
I also found this 3-year-old discussion about how to get multipart/form-data form fields in a request filter. In it, Paul Sandoz uses the following code:
// Buffer
InputStream in = request.getEntityInputStream();
if (in.getClass() != ByteArrayInputStream.class) {
// Buffer input
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ReaderWriter.writeTo(in, baos);
} catch (IOException ex) {
throw new ContainerException(ex);
}
in = new ByteArrayInputStream(baos.toByteArray());
request.setEntityInputStream(in);
}
// Read entity
FormDataMultiPart multiPart = request.getEntity(FormDataMultiPart.class);
I tried emulating that approach for Form
instead, but the result of request.getEntityInputStream()
is always an empty stream. And looking at the source of getFormParameters
, that method is in fact doing the same thing already:
@Override
public Form getFormParameters() {
if (MediaTypes.typeEquals(MediaType.APPLICATION_FORM_URLENCODED_TYPE, getMediaType())) {
InputStream in = getEntityInputStream();
if (in.getClass() != ByteArrayInputStream.class) {
// Buffer input
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
ReaderWriter.writeTo(in, byteArrayOutputStream);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
in = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
setEntityInputStream(in);
}
ByteArrayInputStream byteArrayInputStream = (ByteArrayInputStream) in;
Form f = getEntity(Form.class);
byteArrayInputStream.reset();
return f;
} else {
return new Form();
}
}
I can't figure out what's slurping up the entity input stream before I get to it. Something in Jersey must be consuming it because the form params are later passed into the resource method. What am I doing wrong here, or is this impossible (and why)?
EDIT: Here's an example of a request being sent:
POST /test/post-stuff HTTP/1.1
Host: local.my.application.com:8443
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
form_param_1=foo&form_param_2=bar
Here's the (somewhat redundant) request logging:
INFO: 1 * Server in-bound request
1 > POST https://local.my.application.com:8443/test/post-stuff
1 > host: local.my.application.com:8443
1 > connection: keep-alive
1 > content-length: 33
1 > cache-control: no-cache
1 > origin: chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm
1 > user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36
1 > content-type: application/x-www-form-urlencoded
1 > accept: */*
1 > accept-encoding: gzip,deflate,sdch
1 > accept-language: en-US,en;q=0.8
1 > cookie: [omitted]
1 >
Here are the response headers of that request, including the Jersey Trace:
Content-Type →application/json;charset=UTF-8
Date →Fri, 09 Aug 2013 18:00:17 GMT
Location →https://local.my.application.com:8443/test/post-stuff/
Server →Apache-Coyote/1.1
Transfer-Encoding →chunked
X-Jersey-Trace-000 →accept root resource classes: "/post-stuff"
X-Jersey-Trace-001 →match path "/post-stuff" -> "/post\-stuff(/.*)?", [...], "(/.*)?"
X-Jersey-Trace-002 →accept right hand path java.util.regex.Matcher[pattern=/post\-stuff(/.*)? region=0,11 lastmatch=/post-stuff]: "/post-stuff" -> "/post-stuff" : ""
X-Jersey-Trace-003 →accept resource: "post-stuff" -> @Path("/post-stuff") com.application.my.jersey.resource.TestResource@7612e9d2
X-Jersey-Trace-004 →match path "" -> ""
X-Jersey-Trace-005 →accept resource methods: "post-stuff", POST -> com.application.my.jersey.resource.TestResource@7612e9d2
X-Jersey-Trace-006 →matched resource method: public javax.ws.rs.core.Response com.application.my.jersey.resource.TestResource.execute(java.lang.String,java.lang.String)
X-Jersey-Trace-007 →matched message body reader: class com.sun.jersey.api.representation.Form, "application/x-www-form-urlencoded" -> com.sun.jersey.core.impl.provider.entity.FormProvider@b98df1f
X-Jersey-Trace-008 →matched message body writer: java.lang.String@f62, "application/json" -> com.sun.jersey.core.impl.provider.entity.StringProvider@1c5ddffa
Here is the (unremarkable) servlet config:
<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.jersey</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
<param-value>com.application.my.jersey.MyFilterFactory</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.feature.Trace</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Here's the example resource:
@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
) {
return Response.created(URI.create("/")).entity("{}").build();
}
}
I'm using Jersey 1.17.
For those interested, I'm trying to roll my own required parameter validation, as described in JERSEY-351. My solution here worked for query, cookie, and header params - form params are holding out on me.
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()
or getReader()
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();
}
}
Make sure your ResourceFilterFactory
creates an instance of ResourceFilter
for the TestResource#execute
method, which then creates a ContainerRequestFilter
instance:
public class MyFilterFactory implements ResourceFilterFactory {
@Override
public List<ResourceFilter> create(final AbstractMethod am) {
return new ArrayList<ResourceFilter>() {{
add(new ResourceFilter() {
@Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
@Override
public ContainerRequest filter(final ContainerRequest request) {
System.out.println(request.getFormParameters());
return request;
}
};
}
@Override
public ContainerResponseFilter getResponseFilter() {
return null;
}
});
}};
}
}
From the trace
you have provided I am not sure whether your ContainerRequestFilter
is called. There should be one more trace header containing something like this:
→matched message body reader: class com.sun.jersey.api.representation.Form, "application/x-www-form-urlencoded" -> com.sun.jersey.core.impl.provider.entity.FormProvider@b98df1f
The whole trace from my test:
HTTP/1.1 201 Created
Location: http://localhost:8080/helloworld-webapp/helloworld/
Content-Type: text/plain
X-Jersey-Trace-000: accept root resource classes: "/helloworld"
X-Jersey-Trace-001: match path "/helloworld" -> "/application\.wadl(/.*)?", "/helloworld(/.*)?"
X-Jersey-Trace-002: accept right hand path java.util.regex.Matcher[pattern=/helloworld(/.*)? region=0,11 lastmatch=/helloworld]: "/helloworld" -> "/helloworld" : ""
X-Jersey-Trace-003: accept resource: "helloworld" -> @Path("/helloworld") com.sun.jersey.samples.helloworld.resources.HelloWorldResource@7449df0f
X-Jersey-Trace-004: match path "" -> ""
X-Jersey-Trace-005: accept resource methods: "helloworld", POST -> com.sun.jersey.samples.helloworld.resources.HelloWorldResource@7449df0f
X-Jersey-Trace-006: matched resource method: public javax.ws.rs.core.Response com.sun.jersey.samples.helloworld.resources.HelloWorldResource.execute(java.lang.String,java.lang.String)
X-Jersey-Trace-007: matched message body reader: class com.sun.jersey.api.representation.Form, "application/x-www-form-urlencoded" -> com.sun.jersey.core.impl.provider.entity.FormProvider@6bc1b916
X-Jersey-Trace-008: matched message body reader: class com.sun.jersey.api.representation.Form, "application/x-www-form-urlencoded" -> com.sun.jersey.core.impl.provider.entity.FormProvider@6bc1b916
X-Jersey-Trace-009: matched message body writer: java.lang.String@f62, "text/plain" -> com.sun.jersey.core.impl.provider.entity.StringProvider@4aae6c4e
Transfer-Encoding: chunked
Server: Jetty(6.1.24)
EDIT 1:
Enable request LoggingFilter
:
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
<param-value>com.sun.jersey.api.container.filter.LoggingFilter</param-value>
</init-param>
EDIT 2:
Also make sure no other Servlet or Jersey filter has read the InputStream
before. In such a case the entity input stream may no longer be available (but you can still inject @FormParam
into your resource method - as in this case).