Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

[2.2.1] Multiple calls to WS.Response.asJson() results in exception #2096

Open
torbinsky opened this Issue · 13 comments

9 participants

@torbinsky

Basically, it looks like the WS.Response Json parser is consuming a stream each time a call to asJson() is made, so more than one call results in an attempt to read and parse an empty stream. It seems that it should only read from the stream once and store that result so that it can return it in subsequent calls to asJson().

The exception being encountered:

java.lang.RuntimeException: com.fasterxml.jackson.databind.JsonMappingException: No content to map due to end-of-input

Reproduction code:

    public static void main(String[] args){
        WS.url("http://api.openweathermap.org/data/2.5/weather?q=London,uk").get().flatMap(new Function<WS.Response, F.Promise<String>>() {

            @Override
            public Promise<String> apply(Response a) throws Throwable {
                System.out.println(a.asJson()); // prints fine
                System.out.println(a.asJson()); // throws exception
                return null;
            }       
        }).recover(new Function<Throwable, String>() {
            @Override
            public String apply(Throwable throwable) throws Throwable {
                throwable.printStackTrace();
                return null;
            }
        });
    }
@huntc
Owner

Thanks! What'd think @wsargent ?

@jroper
Owner

Interesting question about what should be the correct behaviour here. The WS API parses the body lazily when requested. It doesn't cache the result. Considering that the body of an HTTP response is a stream, and not something that you want to necessarily buffer in memory, then it makes sense that accessing the body twice (regardless of what format you are accessing the body in) would not be supported. Imagine this:

response.asJson();
response.getBodyAsStream();

What would you expect the second call to return? An empty stream? I'd expect either an empty stream, or an exception saying you can't consume the body twice. What about this:

response.asJson();
response.asByteArray();

Again, I'd expect either an exception, or an empty byte array, since the body has already been consumed via the streaming JSON parser, it's not possible to access it a second time. So, for consistency, I think having two calls to asJson being illegal as well makes sense.

What is probably needed here is documentation to say that all the as* methods consume the body, and that it's illegal to consume the body twice.

@hepin1989

@torbinsky Hi,we are using Play java two,and so does the play-ws,the asJson call will consume the response body as an stream,so after the first call,the second call will be an empty stream.You could look the WS's source code

@wsargent
Collaborator

I think @jroper is correct, we can only consume the stream once -- but asJson() implies that it can be called repeatedly (while getBodyAsStream() is just redundant, as it can hardly stream anything else). An API that showed response.stream().asJson() or response.stream().asByteArray() would be far clearer, as a stream is transient.

@torbinsky

Given the examples that @jroper provided it does seem that it would be a bit inconsistent to have the as*() methods consume and then cache a response body while there are other methods that wouldn't. I don't think the API makes it clear that these are stream operations though, so documentation is definitely needed as it is really not obvious that these methods are stream operators. I think the API described by @wsargent would be one step better and makes it much more clear that as*() methods are stream operations.

Given that the previous versions allowed multiple calls, I would guess that the WS Response API wasn't originally designed with this in mind which might be why it wasn't set up to more clearly reflect the streaming nature it now has.

@huntc
Owner

I agree with @wsargent that a new stream() method would make this nice and clear. as* and to* methods don't normally consume streams by convention.

@jroper
Owner

I think stream().asJson() might be a bit confusing, implying that it's a stream of JSON. Perhaps parseAsJson() is better, because the parse verb here implies that we are doing something. Or consumeAsJson().

@hadesara

I had to pull my hairs coz of this recently. I think consistency is important. And because stream and other types can only be read once, we should stick with reading it only once. However, returning an empty byte stream with reading the next time might be confusing. Instead of that, may be you can return End of stream exception so that the user would know that it is already read once and also that it can be consistent across other types.

@jroper
Owner

@hadesara There is no EndOfStreamException in Java. If you read from an InputStream once it has reached the end of stream, it just keeps returning empty arrays. So in fact that this is consistent with the way Java handles InputStream at least, to diverge from that would be to be inconsistent.

@hadesara

How about EOFException ?

@berendiwema

Glad you opened this issue. The docs/API don't make this clear, finding this ticket saved us a few hours of headache.

@aelex13

Thanks to this issue i found that my error was caused by multiple calls to the WS.Response.asJson() method. I would really recommend updating the docs on the asJson() method to prevent other people from running into a bug that is completely preventable by just calling the asJson() once.

@keepscoding

maybe should rename like stack popJson...
remember update / correct the doc here too..
https://www.playframework.com/documentation/2.3.4/JavaWS#Processing-the-Response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.