/*
* Copyright (c) 2002, 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
* @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.*;
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();
Random r = new Random();
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 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();
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 {
Random r = new Random();
// start server
ServerSocket ss = new ServerSocket(0);
Server svr = new Server(ss);
svr.start();
String uri = "http://localhost:" +
Integer.toString(ss.getLocalPort()) +
"/foo.html";
/*
* 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.currentThread().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;
}
}
}