1 /*
   2  * Copyright (c) 2005, 2012, 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 5045306 6356004 6993490
  27  * @modules java.base/sun.net.www
  28  * @library ../../httptest/
  29  * @build HttpCallback TestHttpServer HttpTransaction
  30  * @run main/othervm B5045306
  31  * @summary Http keep-alive implementation is not efficient
  32  */
  33 
  34 import java.net.*;
  35 import java.io.*;
  36 import java.lang.management.*;
  37 
  38 /* Part 1:
  39  * The http client makes a connection to a URL whos content contains a lot of
  40  * data, more than can fit in the socket buffer. The client only reads
  41  * 1 byte of the data from the InputStream leaving behind more data than can
  42  * fit in the socket buffer. The client then makes a second call to the http
  43  * server. If the connection port used by the client is the same as for the
  44  * first call then that means that the connection is being reused.
  45  *
  46  * Part 2:
  47  * Test buggy webserver that sends less data than it specifies in its
  48  * Content-length header.
  49  */
  50 
  51 public class B5045306
  52 {
  53     static SimpleHttpTransaction httpTrans;
  54     static TestHttpServer server;
  55 
  56     public static void main(String[] args) throws Exception {
  57         startHttpServer();
  58         clientHttpCalls();
  59     }
  60 
  61     public static void startHttpServer() {
  62         try {
  63             httpTrans = new SimpleHttpTransaction();
  64             server = new TestHttpServer(httpTrans, 1, 10, 0);
  65         } catch (IOException e) {
  66             e.printStackTrace();
  67         }
  68     }
  69 
  70     public static void clientHttpCalls() {
  71         try {
  72             System.out.println("http server listen on: " + server.getLocalPort());
  73             String baseURLStr = "http://" + InetAddress.getLocalHost().getHostAddress() + ":" +
  74                                   server.getLocalPort() + "/";
  75 
  76             URL bigDataURL = new URL (baseURLStr + "firstCall");
  77             URL smallDataURL = new URL (baseURLStr + "secondCall");
  78 
  79             HttpURLConnection uc = (HttpURLConnection)bigDataURL.openConnection();
  80 
  81             //Only read 1 byte of response data and close the stream
  82             InputStream is = uc.getInputStream();
  83             byte[] ba = new byte[1];
  84             is.read(ba);
  85             is.close();
  86 
  87             // Allow the KeepAliveStreamCleaner thread to read the data left behind and cache the connection.
  88             try { Thread.sleep(2000); } catch (Exception e) {}
  89 
  90             uc = (HttpURLConnection)smallDataURL.openConnection();
  91             uc.getResponseCode();
  92 
  93             if (SimpleHttpTransaction.failed)
  94                 throw new RuntimeException("Failed: Initial Keep Alive Connection is not being reused");
  95 
  96             // Part 2
  97             URL part2Url = new URL (baseURLStr + "part2");
  98             uc = (HttpURLConnection)part2Url.openConnection();
  99             is = uc.getInputStream();
 100             is.close();
 101 
 102             // Allow the KeepAliveStreamCleaner thread to try and read the data left behind and cache the connection.
 103             try { Thread.sleep(2000); } catch (Exception e) {}
 104 
 105             ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
 106             if (threadMXBean.isThreadCpuTimeSupported()) {
 107                 long[] threads = threadMXBean.getAllThreadIds();
 108                 ThreadInfo[] threadInfo = threadMXBean.getThreadInfo(threads);
 109                 for (int i=0; i<threadInfo.length; i++) {
 110                     if (threadInfo[i].getThreadName().equals("Keep-Alive-SocketCleaner"))  {
 111                         System.out.println("Found Keep-Alive-SocketCleaner thread");
 112                         long threadID = threadInfo[i].getThreadId();
 113                         long before = threadMXBean.getThreadCpuTime(threadID);
 114                         try { Thread.sleep(2000); } catch (Exception e) {}
 115                         long after = threadMXBean.getThreadCpuTime(threadID);
 116 
 117                         if (before ==-1 || after == -1)
 118                             break;  // thread has died, OK
 119 
 120                         // if Keep-Alive-SocketCleaner consumes more than 50% of cpu then we
 121                         // can assume a recursive loop.
 122                         long total = after - before;
 123                         if (total >= 1000000000)  // 1 second, or 1 billion nanoseconds
 124                             throw new RuntimeException("Failed: possible recursive loop in Keep-Alive-SocketCleaner");
 125                     }
 126                 }
 127             }
 128 
 129         } catch (IOException e) {
 130             e.printStackTrace();
 131         } finally {
 132             server.terminate();
 133         }
 134     }
 135 }
 136 
 137 class SimpleHttpTransaction implements HttpCallback
 138 {
 139     static boolean failed = false;
 140 
 141     // Need to have enough data here that is too large for the socket buffer to hold.
 142     // Also http.KeepAlive.remainingData must be greater than this value, default is 256K.
 143     static final int RESPONSE_DATA_LENGTH = 128 * 1024;
 144 
 145     int port1;
 146 
 147     public void request(HttpTransaction trans) {
 148         try {
 149             String path = trans.getRequestURI().getPath();
 150             if (path.equals("/firstCall")) {
 151                 port1 = trans.channel().socket().getPort();
 152                 System.out.println("First connection on client port = " + port1);
 153 
 154                 byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
 155                 for (int i=0; i<responseBody.length; i++)
 156                     responseBody[i] = 0x41;
 157                 trans.setResponseEntityBody (responseBody, responseBody.length);
 158                 trans.sendResponse(200, "OK");
 159             } else if (path.equals("/secondCall")) {
 160                 int port2 = trans.channel().socket().getPort();
 161                 System.out.println("Second connection on client port = " + port2);
 162 
 163                 if (port1 != port2)
 164                     failed = true;
 165 
 166                 trans.setResponseHeader ("Content-length", Integer.toString(0));
 167 
 168                  /* Force the server to not respond for more that the timeout
 169                   * set by the keepalive cleaner (5000 millis). This ensures the
 170                   * timeout is correctly resets the default read timeout,
 171                   * infinity. See 6993490. */
 172                 System.out.println("server sleeping...");
 173                 try {Thread.sleep(6000); } catch (InterruptedException e) {}
 174 
 175                 trans.sendResponse(200, "OK");
 176             } else if(path.equals("/part2")) {
 177                 System.out.println("Call to /part2");
 178                 byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
 179                 for (int i=0; i<responseBody.length; i++)
 180                     responseBody[i] = 0x41;
 181                 trans.setResponseEntityBody (responseBody, responseBody.length);
 182 
 183                 // override the Content-length header to be greater than the actual response body
 184                 trans.setResponseHeader("Content-length", Integer.toString(responseBody.length+1));
 185                 trans.sendResponse(200, "OK");
 186 
 187                 // now close the socket
 188                 trans.channel().socket().close();
 189             }
 190         } catch (Exception e) {
 191             e.printStackTrace();
 192         }
 193     }
 194 }