1 29 30 package com.caucho.server.connection; 31 32 import com.caucho.log.Log; 33 import com.caucho.server.webapp.WebApp; 34 import com.caucho.util.L10N; 35 import com.caucho.vfs.ClientDisconnectException; 36 import com.caucho.vfs.WriteStream; 37 38 import javax.servlet.ServletContext ; 39 import java.io.IOException ; 40 import java.io.OutputStream ; 41 import java.util.logging.Level ; 42 import java.util.logging.Logger ; 43 44 class ResponseStream extends ToByteResponseStream { 45 static final Logger log = Log.open(ResponseStream.class); 46 47 static final L10N L = new L10N(ResponseStream.class); 48 49 private static final int _tailChunkedLength = 7; 50 private static final byte []_tailChunked = 51 new byte[] {'\r', '\n', '0', '\r', '\n', '\r', '\n'}; 52 53 private final AbstractHttpResponse _response; 54 55 private WriteStream _next; 56 57 private OutputStream _cacheStream; 58 private long _cacheMaxLength; 59 private int _bufferStartOffset; 61 62 private boolean _chunkedEncoding; 63 64 private int _bufferSize; 65 private boolean _disableAutoFlush; 66 67 private int _contentLength; 69 private boolean _isFirst; 71 private boolean _isDisconnected; 72 private boolean _isCommitted; 73 74 private boolean _allowFlush = true; 75 private boolean _isHead = false; 76 private boolean _isClosed = false; 77 78 private final byte []_buffer = new byte[16]; 79 80 ResponseStream(AbstractHttpResponse response) 81 { 82 _response = response; 83 } 84 85 public void init(WriteStream next) 86 { 87 _next = next; 88 } 89 90 93 public void start() 94 { 95 super.start(); 96 97 _chunkedEncoding = false; 98 99 _contentLength = 0; 100 _allowFlush = true; 101 _disableAutoFlush = false; 102 _isClosed = false; 103 _isHead = false; 104 _cacheStream = null; 105 _isDisconnected = false; 106 _isCommitted = false; 107 _isFirst = true; 108 _bufferStartOffset = 0; 109 } 110 111 114 public boolean isCauchoResponseStream() 115 { 116 return true; 117 } 118 119 124 public void setByteCacheStream(OutputStream cacheStream) 125 { 126 _cacheStream = cacheStream; 127 128 CauchoRequest req = _response.getRequest(); 129 WebApp app = req.getWebApp(); 130 _cacheMaxLength = app.getCacheMaxLength(); 131 } 132 133 136 public boolean canWrite() 137 { 138 return true; 139 } 140 141 void setFlush(boolean flush) 142 { 143 _allowFlush = flush; 144 } 145 146 public void setAutoFlush(boolean isAutoFlush) 147 { 148 setDisableAutoFlush(! isAutoFlush); 149 } 150 151 void setDisableAutoFlush(boolean disable) 152 { 153 _disableAutoFlush = disable; 154 } 155 156 public void setHead() 157 { 158 _isHead = true; 159 _bufferSize = 0; 160 } 161 162 public boolean isHead() 163 { 164 return _isHead; 165 } 166 167 public int getContentLength() 168 { 169 return _contentLength; 170 } 171 172 public void setBufferSize(int size) 173 { 174 if (isCommitted()) 175 throw new IllegalStateException (L.l("Buffer size cannot be set after commit")); 176 177 super.setBufferSize(size); 178 } 179 180 public boolean isCommitted() 181 { 182 return _isCommitted || _isClosed; 184 } 185 186 public void clear() 187 throws IOException 188 { 189 clearBuffer(); 190 191 if (_isCommitted) 192 throw new IOException (L.l("can't clear response after writing headers")); 193 } 194 195 public void clearBuffer() 196 { 197 super.clearBuffer(); 198 199 if (! _isCommitted) { 200 _isFirst = true; 202 _bufferStartOffset = 0; 203 _response.setHeaderWritten(false); 204 } 205 206 _next.setBufferOffset(_bufferStartOffset); 207 } 208 209 212 public void clearClosed() 213 { 214 _isClosed = false; 215 } 216 217 private void writeHeaders(int length) 218 throws IOException 219 { 220 _chunkedEncoding = _response.writeHeaders(_next, length); 221 } 222 223 226 public byte []getBuffer() 227 throws IOException 228 { 229 flushBuffer(); 230 231 return _next.getBuffer(); 232 } 233 234 237 public int getBufferOffset() 238 throws IOException 239 { 240 byte []buffer; 241 int offset; 242 243 flushBuffer(); 244 245 offset = _next.getBufferOffset(); 246 247 if (! _chunkedEncoding) { 248 _bufferStartOffset = offset; 249 return offset; 250 } 251 else if (_bufferStartOffset > 0) { 252 return offset; 253 } 254 255 buffer = _next.getBuffer(); 257 if (buffer.length - offset < 8) { 258 _isCommitted = true; 259 _next.flushBuffer(); 260 261 buffer = _next.getBuffer(); 262 offset = _next.getBufferOffset(); 263 } 264 265 _bufferStartOffset = offset + 8; 266 _next.setBufferOffset(offset + 8); 267 268 return _bufferStartOffset; 269 } 270 271 274 public byte []nextBuffer(int offset) 275 throws IOException 276 { 277 if (_isClosed) 278 return _next.getBuffer(); 279 280 _isCommitted = true; 281 282 int startOffset = _bufferStartOffset; 283 _bufferStartOffset = 0; 284 285 int length = offset - startOffset; 286 long lengthHeader = _response.getContentLengthHeader(); 287 288 if (lengthHeader > 0 && lengthHeader < _contentLength + length) { 289 lengthException(_next.getBuffer(), startOffset, length, lengthHeader); 290 291 length = (int) (lengthHeader - _contentLength); 292 offset = startOffset + length; 293 } 294 295 _contentLength += length; 296 297 try { 298 if (_isHead) { 299 return _next.getBuffer(); 300 } 301 else if (_chunkedEncoding) { 302 if (length == 0) 303 throw new IllegalStateException (); 304 305 byte []buffer = _next.getBuffer(); 306 307 writeChunk(buffer, startOffset, length); 308 309 buffer = _next.nextBuffer(offset); 310 311 if (log.isLoggable(Level.FINE)) 312 log.fine("[" + dbgId() + "] write-chunk(" + offset + ")"); 313 314 _bufferStartOffset = 8 + _next.getBufferOffset(); 315 _next.setBufferOffset(_bufferStartOffset); 316 317 return buffer; 318 } 319 else { 320 if (_cacheStream != null) 321 writeCache(_next.getBuffer(), startOffset, length); 322 323 byte []buffer = _next.nextBuffer(offset); 324 325 if (log.isLoggable(Level.FINE)) 326 log.fine("[" + dbgId() + "] write-chunk(" + offset + ")"); 327 328 return buffer; 329 } 330 } catch (ClientDisconnectException e) { 331 _response.killCache(); 332 333 if (_response.isIgnoreClientDisconnect()) { 334 _isDisconnected = true; 335 return _next.getBuffer(); 336 } 337 else 338 throw e; 339 } catch (IOException e) { 340 _response.killCache(); 341 342 throw e; 343 } 344 } 345 346 349 public void setBufferOffset(int offset) 350 throws IOException 351 { 352 if (_isClosed) 353 return; 354 355 int startOffset = _bufferStartOffset; 356 if (offset == startOffset) 357 return; 358 359 int length = offset - startOffset; 360 long lengthHeader = _response.getContentLengthHeader(); 361 362 if (lengthHeader > 0 && lengthHeader < _contentLength + length) { 363 lengthException(_next.getBuffer(), startOffset, length, lengthHeader); 364 365 length = (int) (lengthHeader - _contentLength); 366 offset = startOffset + length; 367 } 368 369 _contentLength += length; 370 371 if (_cacheStream != null && ! _chunkedEncoding) { 372 _bufferStartOffset = offset; 373 writeCache(_next.getBuffer(), startOffset, length); 374 } 375 376 if (! _isHead) { 377 _next.setBufferOffset(offset); 378 } 379 } 380 381 388 protected void writeNext(byte []buf, int offset, int length, 389 boolean isFinished) 390 throws IOException 391 { 392 try { 393 if (_isClosed) 394 return; 395 396 if (_disableAutoFlush && ! isFinished) 397 throw new IOException (L.l("auto-flushing has been disabled")); 398 399 boolean isFirst = _isFirst; 400 _isFirst = false; 401 402 if (! isFirst) { 403 } 404 else if (isFinished) 405 writeHeaders(getBufferLength()); 406 else 407 writeHeaders(-1); 408 409 int bufferStart = _bufferStartOffset; 410 int bufferOffset = _next.getBufferOffset(); 411 412 if (length == 0 && ! isFinished && bufferStart == bufferOffset) 414 return; 415 416 long contentLengthHeader = _response.getContentLengthHeader(); 417 if (0 < contentLengthHeader && 419 contentLengthHeader < length + _contentLength) { 420 if (lengthException(buf, offset, length, contentLengthHeader)) 421 return; 422 423 length = (int) (contentLengthHeader - _contentLength); 424 } 425 426 if (_next != null && ! _isHead) { 427 if (length > 0 && log.isLoggable(Level.FINE)) { 428 String id; 429 if (_response.getRequest() instanceof AbstractHttpRequest) { 430 Connection conn = ((AbstractHttpRequest) _response.getRequest()).getConnection(); 431 if (conn != null) 432 id = String.valueOf(conn.getId()); 433 else 434 id = "jni"; 435 } 436 else 437 id = "inc"; 438 439 log.fine("[" + id + "] chunk: " + length); 440 } 441 442 if (! _chunkedEncoding) { 443 byte []nextBuffer = _next.getBuffer(); 444 int nextOffset = _next.getBufferOffset(); 445 446 if (nextOffset + length < nextBuffer.length) { 447 System.arraycopy(buf, offset, nextBuffer, nextOffset, length); 448 _next.setBufferOffset(nextOffset + length); 449 } 450 else { 451 _isCommitted = true; 452 _next.write(buf, offset, length); 453 454 if (log.isLoggable(Level.FINE)) 455 log.fine("[" + dbgId() + "] write-data(" + _tailChunkedLength + ")"); 456 } 457 458 if (_cacheStream != null) 459 writeCache(buf, offset, length); 460 } 461 else { 462 byte []buffer = _next.getBuffer(); 463 int writeLength = length; 464 465 if (bufferStart == 0 && writeLength > 0) { 466 bufferStart = bufferOffset + 8; 467 bufferOffset = bufferStart; 468 } 469 470 while (writeLength > 0) { 471 int sublen = buffer.length - bufferOffset; 472 473 if (writeLength < sublen) 474 sublen = writeLength; 475 476 System.arraycopy(buf, offset, buffer, bufferOffset, sublen); 477 478 writeLength -= sublen; 479 offset += sublen; 480 bufferOffset += sublen; 481 482 if (writeLength > 0) { 483 int delta = bufferOffset - bufferStart; 484 writeChunk(buffer, bufferStart, delta); 485 486 _isCommitted = true; 487 buffer = _next.nextBuffer(bufferOffset); 488 489 if (log.isLoggable(Level.FINE)) 490 log.fine("[" + dbgId() + "] write-chunk(" + bufferOffset + ")"); 491 492 bufferStart = _next.getBufferOffset() + 8; 493 bufferOffset = bufferStart; 494 } 495 } 496 497 _next.setBufferOffset(bufferOffset); 498 _bufferStartOffset = bufferStart; 499 } 500 } 501 502 if (! _isDisconnected) 503 _contentLength += length; 504 } catch (ClientDisconnectException e) { 505 _response.killCache(); 507 508 if (_response.isIgnoreClientDisconnect()) 509 _isDisconnected = true; 510 else { 511 throw e; 512 } 513 } 514 } 515 516 private boolean lengthException(byte []buf, int offset, int length, 517 long contentLengthHeader) 518 { 519 if (_isDisconnected || _isHead || _isClosed) { 520 } 521 else if (contentLengthHeader < _contentLength) { 522 CauchoRequest request = _response.getRequest(); 523 ServletContext app = request.getWebApp(); 524 525 Exception exn = 526 new IllegalStateException (L.l("{0}: tried to write {1} bytes with content-length {2}.", 527 request.getRequestURL(), 528 "" + (length + _contentLength), 529 "" + contentLengthHeader)); 530 531 if (app != null) 532 app.log(exn.getMessage(), exn); 533 else 534 exn.printStackTrace(); 535 536 return false; 537 } 538 539 for (int i = (int) (offset + contentLengthHeader - _contentLength); 540 i < offset + length; 541 i++) { 542 int ch = buf[i]; 543 544 if (ch != '\r' && ch != '\n' && ch != ' ' && ch != '\t') { 545 CauchoRequest request = _response.getRequest(); 546 ServletContext app = request.getWebApp(); 547 String graph = ""; 548 549 if (Character.isLetterOrDigit((char) ch)) 550 graph = "'" + (char) ch + "', "; 551 552 Exception exn = 553 new IllegalStateException (L.l("{0}: tried to write {1} bytes with content-length {2} (At {3}char={4}).", 554 request.getRequestURL(), 555 "" + (length + _contentLength), 556 "" + contentLengthHeader, 557 graph, 558 "" + ch)); 559 560 if (app != null) 561 app.log(exn.getMessage(), exn); 562 else 563 exn.printStackTrace(); 564 break; 565 } 566 } 567 568 length = (int) (contentLengthHeader - _contentLength); 569 return (length <= 0); 570 } 571 572 public void flushBuffer() 573 throws IOException 574 { 575 super.flushBuffer(); 576 577 _isCommitted = true; 578 } 579 580 583 public void flush() 584 throws IOException 585 { 586 try { 587 _disableAutoFlush = false; 588 _isCommitted = true; 589 590 if (_allowFlush && ! _isClosed) { 591 flushBuffer(); 592 593 if (_chunkedEncoding) { 594 int bufferStart = _bufferStartOffset; 595 _bufferStartOffset = 0; 596 597 if (bufferStart > 0) { 598 int bufferOffset = _next.getBufferOffset(); 599 600 if (bufferStart != bufferOffset) { 601 writeChunk(_next.getBuffer(), bufferStart, 602 bufferOffset - bufferStart); 603 } 604 else 605 _next.setBufferOffset(bufferStart - 8); 606 } 607 } 608 else { 609 _bufferStartOffset = 0; 611 } 612 613 if (_next != null) 614 _next.flush(); 615 } 616 } catch (ClientDisconnectException e) { 617 if (_response.isIgnoreClientDisconnect()) 618 _isDisconnected = true; 619 else 620 throw e; 621 } 622 } 623 624 627 public void flushByte() 628 throws IOException 629 { 630 flush(); 631 } 632 633 636 public void flushChar() 637 throws IOException 638 { 639 flush(); 640 } 641 642 645 655 656 659 public void finish() 660 throws IOException 661 { 662 boolean isClosed = _isClosed; 663 664 if (_next == null || isClosed) { 665 _isClosed = true; 666 return; 667 } 668 669 _disableAutoFlush = false; 670 671 flushCharBuffer(); 672 673 _isFinished = true; 674 _allowFlush = true; 675 676 flushBuffer(); 677 678 int bufferStart = _bufferStartOffset; 679 _bufferStartOffset = 0; 680 _isClosed = true; 681 682 if (isClosed || _next == null) { 685 return; 686 } 687 688 try { 689 if (_chunkedEncoding) { 690 int bufferOffset = _next.getBufferOffset(); 691 692 if (bufferStart > 0 && bufferOffset != bufferStart) { 693 byte []buffer = _next.getBuffer(); 694 695 writeChunk(buffer, bufferStart, bufferOffset - bufferStart); 696 } 697 698 _isCommitted = true; 699 _next.write(_tailChunked, 0, _tailChunkedLength); 700 701 if (log.isLoggable(Level.FINE)) 702 log.fine("[" + dbgId() + "] write-chunk(" + _tailChunkedLength + ")"); 703 } 704 705 CauchoRequest req = _response.getRequest(); 706 if (! req.allowKeepalive()) { 707 if (log.isLoggable(Level.FINE)) { 708 String id; 709 if (req instanceof AbstractHttpRequest) { 710 Connection conn = ((AbstractHttpRequest) req).getConnection(); 711 if (conn != null) 712 id = String.valueOf(conn.getId()); 713 else 714 id = "jni"; 715 } 716 else 717 id = "inc"; 718 log.fine("[" + id + "] close stream"); 719 } 720 721 _next.close(); 722 } 723 729 } catch (ClientDisconnectException e) { 730 if (_response.isIgnoreClientDisconnect()) 731 _isDisconnected = true; 732 else 733 throw e; 734 } 735 } 736 737 740 private void writeChunk(byte []buffer, int start, int length) 741 throws IOException 742 { 743 buffer[start - 8] = (byte) '\r'; 744 buffer[start - 7] = (byte) '\n'; 745 buffer[start - 6] = hexDigit(length >> 12); 746 buffer[start - 5] = hexDigit(length >> 8); 747 buffer[start - 4] = hexDigit(length >> 4); 748 buffer[start - 3] = hexDigit(length); 749 buffer[start - 2] = (byte) '\r'; 750 buffer[start - 1] = (byte) '\n'; 751 752 if (_cacheStream != null) 753 writeCache(buffer, start, length); 754 } 755 756 759 private static byte hexDigit(int value) 760 { 761 value &= 0xf; 762 763 if (value <= 9) 764 return (byte) ('0' + value); 765 else 766 return (byte) ('a' + value - 10); 767 } 768 769 private void writeCache(byte []buf, int offset, int length) 770 throws IOException 771 { 772 if (length == 0) 773 return; 774 775 if (_cacheMaxLength < _contentLength) { 776 _cacheStream = null; 777 _response.killCache(); 778 } 779 else { 780 _cacheStream.write(buf, offset, length); 781 } 782 } 783 784 private String dbgId() 785 { 786 Object req = _response.getRequest(); 787 788 if (req instanceof AbstractHttpRequest) { 789 Connection conn = ((AbstractHttpRequest) req).getConnection(); 790 if (conn != null) 791 return String.valueOf(conn.getId()); 792 else 793 return "jni"; 794 } 795 else 796 return "inc"; 797 } 798 799 802 public void close() 803 throws IOException 804 { 805 finish(); 806 } 807 } 808 | Popular Tags |