456 lines · 13.6 KB · Java
RawDownload
/* * Copyright (c) 2005, 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.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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. */ package sun.net.httpserver; import java.io.*;import java.net.*;import javax.net.ssl.*;import java.util.*;import java.util.logging.Logger;import java.text.*;import com.sun.net.httpserver.*; class ExchangeImpl {     Headers reqHdrs, rspHdrs;    Request req;    String method;    boolean writefinished;    URI uri;    HttpConnection connection;    long reqContentLen;    long rspContentLen;    /* raw streams which access the socket directly */    InputStream ris;    OutputStream ros;    Thread thread;    /* close the underlying connection when this exchange finished */    boolean close;    boolean closed;    boolean http10 = false;     /* for formatting the Date: header */    private static final String pattern = "EEE, dd MMM yyyy HH:mm:ss zzz";    private static final TimeZone gmtTZ = TimeZone.getTimeZone("GMT");    private static final ThreadLocal<DateFormat> dateFormat =         new ThreadLocal<DateFormat>() {             @Override protected DateFormat initialValue() {                 DateFormat df = new SimpleDateFormat(pattern, Locale.US);                 df.setTimeZone(gmtTZ);                 return df;         }     };     private static final String HEAD = "HEAD";     /* streams which take care of the HTTP protocol framing     * and are passed up to higher layers     */    InputStream uis;    OutputStream uos;    LeftOverInputStream uis_orig; // uis may have be a user supplied wrapper    PlaceholderOutputStream uos_orig;     boolean sentHeaders; /* true after response headers sent */    Map<String,Object> attributes;    int rcode = -1;    HttpPrincipal principal;    ServerImpl server;     ExchangeImpl (        String m, URI u, Request req, long len, HttpConnection connection    ) throws IOException {        this.req = req;        this.reqHdrs = req.headers();        this.rspHdrs = new Headers();        this.method = m;        this.uri = u;        this.connection = connection;        this.reqContentLen = len;        /* ros only used for headers, body written directly to stream */        this.ros = req.outputStream();        this.ris = req.inputStream();        server = getServerImpl();        server.startExchange();    }     public Headers getRequestHeaders () {        return new UnmodifiableHeaders (reqHdrs);    }     public Headers getResponseHeaders () {        return rspHdrs;    }     public URI getRequestURI () {        return uri;    }     public String getRequestMethod (){        return method;    }     public HttpContextImpl getHttpContext (){        return connection.getHttpContext();    }     private boolean isHeadRequest() {        return HEAD.equals(getRequestMethod());    }     public void close () {        if (closed) {            return;        }        closed = true;         /* close the underlying connection if,         * a) the streams not set up yet, no response can be sent, or         * b) if the wrapper output stream is not set up, or         * c) if the close of the input/outpu stream fails         */        try {            if (uis_orig == null || uos == null) {                connection.close();                return;            }            if (!uos_orig.isWrapped()) {                connection.close();                return;            }            if (!uis_orig.isClosed()) {                uis_orig.close();            }            uos.close();        } catch (IOException e) {            connection.close();        }    }     public InputStream getRequestBody () {        if (uis != null) {            return uis;        }        if (reqContentLen == -1L) {            uis_orig = new ChunkedInputStream (this, ris);            uis = uis_orig;        } else {            uis_orig = new FixedLengthInputStream (this, ris, reqContentLen);            uis = uis_orig;        }        return uis;    }     LeftOverInputStream getOriginalInputStream () {        return uis_orig;    }     public int getResponseCode () {        return rcode;    }     public OutputStream getResponseBody () {        /* TODO. Change spec to remove restriction below. Filters         * cannot work with this restriction         *         * if (!sentHeaders) {         *    throw new IllegalStateException ("headers not sent");         * }         */        if (uos == null) {            uos_orig = new PlaceholderOutputStream (null);            uos = uos_orig;        }        return uos;    }      /* returns the place holder stream, which is the stream     * returned from the 1st call to getResponseBody()     * The "real" ouputstream is then placed inside this     */    PlaceholderOutputStream getPlaceholderResponseBody () {        getResponseBody();        return uos_orig;    }     public void sendResponseHeaders (int rCode, long contentLen)    throws IOException    {        if (sentHeaders) {            throw new IOException ("headers already sent");        }        this.rcode = rCode;        String statusLine = "HTTP/1.1 "+rCode+Code.msg(rCode)+"\r\n";        OutputStream tmpout = new BufferedOutputStream (ros);        PlaceholderOutputStream o = getPlaceholderResponseBody();        tmpout.write (bytes(statusLine, 0), 0, statusLine.length());        boolean noContentToSend = false; // assume there is content        rspHdrs.set ("Date", dateFormat.get().format (new Date()));         /* check for response type that is not allowed to send a body */         if ((rCode>=100 && rCode <200) /* informational */            ||(rCode == 204)           /* no content */            ||(rCode == 304))          /* not modified */        {            if (contentLen != -1) {                Logger logger = server.getLogger();                String msg = "sendResponseHeaders: rCode = "+ rCode                    + ": forcing contentLen = -1";                logger.warning (msg);            }            contentLen = -1;        }         if (isHeadRequest()) {            /* HEAD requests should not set a content length by passing it             * through this API, but should instead manually set the required             * headers.*/            if (contentLen >= 0) {                final Logger logger = server.getLogger();                String msg =                    "sendResponseHeaders: being invoked with a content length for a HEAD request";                logger.warning (msg);            }            noContentToSend = true;            contentLen = 0;        } else { /* not a HEAD request */            if (contentLen == 0) {                if (http10) {                    o.setWrappedStream (new UndefLengthOutputStream (this, ros));                    close = true;                } else {                    rspHdrs.set ("Transfer-encoding", "chunked");                    o.setWrappedStream (new ChunkedOutputStream (this, ros));                }            } else {                if (contentLen == -1) {                    noContentToSend = true;                    contentLen = 0;                }                rspHdrs.set("Content-length", Long.toString(contentLen));                o.setWrappedStream (new FixedLengthOutputStream (this, ros, contentLen));            }        }        write (rspHdrs, tmpout);        this.rspContentLen = contentLen;        tmpout.flush() ;        tmpout = null;        sentHeaders = true;        if (noContentToSend) {            WriteFinishedEvent e = new WriteFinishedEvent (this);            server.addEvent (e);            closed = true;        }        server.logReply (rCode, req.requestLine(), null);    }     void write (Headers map, OutputStream os) throws IOException {        Set<Map.Entry<String,List<String>>> entries = map.entrySet();        for (Map.Entry<String,List<String>> entry : entries) {            String key = entry.getKey();            byte[] buf;            List<String> values = entry.getValue();            for (String val : values) {                int i = key.length();                buf = bytes (key, 2);                buf[i++] = ':';                buf[i++] = ' ';                os.write (buf, 0, i);                buf = bytes (val, 2);                i = val.length();                buf[i++] = '\r';                buf[i++] = '\n';                os.write (buf, 0, i);            }        }        os.write ('\r');        os.write ('\n');    }     private byte[] rspbuf = new byte [128]; // used by bytes()     /**     * convert string to byte[], using rspbuf     * Make sure that at least "extra" bytes are free at end     * of rspbuf. Reallocate rspbuf if not big enough.     * caller must check return value to see if rspbuf moved     */    private byte[] bytes (String s, int extra) {        int slen = s.length();        if (slen+extra > rspbuf.length) {            int diff = slen + extra - rspbuf.length;            rspbuf = new byte [2* (rspbuf.length + diff)];        }        char c[] = s.toCharArray();        for (int i=0; i<c.length; i++) {            rspbuf[i] = (byte)c[i];        }        return rspbuf;    }     public InetSocketAddress getRemoteAddress (){        Socket s = connection.getChannel().socket();        InetAddress ia = s.getInetAddress();        int port = s.getPort();        return new InetSocketAddress (ia, port);    }     public InetSocketAddress getLocalAddress (){        Socket s = connection.getChannel().socket();        InetAddress ia = s.getLocalAddress();        int port = s.getLocalPort();        return new InetSocketAddress (ia, port);    }     public String getProtocol (){        String reqline = req.requestLine();        int index = reqline.lastIndexOf (' ');        return reqline.substring (index+1);    }     public SSLSession getSSLSession () {        SSLEngine e = connection.getSSLEngine();        if (e == null) {            return null;        }        return e.getSession();    }     public Object getAttribute (String name) {        if (name == null) {            throw new NullPointerException ("null name parameter");        }        if (attributes == null) {            attributes = getHttpContext().getAttributes();        }        return attributes.get (name);    }     public void setAttribute (String name, Object value) {        if (name == null) {            throw new NullPointerException ("null name parameter");        }        if (attributes == null) {            attributes = getHttpContext().getAttributes();        }        attributes.put (name, value);    }     public void setStreams (InputStream i, OutputStream o) {        assert uis != null;        if (i != null) {            uis = i;        }        if (o != null) {            uos = o;        }    }     /**     * PP     */    HttpConnection getConnection () {        return connection;    }     ServerImpl getServerImpl () {        return getHttpContext().getServerImpl();    }     public HttpPrincipal getPrincipal () {        return principal;    }     void setPrincipal (HttpPrincipal principal) {        this.principal = principal;    }     static ExchangeImpl get (HttpExchange t) {        if (t instanceof HttpExchangeImpl) {            return ((HttpExchangeImpl)t).getExchangeImpl();        } else {            assert t instanceof HttpsExchangeImpl;            return ((HttpsExchangeImpl)t).getExchangeImpl();        }    }} /** * An OutputStream which wraps another stream * which is supplied either at creation time, or sometime later. * If a caller/user tries to write to this stream before * the wrapped stream has been provided, then an IOException will * be thrown. */class PlaceholderOutputStream extends java.io.OutputStream {     OutputStream wrapped;     PlaceholderOutputStream (OutputStream os) {        wrapped = os;    }     void setWrappedStream (OutputStream os) {        wrapped = os;    }     boolean isWrapped () {        return wrapped != null;    }     private void checkWrap () throws IOException {        if (wrapped == null) {            throw new IOException ("response headers not sent yet");        }    }     public void write(int b) throws IOException {        checkWrap();        wrapped.write (b);    }     public void write(byte b[]) throws IOException {        checkWrap();        wrapped.write (b);    }     public void write(byte b[], int off, int len) throws IOException {        checkWrap();        wrapped.write (b, off, len);    }     public void flush() throws IOException {        checkWrap();        wrapped.flush();    }     public void close() throws IOException {        checkWrap();        wrapped.close();    }}