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