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