0N/A/*
3261N/A * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage sun.net.httpserver;
0N/A
0N/Aimport java.io.*;
0N/Aimport java.net.*;
0N/Aimport javax.net.ssl.*;
0N/Aimport java.util.*;
1754N/Aimport java.util.logging.Logger;
0N/Aimport java.text.*;
0N/Aimport com.sun.net.httpserver.*;
0N/A
0N/Aclass ExchangeImpl {
0N/A
0N/A Headers reqHdrs, rspHdrs;
0N/A Request req;
0N/A String method;
3105N/A boolean writefinished;
0N/A URI uri;
0N/A HttpConnection connection;
687N/A long reqContentLen;
0N/A long rspContentLen;
0N/A /* raw streams which access the socket directly */
0N/A InputStream ris;
0N/A OutputStream ros;
0N/A Thread thread;
0N/A /* close the underlying connection when this exchange finished */
0N/A boolean close;
0N/A boolean closed;
0N/A boolean http10 = false;
0N/A
0N/A /* for formatting the Date: header */
2579N/A private static final String pattern = "EEE, dd MMM yyyy HH:mm:ss zzz";
2579N/A private static final TimeZone gmtTZ = TimeZone.getTimeZone("GMT");
2579N/A private static final ThreadLocal<DateFormat> dateFormat =
2579N/A new ThreadLocal<DateFormat>() {
2579N/A @Override protected DateFormat initialValue() {
2579N/A DateFormat df = new SimpleDateFormat(pattern, Locale.US);
2579N/A df.setTimeZone(gmtTZ);
2579N/A return df;
2579N/A }
2579N/A };
0N/A
2344N/A private static final String HEAD = "HEAD";
2344N/A
0N/A /* streams which take care of the HTTP protocol framing
0N/A * and are passed up to higher layers
0N/A */
0N/A InputStream uis;
0N/A OutputStream uos;
0N/A LeftOverInputStream uis_orig; // uis may have be a user supplied wrapper
0N/A PlaceholderOutputStream uos_orig;
0N/A
0N/A boolean sentHeaders; /* true after response headers sent */
0N/A Map<String,Object> attributes;
0N/A int rcode = -1;
0N/A HttpPrincipal principal;
0N/A ServerImpl server;
0N/A
0N/A ExchangeImpl (
687N/A String m, URI u, Request req, long len, HttpConnection connection
0N/A ) throws IOException {
0N/A this.req = req;
0N/A this.reqHdrs = req.headers();
0N/A this.rspHdrs = new Headers();
0N/A this.method = m;
0N/A this.uri = u;
0N/A this.connection = connection;
0N/A this.reqContentLen = len;
0N/A /* ros only used for headers, body written directly to stream */
0N/A this.ros = req.outputStream();
0N/A this.ris = req.inputStream();
0N/A server = getServerImpl();
0N/A server.startExchange();
0N/A }
0N/A
0N/A public Headers getRequestHeaders () {
0N/A return new UnmodifiableHeaders (reqHdrs);
0N/A }
0N/A
0N/A public Headers getResponseHeaders () {
0N/A return rspHdrs;
0N/A }
0N/A
0N/A public URI getRequestURI () {
0N/A return uri;
0N/A }
0N/A
0N/A public String getRequestMethod (){
0N/A return method;
0N/A }
0N/A
0N/A public HttpContextImpl getHttpContext (){
0N/A return connection.getHttpContext();
0N/A }
0N/A
2344N/A private boolean isHeadRequest() {
2344N/A return HEAD.equals(getRequestMethod());
2344N/A }
2344N/A
0N/A public void close () {
0N/A if (closed) {
0N/A return;
0N/A }
0N/A closed = true;
0N/A
0N/A /* close the underlying connection if,
0N/A * a) the streams not set up yet, no response can be sent, or
0N/A * b) if the wrapper output stream is not set up, or
0N/A * c) if the close of the input/outpu stream fails
0N/A */
0N/A try {
0N/A if (uis_orig == null || uos == null) {
0N/A connection.close();
0N/A return;
0N/A }
0N/A if (!uos_orig.isWrapped()) {
0N/A connection.close();
0N/A return;
0N/A }
0N/A if (!uis_orig.isClosed()) {
0N/A uis_orig.close();
0N/A }
0N/A uos.close();
0N/A } catch (IOException e) {
0N/A connection.close();
0N/A }
0N/A }
0N/A
0N/A public InputStream getRequestBody () {
0N/A if (uis != null) {
0N/A return uis;
0N/A }
687N/A if (reqContentLen == -1L) {
0N/A uis_orig = new ChunkedInputStream (this, ris);
0N/A uis = uis_orig;
0N/A } else {
0N/A uis_orig = new FixedLengthInputStream (this, ris, reqContentLen);
0N/A uis = uis_orig;
0N/A }
0N/A return uis;
0N/A }
0N/A
0N/A LeftOverInputStream getOriginalInputStream () {
0N/A return uis_orig;
0N/A }
0N/A
0N/A public int getResponseCode () {
0N/A return rcode;
0N/A }
0N/A
0N/A public OutputStream getResponseBody () {
0N/A /* TODO. Change spec to remove restriction below. Filters
0N/A * cannot work with this restriction
0N/A *
0N/A * if (!sentHeaders) {
0N/A * throw new IllegalStateException ("headers not sent");
0N/A * }
0N/A */
0N/A if (uos == null) {
0N/A uos_orig = new PlaceholderOutputStream (null);
0N/A uos = uos_orig;
0N/A }
0N/A return uos;
0N/A }
0N/A
0N/A
0N/A /* returns the place holder stream, which is the stream
0N/A * returned from the 1st call to getResponseBody()
0N/A * The "real" ouputstream is then placed inside this
0N/A */
0N/A PlaceholderOutputStream getPlaceholderResponseBody () {
0N/A getResponseBody();
0N/A return uos_orig;
0N/A }
0N/A
0N/A public void sendResponseHeaders (int rCode, long contentLen)
0N/A throws IOException
0N/A {
0N/A if (sentHeaders) {
0N/A throw new IOException ("headers already sent");
0N/A }
0N/A this.rcode = rCode;
0N/A String statusLine = "HTTP/1.1 "+rCode+Code.msg(rCode)+"\r\n";
0N/A OutputStream tmpout = new BufferedOutputStream (ros);
0N/A PlaceholderOutputStream o = getPlaceholderResponseBody();
0N/A tmpout.write (bytes(statusLine, 0), 0, statusLine.length());
0N/A boolean noContentToSend = false; // assume there is content
2579N/A rspHdrs.set ("Date", dateFormat.get().format (new Date()));
1754N/A
1754N/A /* check for response type that is not allowed to send a body */
1754N/A
1754N/A if ((rCode>=100 && rCode <200) /* informational */
1754N/A ||(rCode == 204) /* no content */
1754N/A ||(rCode == 304)) /* not modified */
1754N/A {
1754N/A if (contentLen != -1) {
1754N/A Logger logger = server.getLogger();
1754N/A String msg = "sendResponseHeaders: rCode = "+ rCode
1754N/A + ": forcing contentLen = -1";
1754N/A logger.warning (msg);
1754N/A }
1754N/A contentLen = -1;
1754N/A }
2344N/A
2344N/A if (isHeadRequest()) {
2344N/A /* HEAD requests should not set a content length by passing it
2344N/A * through this API, but should instead manually set the required
2344N/A * headers.*/
2344N/A if (contentLen >= 0) {
2344N/A final Logger logger = server.getLogger();
2344N/A String msg =
2344N/A "sendResponseHeaders: being invoked with a content length for a HEAD request";
2344N/A logger.warning (msg);
0N/A }
2344N/A noContentToSend = true;
2344N/A contentLen = 0;
2344N/A } else { /* not a HEAD request */
2344N/A if (contentLen == 0) {
2344N/A if (http10) {
2344N/A o.setWrappedStream (new UndefLengthOutputStream (this, ros));
2344N/A close = true;
2344N/A } else {
2344N/A rspHdrs.set ("Transfer-encoding", "chunked");
2344N/A o.setWrappedStream (new ChunkedOutputStream (this, ros));
2344N/A }
2344N/A } else {
2344N/A if (contentLen == -1) {
2344N/A noContentToSend = true;
2344N/A contentLen = 0;
2344N/A }
2344N/A rspHdrs.set("Content-length", Long.toString(contentLen));
2344N/A o.setWrappedStream (new FixedLengthOutputStream (this, ros, contentLen));
0N/A }
0N/A }
0N/A write (rspHdrs, tmpout);
0N/A this.rspContentLen = contentLen;
0N/A tmpout.flush() ;
0N/A tmpout = null;
0N/A sentHeaders = true;
0N/A if (noContentToSend) {
0N/A WriteFinishedEvent e = new WriteFinishedEvent (this);
0N/A server.addEvent (e);
0N/A closed = true;
0N/A }
0N/A server.logReply (rCode, req.requestLine(), null);
0N/A }
0N/A
0N/A void write (Headers map, OutputStream os) throws IOException {
0N/A Set<Map.Entry<String,List<String>>> entries = map.entrySet();
0N/A for (Map.Entry<String,List<String>> entry : entries) {
0N/A String key = entry.getKey();
0N/A byte[] buf;
0N/A List<String> values = entry.getValue();
0N/A for (String val : values) {
0N/A int i = key.length();
0N/A buf = bytes (key, 2);
0N/A buf[i++] = ':';
0N/A buf[i++] = ' ';
0N/A os.write (buf, 0, i);
0N/A buf = bytes (val, 2);
0N/A i = val.length();
0N/A buf[i++] = '\r';
0N/A buf[i++] = '\n';
0N/A os.write (buf, 0, i);
0N/A }
0N/A }
0N/A os.write ('\r');
0N/A os.write ('\n');
0N/A }
0N/A
0N/A private byte[] rspbuf = new byte [128]; // used by bytes()
0N/A
0N/A /**
0N/A * convert string to byte[], using rspbuf
0N/A * Make sure that at least "extra" bytes are free at end
0N/A * of rspbuf. Reallocate rspbuf if not big enough.
0N/A * caller must check return value to see if rspbuf moved
0N/A */
0N/A private byte[] bytes (String s, int extra) {
0N/A int slen = s.length();
0N/A if (slen+extra > rspbuf.length) {
0N/A int diff = slen + extra - rspbuf.length;
0N/A rspbuf = new byte [2* (rspbuf.length + diff)];
0N/A }
0N/A char c[] = s.toCharArray();
0N/A for (int i=0; i<c.length; i++) {
0N/A rspbuf[i] = (byte)c[i];
0N/A }
0N/A return rspbuf;
0N/A }
0N/A
0N/A public InetSocketAddress getRemoteAddress (){
0N/A Socket s = connection.getChannel().socket();
0N/A InetAddress ia = s.getInetAddress();
0N/A int port = s.getPort();
0N/A return new InetSocketAddress (ia, port);
0N/A }
0N/A
0N/A public InetSocketAddress getLocalAddress (){
0N/A Socket s = connection.getChannel().socket();
0N/A InetAddress ia = s.getLocalAddress();
0N/A int port = s.getLocalPort();
0N/A return new InetSocketAddress (ia, port);
0N/A }
0N/A
0N/A public String getProtocol (){
0N/A String reqline = req.requestLine();
0N/A int index = reqline.lastIndexOf (' ');
0N/A return reqline.substring (index+1);
0N/A }
0N/A
0N/A public SSLSession getSSLSession () {
0N/A SSLEngine e = connection.getSSLEngine();
0N/A if (e == null) {
0N/A return null;
0N/A }
0N/A return e.getSession();
0N/A }
0N/A
0N/A public Object getAttribute (String name) {
0N/A if (name == null) {
0N/A throw new NullPointerException ("null name parameter");
0N/A }
0N/A if (attributes == null) {
0N/A attributes = getHttpContext().getAttributes();
0N/A }
0N/A return attributes.get (name);
0N/A }
0N/A
0N/A public void setAttribute (String name, Object value) {
0N/A if (name == null) {
0N/A throw new NullPointerException ("null name parameter");
0N/A }
0N/A if (attributes == null) {
0N/A attributes = getHttpContext().getAttributes();
0N/A }
0N/A attributes.put (name, value);
0N/A }
0N/A
0N/A public void setStreams (InputStream i, OutputStream o) {
0N/A assert uis != null;
0N/A if (i != null) {
0N/A uis = i;
0N/A }
0N/A if (o != null) {
0N/A uos = o;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * PP
0N/A */
0N/A HttpConnection getConnection () {
0N/A return connection;
0N/A }
0N/A
0N/A ServerImpl getServerImpl () {
0N/A return getHttpContext().getServerImpl();
0N/A }
0N/A
0N/A public HttpPrincipal getPrincipal () {
0N/A return principal;
0N/A }
0N/A
0N/A void setPrincipal (HttpPrincipal principal) {
0N/A this.principal = principal;
0N/A }
0N/A
0N/A static ExchangeImpl get (HttpExchange t) {
0N/A if (t instanceof HttpExchangeImpl) {
0N/A return ((HttpExchangeImpl)t).getExchangeImpl();
0N/A } else {
0N/A assert t instanceof HttpsExchangeImpl;
0N/A return ((HttpsExchangeImpl)t).getExchangeImpl();
0N/A }
0N/A }
0N/A}
0N/A
0N/A/**
0N/A * An OutputStream which wraps another stream
0N/A * which is supplied either at creation time, or sometime later.
0N/A * If a caller/user tries to write to this stream before
0N/A * the wrapped stream has been provided, then an IOException will
0N/A * be thrown.
0N/A */
0N/Aclass PlaceholderOutputStream extends java.io.OutputStream {
0N/A
0N/A OutputStream wrapped;
0N/A
0N/A PlaceholderOutputStream (OutputStream os) {
0N/A wrapped = os;
0N/A }
0N/A
0N/A void setWrappedStream (OutputStream os) {
0N/A wrapped = os;
0N/A }
0N/A
0N/A boolean isWrapped () {
0N/A return wrapped != null;
0N/A }
0N/A
0N/A private void checkWrap () throws IOException {
0N/A if (wrapped == null) {
0N/A throw new IOException ("response headers not sent yet");
0N/A }
0N/A }
0N/A
0N/A public void write(int b) throws IOException {
0N/A checkWrap();
0N/A wrapped.write (b);
0N/A }
0N/A
0N/A public void write(byte b[]) throws IOException {
0N/A checkWrap();
0N/A wrapped.write (b);
0N/A }
0N/A
0N/A public void write(byte b[], int off, int len) throws IOException {
0N/A checkWrap();
0N/A wrapped.write (b, off, len);
0N/A }
0N/A
0N/A public void flush() throws IOException {
0N/A checkWrap();
0N/A wrapped.flush();
0N/A }
0N/A
0N/A public void close() throws IOException {
0N/A checkWrap();
0N/A wrapped.close();
0N/A }
0N/A}