question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

chunked encoding + GZIP prevents keep-alive

See original GitHub issue

GZIPInputStream.read() starts returning -1 once it finishes reading the gzip trailer. Depending on the underlying InputStream, this may occur before it has finished consuming the underlying InputStream.

In particular, if the underlying InputStream is a ChunkedInputStream, the ChunkedInputStream never reads the final chunk header (which indicates a chunk of length 0).

When ChunkedInputStream.close() is called, it is in a non-STATE_DONE state, and does not call HttpClient.finished(), which is required for the HttpClient object to get into the keep alive cache.

One possible fix is here: https://github.com/google/google-http-java-client/blob/acdaeacc3c43402d795266d2ebcb584abe918961/google-http-client/src/main/java/com/google/api/client/http/HttpResponse.java#L363

Rather than returning a GZIPInputStream, return a proxy input stream that retains references to both the gzip stream and the underlying stream. The proxy’s close() method first consumes the underlying stream by calling read() and then calls close() on the gzip stream.

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
hiranya911commented, Mar 5, 2020

I believe the solution implemented in #840 is inadequate. Consider the following test case, which is very similar to the code used in the NetHttpTransport:

  @Test
  public void testConnectionReuse() throws Exception {
    for (int i = 0; i < 3; i++) {
      URL url = new URL("https://jsonplaceholder.typicode.com/todos/1");
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setRequestMethod("GET");
      conn.addRequestProperty("accept-encoding", "gzip");

      InputStream original = conn.getInputStream();
      InputStream is = new GZIPInputStream(original);
      byte[] data = ByteStreams.toByteArray(is);
      System.out.println(new String(data));

      is.close();
    }
  }

I expect the above code to open only 1 connection due to keep-alive, but it actually opens 3:

$ sudo tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn) != 0' and dst host typicode.com
14:34:01.425773 IP my.host.49644 > 104.28.28.194.https: Flags [S], seq 2833089104, win 64240, options [mss 1460,sackOK,TS val 2275669545 ecr 0,nop,wscale 7], length 0
14:34:01.548967 IP my.host.49646 > 104.28.28.194.https: Flags [S], seq 538857976, win 64240, options [mss 1460,sackOK,TS val 2275669668 ecr 0,nop,wscale 7], length 0
14:34:01.573442 IP my.host.49648 > 104.28.28.194.https: Flags [S], seq 3217697064, win 64240, options [mss 1460,sackOK,TS val 2275669693 ecr 0,nop,wscale 7], length 0

The solution in #840 simply wraps the GZIPInputStream in a ConsumingInputStream:

InputStream is = new ConsumingInputStream(new GZIPInputStream(original));

But this won’t change the behavior, because ConsumingInputStream only does this:

@Override
  public void close() throws IOException {
    if (!closed && in != null) {
      try {
        ByteStreams.exhaust(this);
        super.in.close();
      } finally {
        this.closed = true;
      }
    }
  }

Since ByteStreams.exhaust() is simply trying to read any remaining bytes from the GZIPInputStream, it doesn’t really help clean up the underlying the ChunkedInputStream.

To get the desired keep-alive behavior we need to do something like the following in the test case:

ByteStreams.exhaust(original);
0reactions
eddavissoncommented, Oct 7, 2019

Note that the hack we put in place for google-cloud-datastore is going to become increasingly inconvenient in future JDK releases.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Can nginx send dynamically-gzipped content without using ...
For me this did successfully disable chunked encoding, however it did not send a Content-Length header, it just added Connection: close ...
Read more >
Apache sending Transfer-Encoding: chunked - Server Fault
Chunked output occurs when Apache doesn't know the total output size before sending, as is the case with compressed transfer (Apache compresses data...
Read more >
Transfer-Encoding - HTTP - MDN Web Docs
Chunked encoding is useful when larger amounts of data are sent to the client and the total size of the response may not...
Read more >
Php – if connection is keep alive how to read until end of stream php ...
It depends on the response, if the transfer-encoding of the response is chunked , then you read until you encounter the "last chunk"...
Read more >
Transfer-Encoding = "chunked" header for a Response Status ...
While assisting our customer, we found that when a browser agent makes a request to the application endpoint “/keepalive” it returns a ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found