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