New HttpClient PUT request fails oddly - is it size is it number of invocations!

Jaikiran Pai jai.forums2013 at gmail.com
Tue Jun 26 13:42:01 UTC 2018


In my random experimentation with the new HttpClient API usage, I have 
ended up running into an odd and hard to decipher exception when dealing 
with PUT requests. I am noticing that if I issue multiple PUT requests 
using the same HttpClient instance, the first 2 invocations succeed 
while the 3rd one fails with an exception[1]. Initially when I ran into 
this, I saw this happening depending on the size of the data being 
uploaded via PUT request. As soon as it hit 16385 bytes (16 KB + 1), it 
would end up throwing the odd exception. However, when I decided to 
narrow it down to a testcase, I was able to reproduce this without the 
size of the data playing a role. And weirdly, it now keeps failing for 
the third invocation.

I have now isolated this into a jtreg testcase and created a patch 
against the latest upstream jdk to reproduce this issue. I've attached 
the patch in this mail. In the test, I create a local server and keep 
sending PUT requests to the server using the same instance of 
HttpClient. As soon as it hits the 3rd invocation, I end up seeing the 
exception[1].

It's really odd, since, initially I thought it could be a size based 
issue, which would have been more understandable. However with the way 
it's failing now in this jtreg test, I am starting to wonder if I have 
got some basics wrong.


[1]

java.io.IOException: HTTP/1.1 header parser received no bytes
	at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:546)
	at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:113)
	at PUTRequestSizeTest.issuePUT(PUTRequestSizeTest.java:76)
	at PUTRequestSizeTest.main(PUTRequestSizeTest.java:61)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at com.sun.javatest.regtest.agent.MainWrapper$MainThread.run(MainWrapper.java:115)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.io.IOException: HTTP/1.1 header parser received no bytes
	at java.net.http/jdk.internal.net.http.common.Utils.wrapWithExtraDetail(Utils.java:293)
	at java.net.http/jdk.internal.net.http.Http1Response$HeadersReader.onReadError(Http1Response.java:646)
	at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.checkForErrors(Http1AsyncReceiver.java:297)
	at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.flush(Http1AsyncReceiver.java:263)
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SynchronizedRestartableTask.run(SequentialScheduler.java:175)
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:147)
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	... 1 more
Caused by: java.io.EOFException: EOF reached while reading
	at java.net.http/jdk.internal.net.http.Http1AsyncReceiver$Http1TubeSubscriber.onComplete(Http1AsyncReceiver.java:587)
	at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$ReadSubscription.signalCompletion(SocketTube.java:629)
	at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.read(SocketTube.java:830)
	at java.net.http/jdk.internal.net.http.SocketTube$SocketFlowTask.run(SocketTube.java:175)
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198)
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:271)
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:224)
	at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.handleSubscribeEvent(SocketTube.java:687)
	at java.net.http/jdk.internal.net.http.AsyncTriggerEvent.handle(AsyncTriggerEvent.java:54)
	at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:796)

-------------- next part --------------
# HG changeset patch
# User Jaikiran Pai <jaikiran.pai at gmail.com>
# Date 1530018198 -19800
#      Tue Jun 26 18:33:18 2018 +0530
# Node ID 955a66f0f04a0c73be1287203df644dbb0bad7ac
# Parent  6274aee1f6922bdb6f819bee02fe2ccfffbc4501
Add a testcase to reproduce the failure of PUT requests if it exceeded a certain number of invocations

diff --git a/test/jdk/java/net/httpclient/PUTRequestSizeTest.java b/test/jdk/java/net/httpclient/PUTRequestSizeTest.java
new file mode 100644
--- /dev/null
+++ b/test/jdk/java/net/httpclient/PUTRequestSizeTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.io.IOException;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import java.net.InetSocketAddress;
+import java.net.InetAddress;
+import java.net.HttpURLConnection;
+
+
+/**
+ * @test
+ * @run main PUTRequestSizeTest
+ * @summary Tests that a PUT request works successfully when invoked
+            using the same HttpClient instance, multiple times
+ */
+public class PUTRequestSizeTest {
+
+    public static void main(final String[] args) throws Exception {
+        final String webAppContext = "/puttest";
+        // create a local http server which has a web app context that just returns back
+        // a response 200
+        final HttpServer server = createAndStartServer(webAppContext);
+        try {
+            final HttpClient httpClient = HttpClient.newBuilder().build();
+            final URL requestURL = new URL("http://" + server.getAddress().getHostString()
+                                + ":" + server.getAddress().getPort() + webAppContext + "/");
+            // invoke a few times
+            for (int i = 0; i < 10; i++) {
+                System.out.println("Sending PUT request " + (i +1));
+            	issuePUT(httpClient, requestURL);
+            }
+        } finally {
+            server.stop(0);
+        }
+    }
+
+    private static void issuePUT(final HttpClient httpClient, final URL requestURL) throws Exception {
+        final byte[] data = new byte[1];
+        final HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
+        		.PUT(HttpRequest.BodyPublishers.ofByteArray(data))
+                .uri(requestURL.toURI());
+
+        final HttpRequest request = requestBuilder.build();
+        System.out.println("Initiating PUT request with " + data.length + " bytes");
+        final HttpResponse<Void> response = httpClient.send(request, HttpResponse.BodyHandlers.discarding());
+        System.out.println("Status code for data with " + data.length + " bytes is " + response.statusCode());
+    }
+
+    private static HttpServer createAndStartServer(final String webAppContext) throws IOException {
+        final InetSocketAddress address = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        final HttpServer server = HttpServer.create(address, 0);
+        // setup the handler
+        final HttpContext context = server.createContext(webAppContext, new SimpleHandler());
+        // start the server
+        server.start();
+        return server;
+    }
+
+    private static class SimpleHandler implements HttpHandler {
+        @Override
+        public void handle(final HttpExchange httpExchange) throws IOException {
+            final URI requestURI = httpExchange.getRequestURI();
+            System.out.println("Handling " + httpExchange.getRequestMethod() + " request " + requestURI);
+			httpExchange.sendResponseHeaders(200, -1);
+        }
+    }
+
+}
\ No newline at end of file


More information about the net-dev mailing list