0N/A/*
3261N/A * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
0N/A * published by the Free Software Foundation.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/A/*
0N/A * @test
2981N/A * @bug 5045306 6356004 6993490
0N/A * @library ../../httptest/
0N/A * @build HttpCallback HttpServer HttpTransaction
0N/A * @run main/othervm B5045306
0N/A * @summary Http keep-alive implementation is not efficient
0N/A */
0N/A
0N/Aimport java.net.*;
0N/Aimport java.io.*;
0N/Aimport java.lang.management.*;
0N/A
0N/A/* Part 1:
0N/A * The http client makes a connection to a URL whos content contains a lot of
0N/A * data, more than can fit in the socket buffer. The client only reads
0N/A * 1 byte of the data from the InputStream leaving behind more data than can
0N/A * fit in the socket buffer. The client then makes a second call to the http
0N/A * server. If the connection port used by the client is the same as for the
0N/A * first call then that means that the connection is being reused.
0N/A *
0N/A * Part 2:
0N/A * Test buggy webserver that sends less data than it specifies in its
0N/A * Content-length header.
0N/A */
0N/A
0N/Apublic class B5045306
0N/A{
0N/A static SimpleHttpTransaction httpTrans;
0N/A static HttpServer server;
0N/A
0N/A public static void main(String[] args) throws Exception {
0N/A startHttpServer();
0N/A clientHttpCalls();
0N/A }
0N/A
0N/A public static void startHttpServer() {
0N/A try {
0N/A httpTrans = new SimpleHttpTransaction();
0N/A server = new HttpServer(httpTrans, 1, 10, 0);
0N/A } catch (IOException e) {
0N/A e.printStackTrace();
0N/A }
0N/A }
0N/A
0N/A public static void clientHttpCalls() {
0N/A try {
0N/A System.out.println("http server listen on: " + server.getLocalPort());
0N/A String baseURLStr = "http://" + InetAddress.getLocalHost().getHostAddress() + ":" +
0N/A server.getLocalPort() + "/";
0N/A
0N/A URL bigDataURL = new URL (baseURLStr + "firstCall");
0N/A URL smallDataURL = new URL (baseURLStr + "secondCall");
0N/A
0N/A HttpURLConnection uc = (HttpURLConnection)bigDataURL.openConnection();
0N/A
0N/A //Only read 1 byte of response data and close the stream
0N/A InputStream is = uc.getInputStream();
0N/A byte[] ba = new byte[1];
0N/A is.read(ba);
0N/A is.close();
0N/A
0N/A // Allow the KeepAliveStreamCleaner thread to read the data left behind and cache the connection.
0N/A try { Thread.sleep(2000); } catch (Exception e) {}
0N/A
0N/A uc = (HttpURLConnection)smallDataURL.openConnection();
0N/A uc.getResponseCode();
0N/A
0N/A if (SimpleHttpTransaction.failed)
0N/A throw new RuntimeException("Failed: Initial Keep Alive Connection is not being reused");
0N/A
0N/A // Part 2
0N/A URL part2Url = new URL (baseURLStr + "part2");
0N/A uc = (HttpURLConnection)part2Url.openConnection();
0N/A is = uc.getInputStream();
0N/A is.close();
0N/A
0N/A // Allow the KeepAliveStreamCleaner thread to try and read the data left behind and cache the connection.
0N/A try { Thread.sleep(2000); } catch (Exception e) {}
0N/A
0N/A ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
0N/A if (threadMXBean.isThreadCpuTimeSupported()) {
0N/A long[] threads = threadMXBean.getAllThreadIds();
0N/A ThreadInfo[] threadInfo = threadMXBean.getThreadInfo(threads);
0N/A for (int i=0; i<threadInfo.length; i++) {
0N/A if (threadInfo[i].getThreadName().equals("Keep-Alive-SocketCleaner")) {
0N/A System.out.println("Found Keep-Alive-SocketCleaner thread");
0N/A long threadID = threadInfo[i].getThreadId();
0N/A long before = threadMXBean.getThreadCpuTime(threadID);
0N/A try { Thread.sleep(2000); } catch (Exception e) {}
0N/A long after = threadMXBean.getThreadCpuTime(threadID);
0N/A
0N/A if (before ==-1 || after == -1)
0N/A break; // thread has died, OK
0N/A
0N/A // if Keep-Alive-SocketCleaner consumes more than 50% of cpu then we
0N/A // can assume a recursive loop.
0N/A long total = after - before;
0N/A if (total >= 1000000000) // 1 second, or 1 billion nanoseconds
0N/A throw new RuntimeException("Failed: possible recursive loop in Keep-Alive-SocketCleaner");
0N/A }
0N/A }
0N/A }
0N/A
0N/A } catch (IOException e) {
0N/A e.printStackTrace();
0N/A } finally {
0N/A server.terminate();
0N/A }
0N/A }
0N/A}
0N/A
0N/Aclass SimpleHttpTransaction implements HttpCallback
0N/A{
0N/A static boolean failed = false;
0N/A
0N/A // Need to have enough data here that is too large for the socket buffer to hold.
0N/A // Also http.KeepAlive.remainingData must be greater than this value, default is 256K.
0N/A static final int RESPONSE_DATA_LENGTH = 128 * 1024;
0N/A
0N/A int port1;
0N/A
0N/A public void request(HttpTransaction trans) {
0N/A try {
0N/A String path = trans.getRequestURI().getPath();
0N/A if (path.equals("/firstCall")) {
0N/A port1 = trans.channel().socket().getPort();
0N/A System.out.println("First connection on client port = " + port1);
0N/A
0N/A byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
0N/A for (int i=0; i<responseBody.length; i++)
0N/A responseBody[i] = 0x41;
0N/A trans.setResponseEntityBody (responseBody, responseBody.length);
0N/A trans.sendResponse(200, "OK");
0N/A } else if (path.equals("/secondCall")) {
0N/A int port2 = trans.channel().socket().getPort();
0N/A System.out.println("Second connection on client port = " + port2);
0N/A
0N/A if (port1 != port2)
0N/A failed = true;
0N/A
0N/A trans.setResponseHeader ("Content-length", Integer.toString(0));
2981N/A
2981N/A /* Force the server to not respond for more that the timeout
2981N/A * set by the keepalive cleaner (5000 millis). This ensures the
2981N/A * timeout is correctly resets the default read timeout,
2981N/A * infinity. See 6993490. */
2981N/A System.out.println("server sleeping...");
2981N/A try {Thread.sleep(6000); } catch (InterruptedException e) {}
2981N/A
0N/A trans.sendResponse(200, "OK");
0N/A } else if(path.equals("/part2")) {
0N/A System.out.println("Call to /part2");
0N/A byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
0N/A for (int i=0; i<responseBody.length; i++)
0N/A responseBody[i] = 0x41;
0N/A trans.setResponseEntityBody (responseBody, responseBody.length);
0N/A
0N/A // override the Content-length header to be greater than the actual response body
0N/A trans.setResponseHeader("Content-length", Integer.toString(responseBody.length+1));
0N/A trans.sendResponse(200, "OK");
0N/A
0N/A // now close the socket
0N/A trans.channel().socket().close();
0N/A }
0N/A } catch (Exception e) {
0N/A e.printStackTrace();
0N/A }
0N/A }
0N/A}