11

I'm targeting a REST web service from Android 4.0 using HttpsURLConnection. This works fine unless I try to POST something. This is the relevant code section:

   connection.setDoOutput(true);
   connection.setChunkedStreamingMode(0);

   ByteArrayOutputStream out = new ByteArrayOutputStream();
   serializeObjectToStream(out, object);
   byte[] array = out.toByteArray();
   connection.getOutputStream().write(array, 0, array.length);

This throws the following exception:

   java.net.HttpRetryException: Cannot retry streamed HTTP body

From debugging I realized that the output stream I get via connection.getOuputStream() is of type ChunkedOutputStream and from digging in Androids source code I figured that if a request needs to be retried (for whatever reason), it pokes with the above exception, because it figures out that it is not using a RetryableOutputStream that it wants there.

The question is now: How do I make my HttpsURLConnection return such a RetryableOutputStream, or rather, how can I prevent chunked request encoding properly? I thought I did that already with setChunkedStreamingMode(0), but apparently this is not the case...

[edit]

No, the implementation of java.net.HTTPUrlConnection ignores a streaming mode of 0 or lower:

 public void setChunkedStreamingMode(int chunkLength) {
    [...]
    if (chunkLength <= 0) {
        this.chunkLength = HttpEngine.DEFAULT_CHUNK_LENGTH;
    } else {
        this.chunkLength = chunkLength;
    }
}
Thomas Keller
  • 5,933
  • 6
  • 48
  • 80
  • On a related note: I think the reason _why_ it wants a RetryableOutputStream at all is because there is a Basic Auth protection configured on the REST server and Android's HttpURLConnectionImpl seems to automatically retry a request when it gets an 401 Unauthorized answer. – Thomas Keller Aug 22 '12 at 12:36

2 Answers2

12

Bummer! The solution is to not call setChunkedStreamingMode() (or even setFixedStreamingMode()) from the client code at all! "-1" are the internal default values for fixedLength and chunkedLength and cannot be set client-side, because setting a value lower or equal to "0" lets it default to HttpEngine.DEFAULT_CHUNK_LENGTH (or throws an exception in the fixed streaming mode case).

Thomas Keller
  • 5,933
  • 6
  • 48
  • 80
1

The solution is to set a Content-Length header (which may be set by this next part) and call setFixedLengthStreamingMode with the proper length of the POST message you intend to send.

See the "streaming mode" section in this SO FAQ:

Using java.net.URLConnection to fire and handle HTTP requests

Community
  • 1
  • 1
Christopher Schultz
  • 20,221
  • 9
  • 60
  • 77
  • You should never set the `Content-length` header in Java. `setFixedLengthStreamingMode()` will do that anyway, and override whatever is already set. NB If you call `setChunkedStreamingMode()` there *is* no `Content-length` header, and you should definitely not set one. And if you don't call either of these methods, `HttpURLConnection` will set it to the correct value all by itself. – user207421 Jun 20 '18 at 02:02
  • @EJP Perhaps my comment was unclear, but I tried to imply that `setFixedLengthStreamingMode` would set the header for you. The original question is clearly requesting a non-chunked request so that it may be "retried". The original poster also replied *6 years ago* saying that any call to `set*StreamingMode` would break this. So, manually-setting the `Content-Length` does make some sense under certain conditions. – Christopher Schultz Jun 21 '18 at 13:49