Path: blob/master/src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java
41159 views
/*1* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.net.httpserver;2627import java.io.*;28import java.net.*;29import javax.net.ssl.*;30import java.util.*;31import java.lang.System.Logger;32import java.lang.System.Logger.Level;33import java.text.*;34import java.time.Instant;35import java.time.ZoneId;36import java.time.format.DateTimeFormatter;37import java.util.stream.Stream;38import com.sun.net.httpserver.*;3940class ExchangeImpl {4142Headers reqHdrs, rspHdrs;43Request req;44String method;45boolean writefinished;46URI uri;47HttpConnection connection;48long reqContentLen;49long rspContentLen;50/* raw streams which access the socket directly */51InputStream ris;52OutputStream ros;53Thread thread;54/* close the underlying connection when this exchange finished */55boolean close;56boolean closed;57boolean http10 = false;5859/* for formatting the Date: header */60private static final DateTimeFormatter FORMATTER;61static {62String pattern = "EEE, dd MMM yyyy HH:mm:ss zzz";63FORMATTER = DateTimeFormatter.ofPattern(pattern, Locale.US)64.withZone(ZoneId.of("GMT"));65}6667private static final String HEAD = "HEAD";6869/* streams which take care of the HTTP protocol framing70* and are passed up to higher layers71*/72InputStream uis;73OutputStream uos;74LeftOverInputStream uis_orig; // uis may have be a user supplied wrapper75PlaceholderOutputStream uos_orig;7677boolean sentHeaders; /* true after response headers sent */78Map<String,Object> attributes;79int rcode = -1;80HttpPrincipal principal;81ServerImpl server;8283ExchangeImpl (84String m, URI u, Request req, long len, HttpConnection connection85) throws IOException {86this.req = req;87this.reqHdrs = new UnmodifiableHeaders(req.headers());88this.rspHdrs = new Headers();89this.method = m;90this.uri = u;91this.connection = connection;92this.reqContentLen = len;93/* ros only used for headers, body written directly to stream */94this.ros = req.outputStream();95this.ris = req.inputStream();96server = getServerImpl();97server.startExchange();98}99100public Headers getRequestHeaders () {101return reqHdrs;102}103104public Headers getResponseHeaders () {105return rspHdrs;106}107108public URI getRequestURI () {109return uri;110}111112public String getRequestMethod (){113return method;114}115116public HttpContextImpl getHttpContext (){117return connection.getHttpContext();118}119120private boolean isHeadRequest() {121return HEAD.equals(getRequestMethod());122}123124public void close () {125if (closed) {126return;127}128closed = true;129130/* close the underlying connection if,131* a) the streams not set up yet, no response can be sent, or132* b) if the wrapper output stream is not set up, or133* c) if the close of the input/outpu stream fails134*/135try {136if (uis_orig == null || uos == null) {137connection.close();138return;139}140if (!uos_orig.isWrapped()) {141connection.close();142return;143}144if (!uis_orig.isClosed()) {145uis_orig.close();146}147uos.close();148} catch (IOException e) {149connection.close();150}151}152153public InputStream getRequestBody () {154if (uis != null) {155return uis;156}157if (reqContentLen == -1L) {158uis_orig = new ChunkedInputStream (this, ris);159uis = uis_orig;160} else {161uis_orig = new FixedLengthInputStream (this, ris, reqContentLen);162uis = uis_orig;163}164return uis;165}166167LeftOverInputStream getOriginalInputStream () {168return uis_orig;169}170171public int getResponseCode () {172return rcode;173}174175public OutputStream getResponseBody () {176/* TODO. Change spec to remove restriction below. Filters177* cannot work with this restriction178*179* if (!sentHeaders) {180* throw new IllegalStateException ("headers not sent");181* }182*/183if (uos == null) {184uos_orig = new PlaceholderOutputStream (null);185uos = uos_orig;186}187return uos;188}189190191/* returns the place holder stream, which is the stream192* returned from the 1st call to getResponseBody()193* The "real" ouputstream is then placed inside this194*/195PlaceholderOutputStream getPlaceholderResponseBody () {196getResponseBody();197return uos_orig;198}199200public void sendResponseHeaders (int rCode, long contentLen)201throws IOException202{203final Logger logger = server.getLogger();204if (sentHeaders) {205throw new IOException ("headers already sent");206}207this.rcode = rCode;208String statusLine = "HTTP/1.1 "+rCode+Code.msg(rCode)+"\r\n";209OutputStream tmpout = new BufferedOutputStream (ros);210PlaceholderOutputStream o = getPlaceholderResponseBody();211tmpout.write (bytes(statusLine, 0), 0, statusLine.length());212boolean noContentToSend = false; // assume there is content213boolean noContentLengthHeader = false; // must not send Content-length is set214rspHdrs.set("Date", FORMATTER.format(Instant.now()));215216/* check for response type that is not allowed to send a body */217218if ((rCode>=100 && rCode <200) /* informational */219||(rCode == 204) /* no content */220||(rCode == 304)) /* not modified */221{222if (contentLen != -1) {223String msg = "sendResponseHeaders: rCode = "+ rCode224+ ": forcing contentLen = -1";225logger.log (Level.WARNING, msg);226}227contentLen = -1;228noContentLengthHeader = (rCode != 304);229}230231if (isHeadRequest() || rCode == 304) {232/* HEAD requests or 304 responses should not set a content length by passing it233* through this API, but should instead manually set the required234* headers.*/235if (contentLen >= 0) {236String msg =237"sendResponseHeaders: being invoked with a content length for a HEAD request";238logger.log (Level.WARNING, msg);239}240noContentToSend = true;241contentLen = 0;242} else { /* not a HEAD request or 304 response */243if (contentLen == 0) {244if (http10) {245o.setWrappedStream (new UndefLengthOutputStream (this, ros));246close = true;247} else {248rspHdrs.set ("Transfer-encoding", "chunked");249o.setWrappedStream (new ChunkedOutputStream (this, ros));250}251} else {252if (contentLen == -1) {253noContentToSend = true;254contentLen = 0;255}256if (!noContentLengthHeader) {257rspHdrs.set("Content-length", Long.toString(contentLen));258}259o.setWrappedStream (new FixedLengthOutputStream (this, ros, contentLen));260}261}262263// A custom handler can request that the connection be264// closed after the exchange by supplying Connection: close265// to the response header. Nothing to do if the exchange is266// already set up to be closed.267if (!close) {268Stream<String> conheader =269Optional.ofNullable(rspHdrs.get("Connection"))270.map(List::stream).orElse(Stream.empty());271if (conheader.anyMatch("close"::equalsIgnoreCase)) {272logger.log (Level.DEBUG, "Connection: close requested by handler");273close = true;274}275}276277write (rspHdrs, tmpout);278this.rspContentLen = contentLen;279tmpout.flush() ;280tmpout = null;281sentHeaders = true;282logger.log(Level.TRACE, "Sent headers: noContentToSend=" + noContentToSend);283if (noContentToSend) {284WriteFinishedEvent e = new WriteFinishedEvent (this);285server.addEvent (e);286closed = true;287}288server.logReply (rCode, req.requestLine(), null);289}290291void write (Headers map, OutputStream os) throws IOException {292Set<Map.Entry<String,List<String>>> entries = map.entrySet();293for (Map.Entry<String,List<String>> entry : entries) {294String key = entry.getKey();295byte[] buf;296List<String> values = entry.getValue();297for (String val : values) {298int i = key.length();299buf = bytes (key, 2);300buf[i++] = ':';301buf[i++] = ' ';302os.write (buf, 0, i);303buf = bytes (val, 2);304i = val.length();305buf[i++] = '\r';306buf[i++] = '\n';307os.write (buf, 0, i);308}309}310os.write ('\r');311os.write ('\n');312}313314private byte[] rspbuf = new byte [128]; // used by bytes()315316/**317* convert string to byte[], using rspbuf318* Make sure that at least "extra" bytes are free at end319* of rspbuf. Reallocate rspbuf if not big enough.320* caller must check return value to see if rspbuf moved321*/322private byte[] bytes (String s, int extra) {323int slen = s.length();324if (slen+extra > rspbuf.length) {325int diff = slen + extra - rspbuf.length;326rspbuf = new byte [2* (rspbuf.length + diff)];327}328char c[] = s.toCharArray();329for (int i=0; i<c.length; i++) {330rspbuf[i] = (byte)c[i];331}332return rspbuf;333}334335public InetSocketAddress getRemoteAddress (){336Socket s = connection.getChannel().socket();337InetAddress ia = s.getInetAddress();338int port = s.getPort();339return new InetSocketAddress (ia, port);340}341342public InetSocketAddress getLocalAddress (){343Socket s = connection.getChannel().socket();344InetAddress ia = s.getLocalAddress();345int port = s.getLocalPort();346return new InetSocketAddress (ia, port);347}348349public String getProtocol (){350String reqline = req.requestLine();351int index = reqline.lastIndexOf (' ');352return reqline.substring (index+1);353}354355public SSLSession getSSLSession () {356SSLEngine e = connection.getSSLEngine();357if (e == null) {358return null;359}360return e.getSession();361}362363public Object getAttribute (String name) {364if (name == null) {365throw new NullPointerException ("null name parameter");366}367if (attributes == null) {368attributes = getHttpContext().getAttributes();369}370return attributes.get (name);371}372373public void setAttribute (String name, Object value) {374if (name == null) {375throw new NullPointerException ("null name parameter");376}377if (attributes == null) {378attributes = getHttpContext().getAttributes();379}380attributes.put (name, value);381}382383public void setStreams (InputStream i, OutputStream o) {384assert uis != null;385if (i != null) {386uis = i;387}388if (o != null) {389uos = o;390}391}392393/**394* PP395*/396HttpConnection getConnection () {397return connection;398}399400ServerImpl getServerImpl () {401return getHttpContext().getServerImpl();402}403404public HttpPrincipal getPrincipal () {405return principal;406}407408void setPrincipal (HttpPrincipal principal) {409this.principal = principal;410}411412static ExchangeImpl get (HttpExchange t) {413if (t instanceof HttpExchangeImpl) {414return ((HttpExchangeImpl)t).getExchangeImpl();415} else {416assert t instanceof HttpsExchangeImpl;417return ((HttpsExchangeImpl)t).getExchangeImpl();418}419}420}421422/**423* An OutputStream which wraps another stream424* which is supplied either at creation time, or sometime later.425* If a caller/user tries to write to this stream before426* the wrapped stream has been provided, then an IOException will427* be thrown.428*/429class PlaceholderOutputStream extends java.io.OutputStream {430431OutputStream wrapped;432433PlaceholderOutputStream (OutputStream os) {434wrapped = os;435}436437void setWrappedStream (OutputStream os) {438wrapped = os;439}440441boolean isWrapped () {442return wrapped != null;443}444445private void checkWrap () throws IOException {446if (wrapped == null) {447throw new IOException ("response headers not sent yet");448}449}450451public void write(int b) throws IOException {452checkWrap();453wrapped.write (b);454}455456public void write(byte b[]) throws IOException {457checkWrap();458wrapped.write (b);459}460461public void write(byte b[], int off, int len) throws IOException {462checkWrap();463wrapped.write (b, off, len);464}465466public void flush() throws IOException {467checkWrap();468wrapped.flush();469}470471public void close() throws IOException {472checkWrap();473wrapped.close();474}475}476477478