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.1HttpRequestContext.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
: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
:I tried to simplify the problem even more by removing
MyFilter
and making the same call in the Jersey filter (by injectingHttpServletRequest
) - but the form parameters still showed up. The issue appears to happen specifically when callinggetParameter
on theorg.apache.catalina.connector.RequestFacade
instance that gets passed intojavax.servlet.Filter.doFilter
. So is this in fact a Tomcat bug?The documentation of
ServletRequest.getParameter
says: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
:TestServletFilter.java
:TestFilterFactory.java
:TestResource.java
:Make sure your
ResourceFilterFactory
creates an instance ofResourceFilter
for theTestResource#execute
method, which then creates aContainerRequestFilter
instance:From the
trace
you have provided I am not sure whether yourContainerRequestFilter
is called. There should be one more trace header containing something like this:The whole trace from my test:
EDIT 1:
Enable request
LoggingFilter
: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).