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: Marek Potociar <marek.potociar_at_oracle.com>
Date: Thu, 18 Oct 2012 10:55:49 +0200

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.

>
> As I do not see any feasible use for getOutputString() besides
> transformation (which does not work due to the wrong Content-Length), the
> only conclusion I have is that this is a bug in Jersey 2.0 M08-1. But maybe
> I oversee something. So I would like to ask you to comment on this use case.
> Is this a bug, did I oversee another sensible use case for getOutputString()
> besides transformation, or is there another idiom I have to use to complete
> the filter-based representation rewriting?

As I said, I don't think the transformation is the only use case. Also, even if it was, I'm not sure I would consider the use case broken. Sure, it's not completely easy to implement all (transformation) use cases, but I'm convinced that in many cases (those that let runtime figure out the content-length as well as those in which you know the content-lenght ahead - after all, if you want, you can cache the original outbound entity data yourself as part of the transformation) it is still quite straightforward and useful IMO.

Btw. if you have a proposal for a good solution in the API to your problem above, I'm all ears ;)

Marek

>
> Thanks
> Markus
>