1 /* 2 * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.net.httpserver; 27 28 import java.io.*; 29 import java.net.*; 30 import javax.net.ssl.*; 31 import java.util.*; 32 import java.util.logging.Logger; 33 import java.text.*; 34 import com.sun.net.httpserver.*; 35 36 class ExchangeImpl { 37 38 Headers reqHdrs, rspHdrs; 39 Request req; 40 String method; 41 boolean writefinished; 42 URI uri; 43 HttpConnection connection; 44 long reqContentLen; 45 long rspContentLen; 46 /* raw streams which access the socket directly */ 47 InputStream ris; 48 OutputStream ros; 49 Thread thread; 50 /* close the underlying connection when this exchange finished */ 51 boolean close; 52 boolean closed; 53 boolean http10 = false; 54 55 /* for formatting the Date: header */ 56 private static final String pattern = "EEE, dd MMM yyyy HH:mm:ss zzz"; 57 private static final TimeZone gmtTZ = TimeZone.getTimeZone("GMT"); 58 private static final ThreadLocal<DateFormat> dateFormat = 59 new ThreadLocal<DateFormat>() { 60 @Override protected DateFormat initialValue() { 61 DateFormat df = new SimpleDateFormat(pattern, Locale.US); 62 df.setTimeZone(gmtTZ); 63 return df; 64 } 65 }; 66 67 private static final String HEAD = "HEAD"; 68 69 /* streams which take care of the HTTP protocol framing 70 * and are passed up to higher layers 71 */ 72 InputStream uis; 73 OutputStream uos; 74 LeftOverInputStream uis_orig; // uis may have be a user supplied wrapper 75 PlaceholderOutputStream uos_orig; 76 77 boolean sentHeaders; /* true after response headers sent */ 78 Map<String,Object> attributes; 79 int rcode = -1; 80 HttpPrincipal principal; 81 ServerImpl server; 82 ExchangeImpl( String m, URI u, Request req, long len, HttpConnection connection )83 ExchangeImpl ( 84 String m, URI u, Request req, long len, HttpConnection connection 85 ) throws IOException { 86 this.req = req; 87 this.reqHdrs = req.headers(); 88 this.rspHdrs = new Headers(); 89 this.method = m; 90 this.uri = u; 91 this.connection = connection; 92 this.reqContentLen = len; 93 /* ros only used for headers, body written directly to stream */ 94 this.ros = req.outputStream(); 95 this.ris = req.inputStream(); 96 server = getServerImpl(); 97 server.startExchange(); 98 } 99 getRequestHeaders()100 public Headers getRequestHeaders () { 101 return new UnmodifiableHeaders (reqHdrs); 102 } 103 getResponseHeaders()104 public Headers getResponseHeaders () { 105 return rspHdrs; 106 } 107 getRequestURI()108 public URI getRequestURI () { 109 return uri; 110 } 111 getRequestMethod()112 public String getRequestMethod (){ 113 return method; 114 } 115 getHttpContext()116 public HttpContextImpl getHttpContext (){ 117 return connection.getHttpContext(); 118 } 119 isHeadRequest()120 private boolean isHeadRequest() { 121 return HEAD.equals(getRequestMethod()); 122 } 123 close()124 public void close () { 125 if (closed) { 126 return; 127 } 128 closed = true; 129 130 /* close the underlying connection if, 131 * a) the streams not set up yet, no response can be sent, or 132 * b) if the wrapper output stream is not set up, or 133 * c) if the close of the input/outpu stream fails 134 */ 135 try { 136 if (uis_orig == null || uos == null) { 137 connection.close(); 138 return; 139 } 140 if (!uos_orig.isWrapped()) { 141 connection.close(); 142 return; 143 } 144 if (!uis_orig.isClosed()) { 145 uis_orig.close(); 146 } 147 uos.close(); 148 } catch (IOException e) { 149 connection.close(); 150 } 151 } 152 getRequestBody()153 public InputStream getRequestBody () { 154 if (uis != null) { 155 return uis; 156 } 157 if (reqContentLen == -1L) { 158 uis_orig = new ChunkedInputStream (this, ris); 159 uis = uis_orig; 160 } else { 161 uis_orig = new FixedLengthInputStream (this, ris, reqContentLen); 162 uis = uis_orig; 163 } 164 return uis; 165 } 166 getOriginalInputStream()167 LeftOverInputStream getOriginalInputStream () { 168 return uis_orig; 169 } 170 getResponseCode()171 public int getResponseCode () { 172 return rcode; 173 } 174 getResponseBody()175 public OutputStream getResponseBody () { 176 /* TODO. Change spec to remove restriction below. Filters 177 * cannot work with this restriction 178 * 179 * if (!sentHeaders) { 180 * throw new IllegalStateException ("headers not sent"); 181 * } 182 */ 183 if (uos == null) { 184 uos_orig = new PlaceholderOutputStream (null); 185 uos = uos_orig; 186 } 187 return uos; 188 } 189 190 191 /* returns the place holder stream, which is the stream 192 * returned from the 1st call to getResponseBody() 193 * The "real" ouputstream is then placed inside this 194 */ getPlaceholderResponseBody()195 PlaceholderOutputStream getPlaceholderResponseBody () { 196 getResponseBody(); 197 return uos_orig; 198 } 199 sendResponseHeaders(int rCode, long contentLen)200 public void sendResponseHeaders (int rCode, long contentLen) 201 throws IOException 202 { 203 if (sentHeaders) { 204 throw new IOException ("headers already sent"); 205 } 206 this.rcode = rCode; 207 String statusLine = "HTTP/1.1 "+rCode+Code.msg(rCode)+"\r\n"; 208 OutputStream tmpout = new BufferedOutputStream (ros); 209 PlaceholderOutputStream o = getPlaceholderResponseBody(); 210 tmpout.write (bytes(statusLine, 0), 0, statusLine.length()); 211 boolean noContentToSend = false; // assume there is content 212 rspHdrs.set ("Date", dateFormat.get().format (new Date())); 213 214 /* check for response type that is not allowed to send a body */ 215 216 if ((rCode>=100 && rCode <200) /* informational */ 217 ||(rCode == 204) /* no content */ 218 ||(rCode == 304)) /* not modified */ 219 { 220 if (contentLen != -1) { 221 Logger logger = server.getLogger(); 222 String msg = "sendResponseHeaders: rCode = "+ rCode 223 + ": forcing contentLen = -1"; 224 logger.warning (msg); 225 } 226 contentLen = -1; 227 } 228 229 if (isHeadRequest()) { 230 /* HEAD requests should not set a content length by passing it 231 * through this API, but should instead manually set the required 232 * headers.*/ 233 if (contentLen >= 0) { 234 final Logger logger = server.getLogger(); 235 String msg = 236 "sendResponseHeaders: being invoked with a content length for a HEAD request"; 237 logger.warning (msg); 238 } 239 noContentToSend = true; 240 contentLen = 0; 241 } else { /* not a HEAD request */ 242 if (contentLen == 0) { 243 if (http10) { 244 o.setWrappedStream (new UndefLengthOutputStream (this, ros)); 245 close = true; 246 } else { 247 rspHdrs.set ("Transfer-encoding", "chunked"); 248 o.setWrappedStream (new ChunkedOutputStream (this, ros)); 249 } 250 } else { 251 if (contentLen == -1) { 252 noContentToSend = true; 253 contentLen = 0; 254 } 255 rspHdrs.set("Content-length", Long.toString(contentLen)); 256 o.setWrappedStream (new FixedLengthOutputStream (this, ros, contentLen)); 257 } 258 } 259 write (rspHdrs, tmpout); 260 this.rspContentLen = contentLen; 261 tmpout.flush() ; 262 tmpout = null; 263 sentHeaders = true; 264 if (noContentToSend) { 265 WriteFinishedEvent e = new WriteFinishedEvent (this); 266 server.addEvent (e); 267 closed = true; 268 } 269 server.logReply (rCode, req.requestLine(), null); 270 } 271 write(Headers map, OutputStream os)272 void write (Headers map, OutputStream os) throws IOException { 273 Set<Map.Entry<String,List<String>>> entries = map.entrySet(); 274 for (Map.Entry<String,List<String>> entry : entries) { 275 String key = entry.getKey(); 276 byte[] buf; 277 List<String> values = entry.getValue(); 278 for (String val : values) { 279 int i = key.length(); 280 buf = bytes (key, 2); 281 buf[i++] = ':'; 282 buf[i++] = ' '; 283 os.write (buf, 0, i); 284 buf = bytes (val, 2); 285 i = val.length(); 286 buf[i++] = '\r'; 287 buf[i++] = '\n'; 288 os.write (buf, 0, i); 289 } 290 } 291 os.write ('\r'); 292 os.write ('\n'); 293 } 294 295 private byte[] rspbuf = new byte [128]; // used by bytes() 296 297 /** 298 * convert string to byte[], using rspbuf 299 * Make sure that at least "extra" bytes are free at end 300 * of rspbuf. Reallocate rspbuf if not big enough. 301 * caller must check return value to see if rspbuf moved 302 */ bytes(String s, int extra)303 private byte[] bytes (String s, int extra) { 304 int slen = s.length(); 305 if (slen+extra > rspbuf.length) { 306 int diff = slen + extra - rspbuf.length; 307 rspbuf = new byte [2* (rspbuf.length + diff)]; 308 } 309 char c[] = s.toCharArray(); 310 for (int i=0; i<c.length; i++) { 311 rspbuf[i] = (byte)c[i]; 312 } 313 return rspbuf; 314 } 315 getRemoteAddress()316 public InetSocketAddress getRemoteAddress (){ 317 Socket s = connection.getChannel().socket(); 318 InetAddress ia = s.getInetAddress(); 319 int port = s.getPort(); 320 return new InetSocketAddress (ia, port); 321 } 322 getLocalAddress()323 public InetSocketAddress getLocalAddress (){ 324 Socket s = connection.getChannel().socket(); 325 InetAddress ia = s.getLocalAddress(); 326 int port = s.getLocalPort(); 327 return new InetSocketAddress (ia, port); 328 } 329 getProtocol()330 public String getProtocol (){ 331 String reqline = req.requestLine(); 332 int index = reqline.lastIndexOf (' '); 333 return reqline.substring (index+1); 334 } 335 getSSLSession()336 public SSLSession getSSLSession () { 337 SSLEngine e = connection.getSSLEngine(); 338 if (e == null) { 339 return null; 340 } 341 return e.getSession(); 342 } 343 getAttribute(String name)344 public Object getAttribute (String name) { 345 if (name == null) { 346 throw new NullPointerException ("null name parameter"); 347 } 348 if (attributes == null) { 349 attributes = getHttpContext().getAttributes(); 350 } 351 return attributes.get (name); 352 } 353 setAttribute(String name, Object value)354 public void setAttribute (String name, Object value) { 355 if (name == null) { 356 throw new NullPointerException ("null name parameter"); 357 } 358 if (attributes == null) { 359 attributes = getHttpContext().getAttributes(); 360 } 361 attributes.put (name, value); 362 } 363 setStreams(InputStream i, OutputStream o)364 public void setStreams (InputStream i, OutputStream o) { 365 assert uis != null; 366 if (i != null) { 367 uis = i; 368 } 369 if (o != null) { 370 uos = o; 371 } 372 } 373 374 /** 375 * PP 376 */ getConnection()377 HttpConnection getConnection () { 378 return connection; 379 } 380 getServerImpl()381 ServerImpl getServerImpl () { 382 return getHttpContext().getServerImpl(); 383 } 384 getPrincipal()385 public HttpPrincipal getPrincipal () { 386 return principal; 387 } 388 setPrincipal(HttpPrincipal principal)389 void setPrincipal (HttpPrincipal principal) { 390 this.principal = principal; 391 } 392 get(HttpExchange t)393 static ExchangeImpl get (HttpExchange t) { 394 if (t instanceof HttpExchangeImpl) { 395 return ((HttpExchangeImpl)t).getExchangeImpl(); 396 } else { 397 assert t instanceof HttpsExchangeImpl; 398 return ((HttpsExchangeImpl)t).getExchangeImpl(); 399 } 400 } 401 } 402 403 /** 404 * An OutputStream which wraps another stream 405 * which is supplied either at creation time, or sometime later. 406 * If a caller/user tries to write to this stream before 407 * the wrapped stream has been provided, then an IOException will 408 * be thrown. 409 */ 410 class PlaceholderOutputStream extends java.io.OutputStream { 411 412 OutputStream wrapped; 413 PlaceholderOutputStream(OutputStream os)414 PlaceholderOutputStream (OutputStream os) { 415 wrapped = os; 416 } 417 setWrappedStream(OutputStream os)418 void setWrappedStream (OutputStream os) { 419 wrapped = os; 420 } 421 isWrapped()422 boolean isWrapped () { 423 return wrapped != null; 424 } 425 checkWrap()426 private void checkWrap () throws IOException { 427 if (wrapped == null) { 428 throw new IOException ("response headers not sent yet"); 429 } 430 } 431 write(int b)432 public void write(int b) throws IOException { 433 checkWrap(); 434 wrapped.write (b); 435 } 436 write(byte b[])437 public void write(byte b[]) throws IOException { 438 checkWrap(); 439 wrapped.write (b); 440 } 441 write(byte b[], int off, int len)442 public void write(byte b[], int off, int len) throws IOException { 443 checkWrap(); 444 wrapped.write (b, off, len); 445 } 446 flush()447 public void flush() throws IOException { 448 checkWrap(); 449 wrapped.flush(); 450 } 451 close()452 public void close() throws IOException { 453 checkWrap(); 454 wrapped.close(); 455 } 456 } 457