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 }