jdk/test/sun/net/www/httptest/HttpServer.java
changeset 13801 a89cf77caf0a
parent 13800 2fd4a82efe9c
parent 13797 6d71a40137af
child 13802 7e765d50416f
--- a/jdk/test/sun/net/www/httptest/HttpServer.java	Fri Sep 14 10:13:04 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,728 +0,0 @@
-/*
- * 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.
- */
-
-import java.net.*;
-import java.io.*;
-import java.nio.*;
-import java.nio.channels.*;
-import sun.net.www.MessageHeader;
-import java.util.*;
-
-/**
- * This class implements a simple HTTP server. It uses multiple threads to
- * handle connections in parallel, and also multiple connections/requests
- * can be handled per thread.
- * <p>
- * It must be instantiated with a {@link HttpCallback} object to which
- * requests are given and must be handled.
- * <p>
- * Simple synchronization between the client(s) and server can be done
- * using the {@link #waitForCondition(String)}, {@link #setCondition(String)} and
- * {@link #rendezvous(String,int)} methods.
- *
- * NOTE NOTE NOTE NOTE NOTE NOTE NOTE
- *
- * If changes are made here, please sure they are propagated to
- * the HTTPS equivalent in the JSSE regression test suite.
- *
- * NOTE NOTE NOTE NOTE NOTE NOTE NOTE
- */
-
-public class HttpServer {
-
-    ServerSocketChannel schan;
-    int threads;
-    int cperthread;
-    HttpCallback cb;
-    Server[] servers;
-
-    /**
-     * Create a <code>HttpServer<code> instance with the specified callback object
-     * for handling requests. One thread is created to handle requests,
-     * and up to ten TCP connections will be handled simultaneously.
-     * @param cb the callback object which is invoked to handle each
-     *  incoming request
-     */
-
-    public HttpServer (HttpCallback cb) throws IOException {
-        this (cb, 1, 10, 0);
-    }
-
-    /**
-     * Create a <code>HttpServer<code> instance with the specified number of
-     * threads and maximum number of connections per thread. This functions
-     * the same as the 4 arg constructor, where the port argument is set to zero.
-     * @param cb the callback object which is invoked to handle each
-     *     incoming request
-     * @param threads the number of threads to create to handle requests
-     *     in parallel
-     * @param cperthread the number of simultaneous TCP connections to
-     *     handle per thread
-     */
-
-    public HttpServer (HttpCallback cb, int threads, int cperthread)
-        throws IOException {
-        this (cb, threads, cperthread, 0);
-    }
-
-    /**
-     * Create a <code>HttpServer<code> instance with the specified number
-     * of threads and maximum number of connections per thread and running on
-     * the specified port. The specified number of threads are created to
-     * handle incoming requests, and each thread is allowed
-     * to handle a number of simultaneous TCP connections.
-     * @param cb the callback object which is invoked to handle
-     *  each incoming request
-     * @param threads the number of threads to create to handle
-     *  requests in parallel
-     * @param cperthread the number of simultaneous TCP connections
-     *  to handle per thread
-     * @param port the port number to bind the server to. <code>Zero</code>
-     *  means choose any free port.
-     */
-
-    public HttpServer (HttpCallback cb, int threads, int cperthread, int port)
-        throws IOException {
-        schan = ServerSocketChannel.open ();
-        InetSocketAddress addr = new InetSocketAddress (port);
-        schan.socket().bind (addr);
-        this.threads = threads;
-        this.cb = cb;
-        this.cperthread = cperthread;
-        servers = new Server [threads];
-        for (int i=0; i<threads; i++) {
-            servers[i] = new Server (cb, schan, cperthread);
-            servers[i].start();
-        }
-    }
-
-    /** Tell all threads in the server to exit within 5 seconds.
-     *  This is an abortive termination. Just prior to the thread exiting
-     *  all channels in that thread waiting to be closed are forceably closed.
-     */
-
-    public void terminate () {
-        for (int i=0; i<threads; i++) {
-            servers[i].terminate ();
-        }
-    }
-
-    /**
-     * return the local port number to which the server is bound.
-     * @return the local port number
-     */
-
-    public int getLocalPort () {
-        return schan.socket().getLocalPort ();
-    }
-
-    static class Server extends Thread {
-
-        ServerSocketChannel schan;
-        Selector selector;
-        SelectionKey listenerKey;
-        SelectionKey key; /* the current key being processed */
-        HttpCallback cb;
-        ByteBuffer consumeBuffer;
-        int maxconn;
-        int nconn;
-        ClosedChannelList clist;
-        boolean shutdown;
-
-        Server (HttpCallback cb, ServerSocketChannel schan, int maxconn) {
-            this.schan = schan;
-            this.maxconn = maxconn;
-            this.cb = cb;
-            nconn = 0;
-            consumeBuffer = ByteBuffer.allocate (512);
-            clist = new ClosedChannelList ();
-            try {
-                selector = Selector.open ();
-                schan.configureBlocking (false);
-                listenerKey = schan.register (selector, SelectionKey.OP_ACCEPT);
-            } catch (IOException e) {
-                System.err.println ("Server could not start: " + e);
-            }
-        }
-
-        /* Stop the thread as soon as possible */
-        public synchronized void terminate () {
-            shutdown = true;
-        }
-
-        public void run ()  {
-            try {
-                while (true) {
-                    selector.select (1000);
-                    Set selected = selector.selectedKeys();
-                    Iterator iter = selected.iterator();
-                    while (iter.hasNext()) {
-                        key = (SelectionKey)iter.next();
-                        if (key.equals (listenerKey)) {
-                            SocketChannel sock = schan.accept ();
-                            if (sock == null) {
-                                /* false notification */
-                                iter.remove();
-                                continue;
-                            }
-                            sock.configureBlocking (false);
-                            sock.register (selector, SelectionKey.OP_READ);
-                            nconn ++;
-                            System.out.println("SERVER: new connection. chan[" + sock + "]");
-                            if (nconn == maxconn) {
-                                /* deregister */
-                                listenerKey.cancel ();
-                                listenerKey = null;
-                            }
-                        } else {
-                            if (key.isReadable()) {
-                                boolean closed;
-                                SocketChannel chan = (SocketChannel) key.channel();
-                                System.out.println("SERVER: connection readable. chan[" + chan + "]");
-                                if (key.attachment() != null) {
-                                    System.out.println("Server: comsume");
-                                    closed = consume (chan);
-                                } else {
-                                    closed = read (chan, key);
-                                }
-                                if (closed) {
-                                    chan.close ();
-                                    key.cancel ();
-                                    if (nconn == maxconn) {
-                                        listenerKey = schan.register (selector, SelectionKey.OP_ACCEPT);
-                                    }
-                                    nconn --;
-                                }
-                            }
-                        }
-                        iter.remove();
-                    }
-                    clist.check();
-                    if (shutdown) {
-                        clist.terminate ();
-                        return;
-                    }
-                }
-            } catch (IOException e) {
-                System.out.println ("Server exception: " + e);
-                // TODO finish
-            }
-        }
-
-        /* read all the data off the channel without looking at it
-             * return true if connection closed
-             */
-        boolean consume (SocketChannel chan) {
-            try {
-                consumeBuffer.clear ();
-                int c = chan.read (consumeBuffer);
-                if (c == -1)
-                    return true;
-            } catch (IOException e) {
-                return true;
-            }
-            return false;
-        }
-
-        /* return true if the connection is closed, false otherwise */
-
-        private boolean read (SocketChannel chan, SelectionKey key) {
-            HttpTransaction msg;
-            boolean res;
-            try {
-                InputStream is = new BufferedInputStream (new NioInputStream (chan));
-                String requestline = readLine (is);
-                MessageHeader mhead = new MessageHeader (is);
-                String clen = mhead.findValue ("Content-Length");
-                String trferenc = mhead.findValue ("Transfer-Encoding");
-                String data = null;
-                if (trferenc != null && trferenc.equals ("chunked"))
-                    data = new String (readChunkedData (is));
-                else if (clen != null)
-                    data = new String (readNormalData (is, Integer.parseInt (clen)));
-                String[] req = requestline.split (" ");
-                if (req.length < 2) {
-                    /* invalid request line */
-                    return false;
-                }
-                String cmd = req[0];
-                URI uri = null;
-                try {
-                    uri = new URI (req[1]);
-                    msg = new HttpTransaction (this, cmd, uri, mhead, data, null, key);
-                    cb.request (msg);
-                } catch (URISyntaxException e) {
-                    System.err.println ("Invalid URI: " + e);
-                    msg = new HttpTransaction (this, cmd, null, null, null, null, key);
-                    msg.sendResponse (501, "Whatever");
-                }
-                res = false;
-            } catch (IOException e) {
-                res = true;
-            }
-            return res;
-        }
-
-        byte[] readNormalData (InputStream is, int len) throws IOException {
-            byte [] buf  = new byte [len];
-            int c, off=0, remain=len;
-            while (remain > 0 && ((c=is.read (buf, off, remain))>0)) {
-                remain -= c;
-                off += c;
-            }
-            return buf;
-        }
-
-        private void readCRLF(InputStream is) throws IOException {
-            int cr = is.read();
-            int lf = is.read();
-
-            if (((cr & 0xff) != 0x0d) ||
-                ((lf & 0xff) != 0x0a)) {
-                throw new IOException(
-                    "Expected <CR><LF>:  got '" + cr + "/" + lf + "'");
-            }
-        }
-
-        byte[] readChunkedData (InputStream is) throws IOException {
-            LinkedList l = new LinkedList ();
-            int total = 0;
-            for (int len=readChunkLen(is); len!=0; len=readChunkLen(is)) {
-                l.add (readNormalData(is, len));
-                total += len;
-                readCRLF(is);  // CRLF at end of chunk
-            }
-            readCRLF(is); // CRLF at end of Chunked Stream.
-            byte[] buf = new byte [total];
-            Iterator i = l.iterator();
-            int x = 0;
-            while (i.hasNext()) {
-                byte[] b = (byte[])i.next();
-                System.arraycopy (b, 0, buf, x, b.length);
-                x += b.length;
-            }
-            return buf;
-        }
-
-        private int readChunkLen (InputStream is) throws IOException {
-            int c, len=0;
-            boolean done=false, readCR=false;
-            while (!done) {
-                c = is.read ();
-                if (c == '\n' && readCR) {
-                    done = true;
-                } else {
-                    if (c == '\r' && !readCR) {
-                        readCR = true;
-                    } else {
-                        int x=0;
-                        if (c >= 'a' && c <= 'f') {
-                            x = c - 'a' + 10;
-                        } else if (c >= 'A' && c <= 'F') {
-                            x = c - 'A' + 10;
-                        } else if (c >= '0' && c <= '9') {
-                            x = c - '0';
-                        }
-                        len = len * 16 + x;
-                    }
-                }
-            }
-            return len;
-        }
-
-        private String readLine (InputStream is) throws IOException {
-            boolean done=false, readCR=false;
-            byte[] b = new byte [512];
-            int c, l = 0;
-
-            while (!done) {
-                c = is.read ();
-                if (c == '\n' && readCR) {
-                    done = true;
-                } else {
-                    if (c == '\r' && !readCR) {
-                        readCR = true;
-                    } else {
-                        b[l++] = (byte)c;
-                    }
-                }
-            }
-            return new String (b);
-        }
-
-        /** close the channel associated with the current key by:
-         * 1. shutdownOutput (send a FIN)
-         * 2. mark the key so that incoming data is to be consumed and discarded
-         * 3. After a period, close the socket
-         */
-
-        synchronized void orderlyCloseChannel (SelectionKey key) throws IOException {
-            SocketChannel ch = (SocketChannel)key.channel ();
-            System.out.println("SERVER: orderlyCloseChannel chan[" + ch + "]");
-            ch.socket().shutdownOutput();
-            key.attach (this);
-            clist.add (key);
-        }
-
-        synchronized void abortiveCloseChannel (SelectionKey key) throws IOException {
-            SocketChannel ch = (SocketChannel)key.channel ();
-            System.out.println("SERVER: abortiveCloseChannel chan[" + ch + "]");
-
-            Socket s = ch.socket ();
-            s.setSoLinger (true, 0);
-            ch.close();
-        }
-    }
-
-
-    /**
-     * Implements blocking reading semantics on top of a non-blocking channel
-     */
-
-    static class NioInputStream extends InputStream {
-        SocketChannel channel;
-        Selector selector;
-        ByteBuffer chanbuf;
-        SelectionKey key;
-        int available;
-        byte[] one;
-        boolean closed;
-        ByteBuffer markBuf; /* reads may be satisifed from this buffer */
-        boolean marked;
-        boolean reset;
-        int readlimit;
-
-        public NioInputStream (SocketChannel chan) throws IOException {
-            this.channel = chan;
-            selector = Selector.open();
-            chanbuf = ByteBuffer.allocate (1024);
-            key = chan.register (selector, SelectionKey.OP_READ);
-            available = 0;
-            one = new byte[1];
-            closed = marked = reset = false;
-        }
-
-        public synchronized int read (byte[] b) throws IOException {
-            return read (b, 0, b.length);
-        }
-
-        public synchronized int read () throws IOException {
-            return read (one, 0, 1);
-        }
-
-        public synchronized int read (byte[] b, int off, int srclen) throws IOException {
-
-            int canreturn, willreturn;
-
-            if (closed)
-                return -1;
-
-            if (reset) { /* satisfy from markBuf */
-                canreturn = markBuf.remaining ();
-                willreturn = canreturn>srclen ? srclen : canreturn;
-                markBuf.get(b, off, willreturn);
-                if (canreturn == willreturn) {
-                    reset = false;
-                }
-            } else { /* satisfy from channel */
-                canreturn = available();
-                if (canreturn == 0) {
-                    block ();
-                    canreturn = available();
-                }
-                willreturn = canreturn>srclen ? srclen : canreturn;
-                chanbuf.get(b, off, willreturn);
-                available -= willreturn;
-
-                if (marked) { /* copy into markBuf */
-                    try {
-                        markBuf.put (b, off, willreturn);
-                    } catch (BufferOverflowException e) {
-                        marked = false;
-                    }
-                }
-            }
-            return willreturn;
-        }
-
-        public synchronized int available () throws IOException {
-            if (closed)
-                throw new IOException ("Stream is closed");
-
-            if (reset)
-                return markBuf.remaining();
-
-            if (available > 0)
-                return available;
-
-            chanbuf.clear ();
-            available = channel.read (chanbuf);
-            if (available > 0)
-                chanbuf.flip();
-            else if (available == -1)
-                throw new IOException ("Stream is closed");
-            return available;
-        }
-
-        /**
-         * block() only called when available==0 and buf is empty
-         */
-        private synchronized void block () throws IOException {
-            //assert available == 0;
-            int n = selector.select ();
-            //assert n == 1;
-            selector.selectedKeys().clear();
-            available ();
-        }
-
-        public void close () throws IOException {
-            if (closed)
-                return;
-            channel.close ();
-            closed = true;
-        }
-
-        public synchronized void mark (int readlimit) {
-            if (closed)
-                return;
-            this.readlimit = readlimit;
-            markBuf = ByteBuffer.allocate (readlimit);
-            marked = true;
-            reset = false;
-        }
-
-        public synchronized void reset () throws IOException {
-            if (closed )
-                return;
-            if (!marked)
-                throw new IOException ("Stream not marked");
-            marked = false;
-            reset = true;
-            markBuf.flip ();
-        }
-    }
-
-    static class NioOutputStream extends OutputStream {
-        SocketChannel channel;
-        ByteBuffer buf;
-        SelectionKey key;
-        Selector selector;
-        boolean closed;
-        byte[] one;
-
-        public NioOutputStream (SocketChannel channel) throws IOException {
-            this.channel = channel;
-            selector = Selector.open ();
-            key = channel.register (selector, SelectionKey.OP_WRITE);
-            closed = false;
-            one = new byte [1];
-        }
-
-        public synchronized void write (int b) throws IOException {
-            one[0] = (byte)b;
-            write (one, 0, 1);
-        }
-
-        public synchronized void write (byte[] b) throws IOException {
-            write (b, 0, b.length);
-        }
-
-        public synchronized void write (byte[] b, int off, int len) throws IOException {
-            if (closed)
-                throw new IOException ("stream is closed");
-
-            buf = ByteBuffer.allocate (len);
-            buf.put (b, off, len);
-            buf.flip ();
-            int n;
-            while ((n = channel.write (buf)) < len) {
-                len -= n;
-                if (len == 0)
-                    return;
-                selector.select ();
-                selector.selectedKeys().clear ();
-            }
-        }
-
-        public void close () throws IOException {
-            if (closed)
-                return;
-            channel.close ();
-            closed = true;
-        }
-    }
-
-    /**
-     * Utilities for synchronization. A condition is
-     * identified by a string name, and is initialized
-     * upon first use (ie. setCondition() or waitForCondition()). Threads
-     * are blocked until some thread calls (or has called) setCondition() for the same
-     * condition.
-     * <P>
-     * A rendezvous built on a condition is also provided for synchronizing
-     * N threads.
-     */
-
-    private static HashMap conditions = new HashMap();
-
-    /*
-     * Modifiable boolean object
-     */
-    private static class BValue {
-        boolean v;
-    }
-
-    /*
-     * Modifiable int object
-     */
-    private static class IValue {
-        int v;
-        IValue (int i) {
-            v =i;
-        }
-    }
-
-
-    private static BValue getCond (String condition) {
-        synchronized (conditions) {
-            BValue cond = (BValue) conditions.get (condition);
-            if (cond == null) {
-                cond = new BValue();
-                conditions.put (condition, cond);
-            }
-            return cond;
-        }
-    }
-
-    /**
-     * Set the condition to true. Any threads that are currently blocked
-     * waiting on the condition, will be unblocked and allowed to continue.
-     * Threads that subsequently call waitForCondition() will not block.
-     * If the named condition did not exist prior to the call, then it is created
-     * first.
-     */
-
-    public static void setCondition (String condition) {
-        BValue cond = getCond (condition);
-        synchronized (cond) {
-            if (cond.v) {
-                return;
-            }
-            cond.v = true;
-            cond.notifyAll();
-        }
-    }
-
-    /**
-     * If the named condition does not exist, then it is created and initialized
-     * to false. If the condition exists or has just been created and its value
-     * is false, then the thread blocks until another thread sets the condition.
-     * If the condition exists and is already set to true, then this call returns
-     * immediately without blocking.
-     */
-
-    public static void waitForCondition (String condition) {
-        BValue cond = getCond (condition);
-        synchronized (cond) {
-            if (!cond.v) {
-                try {
-                    cond.wait();
-                } catch (InterruptedException e) {}
-            }
-        }
-    }
-
-    /* conditions must be locked when accessing this */
-    static HashMap rv = new HashMap();
-
-    /**
-     * Force N threads to rendezvous (ie. wait for each other) before proceeding.
-     * The first thread(s) to call are blocked until the last
-     * thread makes the call. Then all threads continue.
-     * <p>
-     * All threads that call with the same condition name, must use the same value
-     * for N (or the results may be not be as expected).
-     * <P>
-     * Obviously, if fewer than N threads make the rendezvous then the result
-     * will be a hang.
-     */
-
-    public static void rendezvous (String condition, int N) {
-        BValue cond;
-        IValue iv;
-        String name = "RV_"+condition;
-
-        /* get the condition */
-
-        synchronized (conditions) {
-            cond = (BValue)conditions.get (name);
-            if (cond == null) {
-                /* we are first caller */
-                if (N < 2) {
-                    throw new RuntimeException ("rendezvous must be called with N >= 2");
-                }
-                cond = new BValue ();
-                conditions.put (name, cond);
-                iv = new IValue (N-1);
-                rv.put (name, iv);
-            } else {
-                /* already initialised, just decrement the counter */
-                iv = (IValue) rv.get (name);
-                iv.v --;
-            }
-        }
-
-        if (iv.v > 0) {
-            waitForCondition (name);
-        } else {
-            setCondition (name);
-            synchronized (conditions) {
-                clearCondition (name);
-                rv.remove (name);
-            }
-        }
-    }
-
-    /**
-     * If the named condition exists and is set then remove it, so it can
-     * be re-initialized and used again. If the condition does not exist, or
-     * exists but is not set, then the call returns without doing anything.
-     * Note, some higher level synchronization
-     * may be needed between clear and the other operations.
-     */
-
-    public static void clearCondition(String condition) {
-        BValue cond;
-        synchronized (conditions) {
-            cond = (BValue) conditions.get (condition);
-            if (cond == null) {
-                return;
-            }
-            synchronized (cond) {
-                if (cond.v) {
-                    conditions.remove (condition);
-                }
-            }
-        }
-    }
-}