1 // 2 // ======================================================================== 3 // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. 4 // ------------------------------------------------------------------------ 5 // All rights reserved. This program and the accompanying materials 6 // are made available under the terms of the Eclipse Public License v1.0 7 // and Apache License v2.0 which accompanies this distribution. 8 // 9 // The Eclipse Public License is available at 10 // http://www.eclipse.org/legal/epl-v10.html 11 // 12 // The Apache License v2.0 is available at 13 // http://www.opensource.org/licenses/apache2.0.php 14 // 15 // You may elect to redistribute this code under either of these licenses. 16 // ======================================================================== 17 // 18 19 package org.eclipse.jetty.http; 20 21 import java.io.IOException; 22 23 import org.eclipse.jetty.io.Buffer; 24 import org.eclipse.jetty.io.Buffers; 25 import org.eclipse.jetty.io.ByteArrayBuffer; 26 import org.eclipse.jetty.io.EndPoint; 27 import org.eclipse.jetty.io.EofException; 28 import org.eclipse.jetty.io.View; 29 import org.eclipse.jetty.util.log.Log; 30 import org.eclipse.jetty.util.log.Logger; 31 32 /* ------------------------------------------------------------ */ 33 /** 34 * Abstract Generator. Builds HTTP Messages. 35 * 36 * Currently this class uses a system parameter "jetty.direct.writers" to control 37 * two optional writer to byte conversions. buffer.writers=true will probably be 38 * faster, but will consume more memory. This option is just for testing and tuning. 39 * 40 */ 41 public abstract class AbstractGenerator implements Generator 42 { 43 private static final Logger LOG = Log.getLogger(AbstractGenerator.class); 44 45 // states 46 public final static int STATE_HEADER = 0; 47 public final static int STATE_CONTENT = 2; 48 public final static int STATE_FLUSHING = 3; 49 public final static int STATE_END = 4; 50 51 public static final byte[] NO_BYTES = {}; 52 53 // data 54 55 protected final Buffers _buffers; // source of buffers 56 protected final EndPoint _endp; 57 58 protected int _state = STATE_HEADER; 59 60 protected int _status = 0; 61 protected int _version = HttpVersions.HTTP_1_1_ORDINAL; 62 protected Buffer _reason; 63 protected Buffer _method; 64 protected String _uri; 65 66 protected long _contentWritten = 0; 67 protected long _contentLength = HttpTokens.UNKNOWN_CONTENT; 68 protected boolean _last = false; 69 protected boolean _head = false; 70 protected boolean _noContent = false; 71 protected Boolean _persistent = null; 72 73 protected Buffer _header; // Buffer for HTTP header (and maybe small _content) 74 protected Buffer _buffer; // Buffer for copy of passed _content 75 protected Buffer _content; // Buffer passed to addContent 76 77 protected Buffer _date; 78 79 private boolean _sendServerVersion; 80 81 82 /* ------------------------------------------------------------------------------- */ 83 /** 84 * Constructor. 85 * 86 * @param buffers buffer pool 87 * @param io the end point 88 */ AbstractGenerator(Buffers buffers, EndPoint io)89 public AbstractGenerator(Buffers buffers, EndPoint io) 90 { 91 this._buffers = buffers; 92 this._endp = io; 93 } 94 95 /* ------------------------------------------------------------------------------- */ isRequest()96 public abstract boolean isRequest(); 97 98 /* ------------------------------------------------------------------------------- */ isResponse()99 public abstract boolean isResponse(); 100 101 /* ------------------------------------------------------------------------------- */ isOpen()102 public boolean isOpen() 103 { 104 return _endp.isOpen(); 105 } 106 107 /* ------------------------------------------------------------------------------- */ reset()108 public void reset() 109 { 110 _state = STATE_HEADER; 111 _status = 0; 112 _version = HttpVersions.HTTP_1_1_ORDINAL; 113 _reason = null; 114 _last = false; 115 _head = false; 116 _noContent=false; 117 _persistent = null; 118 _contentWritten = 0; 119 _contentLength = HttpTokens.UNKNOWN_CONTENT; 120 _date = null; 121 122 _content = null; 123 _method=null; 124 } 125 126 /* ------------------------------------------------------------------------------- */ returnBuffers()127 public void returnBuffers() 128 { 129 if (_buffer!=null && _buffer.length()==0) 130 { 131 _buffers.returnBuffer(_buffer); 132 _buffer=null; 133 } 134 135 if (_header!=null && _header.length()==0) 136 { 137 _buffers.returnBuffer(_header); 138 _header=null; 139 } 140 } 141 142 /* ------------------------------------------------------------------------------- */ resetBuffer()143 public void resetBuffer() 144 { 145 if(_state>=STATE_FLUSHING) 146 throw new IllegalStateException("Flushed"); 147 148 _last = false; 149 _persistent=null; 150 _contentWritten = 0; 151 _contentLength = HttpTokens.UNKNOWN_CONTENT; 152 _content=null; 153 if (_buffer!=null) 154 _buffer.clear(); 155 } 156 157 /* ------------------------------------------------------------ */ 158 /** 159 * @return Returns the contentBufferSize. 160 */ getContentBufferSize()161 public int getContentBufferSize() 162 { 163 if (_buffer==null) 164 _buffer=_buffers.getBuffer(); 165 return _buffer.capacity(); 166 } 167 168 /* ------------------------------------------------------------ */ 169 /** 170 * @param contentBufferSize The contentBufferSize to set. 171 */ increaseContentBufferSize(int contentBufferSize)172 public void increaseContentBufferSize(int contentBufferSize) 173 { 174 if (_buffer==null) 175 _buffer=_buffers.getBuffer(); 176 if (contentBufferSize > _buffer.capacity()) 177 { 178 Buffer nb = _buffers.getBuffer(contentBufferSize); 179 nb.put(_buffer); 180 _buffers.returnBuffer(_buffer); 181 _buffer = nb; 182 } 183 } 184 185 /* ------------------------------------------------------------ */ getUncheckedBuffer()186 public Buffer getUncheckedBuffer() 187 { 188 return _buffer; 189 } 190 191 /* ------------------------------------------------------------ */ getSendServerVersion()192 public boolean getSendServerVersion () 193 { 194 return _sendServerVersion; 195 } 196 197 /* ------------------------------------------------------------ */ setSendServerVersion(boolean sendServerVersion)198 public void setSendServerVersion (boolean sendServerVersion) 199 { 200 _sendServerVersion = sendServerVersion; 201 } 202 203 /* ------------------------------------------------------------ */ getState()204 public int getState() 205 { 206 return _state; 207 } 208 209 /* ------------------------------------------------------------ */ isState(int state)210 public boolean isState(int state) 211 { 212 return _state == state; 213 } 214 215 /* ------------------------------------------------------------ */ isComplete()216 public boolean isComplete() 217 { 218 return _state == STATE_END; 219 } 220 221 /* ------------------------------------------------------------ */ isIdle()222 public boolean isIdle() 223 { 224 return _state == STATE_HEADER && _method==null && _status==0; 225 } 226 227 /* ------------------------------------------------------------ */ isCommitted()228 public boolean isCommitted() 229 { 230 return _state != STATE_HEADER; 231 } 232 233 /* ------------------------------------------------------------ */ 234 /** 235 * @return Returns the head. 236 */ isHead()237 public boolean isHead() 238 { 239 return _head; 240 } 241 242 /* ------------------------------------------------------------ */ setContentLength(long value)243 public void setContentLength(long value) 244 { 245 if (value<0) 246 _contentLength=HttpTokens.UNKNOWN_CONTENT; 247 else 248 _contentLength=value; 249 } 250 251 /* ------------------------------------------------------------ */ 252 /** 253 * @param head The head to set. 254 */ setHead(boolean head)255 public void setHead(boolean head) 256 { 257 _head = head; 258 } 259 260 /* ------------------------------------------------------------ */ 261 /** 262 * @return <code>false</code> if the connection should be closed after a request has been read, 263 * <code>true</code> if it should be used for additional requests. 264 */ isPersistent()265 public boolean isPersistent() 266 { 267 return _persistent!=null 268 ?_persistent.booleanValue() 269 :(isRequest()?true:_version>HttpVersions.HTTP_1_0_ORDINAL); 270 } 271 272 /* ------------------------------------------------------------ */ setPersistent(boolean persistent)273 public void setPersistent(boolean persistent) 274 { 275 _persistent=persistent; 276 } 277 278 /* ------------------------------------------------------------ */ 279 /** 280 * @param version The version of the client the response is being sent to (NB. Not the version 281 * in the response, which is the version of the server). 282 */ setVersion(int version)283 public void setVersion(int version) 284 { 285 if (_state != STATE_HEADER) 286 throw new IllegalStateException("STATE!=START "+_state); 287 _version = version; 288 if (_version==HttpVersions.HTTP_0_9_ORDINAL && _method!=null) 289 _noContent=true; 290 } 291 292 /* ------------------------------------------------------------ */ getVersion()293 public int getVersion() 294 { 295 return _version; 296 } 297 298 /* ------------------------------------------------------------ */ 299 /** 300 * @see org.eclipse.jetty.http.Generator#setDate(org.eclipse.jetty.io.Buffer) 301 */ setDate(Buffer timeStampBuffer)302 public void setDate(Buffer timeStampBuffer) 303 { 304 _date=timeStampBuffer; 305 } 306 307 /* ------------------------------------------------------------ */ 308 /** 309 */ setRequest(String method, String uri)310 public void setRequest(String method, String uri) 311 { 312 if (method==null || HttpMethods.GET.equals(method) ) 313 _method=HttpMethods.GET_BUFFER; 314 else 315 _method=HttpMethods.CACHE.lookup(method); 316 _uri=uri; 317 if (_version==HttpVersions.HTTP_0_9_ORDINAL) 318 _noContent=true; 319 } 320 321 /* ------------------------------------------------------------ */ 322 /** 323 * @param status The status code to send. 324 * @param reason the status message to send. 325 */ setResponse(int status, String reason)326 public void setResponse(int status, String reason) 327 { 328 if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START"); 329 _method=null; 330 _status = status; 331 if (reason!=null) 332 { 333 int len=reason.length(); 334 335 // TODO don't hard code 336 if (len>1024) 337 len=1024; 338 _reason=new ByteArrayBuffer(len); 339 for (int i=0;i<len;i++) 340 { 341 char ch = reason.charAt(i); 342 if (ch!='\r'&&ch!='\n') 343 _reason.put((byte)ch); 344 else 345 _reason.put((byte)' '); 346 } 347 } 348 } 349 350 /* ------------------------------------------------------------ */ 351 /** Prepare buffer for unchecked writes. 352 * Prepare the generator buffer to receive unchecked writes 353 * @return the available space in the buffer. 354 * @throws IOException 355 */ prepareUncheckedAddContent()356 public abstract int prepareUncheckedAddContent() throws IOException; 357 358 /* ------------------------------------------------------------ */ uncheckedAddContent(int b)359 void uncheckedAddContent(int b) 360 { 361 _buffer.put((byte)b); 362 } 363 364 /* ------------------------------------------------------------ */ completeUncheckedAddContent()365 public void completeUncheckedAddContent() 366 { 367 if (_noContent) 368 { 369 if(_buffer!=null) 370 _buffer.clear(); 371 } 372 else 373 { 374 _contentWritten+=_buffer.length(); 375 if (_head) 376 _buffer.clear(); 377 } 378 } 379 380 /* ------------------------------------------------------------ */ isBufferFull()381 public boolean isBufferFull() 382 { 383 if (_buffer != null && _buffer.space()==0) 384 { 385 if (_buffer.length()==0 && !_buffer.isImmutable()) 386 _buffer.compact(); 387 return _buffer.space()==0; 388 } 389 390 return _content!=null && _content.length()>0; 391 } 392 393 /* ------------------------------------------------------------ */ isWritten()394 public boolean isWritten() 395 { 396 return _contentWritten>0; 397 } 398 399 /* ------------------------------------------------------------ */ isAllContentWritten()400 public boolean isAllContentWritten() 401 { 402 return _contentLength>=0 && _contentWritten>=_contentLength; 403 } 404 405 /* ------------------------------------------------------------ */ completeHeader(HttpFields fields, boolean allContentAdded)406 public abstract void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException; 407 408 /* ------------------------------------------------------------ */ 409 /** 410 * Complete the message. 411 * 412 * @throws IOException 413 */ complete()414 public void complete() throws IOException 415 { 416 if (_state == STATE_HEADER) 417 { 418 throw new IllegalStateException("State==HEADER"); 419 } 420 421 if (_contentLength >= 0 && _contentLength != _contentWritten && !_head) 422 { 423 if (LOG.isDebugEnabled()) 424 LOG.debug("ContentLength written=="+_contentWritten+" != contentLength=="+_contentLength); 425 _persistent = false; 426 } 427 } 428 429 /* ------------------------------------------------------------ */ flushBuffer()430 public abstract int flushBuffer() throws IOException; 431 432 433 /* ------------------------------------------------------------ */ flush(long maxIdleTime)434 public void flush(long maxIdleTime) throws IOException 435 { 436 // block until everything is flushed 437 long now=System.currentTimeMillis(); 438 long end=now+maxIdleTime; 439 Buffer content = _content; 440 Buffer buffer = _buffer; 441 if (content!=null && content.length()>0 || buffer!=null && buffer.length()>0 || isBufferFull()) 442 { 443 flushBuffer(); 444 445 while (now<end && (content!=null && content.length()>0 ||buffer!=null && buffer.length()>0) && _endp.isOpen()&& !_endp.isOutputShutdown()) 446 { 447 blockForOutput(end-now); 448 now=System.currentTimeMillis(); 449 } 450 } 451 } 452 453 /* ------------------------------------------------------------ */ 454 /** 455 * Utility method to send an error response. If the builder is not committed, this call is 456 * equivalent to a setResponse, addContent and complete call. 457 * 458 * @param code The error code 459 * @param reason The error reason 460 * @param content Contents of the error page 461 * @param close True if the connection should be closed 462 * @throws IOException if there is a problem flushing the response 463 */ sendError(int code, String reason, String content, boolean close)464 public void sendError(int code, String reason, String content, boolean close) throws IOException 465 { 466 if (close) 467 _persistent=false; 468 if (isCommitted()) 469 { 470 LOG.debug("sendError on committed: {} {}",code,reason); 471 } 472 else 473 { 474 LOG.debug("sendError: {} {}",code,reason); 475 setResponse(code, reason); 476 if (content != null) 477 { 478 completeHeader(null, false); 479 addContent(new View(new ByteArrayBuffer(content)), Generator.LAST); 480 } 481 else if (code>=400) 482 { 483 completeHeader(null, false); 484 addContent(new View(new ByteArrayBuffer("Error: "+(reason==null?(""+code):reason))), Generator.LAST); 485 } 486 else 487 { 488 completeHeader(null, true); 489 } 490 complete(); 491 } 492 } 493 494 /* ------------------------------------------------------------ */ 495 /** 496 * @return Returns the contentWritten. 497 */ getContentWritten()498 public long getContentWritten() 499 { 500 return _contentWritten; 501 } 502 503 504 505 /* ------------------------------------------------------------ */ blockForOutput(long maxIdleTime)506 public void blockForOutput(long maxIdleTime) throws IOException 507 { 508 if (_endp.isBlocking()) 509 { 510 try 511 { 512 flushBuffer(); 513 } 514 catch(IOException e) 515 { 516 _endp.close(); 517 throw e; 518 } 519 } 520 else 521 { 522 if (!_endp.blockWritable(maxIdleTime)) 523 { 524 _endp.close(); 525 throw new EofException("timeout"); 526 } 527 528 flushBuffer(); 529 } 530 } 531 532 } 533