diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedOutputStream.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedOutputStream.java
index 4deb7aa6c34..11ad6c3eaaa 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedOutputStream.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedOutputStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2023, 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
@@ -27,6 +27,8 @@ package sun.net.httpserver;
import java.io.*;
import java.net.*;
+import java.util.Objects;
+
import com.sun.net.httpserver.*;
import com.sun.net.httpserver.spi.*;
@@ -77,6 +79,10 @@ class ChunkedOutputStream extends FilterOutputStream
}
public void write (byte[]b, int off, int len) throws IOException {
+ Objects.checkFromIndexSize(off, len, b.length);
+ if (len == 0) {
+ return;
+ }
if (closed) {
throw new StreamClosedException ();
}
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java
index 6a7398611ac..4f57d77004f 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2023, 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
@@ -282,9 +282,7 @@ class ExchangeImpl {
sentHeaders = true;
logger.log(Level.TRACE, "Sent headers: noContentToSend=" + noContentToSend);
if (noContentToSend) {
- WriteFinishedEvent e = new WriteFinishedEvent (this);
- server.addEvent (e);
- closed = true;
+ close();
}
server.logReply (rCode, req.requestLine(), null);
}
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/FixedLengthOutputStream.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/FixedLengthOutputStream.java
index 4935214c2e1..277e6bb4228 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/FixedLengthOutputStream.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/FixedLengthOutputStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2023, 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
@@ -27,6 +27,8 @@ package sun.net.httpserver;
import java.io.*;
import java.net.*;
+import java.util.Objects;
+
import com.sun.net.httpserver.*;
import com.sun.net.httpserver.spi.*;
@@ -41,7 +43,6 @@ import com.sun.net.httpserver.spi.*;
class FixedLengthOutputStream extends FilterOutputStream
{
private long remaining;
- private boolean eof = false;
private boolean closed = false;
ExchangeImpl t;
@@ -58,8 +59,7 @@ class FixedLengthOutputStream extends FilterOutputStream
if (closed) {
throw new IOException ("stream closed");
}
- eof = (remaining == 0);
- if (eof) {
+ if (remaining == 0) {
throw new StreamClosedException();
}
out.write(b);
@@ -67,13 +67,13 @@ class FixedLengthOutputStream extends FilterOutputStream
}
public void write (byte[]b, int off, int len) throws IOException {
+ Objects.checkFromIndexSize(off, len, b.length);
+ if (len == 0) {
+ return;
+ }
if (closed) {
throw new IOException ("stream closed");
}
- eof = (remaining == 0);
- if (eof) {
- throw new StreamClosedException();
- }
if (len > remaining) {
// stream is still open, caller can retry
throw new IOException ("too many bytes to write to stream");
@@ -92,7 +92,6 @@ class FixedLengthOutputStream extends FilterOutputStream
throw new IOException ("insufficient bytes written to stream");
}
flush();
- eof = true;
LeftOverInputStream is = t.getOriginalInputStream();
if (!is.isClosed()) {
try {
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
index 81442542666..ee0439f7542 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
@@ -713,7 +713,14 @@ class ServerImpl {
return;
}
String uriStr = requestLine.substring (start, space);
- URI uri = new URI (uriStr);
+ URI uri;
+ try {
+ uri = new URI (uriStr);
+ } catch (URISyntaxException e3) {
+ reject(Code.HTTP_BAD_REQUEST,
+ requestLine, "URISyntaxException thrown");
+ return;
+ }
start = space+1;
String version = requestLine.substring (start);
Headers headers = req.headers();
@@ -749,7 +756,13 @@ class ServerImpl {
} else {
headerValue = headers.getFirst("Content-Length");
if (headerValue != null) {
- clen = Long.parseLong(headerValue);
+ try {
+ clen = Long.parseLong(headerValue);
+ } catch (NumberFormatException e2) {
+ reject(Code.HTTP_BAD_REQUEST,
+ requestLine, "NumberFormatException thrown");
+ return;
+ }
if (clen < 0) {
reject(Code.HTTP_BAD_REQUEST, requestLine,
"Illegal Content-Length value");
@@ -834,20 +847,11 @@ class ServerImpl {
uc.doFilter (new HttpExchangeImpl (tx));
}
- } catch (IOException e1) {
- logger.log (Level.TRACE, "ServerImpl.Exchange (1)", e1);
- closeConnection(connection);
- } catch (NumberFormatException e2) {
- logger.log (Level.TRACE, "ServerImpl.Exchange (2)", e2);
- reject (Code.HTTP_BAD_REQUEST,
- requestLine, "NumberFormatException thrown");
- } catch (URISyntaxException e3) {
- logger.log (Level.TRACE, "ServerImpl.Exchange (3)", e3);
- reject (Code.HTTP_BAD_REQUEST,
- requestLine, "URISyntaxException thrown");
- } catch (Exception e4) {
- logger.log (Level.TRACE, "ServerImpl.Exchange (4)", e4);
- closeConnection(connection);
+ } catch (Exception e) {
+ logger.log (Level.TRACE, "ServerImpl.Exchange", e);
+ if (tx == null || !tx.writefinished) {
+ closeConnection(connection);
+ }
} catch (Throwable t) {
logger.log(Level.TRACE, "ServerImpl.Exchange (5)", t);
throw t;
@@ -872,9 +876,8 @@ class ServerImpl {
rejected = true;
logReply (code, requestStr, message);
sendReply (
- code, false, "
"+code+Code.msg(code)+"
"+message
+ code, true, ""+code+Code.msg(code)+"
"+message
);
- closeConnection(connection);
}
void sendReply (
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/UndefLengthOutputStream.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/UndefLengthOutputStream.java
index 2918e42a05f..76a1be6ec5f 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/UndefLengthOutputStream.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/UndefLengthOutputStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 2023, 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
@@ -27,6 +27,8 @@ package sun.net.httpserver;
import java.io.*;
import java.net.*;
+import java.util.Objects;
+
import com.sun.net.httpserver.*;
import com.sun.net.httpserver.spi.*;
@@ -55,6 +57,10 @@ class UndefLengthOutputStream extends FilterOutputStream
}
public void write (byte[]b, int off, int len) throws IOException {
+ Objects.checkFromIndexSize(off, len, b.length);
+ if (len == 0) {
+ return;
+ }
if (closed) {
throw new IOException ("stream closed");
}
diff --git a/test/jdk/com/sun/net/httpserver/bugs/ExceptionKeepAlive.java b/test/jdk/com/sun/net/httpserver/bugs/ExceptionKeepAlive.java
new file mode 100644
index 00000000000..ea3ec9cea28
--- /dev/null
+++ b/test/jdk/com/sun/net/httpserver/bugs/ExceptionKeepAlive.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2023, 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.
+ */
+
+/**
+ * @test
+ * @bug 8219083
+ * @summary Exceptions thrown from HttpHandler.handle should not close connection
+ * if response is completed
+ * @library /test/lib
+ * @run junit ExceptionKeepAlive
+ */
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import jdk.test.lib.net.URIBuilder;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.URL;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;
+import java.util.logging.StreamHandler;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class ExceptionKeepAlive
+{
+
+ public static final Logger LOGGER = Logger.getLogger("com.sun.net.httpserver");
+
+ @Test
+ void test() throws IOException, InterruptedException {
+ HttpServer httpServer = startHttpServer();
+ int port = httpServer.getAddress().getPort();
+ try {
+ URL url = URIBuilder.newBuilder()
+ .scheme("http")
+ .loopback()
+ .port(port)
+ .path("/firstCall")
+ .toURLUnchecked();
+ HttpURLConnection uc = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY);
+ int responseCode = uc.getResponseCode();
+ assertEquals(200, responseCode, "First request should succeed");
+
+ URL url2 = URIBuilder.newBuilder()
+ .scheme("http")
+ .loopback()
+ .port(port)
+ .path("/secondCall")
+ .toURLUnchecked();
+ HttpURLConnection uc2 = (HttpURLConnection)url2.openConnection(Proxy.NO_PROXY);
+ responseCode = uc2.getResponseCode();
+ assertEquals(200, responseCode, "Second request should reuse connection");
+ } finally {
+ httpServer.stop(0);
+ }
+ }
+
+ /**
+ * Http Server
+ */
+ HttpServer startHttpServer() throws IOException {
+ Handler outHandler = new StreamHandler(System.out,
+ new SimpleFormatter());
+ outHandler.setLevel(Level.FINEST);
+ LOGGER.setLevel(Level.FINEST);
+ LOGGER.addHandler(outHandler);
+ InetAddress loopback = InetAddress.getLoopbackAddress();
+ HttpServer httpServer = HttpServer.create(new InetSocketAddress(loopback, 0), 0);
+ httpServer.createContext("/", new MyHandler());
+ httpServer.start();
+ return httpServer;
+ }
+
+ class MyHandler implements HttpHandler {
+
+ volatile int port1;
+ @Override
+ public void handle(HttpExchange t) throws IOException {
+ String path = t.getRequestURI().getPath();
+ if (path.equals("/firstCall")) {
+ port1 = t.getRemoteAddress().getPort();
+ System.out.println("First connection on client port = " + port1);
+
+ // send response
+ t.sendResponseHeaders(200, -1);
+ // response is completed now; throw exception
+ throw new NumberFormatException();
+ // the connection should still be reusable
+ } else if (path.equals("/secondCall")) {
+ int port2 = t.getRemoteAddress().getPort();
+ System.out.println("Second connection on client port = " + port2);
+
+ if (port1 == port2) {
+ t.sendResponseHeaders(200, -1);
+ } else {
+ t.sendResponseHeaders(500, -1);
+ }
+ }
+ t.close();
+ }
+ }
+}
diff --git a/test/jdk/com/sun/net/httpserver/bugs/ZeroLengthOutputStream.java b/test/jdk/com/sun/net/httpserver/bugs/ZeroLengthOutputStream.java
new file mode 100644
index 00000000000..ff446104e3d
--- /dev/null
+++ b/test/jdk/com/sun/net/httpserver/bugs/ZeroLengthOutputStream.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2023, 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.
+ */
+
+/**
+ * @test
+ * @bug 8219083
+ * @summary HttpExchange.getResponseBody write and close should not throw
+ * even when response length is zero
+ * @library /test/lib
+ * @run junit ZeroLengthOutputStream
+ */
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import jdk.test.lib.net.URIBuilder;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.URL;
+import java.util.concurrent.CountDownLatch;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;
+import java.util.logging.StreamHandler;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class ZeroLengthOutputStream
+{
+
+ public static final Logger LOGGER = Logger.getLogger("com.sun.net.httpserver");
+ public volatile boolean closed;
+ public CountDownLatch cdl = new CountDownLatch(1);
+
+ @Test
+ void test() throws IOException, InterruptedException {
+ HttpServer httpServer = startHttpServer();
+ int port = httpServer.getAddress().getPort();
+ try {
+ URL url = URIBuilder.newBuilder()
+ .scheme("http")
+ .loopback()
+ .port(port)
+ .path("/flis/")
+ .toURLUnchecked();
+ HttpURLConnection uc = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY);
+ uc.getResponseCode();
+ cdl.await();
+ assertTrue(closed, "OutputStream close did not complete");
+ } finally {
+ httpServer.stop(0);
+ }
+ }
+
+ /**
+ * Http Server
+ */
+ HttpServer startHttpServer() throws IOException {
+ Handler outHandler = new StreamHandler(System.out,
+ new SimpleFormatter());
+ outHandler.setLevel(Level.FINEST);
+ LOGGER.setLevel(Level.FINEST);
+ LOGGER.addHandler(outHandler);
+ InetAddress loopback = InetAddress.getLoopbackAddress();
+ HttpServer httpServer = HttpServer.create(new InetSocketAddress(loopback, 0), 0);
+ httpServer.createContext("/flis/", new MyHandler());
+ httpServer.start();
+ return httpServer;
+ }
+
+ class MyHandler implements HttpHandler {
+
+ @Override
+ public void handle(HttpExchange t) throws IOException {
+ try {
+ OutputStream os = t.getResponseBody();
+ t.sendResponseHeaders(200, -1);
+ os.write(new byte[0]);
+ os.close();
+ System.out.println("Output stream closed");
+ closed = true;
+ } finally {
+ cdl.countDown();
+ }
+ }
+ }
+}
diff --git a/test/jdk/sun/net/www/http/KeepAliveCache/B5045306.java b/test/jdk/sun/net/www/http/KeepAliveCache/B5045306.java
index ebfb550b826..e6ffc1d4e0b 100644
--- a/test/jdk/sun/net/www/http/KeepAliveCache/B5045306.java
+++ b/test/jdk/sun/net/www/http/KeepAliveCache/B5045306.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2023, 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
@@ -169,7 +169,7 @@ class SimpleHttpTransactionHandler implements HttpHandler
try {
String path = trans.getRequestURI().getPath();
if (path.equals("/firstCall")) {
- port1 = trans.getLocalAddress().getPort();
+ port1 = trans.getRemoteAddress().getPort();
System.out.println("First connection on client port = " + port1);
byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
@@ -180,7 +180,7 @@ class SimpleHttpTransactionHandler implements HttpHandler
pw.print(responseBody);
}
} else if (path.equals("/secondCall")) {
- int port2 = trans.getLocalAddress().getPort();
+ int port2 = trans.getRemoteAddress().getPort();
System.out.println("Second connection on client port = " + port2);
if (port1 != port2)
diff --git a/test/jdk/sun/net/www/http/KeepAliveCache/B8293562.java b/test/jdk/sun/net/www/http/KeepAliveCache/B8293562.java
index 4e0cff8bab4..2e6dbb84e2d 100644
--- a/test/jdk/sun/net/www/http/KeepAliveCache/B8293562.java
+++ b/test/jdk/sun/net/www/http/KeepAliveCache/B8293562.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2023, 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
@@ -29,6 +29,8 @@
* @summary Http keep-alive thread should close sockets without holding a lock
*/
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import javax.net.ssl.HandshakeCompletedListener;
@@ -45,6 +47,7 @@ import java.net.Proxy;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
@@ -64,6 +67,7 @@ public class B8293562 {
public static void startHttpServer() throws Exception {
server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 10);
server.setExecutor(Executors.newCachedThreadPool());
+ server.createContext("/", new NotFoundHandler());
server.start();
}
@@ -234,5 +238,13 @@ public class B8293562 {
throw new UnsupportedOperationException();
}
}
+
+ static class NotFoundHandler implements HttpHandler {
+ @Override
+ public void handle(HttpExchange t) throws IOException {
+ t.sendResponseHeaders(404, 3);
+ t.getResponseBody().write("abc".getBytes(StandardCharsets.UTF_8));
+ }
+ }
}