/*
 * Copyright (c) 2002, 2010, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.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();
                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 {
        // 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.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;
        }
    }
}
