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