53a97cbeb3
Co-authored-by: Alexander Kulyakthin <alexander.kulyakhtin@oracle.com> Co-authored-by: Alan Bateman <alan.bateman@oracle.com> Reviewed-by: alanb, joehw, lancea
195 lines
7.9 KiB
Java
195 lines
7.9 KiB
Java
/*
|
|
* Copyright (c) 2005, 2012, 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 5045306 6356004 6993490
|
|
* @modules java.base/sun.net.www
|
|
* @library ../../httptest/
|
|
* @build HttpCallback TestHttpServer HttpTransaction
|
|
* @run main/othervm B5045306
|
|
* @summary Http keep-alive implementation is not efficient
|
|
*/
|
|
|
|
import java.net.*;
|
|
import java.io.*;
|
|
import java.lang.management.*;
|
|
|
|
/* Part 1:
|
|
* The http client makes a connection to a URL whos content contains a lot of
|
|
* data, more than can fit in the socket buffer. The client only reads
|
|
* 1 byte of the data from the InputStream leaving behind more data than can
|
|
* fit in the socket buffer. The client then makes a second call to the http
|
|
* server. If the connection port used by the client is the same as for the
|
|
* first call then that means that the connection is being reused.
|
|
*
|
|
* Part 2:
|
|
* Test buggy webserver that sends less data than it specifies in its
|
|
* Content-length header.
|
|
*/
|
|
|
|
public class B5045306
|
|
{
|
|
static SimpleHttpTransaction httpTrans;
|
|
static TestHttpServer server;
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
startHttpServer();
|
|
clientHttpCalls();
|
|
}
|
|
|
|
public static void startHttpServer() {
|
|
try {
|
|
httpTrans = new SimpleHttpTransaction();
|
|
server = new TestHttpServer(httpTrans, 1, 10, 0);
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
public static void clientHttpCalls() {
|
|
try {
|
|
System.out.println("http server listen on: " + server.getLocalPort());
|
|
String baseURLStr = "http://" + InetAddress.getLocalHost().getHostAddress() + ":" +
|
|
server.getLocalPort() + "/";
|
|
|
|
URL bigDataURL = new URL (baseURLStr + "firstCall");
|
|
URL smallDataURL = new URL (baseURLStr + "secondCall");
|
|
|
|
HttpURLConnection uc = (HttpURLConnection)bigDataURL.openConnection();
|
|
|
|
//Only read 1 byte of response data and close the stream
|
|
InputStream is = uc.getInputStream();
|
|
byte[] ba = new byte[1];
|
|
is.read(ba);
|
|
is.close();
|
|
|
|
// Allow the KeepAliveStreamCleaner thread to read the data left behind and cache the connection.
|
|
try { Thread.sleep(2000); } catch (Exception e) {}
|
|
|
|
uc = (HttpURLConnection)smallDataURL.openConnection();
|
|
uc.getResponseCode();
|
|
|
|
if (SimpleHttpTransaction.failed)
|
|
throw new RuntimeException("Failed: Initial Keep Alive Connection is not being reused");
|
|
|
|
// Part 2
|
|
URL part2Url = new URL (baseURLStr + "part2");
|
|
uc = (HttpURLConnection)part2Url.openConnection();
|
|
is = uc.getInputStream();
|
|
is.close();
|
|
|
|
// Allow the KeepAliveStreamCleaner thread to try and read the data left behind and cache the connection.
|
|
try { Thread.sleep(2000); } catch (Exception e) {}
|
|
|
|
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
|
|
if (threadMXBean.isThreadCpuTimeSupported()) {
|
|
long[] threads = threadMXBean.getAllThreadIds();
|
|
ThreadInfo[] threadInfo = threadMXBean.getThreadInfo(threads);
|
|
for (int i=0; i<threadInfo.length; i++) {
|
|
if (threadInfo[i].getThreadName().equals("Keep-Alive-SocketCleaner")) {
|
|
System.out.println("Found Keep-Alive-SocketCleaner thread");
|
|
long threadID = threadInfo[i].getThreadId();
|
|
long before = threadMXBean.getThreadCpuTime(threadID);
|
|
try { Thread.sleep(2000); } catch (Exception e) {}
|
|
long after = threadMXBean.getThreadCpuTime(threadID);
|
|
|
|
if (before ==-1 || after == -1)
|
|
break; // thread has died, OK
|
|
|
|
// if Keep-Alive-SocketCleaner consumes more than 50% of cpu then we
|
|
// can assume a recursive loop.
|
|
long total = after - before;
|
|
if (total >= 1000000000) // 1 second, or 1 billion nanoseconds
|
|
throw new RuntimeException("Failed: possible recursive loop in Keep-Alive-SocketCleaner");
|
|
}
|
|
}
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
} finally {
|
|
server.terminate();
|
|
}
|
|
}
|
|
}
|
|
|
|
class SimpleHttpTransaction implements HttpCallback
|
|
{
|
|
static boolean failed = false;
|
|
|
|
// Need to have enough data here that is too large for the socket buffer to hold.
|
|
// Also http.KeepAlive.remainingData must be greater than this value, default is 256K.
|
|
static final int RESPONSE_DATA_LENGTH = 128 * 1024;
|
|
|
|
int port1;
|
|
|
|
public void request(HttpTransaction trans) {
|
|
try {
|
|
String path = trans.getRequestURI().getPath();
|
|
if (path.equals("/firstCall")) {
|
|
port1 = trans.channel().socket().getPort();
|
|
System.out.println("First connection on client port = " + port1);
|
|
|
|
byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
|
|
for (int i=0; i<responseBody.length; i++)
|
|
responseBody[i] = 0x41;
|
|
trans.setResponseEntityBody (responseBody, responseBody.length);
|
|
trans.sendResponse(200, "OK");
|
|
} else if (path.equals("/secondCall")) {
|
|
int port2 = trans.channel().socket().getPort();
|
|
System.out.println("Second connection on client port = " + port2);
|
|
|
|
if (port1 != port2)
|
|
failed = true;
|
|
|
|
trans.setResponseHeader ("Content-length", Integer.toString(0));
|
|
|
|
/* Force the server to not respond for more that the timeout
|
|
* set by the keepalive cleaner (5000 millis). This ensures the
|
|
* timeout is correctly resets the default read timeout,
|
|
* infinity. See 6993490. */
|
|
System.out.println("server sleeping...");
|
|
try {Thread.sleep(6000); } catch (InterruptedException e) {}
|
|
|
|
trans.sendResponse(200, "OK");
|
|
} else if(path.equals("/part2")) {
|
|
System.out.println("Call to /part2");
|
|
byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
|
|
for (int i=0; i<responseBody.length; i++)
|
|
responseBody[i] = 0x41;
|
|
trans.setResponseEntityBody (responseBody, responseBody.length);
|
|
|
|
// override the Content-length header to be greater than the actual response body
|
|
trans.setResponseHeader("Content-length", Integer.toString(responseBody.length+1));
|
|
trans.sendResponse(200, "OK");
|
|
|
|
// now close the socket
|
|
trans.channel().socket().close();
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|