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 19package org.eclipse.jetty.http; 20 21import java.io.IOException; 22import java.io.InterruptedIOException; 23 24import org.eclipse.jetty.io.Buffer; 25import org.eclipse.jetty.io.BufferCache.CachedBuffer; 26import org.eclipse.jetty.io.BufferUtil; 27import org.eclipse.jetty.io.Buffers; 28import org.eclipse.jetty.io.ByteArrayBuffer; 29import org.eclipse.jetty.io.EndPoint; 30import org.eclipse.jetty.io.EofException; 31import org.eclipse.jetty.util.StringUtil; 32import org.eclipse.jetty.util.log.Log; 33import org.eclipse.jetty.util.log.Logger; 34 35/* ------------------------------------------------------------ */ 36/** 37 * HttpGenerator. Builds HTTP Messages. 38 * 39 * 40 * 41 */ 42public class HttpGenerator extends AbstractGenerator 43{ 44 private static final Logger LOG = Log.getLogger(HttpGenerator.class); 45 46 // Build cache of response lines for status 47 private static class Status 48 { 49 Buffer _reason; 50 Buffer _schemeCode; 51 Buffer _responseLine; 52 } 53 private static final Status[] __status = new Status[HttpStatus.MAX_CODE+1]; 54 static 55 { 56 int versionLength=HttpVersions.HTTP_1_1_BUFFER.length(); 57 58 for (int i=0;i<__status.length;i++) 59 { 60 HttpStatus.Code code = HttpStatus.getCode(i); 61 if (code==null) 62 continue; 63 String reason=code.getMessage(); 64 byte[] bytes=new byte[versionLength+5+reason.length()+2]; 65 HttpVersions.HTTP_1_1_BUFFER.peek(0,bytes, 0, versionLength); 66 bytes[versionLength+0]=' '; 67 bytes[versionLength+1]=(byte)('0'+i/100); 68 bytes[versionLength+2]=(byte)('0'+(i%100)/10); 69 bytes[versionLength+3]=(byte)('0'+(i%10)); 70 bytes[versionLength+4]=' '; 71 for (int j=0;j<reason.length();j++) 72 bytes[versionLength+5+j]=(byte)reason.charAt(j); 73 bytes[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN; 74 bytes[versionLength+6+reason.length()]=HttpTokens.LINE_FEED; 75 76 __status[i] = new Status(); 77 __status[i]._reason=new ByteArrayBuffer(bytes,versionLength+5,bytes.length-versionLength-7,Buffer.IMMUTABLE); 78 __status[i]._schemeCode=new ByteArrayBuffer(bytes,0,versionLength+5,Buffer.IMMUTABLE); 79 __status[i]._responseLine=new ByteArrayBuffer(bytes,0,bytes.length,Buffer.IMMUTABLE); 80 } 81 } 82 83 /* ------------------------------------------------------------------------------- */ 84 public static Buffer getReasonBuffer(int code) 85 { 86 Status status = code<__status.length?__status[code]:null; 87 if (status!=null) 88 return status._reason; 89 return null; 90 } 91 92 93 // common _content 94 private static final byte[] LAST_CHUNK = 95 { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'}; 96 private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012"); 97 private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012"); 98 private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012"); 99 private static final byte[] CONNECTION_ = StringUtil.getBytes("Connection: "); 100 private static final byte[] CRLF = StringUtil.getBytes("\015\012"); 101 private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012"); 102 private static byte[] SERVER = StringUtil.getBytes("Server: Jetty(7.0.x)\015\012"); 103 104 // other statics 105 private static final int CHUNK_SPACE = 12; 106 107 public static void setServerVersion(String version) 108 { 109 SERVER=StringUtil.getBytes("Server: Jetty("+version+")\015\012"); 110 } 111 112 // data 113 protected boolean _bypass = false; // True if _content buffer can be written directly to endp and bypass the content buffer 114 private boolean _needCRLF = false; 115 private boolean _needEOC = false; 116 private boolean _bufferChunked = false; 117 118 119 /* ------------------------------------------------------------------------------- */ 120 /** 121 * Constructor. 122 * 123 * @param buffers buffer pool 124 * @param io the end point to use 125 */ 126 public HttpGenerator(Buffers buffers, EndPoint io) 127 { 128 super(buffers,io); 129 } 130 131 /* ------------------------------------------------------------------------------- */ 132 @Override 133 public void reset() 134 { 135 if (_persistent!=null && !_persistent && _endp!=null && !_endp.isOutputShutdown()) 136 { 137 try 138 { 139 _endp.shutdownOutput(); 140 } 141 catch(IOException e) 142 { 143 LOG.ignore(e); 144 } 145 } 146 super.reset(); 147 if (_buffer!=null) 148 _buffer.clear(); 149 if (_header!=null) 150 _header.clear(); 151 if (_content!=null) 152 _content=null; 153 _bypass = false; 154 _needCRLF = false; 155 _needEOC = false; 156 _bufferChunked=false; 157 _method=null; 158 _uri=null; 159 _noContent=false; 160 } 161 162 /* ------------------------------------------------------------ */ 163 /** 164 * Add content. 165 * 166 * @param content 167 * @param last 168 * @throws IllegalArgumentException if <code>content</code> is {@link Buffer#isImmutable immutable}. 169 * @throws IllegalStateException If the request is not expecting any more content, 170 * or if the buffers are full and cannot be flushed. 171 * @throws IOException if there is a problem flushing the buffers. 172 */ 173 public void addContent(Buffer content, boolean last) throws IOException 174 { 175 if (_noContent) 176 throw new IllegalStateException("NO CONTENT"); 177 178 if (_last || _state==STATE_END) 179 { 180 LOG.warn("Ignoring extra content {}",content); 181 content.clear(); 182 return; 183 } 184 _last = last; 185 186 // Handle any unfinished business? 187 if (_content!=null && _content.length()>0 || _bufferChunked) 188 { 189 if (_endp.isOutputShutdown()) 190 throw new EofException(); 191 flushBuffer(); 192 if (_content != null && _content.length()>0) 193 { 194 if (_bufferChunked) 195 { 196 Buffer nc=_buffers.getBuffer(_content.length()+CHUNK_SPACE+content.length()); 197 nc.put(_content); 198 nc.put(HttpTokens.CRLF); 199 BufferUtil.putHexInt(nc, content.length()); 200 nc.put(HttpTokens.CRLF); 201 nc.put(content); 202 content=nc; 203 } 204 else 205 { 206 Buffer nc=_buffers.getBuffer(_content.length()+content.length()); 207 nc.put(_content); 208 nc.put(content); 209 content=nc; 210 } 211 } 212 } 213 214 _content = content; 215 _contentWritten += content.length(); 216 217 // Handle the _content 218 if (_head) 219 { 220 content.clear(); 221 _content=null; 222 } 223 else if (_endp != null && (_buffer==null || _buffer.length()==0) && _content.length() > 0 && (_last || isCommitted() && _content.length()>1024)) 224 { 225 _bypass = true; 226 } 227 else if (!_bufferChunked) 228 { 229 // Yes - so we better check we have a buffer 230 if (_buffer == null) 231 _buffer = _buffers.getBuffer(); 232 233 // Copy _content to buffer; 234 int len=_buffer.put(_content); 235 _content.skip(len); 236 if (_content.length() == 0) 237 _content = null; 238 } 239 } 240 241 /* ------------------------------------------------------------ */ 242 /** 243 * send complete response. 244 * 245 * @param response 246 */ 247 public void sendResponse(Buffer response) throws IOException 248 { 249 if (_noContent || _state!=STATE_HEADER || _content!=null && _content.length()>0 || _bufferChunked || _head ) 250 throw new IllegalStateException(); 251 252 _last = true; 253 254 _content = response; 255 _bypass = true; 256 _state = STATE_FLUSHING; 257 258 // TODO this is not exactly right, but should do. 259 _contentLength =_contentWritten = response.length(); 260 261 } 262 263 /* ------------------------------------------------------------ */ 264 /** Prepare buffer for unchecked writes. 265 * Prepare the generator buffer to receive unchecked writes 266 * @return the available space in the buffer. 267 * @throws IOException 268 */ 269 @Override 270 public int prepareUncheckedAddContent() throws IOException 271 { 272 if (_noContent) 273 return -1; 274 275 if (_last || _state==STATE_END) 276 return -1; 277 278 // Handle any unfinished business? 279 Buffer content = _content; 280 if (content != null && content.length()>0 || _bufferChunked) 281 { 282 flushBuffer(); 283 if (content != null && content.length()>0 || _bufferChunked) 284 throw new IllegalStateException("FULL"); 285 } 286 287 // we better check we have a buffer 288 if (_buffer == null) 289 _buffer = _buffers.getBuffer(); 290 291 _contentWritten-=_buffer.length(); 292 293 // Handle the _content 294 if (_head) 295 return Integer.MAX_VALUE; 296 297 return _buffer.space()-(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0); 298 } 299 300 /* ------------------------------------------------------------ */ 301 @Override 302 public boolean isBufferFull() 303 { 304 // Should we flush the buffers? 305 return super.isBufferFull() || _bufferChunked || _bypass || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE); 306 } 307 308 /* ------------------------------------------------------------ */ 309 public void send1xx(int code) throws IOException 310 { 311 if (_state != STATE_HEADER) 312 return; 313 314 if (code<100||code>199) 315 throw new IllegalArgumentException("!1xx"); 316 Status status=__status[code]; 317 if (status==null) 318 throw new IllegalArgumentException(code+"?"); 319 320 // get a header buffer 321 if (_header == null) 322 _header = _buffers.getHeader(); 323 324 _header.put(status._responseLine); 325 _header.put(HttpTokens.CRLF); 326 327 try 328 { 329 // nasty semi busy flush! 330 while(_header.length()>0) 331 { 332 int len = _endp.flush(_header); 333 if (len<0) 334 throw new EofException(); 335 if (len==0) 336 Thread.sleep(100); 337 } 338 } 339 catch(InterruptedException e) 340 { 341 LOG.debug(e); 342 throw new InterruptedIOException(e.toString()); 343 } 344 } 345 346 /* ------------------------------------------------------------ */ 347 @Override 348 public boolean isRequest() 349 { 350 return _method!=null; 351 } 352 353 /* ------------------------------------------------------------ */ 354 @Override 355 public boolean isResponse() 356 { 357 return _method==null; 358 } 359 360 /* ------------------------------------------------------------ */ 361 @Override 362 public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException 363 { 364 if (_state != STATE_HEADER) 365 return; 366 367 // handle a reset 368 if (isResponse() && _status==0) 369 throw new EofException(); 370 371 if (_last && !allContentAdded) 372 throw new IllegalStateException("last?"); 373 _last = _last | allContentAdded; 374 375 // get a header buffer 376 if (_header == null) 377 _header = _buffers.getHeader(); 378 379 boolean has_server = false; 380 381 try 382 { 383 if (isRequest()) 384 { 385 _persistent=true; 386 387 if (_version == HttpVersions.HTTP_0_9_ORDINAL) 388 { 389 _contentLength = HttpTokens.NO_CONTENT; 390 _header.put(_method); 391 _header.put((byte)' '); 392 _header.put(_uri.getBytes("UTF-8")); // TODO check 393 _header.put(HttpTokens.CRLF); 394 _state = STATE_FLUSHING; 395 _noContent=true; 396 return; 397 } 398 else 399 { 400 _header.put(_method); 401 _header.put((byte)' '); 402 _header.put(_uri.getBytes("UTF-8")); // TODO check 403 _header.put((byte)' '); 404 _header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER); 405 _header.put(HttpTokens.CRLF); 406 } 407 } 408 else 409 { 410 // Responses 411 if (_version == HttpVersions.HTTP_0_9_ORDINAL) 412 { 413 _persistent = false; 414 _contentLength = HttpTokens.EOF_CONTENT; 415 _state = STATE_CONTENT; 416 return; 417 } 418 else 419 { 420 if (_persistent==null) 421 _persistent= (_version > HttpVersions.HTTP_1_0_ORDINAL); 422 423 // add response line 424 Status status = _status<__status.length?__status[_status]:null; 425 426 if (status==null) 427 { 428 _header.put(HttpVersions.HTTP_1_1_BUFFER); 429 _header.put((byte) ' '); 430 _header.put((byte) ('0' + _status / 100)); 431 _header.put((byte) ('0' + (_status % 100) / 10)); 432 _header.put((byte) ('0' + (_status % 10))); 433 _header.put((byte) ' '); 434 if (_reason==null) 435 { 436 _header.put((byte) ('0' + _status / 100)); 437 _header.put((byte) ('0' + (_status % 100) / 10)); 438 _header.put((byte) ('0' + (_status % 10))); 439 } 440 else 441 _header.put(_reason); 442 _header.put(HttpTokens.CRLF); 443 } 444 else 445 { 446 if (_reason==null) 447 _header.put(status._responseLine); 448 else 449 { 450 _header.put(status._schemeCode); 451 _header.put(_reason); 452 _header.put(HttpTokens.CRLF); 453 } 454 } 455 456 if (_status<200 && _status>=100 ) 457 { 458 _noContent=true; 459 _content=null; 460 if (_buffer!=null) 461 _buffer.clear(); 462 // end the header. 463 464 if (_status!=101 ) 465 { 466 _header.put(HttpTokens.CRLF); 467 _state = STATE_CONTENT; 468 return; 469 } 470 } 471 else if (_status==204 || _status==304) 472 { 473 _noContent=true; 474 _content=null; 475 if (_buffer!=null) 476 _buffer.clear(); 477 } 478 } 479 } 480 481 // Add headers 482 if (_status>=200 && _date!=null) 483 { 484 _header.put(HttpHeaders.DATE_BUFFER); 485 _header.put((byte)':'); 486 _header.put((byte)' '); 487 _header.put(_date); 488 _header.put(CRLF); 489 } 490 491 // key field values 492 HttpFields.Field content_length = null; 493 HttpFields.Field transfer_encoding = null; 494 boolean keep_alive = false; 495 boolean close=false; 496 boolean content_type=false; 497 StringBuilder connection = null; 498 499 if (fields != null) 500 { 501 int s=fields.size(); 502 for (int f=0;f<s;f++) 503 { 504 HttpFields.Field field = fields.getField(f); 505 if (field==null) 506 continue; 507 508 switch (field.getNameOrdinal()) 509 { 510 case HttpHeaders.CONTENT_LENGTH_ORDINAL: 511 content_length = field; 512 _contentLength = field.getLongValue(); 513 514 if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten) 515 content_length = null; 516 517 // write the field to the header buffer 518 field.putTo(_header); 519 break; 520 521 case HttpHeaders.CONTENT_TYPE_ORDINAL: 522 if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT; 523 524 // write the field to the header buffer 525 content_type=true; 526 field.putTo(_header); 527 break; 528 529 case HttpHeaders.TRANSFER_ENCODING_ORDINAL: 530 if (_version == HttpVersions.HTTP_1_1_ORDINAL) 531 transfer_encoding = field; 532 // Do NOT add yet! 533 break; 534 535 case HttpHeaders.CONNECTION_ORDINAL: 536 if (isRequest()) 537 field.putTo(_header); 538 539 int connection_value = field.getValueOrdinal(); 540 switch (connection_value) 541 { 542 case -1: 543 { 544 String[] values = field.getValue().split(","); 545 for (int i=0;values!=null && i<values.length;i++) 546 { 547 CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim()); 548 549 if (cb!=null) 550 { 551 switch(cb.getOrdinal()) 552 { 553 case HttpHeaderValues.CLOSE_ORDINAL: 554 close=true; 555 if (isResponse()) 556 _persistent=false; 557 keep_alive=false; 558 if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT) 559 _contentLength = HttpTokens.EOF_CONTENT; 560 break; 561 562 case HttpHeaderValues.KEEP_ALIVE_ORDINAL: 563 if (_version == HttpVersions.HTTP_1_0_ORDINAL) 564 { 565 keep_alive = true; 566 if (isResponse()) 567 _persistent = true; 568 } 569 break; 570 571 default: 572 if (connection==null) 573 connection=new StringBuilder(); 574 else 575 connection.append(','); 576 connection.append(values[i]); 577 } 578 } 579 else 580 { 581 if (connection==null) 582 connection=new StringBuilder(); 583 else 584 connection.append(','); 585 connection.append(values[i]); 586 } 587 } 588 589 break; 590 } 591 case HttpHeaderValues.UPGRADE_ORDINAL: 592 { 593 // special case for websocket connection ordering 594 if (isResponse()) 595 { 596 field.putTo(_header); 597 continue; 598 } 599 } 600 case HttpHeaderValues.CLOSE_ORDINAL: 601 { 602 close=true; 603 if (isResponse()) 604 _persistent=false; 605 if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT) 606 _contentLength = HttpTokens.EOF_CONTENT; 607 break; 608 } 609 case HttpHeaderValues.KEEP_ALIVE_ORDINAL: 610 { 611 if (_version == HttpVersions.HTTP_1_0_ORDINAL) 612 { 613 keep_alive = true; 614 if (isResponse()) 615 _persistent=true; 616 } 617 break; 618 } 619 default: 620 { 621 if (connection==null) 622 connection=new StringBuilder(); 623 else 624 connection.append(','); 625 connection.append(field.getValue()); 626 } 627 } 628 629 // Do NOT add yet! 630 break; 631 632 case HttpHeaders.SERVER_ORDINAL: 633 if (getSendServerVersion()) 634 { 635 has_server=true; 636 field.putTo(_header); 637 } 638 break; 639 640 default: 641 // write the field to the header buffer 642 field.putTo(_header); 643 } 644 } 645 } 646 647 // Calculate how to end _content and connection, _content length and transfer encoding 648 // settings. 649 // From RFC 2616 4.4: 650 // 1. No body for 1xx, 204, 304 & HEAD response 651 // 2. Force _content-length? 652 // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk 653 // 4. Content-Length 654 // 5. multipart/byteranges 655 // 6. close 656 switch ((int) _contentLength) 657 { 658 case HttpTokens.UNKNOWN_CONTENT: 659 // It may be that we have no _content, or perhaps _content just has not been 660 // written yet? 661 662 // Response known not to have a body 663 if (_contentWritten == 0 && isResponse() && (_status < 200 || _status == 204 || _status == 304)) 664 _contentLength = HttpTokens.NO_CONTENT; 665 else if (_last) 666 { 667 // we have seen all the _content there is 668 _contentLength = _contentWritten; 669 if (content_length == null && (isResponse() || _contentLength>0 || content_type ) && !_noContent) 670 { 671 // known length but not actually set. 672 _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER); 673 _header.put(HttpTokens.COLON); 674 _header.put((byte) ' '); 675 BufferUtil.putDecLong(_header, _contentLength); 676 _header.put(HttpTokens.CRLF); 677 } 678 } 679 else 680 { 681 // No idea, so we must assume that a body is coming 682 _contentLength = (!_persistent || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT; 683 if (isRequest() && _contentLength==HttpTokens.EOF_CONTENT) 684 { 685 _contentLength=HttpTokens.NO_CONTENT; 686 _noContent=true; 687 } 688 } 689 break; 690 691 case HttpTokens.NO_CONTENT: 692 if (content_length == null && isResponse() && _status >= 200 && _status != 204 && _status != 304) 693 _header.put(CONTENT_LENGTH_0); 694 break; 695 696 case HttpTokens.EOF_CONTENT: 697 _persistent = isRequest(); 698 break; 699 700 case HttpTokens.CHUNKED_CONTENT: 701 break; 702 703 default: 704 // TODO - maybe allow forced chunking by setting te ??? 705 break; 706 } 707 708 // Add transfer_encoding if needed 709 if (_contentLength == HttpTokens.CHUNKED_CONTENT) 710 { 711 // try to use user supplied encoding as it may have other values. 712 if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal()) 713 { 714 String c = transfer_encoding.getValue(); 715 if (c.endsWith(HttpHeaderValues.CHUNKED)) 716 transfer_encoding.putTo(_header); 717 else 718 throw new IllegalArgumentException("BAD TE"); 719 } 720 else 721 _header.put(TRANSFER_ENCODING_CHUNKED); 722 } 723 724 // Handle connection if need be 725 if (_contentLength==HttpTokens.EOF_CONTENT) 726 { 727 keep_alive=false; 728 _persistent=false; 729 } 730 731 if (isResponse()) 732 { 733 if (!_persistent && (close || _version > HttpVersions.HTTP_1_0_ORDINAL)) 734 { 735 _header.put(CONNECTION_CLOSE); 736 if (connection!=null) 737 { 738 _header.setPutIndex(_header.putIndex()-2); 739 _header.put((byte)','); 740 _header.put(connection.toString().getBytes()); 741 _header.put(CRLF); 742 } 743 } 744 else if (keep_alive) 745 { 746 _header.put(CONNECTION_KEEP_ALIVE); 747 if (connection!=null) 748 { 749 _header.setPutIndex(_header.putIndex()-2); 750 _header.put((byte)','); 751 _header.put(connection.toString().getBytes()); 752 _header.put(CRLF); 753 } 754 } 755 else if (connection!=null) 756 { 757 _header.put(CONNECTION_); 758 _header.put(connection.toString().getBytes()); 759 _header.put(CRLF); 760 } 761 } 762 763 if (!has_server && _status>199 && getSendServerVersion()) 764 _header.put(SERVER); 765 766 // end the header. 767 _header.put(HttpTokens.CRLF); 768 _state = STATE_CONTENT; 769 770 } 771 catch(ArrayIndexOutOfBoundsException e) 772 { 773 throw new RuntimeException("Header>"+_header.capacity(),e); 774 } 775 } 776 777 /* ------------------------------------------------------------ */ 778 /** 779 * Complete the message. 780 * 781 * @throws IOException 782 */ 783 @Override 784 public void complete() throws IOException 785 { 786 if (_state == STATE_END) 787 return; 788 789 super.complete(); 790 791 if (_state < STATE_FLUSHING) 792 { 793 _state = STATE_FLUSHING; 794 if (_contentLength == HttpTokens.CHUNKED_CONTENT) 795 _needEOC = true; 796 } 797 798 flushBuffer(); 799 } 800 801 /* ------------------------------------------------------------ */ 802 @Override 803 public int flushBuffer() throws IOException 804 { 805 try 806 { 807 808 if (_state == STATE_HEADER) 809 throw new IllegalStateException("State==HEADER"); 810 811 prepareBuffers(); 812 813 if (_endp == null) 814 { 815 if (_needCRLF && _buffer!=null) 816 _buffer.put(HttpTokens.CRLF); 817 if (_needEOC && _buffer!=null && !_head) 818 _buffer.put(LAST_CHUNK); 819 _needCRLF=false; 820 _needEOC=false; 821 return 0; 822 } 823 824 int total= 0; 825 826 int len = -1; 827 int to_flush = flushMask(); 828 int last_flush; 829 830 do 831 { 832 last_flush=to_flush; 833 switch (to_flush) 834 { 835 case 7: 836 throw new IllegalStateException(); // should never happen! 837 case 6: 838 len = _endp.flush(_header, _buffer, null); 839 break; 840 case 5: 841 len = _endp.flush(_header, _content, null); 842 break; 843 case 4: 844 len = _endp.flush(_header); 845 break; 846 case 3: 847 len = _endp.flush(_buffer, _content, null); 848 break; 849 case 2: 850 len = _endp.flush(_buffer); 851 break; 852 case 1: 853 len = _endp.flush(_content); 854 break; 855 case 0: 856 { 857 len=0; 858 // Nothing more we can write now. 859 if (_header != null) 860 _header.clear(); 861 862 _bypass = false; 863 _bufferChunked = false; 864 865 if (_buffer != null) 866 { 867 _buffer.clear(); 868 if (_contentLength == HttpTokens.CHUNKED_CONTENT) 869 { 870 // reserve some space for the chunk header 871 _buffer.setPutIndex(CHUNK_SPACE); 872 _buffer.setGetIndex(CHUNK_SPACE); 873 874 // Special case handling for small left over buffer from 875 // an addContent that caused a buffer flush. 876 if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING) 877 { 878 _buffer.put(_content); 879 _content.clear(); 880 _content=null; 881 } 882 } 883 } 884 885 // Are we completely finished for now? 886 if (!_needCRLF && !_needEOC && (_content==null || _content.length()==0)) 887 { 888 if (_state == STATE_FLUSHING) 889 _state = STATE_END; 890 891 if (_state==STATE_END && _persistent != null && !_persistent && _status!=100 && _method==null) 892 _endp.shutdownOutput(); 893 } 894 else 895 // Try to prepare more to write. 896 prepareBuffers(); 897 } 898 899 } 900 901 if (len > 0) 902 total+=len; 903 904 to_flush = flushMask(); 905 } 906 // loop while progress is being made (OR we have prepared some buffers that might make progress) 907 while (len>0 || (to_flush!=0 && last_flush==0)); 908 909 return total; 910 } 911 catch (IOException e) 912 { 913 LOG.ignore(e); 914 throw (e instanceof EofException) ? e:new EofException(e); 915 } 916 } 917 918 /* ------------------------------------------------------------ */ 919 private int flushMask() 920 { 921 return ((_header != null && _header.length() > 0)?4:0) 922 | ((_buffer != null && _buffer.length() > 0)?2:0) 923 | ((_bypass && _content != null && _content.length() > 0)?1:0); 924 } 925 926 /* ------------------------------------------------------------ */ 927 private void prepareBuffers() 928 { 929 // if we are not flushing an existing chunk 930 if (!_bufferChunked) 931 { 932 // Refill buffer if possible 933 if (!_bypass && _content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0) 934 { 935 int len = _buffer.put(_content); 936 _content.skip(len); 937 if (_content.length() == 0) 938 _content = null; 939 } 940 941 // Chunk buffer if need be 942 if (_contentLength == HttpTokens.CHUNKED_CONTENT) 943 { 944 if (_bypass && (_buffer==null||_buffer.length()==0) && _content!=null) 945 { 946 // this is a bypass write 947 int size = _content.length(); 948 _bufferChunked = true; 949 950 if (_header == null) 951 _header = _buffers.getHeader(); 952 953 // if we need CRLF add this to header 954 if (_needCRLF) 955 { 956 if (_header.length() > 0) throw new IllegalStateException("EOC"); 957 _header.put(HttpTokens.CRLF); 958 _needCRLF = false; 959 } 960 // Add the chunk size to the header 961 BufferUtil.putHexInt(_header, size); 962 _header.put(HttpTokens.CRLF); 963 964 // Need a CRLF after the content 965 _needCRLF=true; 966 } 967 else if (_buffer!=null) 968 { 969 int size = _buffer.length(); 970 if (size > 0) 971 { 972 // Prepare a chunk! 973 _bufferChunked = true; 974 975 // Did we leave space at the start of the buffer. 976 //noinspection ConstantConditions 977 if (_buffer.getIndex() == CHUNK_SPACE) 978 { 979 // Oh yes, goodie! let's use it then! 980 _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2); 981 _buffer.setGetIndex(_buffer.getIndex() - 2); 982 BufferUtil.prependHexInt(_buffer, size); 983 984 if (_needCRLF) 985 { 986 _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2); 987 _buffer.setGetIndex(_buffer.getIndex() - 2); 988 _needCRLF = false; 989 } 990 } 991 else 992 { 993 // No space so lets use a header buffer. 994 if (_header == null) 995 _header = _buffers.getHeader(); 996 997 if (_needCRLF) 998 { 999 if (_header.length() > 0) throw new IllegalStateException("EOC"); 1000 _header.put(HttpTokens.CRLF); 1001 _needCRLF = false; 1002 } 1003 BufferUtil.putHexInt(_header, size); 1004 _header.put(HttpTokens.CRLF); 1005 } 1006 1007 // Add end chunk trailer. 1008 if (_buffer.space() >= 2) 1009 _buffer.put(HttpTokens.CRLF); 1010 else 1011 _needCRLF = true; 1012 } 1013 } 1014 1015 // If we need EOC and everything written 1016 if (_needEOC && (_content == null || _content.length() == 0)) 1017 { 1018 if (_header == null && _buffer == null) 1019 _header = _buffers.getHeader(); 1020 1021 if (_needCRLF) 1022 { 1023 if (_buffer == null && _header != null && _header.space() >= HttpTokens.CRLF.length) 1024 { 1025 _header.put(HttpTokens.CRLF); 1026 _needCRLF = false; 1027 } 1028 else if (_buffer!=null && _buffer.space() >= HttpTokens.CRLF.length) 1029 { 1030 _buffer.put(HttpTokens.CRLF); 1031 _needCRLF = false; 1032 } 1033 } 1034 1035 if (!_needCRLF && _needEOC) 1036 { 1037 if (_buffer == null && _header != null && _header.space() >= LAST_CHUNK.length) 1038 { 1039 if (!_head) 1040 { 1041 _header.put(LAST_CHUNK); 1042 _bufferChunked=true; 1043 } 1044 _needEOC = false; 1045 } 1046 else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length) 1047 { 1048 if (!_head) 1049 { 1050 _buffer.put(LAST_CHUNK); 1051 _bufferChunked=true; 1052 } 1053 _needEOC = false; 1054 } 1055 } 1056 } 1057 } 1058 } 1059 1060 if (_content != null && _content.length() == 0) 1061 _content = null; 1062 1063 } 1064 1065 public int getBytesBuffered() 1066 { 1067 return(_header==null?0:_header.length())+ 1068 (_buffer==null?0:_buffer.length())+ 1069 (_content==null?0:_content.length()); 1070 } 1071 1072 public boolean isEmpty() 1073 { 1074 return (_header==null||_header.length()==0) && 1075 (_buffer==null||_buffer.length()==0) && 1076 (_content==null||_content.length()==0); 1077 } 1078 1079 @Override 1080 public String toString() 1081 { 1082 Buffer header=_header; 1083 Buffer buffer=_buffer; 1084 Buffer content=_content; 1085 return String.format("%s{s=%d,h=%d,b=%d,c=%d}", 1086 getClass().getSimpleName(), 1087 _state, 1088 header == null ? -1 : header.length(), 1089 buffer == null ? -1 : buffer.length(), 1090 content == null ? -1 : content.length()); 1091 } 1092} 1093