Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.

Commit bde87a3

Browse files
author
Yuri Nesterenko
committed
8266761: AssertionError in sun.net.httpserver.ServerImpl.responseCompleted
Backport-of: 2d2cd78bde7bb9101614a1ba2285d1e37d5d3249
1 parent 10ec0b5 commit bde87a3

File tree

3 files changed

+241
-4
lines changed

3 files changed

+241
-4
lines changed

src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ PlaceholderOutputStream getPlaceholderResponseBody () {
202202
public void sendResponseHeaders (int rCode, long contentLen)
203203
throws IOException
204204
{
205+
final Logger logger = server.getLogger();
205206
if (sentHeaders) {
206207
throw new IOException ("headers already sent");
207208
}
@@ -221,7 +222,6 @@ public void sendResponseHeaders (int rCode, long contentLen)
221222
||(rCode == 304)) /* not modified */
222223
{
223224
if (contentLen != -1) {
224-
Logger logger = server.getLogger();
225225
String msg = "sendResponseHeaders: rCode = "+ rCode
226226
+ ": forcing contentLen = -1";
227227
logger.log (Level.WARNING, msg);
@@ -235,7 +235,6 @@ public void sendResponseHeaders (int rCode, long contentLen)
235235
* through this API, but should instead manually set the required
236236
* headers.*/
237237
if (contentLen >= 0) {
238-
final Logger logger = server.getLogger();
239238
String msg =
240239
"sendResponseHeaders: being invoked with a content length for a HEAD request";
241240
logger.log (Level.WARNING, msg);
@@ -272,7 +271,6 @@ public void sendResponseHeaders (int rCode, long contentLen)
272271
Optional.ofNullable(rspHdrs.get("Connection"))
273272
.map(List::stream).orElse(Stream.empty());
274273
if (conheader.anyMatch("close"::equalsIgnoreCase)) {
275-
Logger logger = server.getLogger();
276274
logger.log (Level.DEBUG, "Connection: close requested by handler");
277275
close = true;
278276
}
@@ -283,6 +281,7 @@ public void sendResponseHeaders (int rCode, long contentLen)
283281
tmpout.flush() ;
284282
tmpout = null;
285283
sentHeaders = true;
284+
logger.log(Level.TRACE, "Sent headers: noContentToSend=" + noContentToSend);
286285
if (noContentToSend) {
287286
WriteFinishedEvent e = new WriteFinishedEvent (this);
288287
server.addEvent (e);

src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,15 +290,19 @@ private void handleEvent (Event r) {
290290
try {
291291
if (r instanceof WriteFinishedEvent) {
292292

293+
logger.log(Level.TRACE, "Write Finished");
293294
int exchanges = endExchange();
294295
if (terminating && exchanges == 0) {
295296
finished = true;
296297
}
297-
responseCompleted (c);
298298
LeftOverInputStream is = t.getOriginalInputStream();
299299
if (!is.isEOF()) {
300300
t.close = true;
301+
if (c.getState() == State.REQUEST) {
302+
requestCompleted(c);
303+
}
301304
}
305+
responseCompleted (c);
302306
if (t.close || idleConnections.size() >= MAX_IDLE_CONNECTIONS) {
303307
c.close();
304308
allConnections.remove (c);
@@ -512,6 +516,7 @@ class Exchange implements Runnable {
512516

513517
public void run () {
514518
/* context will be null for new connections */
519+
logger.log(Level.TRACE, "exchange started");
515520
context = connection.getHttpContext();
516521
boolean newconnection;
517522
SSLEngine engine = null;
@@ -701,6 +706,9 @@ public void run () {
701706
} catch (Exception e4) {
702707
logger.log (Level.TRACE, "ServerImpl.Exchange (2)", e4);
703708
closeConnection(connection);
709+
} catch (Throwable t) {
710+
logger.log(Level.TRACE, "ServerImpl.Exchange (5)", t);
711+
throw t;
704712
}
705713
}
706714

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/*
2+
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/**
25+
* @test
26+
* @bug 8266761
27+
* @summary HttpServer can fail with an assertion error when a handler doesn't fully
28+
* read the request body and sends back a reply with no content, or when
29+
* the client closes its outputstream while the server tries to drains
30+
* its content.
31+
* @run testng/othervm InputNotRead
32+
* @run testng/othervm -Djava.net.preferIPv6Addresses=true InputNotRead
33+
*/
34+
35+
import java.io.BufferedReader;
36+
import java.io.IOException;
37+
import java.io.InputStreamReader;
38+
import java.io.OutputStreamWriter;
39+
import java.io.PrintWriter;
40+
import java.net.InetAddress;
41+
import java.net.InetSocketAddress;
42+
import java.net.Socket;
43+
import java.util.concurrent.ExecutorService;
44+
import java.util.concurrent.Executors;
45+
import java.util.concurrent.ThreadFactory;
46+
import java.util.concurrent.atomic.AtomicLong;
47+
import java.util.logging.Level;
48+
import java.util.logging.Logger;
49+
50+
import com.sun.net.httpserver.HttpExchange;
51+
import com.sun.net.httpserver.HttpHandler;
52+
import com.sun.net.httpserver.HttpServer;
53+
54+
import org.testng.annotations.Test;
55+
56+
import static java.nio.charset.StandardCharsets.*;
57+
58+
public class InputNotRead {
59+
60+
private static final int msgCode = 200;
61+
private static final String someContext = "/context";
62+
63+
static class ServerThreadFactory implements ThreadFactory {
64+
static final AtomicLong tokens = new AtomicLong();
65+
@Override
66+
public Thread newThread(Runnable r) {
67+
var thread = new Thread(r, "Server-" + tokens.incrementAndGet());
68+
thread.setDaemon(true);
69+
return thread;
70+
}
71+
}
72+
73+
static {
74+
Logger.getLogger("").setLevel(Level.ALL);
75+
Logger.getLogger("").getHandlers()[0].setLevel(Level.ALL);
76+
}
77+
78+
@Test
79+
public void testSendResponse() throws Exception {
80+
System.out.println("testSendResponse()");
81+
InetAddress loopback = InetAddress.getLoopbackAddress();
82+
HttpServer server = HttpServer.create(new InetSocketAddress(loopback, 0), 0);
83+
ExecutorService executor = Executors.newCachedThreadPool(new ServerThreadFactory());
84+
server.setExecutor(executor);
85+
try {
86+
server.createContext(someContext, new HttpHandler() {
87+
@Override
88+
public void handle(HttpExchange msg) throws IOException {
89+
System.err.println("Handling request: " + msg.getRequestURI());
90+
byte[] reply = new byte[0];
91+
try {
92+
msg.getRequestBody().read();
93+
try {
94+
msg.sendResponseHeaders(msgCode, reply.length == 0 ? -1 : reply.length);
95+
} catch(IOException ioe) {
96+
ioe.printStackTrace();
97+
}
98+
} finally {
99+
// don't close the exchange and don't close any stream
100+
// to trigger the assertion.
101+
System.err.println("Request handled: " + msg.getRequestURI());
102+
}
103+
}
104+
});
105+
server.start();
106+
System.out.println("Server started at port "
107+
+ server.getAddress().getPort());
108+
109+
runRawSocketHttpClient(loopback, server.getAddress().getPort(), -1);
110+
} finally {
111+
System.out.println("shutting server down");
112+
executor.shutdown();
113+
server.stop(0);
114+
}
115+
System.out.println("Server finished.");
116+
}
117+
118+
@Test
119+
public void testCloseOutputStream() throws Exception {
120+
System.out.println("testCloseOutputStream()");
121+
InetAddress loopback = InetAddress.getLoopbackAddress();
122+
HttpServer server = HttpServer.create(new InetSocketAddress(loopback, 0), 0);
123+
ExecutorService executor = Executors.newCachedThreadPool(new ServerThreadFactory());
124+
server.setExecutor(executor);
125+
try {
126+
server.createContext(someContext, new HttpHandler() {
127+
@Override
128+
public void handle(HttpExchange msg) throws IOException {
129+
System.err.println("Handling request: " + msg.getRequestURI());
130+
byte[] reply = "Here is my reply!".getBytes(UTF_8);
131+
try {
132+
BufferedReader r = new BufferedReader(new InputStreamReader(msg.getRequestBody()));
133+
r.read();
134+
try {
135+
msg.sendResponseHeaders(msgCode, reply.length == 0 ? -1 : reply.length);
136+
msg.getResponseBody().write(reply);
137+
msg.getResponseBody().close();
138+
Thread.sleep(50);
139+
} catch(IOException | InterruptedException ie) {
140+
ie.printStackTrace();
141+
}
142+
} finally {
143+
System.err.println("Request handled: " + msg.getRequestURI());
144+
}
145+
}
146+
});
147+
server.start();
148+
System.out.println("Server started at port "
149+
+ server.getAddress().getPort());
150+
151+
runRawSocketHttpClient(loopback, server.getAddress().getPort(), 64 * 1024 + 16);
152+
} finally {
153+
System.out.println("shutting server down");
154+
executor.shutdown();
155+
server.stop(0);
156+
}
157+
System.out.println("Server finished.");
158+
}
159+
160+
static void runRawSocketHttpClient(InetAddress address, int port, int contentLength)
161+
throws Exception
162+
{
163+
Socket socket = null;
164+
PrintWriter writer = null;
165+
BufferedReader reader = null;
166+
final String CRLF = "\r\n";
167+
try {
168+
socket = new Socket(address, port);
169+
writer = new PrintWriter(new OutputStreamWriter(
170+
socket.getOutputStream()));
171+
System.out.println("Client connected by socket: " + socket);
172+
String body = "I will send all the data.";
173+
if (contentLength <= 0)
174+
contentLength = body.getBytes(UTF_8).length;
175+
176+
writer.print("GET " + someContext + "/ HTTP/1.1" + CRLF);
177+
writer.print("User-Agent: Java/"
178+
+ System.getProperty("java.version")
179+
+ CRLF);
180+
writer.print("Host: " + address.getHostName() + CRLF);
181+
writer.print("Accept: */*" + CRLF);
182+
writer.print("Content-Length: " + contentLength + CRLF);
183+
writer.print("Connection: keep-alive" + CRLF);
184+
writer.print(CRLF); // Important, else the server will expect that
185+
// there's more into the request.
186+
writer.flush();
187+
System.out.println("Client wrote request to socket: " + socket);
188+
writer.print(body);
189+
writer.flush();
190+
191+
reader = new BufferedReader(new InputStreamReader(
192+
socket.getInputStream()));
193+
System.out.println("Client start reading from server:" );
194+
String line = reader.readLine();
195+
for (; line != null; line = reader.readLine()) {
196+
if (line.isEmpty()) {
197+
break;
198+
}
199+
System.out.println("\"" + line + "\"");
200+
}
201+
System.out.println("Client finished reading from server" );
202+
} finally {
203+
// give time to the server to try & drain its input stream
204+
Thread.sleep(500);
205+
// closes the client outputstream while the server is draining
206+
// it
207+
if (writer != null) {
208+
writer.close();
209+
}
210+
// give time to the server to trigger its assertion
211+
// error before closing the connection
212+
Thread.sleep(500);
213+
if (reader != null)
214+
try {
215+
reader.close();
216+
} catch (IOException logOrIgnore) {
217+
logOrIgnore.printStackTrace();
218+
}
219+
if (socket != null) {
220+
try {
221+
socket.close();
222+
} catch (IOException logOrIgnore) {
223+
logOrIgnore.printStackTrace();
224+
}
225+
}
226+
}
227+
System.out.println("Client finished." );
228+
}
229+
230+
}

0 commit comments

Comments
 (0)