0ce182723b
Co-authored-by: Michael Ernst <mernst@openjdk.org> Reviewed-by: alanb, lancea, dfuchs
308 lines
10 KiB
Java
308 lines
10 KiB
Java
/*
|
|
* Copyright (c) 2002, 2019, 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 4774503
|
|
* @library /test/lib
|
|
* @summary Calling HttpURLConnection's disconnect method after the
|
|
* response has been received causes havoc with persistent
|
|
* connections.
|
|
*/
|
|
import java.net.*;
|
|
import java.io.*;
|
|
import java.util.*;
|
|
import jdk.test.lib.net.URIBuilder;
|
|
import static java.net.Proxy.NO_PROXY;
|
|
|
|
public class DisconnectAfterEOF {
|
|
|
|
/*
|
|
* Worker thread to service single connection - can service
|
|
* multiple http requests on same connection.
|
|
*/
|
|
static class Worker extends Thread {
|
|
Socket s;
|
|
|
|
Worker(Socket s) {
|
|
this.s = s;
|
|
}
|
|
|
|
public void run() {
|
|
try {
|
|
InputStream in = s.getInputStream();
|
|
PrintStream out = new PrintStream(
|
|
new BufferedOutputStream(
|
|
s.getOutputStream() ));
|
|
byte b[] = new byte[1024];
|
|
int n = -1;
|
|
int cl = -1;
|
|
int remaining = -1;
|
|
StringBuffer sb = new StringBuffer();
|
|
boolean close = false;
|
|
|
|
boolean inBody = false;
|
|
for (;;) {
|
|
boolean sendResponse = false;
|
|
|
|
try {
|
|
n = in.read(b);
|
|
} catch (IOException ioe) {
|
|
n = -1;
|
|
}
|
|
if (n <= 0) {
|
|
if (inBody) {
|
|
System.err.println("ERROR: Client closed before " +
|
|
"entire request received.");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// reading entity-body
|
|
if (inBody) {
|
|
if (n > remaining) {
|
|
System.err.println("Receiving more than expected!!!");
|
|
return;
|
|
}
|
|
remaining -= n;
|
|
|
|
if (remaining == 0) {
|
|
sendResponse = true;
|
|
n = 0;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// reading headers
|
|
for (int i=0; i<n; i++) {
|
|
char c = (char)b[i];
|
|
|
|
if (c != '\n') {
|
|
sb.append(c);
|
|
continue;
|
|
}
|
|
|
|
|
|
// Got end-of-line
|
|
int len = sb.length();
|
|
if (len > 0) {
|
|
if (sb.charAt(len-1) != '\r') {
|
|
System.err.println("Unexpected CR in header!!");
|
|
return;
|
|
}
|
|
}
|
|
sb.setLength(len-1);
|
|
|
|
// empty line
|
|
if (sb.length() == 0) {
|
|
if (cl < 0) {
|
|
System.err.println("Content-Length not found!!!");
|
|
return;
|
|
}
|
|
|
|
// the surplus is body data
|
|
int dataRead = n - (i+1);
|
|
remaining = cl - dataRead;
|
|
if (remaining > 0) {
|
|
inBody = true;
|
|
break;
|
|
} else {
|
|
// entire body has been read
|
|
sendResponse = true;
|
|
}
|
|
} else {
|
|
// non-empty line - check for Content-Length
|
|
String line = sb.toString().toLowerCase();
|
|
if (line.startsWith("content-length")) {
|
|
StringTokenizer st = new StringTokenizer(line, ":");
|
|
st.nextToken();
|
|
cl = Integer.parseInt(st.nextToken().trim());
|
|
}
|
|
if (line.startsWith("connection")) {
|
|
StringTokenizer st = new StringTokenizer(line, ":");
|
|
st.nextToken();
|
|
if (st.nextToken().trim().equals("close")) {
|
|
close =true;
|
|
}
|
|
}
|
|
}
|
|
sb = new StringBuffer();
|
|
}
|
|
|
|
|
|
if (sendResponse) {
|
|
// send a large response
|
|
int rspLen = 32000;
|
|
|
|
out.print("HTTP/1.1 200 OK\r\n");
|
|
out.print("Content-Length: " + rspLen + "\r\n");
|
|
out.print("\r\n");
|
|
|
|
if (rspLen > 0)
|
|
out.write(new byte[rspLen]);
|
|
|
|
out.flush();
|
|
|
|
if (close)
|
|
return;
|
|
|
|
sendResponse = false;
|
|
inBody = false;
|
|
cl = -1;
|
|
}
|
|
}
|
|
|
|
} catch (IOException ioe) {
|
|
} finally {
|
|
try {
|
|
s.close();
|
|
} catch (Exception e) { }
|
|
System.out.println("+ Worker thread shutdown.");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Server thread to accept connection and create worker threads
|
|
* to service each connection.
|
|
*/
|
|
static class Server extends Thread {
|
|
ServerSocket ss;
|
|
|
|
Server(ServerSocket ss) {
|
|
this.ss = ss;
|
|
}
|
|
|
|
public void run() {
|
|
try {
|
|
for (;;) {
|
|
Socket s = ss.accept();
|
|
Worker w = new Worker(s);
|
|
w.start();
|
|
}
|
|
|
|
} catch (IOException ioe) {
|
|
}
|
|
|
|
System.out.println("+ Server shutdown.");
|
|
}
|
|
|
|
public void shutdown() {
|
|
try {
|
|
ss.close();
|
|
} catch (IOException ioe) { }
|
|
}
|
|
}
|
|
|
|
static URLConnection doRequest(String uri) throws IOException {
|
|
URLConnection uc = (new URL(uri)).openConnection(NO_PROXY);
|
|
uc.setDoOutput(true);
|
|
OutputStream out = uc.getOutputStream();
|
|
out.write(new byte[16000]);
|
|
|
|
// force the request to be sent
|
|
uc.getInputStream();
|
|
return uc;
|
|
}
|
|
|
|
static URLConnection doResponse(URLConnection uc) throws IOException {
|
|
int cl = ((HttpURLConnection)uc).getContentLength();
|
|
byte b[] = new byte[4096];
|
|
int n;
|
|
do {
|
|
n = uc.getInputStream().read(b);
|
|
if (n > 0) cl -= n;
|
|
} while (n > 0);
|
|
if (cl != 0) {
|
|
throw new RuntimeException("ERROR: content-length mismatch");
|
|
}
|
|
return uc;
|
|
}
|
|
|
|
public static void main(String args[]) throws Exception {
|
|
// start server
|
|
InetAddress loopback = InetAddress.getLoopbackAddress();
|
|
ServerSocket ss = new ServerSocket();
|
|
ss.bind(new InetSocketAddress(loopback, 0));
|
|
Server svr = new Server(ss);
|
|
svr.start();
|
|
|
|
String uri = URIBuilder.newBuilder()
|
|
.scheme("http")
|
|
.loopback()
|
|
.port(ss.getLocalPort())
|
|
.path("/foo.html")
|
|
.build().toString();
|
|
|
|
/*
|
|
* The following is the test scenario we create here :-
|
|
*
|
|
* 1. We do a http request/response and read the response
|
|
* to EOF. As it's a persistent connection the idle
|
|
* connection should go into the keep-alive cache for a
|
|
* few seconds.
|
|
*
|
|
* 2. We start a second request but don't read the response.
|
|
* As the request is to the same server we can assume it
|
|
* (for our implementation anyway) that it will use the
|
|
* same TCP connection.
|
|
*
|
|
* 3. We "disconnect" the first HttpURLConnection. This
|
|
* should be no-op because the connection is in use
|
|
* but another request. However with 1.3.1 and 1.4/1.4.1
|
|
* this causes the TCP connection for the second request
|
|
* to be closed.
|
|
*
|
|
*/
|
|
URLConnection uc1 = doRequest(uri);
|
|
doResponse(uc1);
|
|
|
|
Thread.sleep(2000);
|
|
|
|
URLConnection uc2 = doRequest(uri);
|
|
|
|
((HttpURLConnection)uc1).disconnect();
|
|
|
|
IOException ioe = null;
|
|
try {
|
|
doResponse(uc2);
|
|
} catch (IOException x) {
|
|
ioe = x;
|
|
}
|
|
|
|
((HttpURLConnection)uc2).disconnect();
|
|
|
|
/*
|
|
* Shutdown server as we are done. Worker threads created
|
|
* by the server will shutdown automatically when the
|
|
* client connection closes.
|
|
*/
|
|
svr.shutdown();
|
|
|
|
if (ioe != null) {
|
|
throw ioe;
|
|
}
|
|
}
|
|
}
|