users@jersey.java.net

Re: [Jersey] MIMEParsingException (IOException: Stream closed) when accessing a multipart form from a filter

From: Igor Minar <iiminar_at_gmail.com>
Date: Wed, 15 Jul 2009 14:10:38 -0700

On Jul 15, 2009, at 12:59 AM, Paul Sandoz wrote:

>
> On Jul 15, 2009, at 1:02 AM, Igor Minar wrote:
>
>> Hi Paul,
>>
>> Thanks for the answer, but I'm afraid that due to the expected size
>> of the requests handled in this way, I don't think that buffering
>> things in the memory will be feasible.
>>
>> If I read the jersey multipart docs/emails on the list correctly
>> then the multipart implementation buffers large requests on the
>> disk, which is something that I'd like to take advantage of.
>>
>
> That is the problem, the filter may not necessarily process the same
> Java type as required by the resource method.

Do you mean the case where filter would want to process the request as
FormDataMultiPart and resource would instead be interested in for
example the raw inputstream? I can see that being a problem, but
that's not what we are doing.

>
> Are you using a resource filter and specifically associating it with
> the resource method or a container request filter that would apply
> to all resource methods?

Currently it's a container request filter. It applies to all resource
methods, but we don't process the request in the filter if the request
doesn't have multipart media type (which is most of the time).

The filter is a generic authentication filter, which is capable
extracting credentials via several different ways and one of them is
an authentication mechanism which uses tokens and multipart form
fields - similar to how S3 browser based uploads work.

>
>
>> In the meantime I did implement a workaround where I pass the
>> FormDataMultiPart instance from filter to my resource via a thread
>> local class, but I still would like to see a nicer solution to this
>> problem.
>>
>
> Yes, this of course does not work for @FormDataParam though.

FormDataMultiPart object contains all the FormDataParam objects, so if
I pass it around, I can get access to the individual FormDataParam
objects too. It's not ideal, but it works.

>
>
>>> In general when using Jersey filters one should not assume that
>>> the Java type the filter wants for an entity is the same as the
>>> Java type for the resource method. But there is probably some way
>>> i could optimize this, for the case of when it is. Could you log
>>> an enhancement issue?
>>
>> You mean that you would cache the entity within the provider, so
>> that when it is requested the second time provider just retrieves
>> the entity from the cache? Or are you thinking of something else?
>>
>
> Cache it within the ContainerRequest. But this will not work if one
> requests a different Java type for the entity the second time.

As I mentioned we extract the same java type from the request.

>
> The right way to do this would i think be for the filter to extend
> ContainerRequest with such functionality by overriding the getEntity
> method. Then the filter can adapt the ContainerRequest passed in and
> return the adapted instance. Unfortunately the ContainerRequest
> class is currently set up to easily do this so we need to modify
> this class so it can adapted (likewise for ContainerResponse). Could
> you log an issue?

done: https://jersey.dev.java.net/issues/show_bug.cgi?id=330

>
>
>> As an alternative to thread local hack, I also looked for an api
>> similar to servlet's request.setAttribute but I didn't find
>> anything like that in jersey. Am I just not looking hard enough or
>> there really isn't anything like that in Jersey.
>>
>
> See:
>
> https://jersey.dev.java.net/nonav/apidocs/1.1.0-ea/jersey/com/sun/jersey/api/core/HttpContext.html
> #getProperties()
> See:
>
> and you can inject HttpContext e.g. @Context HttpContext hc.
>
> Note that the same properties are accessible that ContainerRequest
> as well:
>
> https://jersey.dev.java.net/nonav/apidocs/1.1.0-ea/jersey/com/sun/jersey/spi/container/ContainerRequest.html
> #getProperties()
>
> So in your filter you do not need to inject HttpContext.

ahhh.. sweet! I was looking for setters and missed the properties
getter which returns a mutable Properties object.

thanks! I'll use this in the meantime to transport the extracted
FormDataMultiPart object from our filter to the resource instead of
the thread local hack we use now.

/i


>
> Paul.
>
>> thanks,
>> Igor
>>
>>
>> On Jul 13, 2009, at 12:11 AM, Paul Sandoz wrote:
>>
>>> H Igor,
>>>
>>> Your filter is consuming the request entity, and unless you buffer
>>> the entity it can only be consumed once.
>>>
>>> For now you will have to get the entity stream read it into a byte
>>> array input stream, set that as the stream, read the entity in the
>>> filter, then, most importantly, reset the byte array input stream
>>> so the entity can be re-read.
>>>
>>> In general when using Jersey filters one should not assume that
>>> the Java type the filter wants for an entity is the same as the
>>> Java type for the resource method. But there is probably some way
>>> i could optimize this, for the case of when it is. Could you log
>>> an enhancement issue?
>>>
>>> Thanks,
>>> Paul.
>>>
>>> On Jul 12, 2009, at 3:09 AM, Igor Minar wrote:
>>>
>>>> Hi there,
>>>>
>>>> I have a application that needs to process a multipart-form
>>>> request. Everything works great, until I try to access my form
>>>> fields both in a jersey filter as well as in my resource.
>>>>
>>>> In my filter I have:
>>>>
>>>> private Credentials attemptBrowserBasedToken(HttpRequestContext
>>>> context) {
>>>> FormDataMultiPart form =
>>>> context.getEntity(FormDataMultiPart.class);
>>>> String key = form.getField("AccessKey").getValue(); //
>>>> TODO NPEs!!!
>>>> String signature =
>>>> Base64Coder.decode(form.getField("signature").getValue());
>>>> ...
>>>> }
>>>>
>>>> In my resource I have:
>>>>
>>>> @POST
>>>> @Consumes(MediaType.MULTIPART_FORM_DATA)
>>>> public Response processForm(@PathParam("bucketName") final String
>>>> bucketName,
>>>> @FormDataParam("policy") final
>>>> String policyEncoded,
>>>> @FormDataParam("key") String key,
>>>>
>>>> @FormDataParam("success_action_redirect") final String
>>>> successRedirect,
>>>> @FormDataParam("acl") final
>>>> String acl,
>>>> final FormDataMultiPart formData,
>>>> @Context final SecurityContext
>>>> securityContext,
>>>> @Context final HttpHeaders
>>>> headers) {
>>>> ...
>>>> }
>>>>
>>>> When I run a request against this resource method, I get the
>>>> exception below some time after the filter returns and before
>>>> processForm in the resource is called.
>>>>
>>>> It looks like when the filter reads the form, it doesn't cache
>>>> the result, but closes the request stream when it reads it. As a
>>>> result, when jersey tries to initialize resource method
>>>> arguments, it can't do it any more.
>>>>
>>>> If I remove all FromData* stuff from the resource method
>>>> signature, the exceptions is not thrown.
>>>>
>>>> Is this a known limitation or a result of a design decision?
>>>>
>>>> Do you know if there is some workaround for this? Maybe a way to
>>>> store the data retrieved in the filter somewhere in the request
>>>> context and then retrieve it somehow in the resource? I suppose I
>>>> could do this via a thread local hack, but I'd rather avoid that
>>>> if possible.
>>>>
>>>> thanks,
>>>> Igor
>>>>
>>>>
>>>>
>>>>
>>>> Jul 11, 2009 4:19:55 PM
>>>> com.sun.jersey.server.impl.application.WebApplicationImpl
>>>> onException
>>>> SEVERE: Internal server error
>>>> javax.ws.rs.WebApplicationException:
>>>> org.jvnet.mimepull.MIMEParsingException: java.io.IOException:
>>>> Stream closed
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .multipart.impl.MultiPartReader.readFrom(MultiPartReader.java:203)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .multipart.impl.MultiPartReader.readFrom(MultiPartReader.java:74)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .spi.container.ContainerRequest.getEntity(ContainerRequest.java:
>>>> 393)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .spi.container.ContainerRequest.getEntity(ContainerRequest.java:
>>>> 402)
>>>> at
>>>> com.sun.jersey.multipart.impl.FormDataMultiPartDispatchProvider
>>>> $
>>>> FormDataInjectableValuesProvider
>>>> .getInjectableValues(FormDataMultiPartDispatchProvider.java:112)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .server
>>>> .impl.model.method.dispatch.AbstractResourceMethodDispatchProvider
>>>> $
>>>> EntityParamInInvoker
>>>> .getParams(AbstractResourceMethodDispatchProvider.java:126)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .server
>>>> .impl.model.method.dispatch.AbstractResourceMethodDispatchProvider
>>>> $
>>>> ResponseOutInvoker
>>>> ._dispatch(AbstractResourceMethodDispatchProvider.java:173)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .server
>>>> .impl
>>>> .model
>>>> .method
>>>> .dispatch
>>>> .ResourceJavaMethodDispatcher
>>>> .dispatch(ResourceJavaMethodDispatcher.java:67)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .server.impl.uri.rules.HttpMethodRule.accept(HttpMethodRule.java:
>>>> 163)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .server
>>>> .impl.uri.rules.ResourceClassRule.accept(ResourceClassRule.java:71)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .server
>>>> .impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:
>>>> 111)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .server
>>>> .impl
>>>> .uri
>>>> .rules
>>>> .RootResourceClassesRule.accept(RootResourceClassesRule.java:63)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .server
>>>> .impl
>>>> .application
>>>> .WebApplicationImpl._handleRequest(WebApplicationImpl.java:654)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .server
>>>> .impl
>>>> .application
>>>> .WebApplicationImpl.handleRequest(WebApplicationImpl.java:612)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .server
>>>> .impl
>>>> .application
>>>> .WebApplicationImpl.handleRequest(WebApplicationImpl.java:603)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .spi.container.servlet.WebComponent.service(WebComponent.java:309)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .spi
>>>> .container.servlet.ServletContainer.service(ServletContainer.java:
>>>> 425)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .spi
>>>> .container.servlet.ServletContainer.service(ServletContainer.java:
>>>> 590)
>>>> at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
>>>> at
>>>> com
>>>> .google
>>>> .inject
>>>> .servlet.ServletDefinition.doService(ServletDefinition.java:216)
>>>> at
>>>> com
>>>> .google
>>>> .inject.servlet.ServletDefinition.service(ServletDefinition.java:
>>>> 141)
>>>> at
>>>> com
>>>> .google
>>>> .inject
>>>> .servlet
>>>> .ManagedServletPipeline.service(ManagedServletPipeline.java:93)
>>>> at
>>>> com
>>>> .google
>>>> .inject
>>>> .servlet
>>>> .FilterChainInvocation.doFilter(FilterChainInvocation.java:63)
>>>> at
>>>> com
>>>> .google
>>>> .inject
>>>> .servlet
>>>> .ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:122)
>>>> at
>>>> com.google.inject.servlet.GuiceFilter.doFilter(GuiceFilter.java:
>>>> 110)
>>>> at
>>>> com
>>>> .sun
>>>> .grizzly
>>>> .http.servlet.FilterChainImpl.doFilter(FilterChainImpl.java:172)
>>>> at
>>>> com
>>>> .sun
>>>> .grizzly
>>>> .http
>>>> .servlet.FilterChainImpl.invokeFilterChain(FilterChainImpl.java:
>>>> 137)
>>>> at
>>>> com
>>>> .sun
>>>> .grizzly.http.servlet.ServletAdapter.service(ServletAdapter.java:
>>>> 322)
>>>> at
>>>> com
>>>> .sun
>>>> .grizzly.tcp.http11.GrizzlyAdapter.service(GrizzlyAdapter.java:165)
>>>> at
>>>> com
>>>> .sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:
>>>> 746)
>>>> at
>>>> com
>>>> .igorminar
>>>> .grizzlysendfile.SendfileFilter.doFilter(SendfileFilter.java:128)
>>>> at
>>>> com
>>>> .sun
>>>> .grizzly
>>>> .arp.DefaultAsyncExecutor.invokeFilters(DefaultAsyncExecutor.java:
>>>> 147)
>>>> at
>>>> com
>>>> .sun
>>>> .grizzly
>>>> .arp.DefaultAsyncExecutor.interrupt(DefaultAsyncExecutor.java:126)
>>>> at
>>>> com
>>>> .sun
>>>> .grizzly.arp.AsyncProcessorTask.doTask(AsyncProcessorTask.java:88)
>>>> at com.sun.grizzly.http.TaskBase.run(TaskBase.java:189)
>>>> at java.util.concurrent.ThreadPoolExecutor
>>>> $Worker.runTask(ThreadPoolExecutor.java:886)
>>>> at java.util.concurrent.ThreadPoolExecutor
>>>> $Worker.run(ThreadPoolExecutor.java:908)
>>>> at java.lang.Thread.run(Thread.java:637)
>>>> Caused by: org.jvnet.mimepull.MIMEParsingException:
>>>> java.io.IOException: Stream closed
>>>> at org.jvnet.mimepull.MIMEParser.fillBuf(MIMEParser.java:436)
>>>> at org.jvnet.mimepull.MIMEParser.skipPreamble(MIMEParser.java:
>>>> 302)
>>>> at org.jvnet.mimepull.MIMEParser.access$300(MIMEParser.java:62)
>>>> at org.jvnet.mimepull.MIMEParser
>>>> $MIMEEventIterator.next(MIMEParser.java:138)
>>>> at org.jvnet.mimepull.MIMEParser
>>>> $MIMEEventIterator.next(MIMEParser.java:123)
>>>> at
>>>> org.jvnet.mimepull.MIMEMessage.makeProgress(MIMEMessage.java:193)
>>>> at org.jvnet.mimepull.MIMEMessage.parseAll(MIMEMessage.java:
>>>> 176)
>>>> at
>>>> org.jvnet.mimepull.MIMEMessage.getAttachments(MIMEMessage.java:101)
>>>> at
>>>> com
>>>> .sun
>>>> .jersey
>>>> .multipart.impl.MultiPartReader.readFrom(MultiPartReader.java:166)
>>>> ... 37 more
>>>> Caused by: java.io.IOException: Stream closed
>>>> at
>>>> com
>>>> .sun
>>>> .grizzly
>>>> .tcp.http11.GrizzlyInputBuffer.read(GrizzlyInputBuffer.java:343)
>>>> at
>>>> com
>>>> .sun
>>>> .grizzly
>>>> .tcp.http11.GrizzlyInputStream.read(GrizzlyInputStream.java:234)
>>>> at
>>>> com
>>>> .sun
>>>> .grizzly
>>>> .http
>>>> .servlet.ServletInputStreamImpl.read(ServletInputStreamImpl.java:
>>>> 91)
>>>> at org.jvnet.mimepull.MIMEParser.fillBuf(MIMEParser.java:434)
>>>> ... 45 more
>>>>
>>>> ---------------------------------------------------------------------
>>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>>
>>>
>>>
>>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>