/* * Copyright 2002 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ /** * @test * @bug 4774503 * @summary Calling HttpURLConnection's disconnect method after the * response has been received causes havoc with persistent * connections. */ import java.net.*; import java.io.*; import java.util.*; public class DisconnectAfterEOF { /* * Worker thread to service single connection - can service * multiple http requests on same connection. */ static class Worker extends Thread { Socket s; Worker(Socket s) { this.s = s; } public void run() { try { InputStream in = s.getInputStream(); PrintStream out = new PrintStream( new BufferedOutputStream( s.getOutputStream() )); byte b[] = new byte[1024]; int n = -1; int cl = -1; int remaining = -1; StringBuffer sb = new StringBuffer(); Random r = new Random(); boolean close = false; boolean inBody = false; for (;;) { boolean sendResponse = false; try { n = in.read(b); } catch (IOException ioe) { n = -1; } if (n <= 0) { if (inBody) { System.err.println("ERROR: Client closed before before " + "entire request received."); } return; } // reading entity-body if (inBody) { if (n > remaining) { System.err.println("Receiving more than expected!!!"); return; } remaining -= n; if (remaining == 0) { sendResponse = true; n = 0; } else { continue; } } // reading headers for (int i=0; i<n; i++) { char c = (char)b[i]; if (c != '\n') { sb.append(c); continue; } // Got end-of-line int len = sb.length(); if (len > 0) { if (sb.charAt(len-1) != '\r') { System.err.println("Unexpected CR in header!!"); return; } } sb.setLength(len-1); // empty line if (sb.length() == 0) { if (cl < 0) { System.err.println("Content-Length not found!!!"); return; } // the surplus is body data int dataRead = n - (i+1); remaining = cl - dataRead; if (remaining > 0) { inBody = true; break; } else { // entire body has been read sendResponse = true; } } else { // non-empty line - check for Content-Length String line = sb.toString().toLowerCase(); if (line.startsWith("content-length")) { StringTokenizer st = new StringTokenizer(line, ":"); st.nextToken(); cl = Integer.parseInt(st.nextToken().trim()); } if (line.startsWith("connection")) { StringTokenizer st = new StringTokenizer(line, ":"); st.nextToken(); if (st.nextToken().trim().equals("close")) { close =true; } } } sb = new StringBuffer(); } if (sendResponse) { // send a large response int rspLen = 32000; out.print("HTTP/1.1 200 OK\r\n"); out.print("Content-Length: " + rspLen + "\r\n"); out.print("\r\n"); if (rspLen > 0) out.write(new byte[rspLen]); out.flush(); if (close) return; sendResponse = false; inBody = false; cl = -1; } } } catch (IOException ioe) { } finally { try { s.close(); } catch (Exception e) { } System.out.println("+ Worker thread shutdown."); } } } /* * Server thread to accept connection and create worker threads * to service each connection. */ static class Server extends Thread { ServerSocket ss; Server(ServerSocket ss) { this.ss = ss; } public void run() { try { for (;;) { Socket s = ss.accept(); Worker w = new Worker(s); w.start(); } } catch (IOException ioe) { } System.out.println("+ Server shutdown."); } public void shutdown() { try { ss.close(); } catch (IOException ioe) { } } } static URLConnection doRequest(String uri) throws IOException { URLConnection uc = (new URL(uri)).openConnection(); uc.setDoOutput(true); OutputStream out = uc.getOutputStream(); out.write(new byte[16000]); // force the request to be sent uc.getInputStream(); return uc; } static URLConnection doResponse(URLConnection uc) throws IOException { int cl = ((HttpURLConnection)uc).getContentLength(); byte b[] = new byte[4096]; int n; do { n = uc.getInputStream().read(b); if (n > 0) cl -= n; } while (n > 0); if (cl != 0) { throw new RuntimeException("ERROR: content-length mismatch"); } return uc; } public static void main(String args[]) throws Exception { Random r = new Random(); // start server ServerSocket ss = new ServerSocket(0); Server svr = new Server(ss); svr.start(); String uri = "http://localhost:" + Integer.toString(ss.getLocalPort()) + "/foo.html"; /* * The following is the test scenario we create here :- * * 1. We do a http request/response and read the response * to EOF. As it's a persistent connection the idle * connection should go into the keep-alive cache for a * few seconds. * * 2. We start a second request but don't read the response. * As the request is to the same server we can assume it * (for our implementation anyway) that it will use the * same TCP connection. * * 3. We "disconnect" the first HttpURLConnection. This * should be no-op because the connection is in use * but another request. However with 1.3.1 and 1.4/1.4.1 * this causes the TCP connection for the second request * to be closed. * */ URLConnection uc1 = doRequest(uri); doResponse(uc1); Thread.currentThread().sleep(2000); URLConnection uc2 = doRequest(uri); ((HttpURLConnection)uc1).disconnect(); IOException ioe = null; try { doResponse(uc2); } catch (IOException x) { ioe = x; } ((HttpURLConnection)uc2).disconnect(); /* * Shutdown server as we are done. Worker threads created * by the server will shutdown automatically when the * client connection closes. */ svr.shutdown(); if (ioe != null) { throw ioe; } } }