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