jsr339-experts@jax-rs-spec.java.net

[jsr339-experts] Re: [jax-rs-spec users] Re: Heads Up: Severe problem when rewriting responses! Is our Filter API suitable?

From: Bill Burke <bburke_at_redhat.com>
Date: Thu, 18 Oct 2012 09:26:14 -0400

On 10/18/2012 4:55 AM, Marek Potociar wrote:
>
> On Oct 13, 2012, at 4:29 PM, Markus KARG <markus_at_headcrashing.eu> wrote:
>
>> Marek,
>>
>>>>> On the other hand, it would be interesting to learn what
>>>>> response context's
>>>>> setOutputStream() method actually is good for...?
>>>>
>>>> If you wrap the output stream returned by getOutputStream(), you need
>>>> to be able to set the wrapped output stream back, right? That's what
>>>> the setter is there for.
>>
>> Understood, but there is a problem with that. I think *transforming* the
>> result is the only sensible use of getOutputStream(), as simple recording of
>> the original content can be solved solely by using setOutputStream(),
>> obviously.
>
> That's IMO wrong assumption. How would you make sure you record the data written to an output stream without wrapping the stream and setting the wrapped stream back? Similarly for the input stream, in which case you could just use getIS but without setting the wrapped IS back, you would just consume data that most likely belong to someone else...
>
> So both, transformation as well as recording are viable use cases. Also, only filters are able to "force" the data to be read from the (input) stream, but that's the other side of the story.
>
>>
>> So given this ContainerResponseFilter...
>>
>> public void filter(ContainerRequestContext request, ContainerResponseContext
>> response) {
>> response.setEntityStream(new
>> ReplacingFilterStream(response.getEntityStream()));
>> }
>>
>> ...and assuming ReplacingFilterStream extends java.io.FilterOutputStream to
>> virtually 'replace' the stream content by writing a transformed variant of
>> what was written into itself into the original OutputStream passed to its
>> constructor...
>>
>> ...this effectively replaces the entity representation in the end, without
>> using a WriterInterceptor, implementing the sole sensible use case as I
>> noted before.
>>
>> But, as I have noticed, this does not replace the Content-Length header
>> automatically set by the JAX-RS implementation (here: RI [Jersey 2.0
>> M08-1]). As a result, the client will cease reading additional bytes in case
>> the transformed variant is longer than the original representation, hence
>> truncating the transformed variant (or, in the reverse case, making the
>> client hang until timeout, as it expecpects more bytes to come).
>>
>> As the filter does not know in advance how long the original response will
>> be, it cannot set the Content-Length in advance of forwarding the request to
>> next next element in the filter chain. As the ContainerResponseContext is
>> invalid after leaving the filter() method, the FilterOutputStream cannot
>> simply store the reference to set the header after its own close(). To sum
>> up: Neither is the Content-Length automatically corrected, nor is there a
>> way to manually correct it.
>
> From the implementor perspective, there's no way to know the content length in advance, unless the stream is fully consumed, but it's typically too late after that since headers are written ahead of entity and you don't want to consume the same stream twice in general. In your filter you can at least make sure that any content-length header, if set, is removed from the response context and then you need to make sure that your MBW returns -1 from getSize(). In that case the (Jersey) runtime will use chunked transfer encoding.
>

Sorry for repeating myself, but, whether to use chunked encoding or set
a content-length header should be left up to the underlying HTTP engine
(i.e. the servlet container). I know for a fact that Jetty and Tomcat
use a buffered output stream. If you finish a request within the limits
of the buffer (without calling flush), then Content-length is set,
otherwise chunk encoding is used.

getSize() should also be deprecated and ignored. With the ability to
wrap streams to do things like GZip encoding, getSize() is often wrong
and should never be relied upon.

-- 
Bill Burke
JBoss, a division of Red Hat
http://bill.burkecentral.com