View Javadoc

    1   //========================================================================
    2   //$Id: HttpGenerator.java,v 1.7 2005/11/25 21:17:12 gregwilkins Exp $
    3   //Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
    4   //------------------------------------------------------------------------
    5   //Licensed under the Apache License, Version 2.0 (the "License");
    6   //you may not use this file except in compliance with the License.
    7   //You may obtain a copy of the License at
    8   //http://www.apache.org/licenses/LICENSE-2.0
    9   //Unless required by applicable law or agreed to in writing, software
    10  //distributed under the License is distributed on an "AS IS" BASIS,
    11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //See the License for the specific language governing permissions and
    13  //limitations under the License.
    14  //========================================================================
    15
    16  package org.mortbay.jetty;
    17
    18  import java.io.IOException;
    19  import java.util.Iterator;
    20
    21  import org.mortbay.io.Buffer;
    22  import org.mortbay.io.BufferUtil;
    23  import org.mortbay.io.Buffers;
    24  import org.mortbay.io.EndPoint;
    25  import org.mortbay.io.Portable;
    26  import org.mortbay.io.BufferCache.CachedBuffer;
    27  import org.mortbay.log.Log;
    28  import org.mortbay.util.QuotedStringTokenizer;
    29
    30  /* ------------------------------------------------------------ */
    31  /**
    32   * HttpGenerator. Builds HTTP Messages.
    33   *
    34   * @author gregw
    35   *
    36   */
    37  public class HttpGenerator extends AbstractGenerator
    38  {
    39      // common _content
    40      private static byte[] LAST_CHUNK =
    41      { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
    42      private static byte[] CONTENT_LENGTH_0 = Portable.getBytes("Content-Length: 0\015\012");
    43      private static byte[] CONNECTION_KEEP_ALIVE = Portable.getBytes("Connection: keep-alive\015\012");
    44      private static byte[] CONNECTION_CLOSE = Portable.getBytes("Connection: close\015\012");
    45      private static byte[] CONNECTION_ = Portable.getBytes("Connection: ");
    46      private static byte[] CRLF = Portable.getBytes("\015\012");
    47      private static byte[] TRANSFER_ENCODING_CHUNKED = Portable.getBytes("Transfer-Encoding: chunked\015\012");
    48      private static byte[] SERVER = Portable.getBytes("Server: Jetty(6.0.x)\015\012");
    49
    50      // other statics
    51      private static int CHUNK_SPACE = 12;
    52
    53      public static void setServerVersion(String version)
    54      {
    55          SERVER=Portable.getBytes("Server: Jetty("+version+")\015\012");
    56      }
    57
    58      // data
    59      private boolean _bypass = false; // True if _content buffer can be written directly to endp and bypass the content buffer
    60      private boolean _needCRLF = false;
    61      private boolean _needEOC = false;
    62      private boolean _bufferChunked = false;
    63
    64
    65      /* ------------------------------------------------------------------------------- */
    66      /**
    67       * Constructor.
    68       *
    69       * @param buffers buffer pool
    70       * @param headerBufferSize Size of the buffer to allocate for HTTP header
    71       * @param contentBufferSize Size of the buffer to allocate for HTTP content
    72       */
    73      public HttpGenerator(Buffers buffers, EndPoint io, int headerBufferSize, int contentBufferSize)
    74      {
    75          super(buffers,io,headerBufferSize,contentBufferSize);
    76      }
    77
    78      /* ------------------------------------------------------------------------------- */
    79      public void reset(boolean returnBuffers)
    80      {
    81          super.reset(returnBuffers);
    82          _bypass = false;
    83          _needCRLF = false;
    84          _needEOC = false;
    85          _bufferChunked=false;
    86          _method=null;
    87          _uri=null;
    88          _noContent=false;
    89      }
    90
    91
    92
    93      /* ------------------------------------------------------------ */
    94      /**
    95       * Add content.
    96       *
    97       * @param content
    98       * @param last
    99       * @throws IllegalArgumentException if <code>content</code> is {@link Buffer#isImmutable immutable}.
    100      * @throws IllegalStateException If the request is not expecting any more content,
    101      *   or if the buffers are full and cannot be flushed.
    102      * @throws IOException if there is a problem flushing the buffers.
    103      */
    104     public void addContent(Buffer content, boolean last) throws IOException
    105     {
    106         if (_noContent)
    107             throw new IllegalStateException("NO CONTENT");
    108
    109         if (_last || _state==STATE_END)
    110         {
    111             Log.debug("Ignoring extra content {}",content);
    112             content.clear();
    113             return;
    114         }
    115         _last = last;
    116
    117         // Handle any unfinished business?
    118         if (_content!=null && _content.length()>0 || _bufferChunked)
    119         {
    120             if (!_endp.isOpen())
    121                 throw new EofException();
    122             flush();
    123             if (_content != null && _content.length()>0 || _bufferChunked)
    124                 throw new IllegalStateException("FULL");
    125         }
    126
    127         _content = content;
    128         _contentWritten += content.length();
    129
    130         // Handle the _content
    131         if (_head)
    132         {
    133             content.clear();
    134             _content=null;
    135         }
    136         else if (_endp != null && _buffer == null && content.length() > 0 && _last)
    137         {
    138             // TODO - use bypass in more cases.
    139             // Make _content a direct buffer
    140             _bypass = true;
    141         }
    142         else
    143         {
    144             // Yes - so we better check we have a buffer
    145             if (_buffer == null)
    146                 _buffer = _buffers.getBuffer(_contentBufferSize);
    147
    148             // Copy _content to buffer;
    149             int len=_buffer.put(_content);
    150             _content.skip(len);
    151             if (_content.length() == 0)
    152                 _content = null;
    153         }
    154     }
    155
    156     /* ------------------------------------------------------------ */
    157     /**
    158      * send complete response.
    159      *
    160      * @param response
    161      */
    162     public void sendResponse(Buffer response) throws IOException
    163     {
    164         if (_noContent || _state!=STATE_HEADER || _content!=null && _content.length()>0 || _bufferChunked || _head )
    165             throw new IllegalStateException();
    166
    167         _last = true;
    168
    169         _content = response;
    170         _bypass = true;
    171         _state = STATE_FLUSHING;
    172
    173         // TODO this is not exactly right, but should do.
    174         _contentLength =_contentWritten = response.length();
    175
    176     }
    177
    178     /* ------------------------------------------------------------ */
    179     /**
    180      * Add content.
    181      *
    182      * @param b byte
    183      * @return true if the buffers are full
    184      * @throws IOException
    185      */
    186     public boolean addContent(byte b) throws IOException
    187     {
    188         if (_noContent)
    189             throw new IllegalStateException("NO CONTENT");
    190
    191         if (_last || _state==STATE_END)
    192         {
    193             Log.debug("Ignoring extra content {}",new Byte(b));
    194             return false;
    195         }
    196
    197         // Handle any unfinished business?
    198         if (_content != null && _content.length()>0 || _bufferChunked)
    199         {
    200             flush();
    201             if (_content != null && _content.length()>0 || _bufferChunked)
    202                 throw new IllegalStateException("FULL");
    203         }
    204
    205         _contentWritten++;
    206
    207         // Handle the _content
    208         if (_head)
    209             return false;
    210
    211         // we better check we have a buffer
    212         if (_buffer == null)
    213             _buffer = _buffers.getBuffer(_contentBufferSize);
    214
    215         // Copy _content to buffer;
    216         _buffer.put(b);
    217
    218         return _buffer.space()<=(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0);
    219     }
    220
    221     /* ------------------------------------------------------------ */
    222     /** Prepare buffer for unchecked writes.
    223      * Prepare the generator buffer to receive unchecked writes
    224      * @return the available space in the buffer.
    225      * @throws IOException
    226      */
    227     protected int prepareUncheckedAddContent() throws IOException
    228     {
    229         if (_noContent)
    230             return -1;
    231
    232         if (_last || _state==STATE_END)
    233             return -1;
    234
    235         // Handle any unfinished business?
    236         Buffer content = _content;
    237         if (content != null && content.length()>0 || _bufferChunked)
    238         {
    239             flush();
    240             if (content != null && content.length()>0 || _bufferChunked)
    241                 throw new IllegalStateException("FULL");
    242         }
    243
    244         // we better check we have a buffer
    245         if (_buffer == null)
    246             _buffer = _buffers.getBuffer(_contentBufferSize);
    247
    248         _contentWritten-=_buffer.length();
    249
    250         // Handle the _content
    251         if (_head)
    252             return Integer.MAX_VALUE;
    253
    254         return _buffer.space()-(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0);
    255     }
    256
    257     /* ------------------------------------------------------------ */
    258     public boolean isBufferFull()
    259     {
    260         // Should we flush the buffers?
    261         boolean full = super.isBufferFull() || _bufferChunked || _bypass  || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE);
    262         return full;
    263     }
    264
    265     /* ------------------------------------------------------------ */
    266     public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
    267     {
    268         if (_state != STATE_HEADER)
    269             return;
    270
    271         // handle a reset
    272         if (_method==null && _status==0)
    273             throw new EofException();
    274
    275         if (_last && !allContentAdded)
    276             throw new IllegalStateException("last?");
    277         _last = _last | allContentAdded;
    278
    279         // get a header buffer
    280         if (_header == null)
    281             _header = _buffers.getBuffer(_headerBufferSize);
    282
    283         boolean has_server = false;
    284
    285         if (_method!=null)
    286         {
    287             _close = false;
    288             // Request
    289             if (_version == HttpVersions.HTTP_0_9_ORDINAL)
    290             {
    291                 _contentLength = HttpTokens.NO_CONTENT;
    292                 _header.put(_method);
    293                 _header.put((byte)' ');
    294                 _header.put(_uri.getBytes("utf-8")); // TODO WRONG!
    295                 _header.put(HttpTokens.CRLF);
    296                 _state = STATE_FLUSHING;
    297                 _noContent=true;
    298                 return;
    299             }
    300             else
    301             {
    302                 _header.put(_method);
    303                 _header.put((byte)' ');
    304                 _header.put(_uri.getBytes("utf-8")); // TODO WRONG!
    305                 _header.put((byte)' ');
    306                 _header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER);
    307                 _header.put(HttpTokens.CRLF);
    308             }
    309         }
    310         else
    311         {
    312             // Response
    313             if (_version == HttpVersions.HTTP_0_9_ORDINAL)
    314             {
    315                 _close = true;
    316                 _contentLength = HttpTokens.EOF_CONTENT;
    317                 _state = STATE_CONTENT;
    318                 return;
    319             }
    320             else
    321             {
    322                 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
    323                     _close = true;
    324
    325                 // add response line
    326                 Buffer line = HttpStatus.getResponseLine(_status);
    327
    328
    329                 if (line==null)
    330                 {
    331                     if (_reason==null)
    332                         _reason=getReasonBuffer(_status);
    333
    334                     _header.put(HttpVersions.HTTP_1_1_BUFFER);
    335                     _header.put((byte) ' ');
    336                     _header.put((byte) ('0' + _status / 100));
    337                     _header.put((byte) ('0' + (_status % 100) / 10));
    338                     _header.put((byte) ('0' + (_status % 10)));
    339                     _header.put((byte) ' ');
    340                     if (_reason==null)
    341                     {
    342                         _header.put((byte) ('0' + _status / 100));
    343                         _header.put((byte) ('0' + (_status % 100) / 10));
    344                         _header.put((byte) ('0' + (_status % 10)));
    345                     }
    346                     else
    347                         _header.put(_reason);
    348                     _header.put(HttpTokens.CRLF);
    349                 }
    350                 else
    351                 {
    352                     if (_reason==null)
    353                         _header.put(line);
    354                     else
    355                     {
    356                         _header.put(line.array(), 0, HttpVersions.HTTP_1_1_BUFFER.length() + 5);
    357                         _header.put(_reason);
    358                         _header.put(HttpTokens.CRLF);
    359                     }
    360                 }
    361
    362                 if (_status<200 && _status>=100 )
    363                 {
    364                     _noContent=true;
    365                     _content=null;
    366                     if (_buffer!=null)
    367                         _buffer.clear();
    368                     // end the header.
    369                     _header.put(HttpTokens.CRLF);
    370                     _state = STATE_CONTENT;
    371                     return;
    372                 }
    373
    374                 if (_status==204 || _status==304)
    375                 {
    376                     _noContent=true;
    377                     _content=null;
    378                     if (_buffer!=null)
    379                         _buffer.clear();
    380                 }
    381             }
    382         }
    383
    384         // Add headers
    385
    386         // key field values
    387         HttpFields.Field content_length = null;
    388         HttpFields.Field transfer_encoding = null;
    389         boolean keep_alive = false;
    390         boolean close=false;
    391         boolean content_type =false;
    392         StringBuffer connection = null;
    393
    394         if (fields != null)
    395         {
    396             Iterator iter = fields.getFields();
    397
    398             while (iter.hasNext())
    399             {
    400                 HttpFields.Field field = (HttpFields.Field) iter.next();
    401
    402                 switch (field.getNameOrdinal())
    403                 {
    404                     case HttpHeaders.CONTENT_LENGTH_ORDINAL:
    405                         content_length = field;
    406                         _contentLength = field.getLongValue();
    407
    408                         if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten)
    409                             content_length = null;
    410
    411                         // write the field to the header buffer
    412                         field.put(_header);
    413                         break;
    414
    415                     case HttpHeaders.CONTENT_TYPE_ORDINAL:
    416                         if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer()))
    417                             _contentLength = HttpTokens.SELF_DEFINING_CONTENT;
    418                         content_type=true;
    419                         // write the field to the header buffer
    420                         field.put(_header);
    421                         break;
    422
    423                     case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
    424                         if (_version == HttpVersions.HTTP_1_1_ORDINAL) transfer_encoding = field;
    425                         // Do NOT add yet!
    426                         break;
    427
    428                     case HttpHeaders.CONNECTION_ORDINAL:
    429                         if (_method!=null)
    430                             field.put(_header);
    431
    432                         int connection_value = field.getValueOrdinal();
    433                         switch (connection_value)
    434                         {
    435                             case -1:
    436                             {
    437
    438                                 QuotedStringTokenizer tok = new QuotedStringTokenizer(field.getValue(), ",");
    439                                 while(tok.hasMoreTokens())
    440                                 {
    441                                 	String token=tok.nextToken().trim();
    442                                     CachedBuffer cb = HttpHeaderValues.CACHE.get(token);
    443                                     if (cb!=null)
    444                                     {
    445                                         switch(cb.getOrdinal())
    446                                         {
    447                                             case HttpHeaderValues.CLOSE_ORDINAL:
    448                                                 close=true;
    449                                                 if (_method==null)
    450                                                     _close=true;
    451                                                 keep_alive=false;
    452                                                 if (_close && _method==null && _contentLength == HttpTokens.UNKNOWN_CONTENT)
    453                                                     _contentLength = HttpTokens.EOF_CONTENT;
    454                                                 break;
    455
    456                                             case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
    457                                                 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
    458                                                 {
    459                                                     keep_alive = true;
    460                                                     if (_method==null)
    461                                                         _close = false;
    462                                                 }
    463                                                 break;
    464
    465                                             default:
    466                                                 if (connection==null)
    467                                                     connection=new StringBuffer();
    468                                                 else
    469                                                     connection.append(',');
    470                                                 connection.append(token);
    471                                         }
    472                                     }
    473                                     else
    474                                     {
    475                                         if (connection==null)
    476                                             connection=new StringBuffer();
    477                                         else
    478                                             connection.append(',');
    479                                         connection.append(token);
    480                                     }
    481                                 }
    482
    483                                 break;
    484                             }
    485                             case HttpHeaderValues.CLOSE_ORDINAL:
    486                             {
    487                                 close=true;
    488                                 if (_method==null)
    489                                     _close=true;
    490                                 if (_close && _method==null && _contentLength == HttpTokens.UNKNOWN_CONTENT)
    491                                     _contentLength = HttpTokens.EOF_CONTENT;
    492                                 break;
    493                             }
    494                             case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
    495                             {
    496                                 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
    497                                 {
    498                                     keep_alive = true;
    499                                     if (_method==null)
    500                                         _close = false;
    501                                 }
    502                                 break;
    503                             }
    504                             default:
    505                             {
    506                                 if (connection==null)
    507                                     connection=new StringBuffer();
    508                                 else
    509                                     connection.append(',');
    510                                 connection.append(field.getValue());
    511                             }
    512                         }
    513
    514                         // Do NOT add yet!
    515                         break;
    516
    517                     case HttpHeaders.SERVER_ORDINAL:
    518                         if (getSendServerVersion())
    519                         {
    520                             has_server=true;
    521                             field.put(_header);
    522                         }
    523                         break;
    524
    525                     default:
    526                         // write the field to the header buffer
    527                         field.put(_header);
    528                 }
    529             }
    530         }
    531
    532         // Calculate how to end _content and connection, _content length and transfer encoding
    533         // settings.
    534         // From RFC 2616 4.4:
    535         // 1. No body for 1xx, 204, 304 & HEAD response
    536         // 2. Force _content-length?
    537         // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
    538         // 4. Content-Length
    539         // 5. multipart/byteranges
    540         // 6. close
    541         switch ((int) _contentLength)
    542         {
    543             case HttpTokens.UNKNOWN_CONTENT:
    544                 // It may be that we have no _content, or perhaps _content just has not been
    545                 // written yet?
    546
    547                 // Response known not to have a body
    548                 if (_contentWritten == 0 && _method==null && (_status < 200 || _status == 204 || _status == 304))
    549                     _contentLength = HttpTokens.NO_CONTENT;
    550                 else if (_last)
    551                 {
    552                     // we have seen all the _content there is
    553                     _contentLength = _contentWritten;
    554                     if (content_length == null && (_method==null || content_type || _contentLength>0))
    555                     {
    556                         // known length but not actually set.
    557                         _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER);
    558                         _header.put(HttpTokens.COLON);
    559                         _header.put((byte) ' ');
    560                         BufferUtil.putDecLong(_header, _contentLength);
    561                         _header.put(HttpTokens.CRLF);
    562                     }
    563                 }
    564                 else
    565                 {
    566                     // No idea, so we must assume that a body is coming
    567                     _contentLength = (_close || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT;
    568                     if (_method!=null && _contentLength==HttpTokens.EOF_CONTENT)
    569                     {
    570                         _contentLength=HttpTokens.NO_CONTENT;
    571                         _noContent=true;
    572                     }
    573                 }
    574                 break;
    575
    576             case HttpTokens.NO_CONTENT:
    577                 if (content_length == null && _method==null && _status >= 200 && _status != 204 && _status != 304)
    578                     _header.put(CONTENT_LENGTH_0);
    579                 break;
    580
    581             case HttpTokens.EOF_CONTENT:
    582                 _close = _method==null;
    583                 break;
    584
    585             case HttpTokens.CHUNKED_CONTENT:
    586                 break;
    587
    588             default:
    589                 // TODO - maybe allow forced chunking by setting te ???
    590                 break;
    591         }
    592
    593         // Add transfer_encoding if needed
    594         if (_contentLength == HttpTokens.CHUNKED_CONTENT)
    595         {
    596             // try to use user supplied encoding as it may have other values.
    597             if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal())
    598             {
    599                 String c = transfer_encoding.getValue();
    600                 if (c.endsWith(HttpHeaderValues.CHUNKED))
    601                     transfer_encoding.put(_header);
    602                 else
    603                     throw new IllegalArgumentException("BAD TE");
    604             }
    605             else
    606                 _header.put(TRANSFER_ENCODING_CHUNKED);
    607         }
    608
    609         // Handle connection if need be
    610         if (_contentLength==HttpTokens.EOF_CONTENT)
    611         {
    612             keep_alive=false;
    613             _close=true;
    614         }
    615
    616         if (_method==null)
    617         {
    618             if (_close && (close || _version > HttpVersions.HTTP_1_0_ORDINAL))
    619             {
    620                 _header.put(CONNECTION_CLOSE);
    621                 if (connection!=null)
    622                 {
    623                     _header.setPutIndex(_header.putIndex()-2);
    624                     _header.put((byte)',');
    625                     _header.put(connection.toString().getBytes());
    626                     _header.put(CRLF);
    627                 }
    628             }
    629             else if (keep_alive)
    630             {
    631                 _header.put(CONNECTION_KEEP_ALIVE);
    632                 if (connection!=null)
    633                 {
    634                     _header.setPutIndex(_header.putIndex()-2);
    635                     _header.put((byte)',');
    636                     _header.put(connection.toString().getBytes());
    637                     _header.put(CRLF);
    638                 }
    639             }
    640             else if (connection!=null)
    641             {
    642                 _header.put(CONNECTION_);
    643                 _header.put(connection.toString().getBytes());
    644                 _header.put(CRLF);
    645             }
    646         }
    647
    648         if (!has_server && _status>100 && getSendServerVersion())
    649             _header.put(SERVER);
    650
    651         // end the header.
    652         _header.put(HttpTokens.CRLF);
    653
    654         _state = STATE_CONTENT;
    655
    656     }
    657
    658     /* ------------------------------------------------------------ */
    659     /**
    660      * Complete the message.
    661      *
    662      * @throws IOException
    663      */
    664     public void complete() throws IOException
    665     {
    666         if (_state == STATE_END)
    667             return;
    668
    669         super.complete();
    670
    671         if (_state < STATE_FLUSHING)
    672         {
    673             _state = STATE_FLUSHING;
    674             if (_contentLength == HttpTokens.CHUNKED_CONTENT)
    675                 _needEOC = true;
    676         }
    677
    678         flush();
    679     }
    680
    681     /* ------------------------------------------------------------ */
    682     public long flush() throws IOException
    683     {
    684         try
    685         {
    686             if (_state == STATE_HEADER)
    687                 throw new IllegalStateException("State==HEADER");
    688
    689             prepareBuffers();
    690
    691             if (_endp == null)
    692             {
    693                 if (_needCRLF && _buffer!=null)
    694                     _buffer.put(HttpTokens.CRLF);
    695                 if (_needEOC && _buffer!=null && !_head)
    696                     _buffer.put(LAST_CHUNK);
    697                 _needCRLF=false;
    698                 _needEOC=false;
    699                 return 0;
    700             }
    701
    702             // Keep flushing while there is something to flush (except break below)
    703             int total= 0;
    704             long last_len = -1;
    705             Flushing: while (true)
    706             {
    707                 int len = -1;
    708                 int to_flush = ((_header != null && _header.length() > 0)?4:0) | ((_buffer != null && _buffer.length() > 0)?2:0) | ((_bypass && _content != null && _content.length() > 0)?1:0);
    709                 switch (to_flush)
    710                 {
    711                     case 7:
    712                         throw new IllegalStateException(); // should never happen!
    713                     case 6:
    714                         len = _endp.flush(_header, _buffer, null);
    715                         break;
    716                     case 5:
    717                         len = _endp.flush(_header, _content, null);
    718                         break;
    719                     case 4:
    720                         len = _endp.flush(_header);
    721                         break;
    722                     case 3:
    723                         throw new IllegalStateException(); // should never happen!
    724                     case 2:
    725                         len = _endp.flush(_buffer);
    726                         break;
    727                     case 1:
    728                         len = _endp.flush(_content);
    729                         break;
    730                     case 0:
    731                     {
    732                         // Nothing more we can write now.
    733                         if (_header != null)
    734                             _header.clear();
    735
    736                         _bypass = false;
    737                         _bufferChunked = false;
    738
    739                         if (_buffer != null)
    740                         {
    741                             _buffer.clear();
    742                             if (_contentLength == HttpTokens.CHUNKED_CONTENT)
    743                             {
    744                                 // reserve some space for the chunk header
    745                                 _buffer.setPutIndex(CHUNK_SPACE);
    746                                 _buffer.setGetIndex(CHUNK_SPACE);
    747
    748                                 // Special case handling for small left over buffer from
    749                                 // an addContent that caused a buffer flush.
    750                                 if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING)
    751                                 {
    752                                     _buffer.put(_content);
    753                                     _content.clear();
    754                                     _content = null;
    755                                     break Flushing;
    756                                 }
    757                             }
    758                         }
    759
    760                         // Are we completely finished for now?
    761                         if (!_needCRLF && !_needEOC && (_content == null || _content.length() == 0))
    762                         {
    763                             if (_state == STATE_FLUSHING)
    764                                 _state = STATE_END;
    765                             if (_state==STATE_END && _close && _status!=100)
    766                                 _endp.shutdownOutput();
    767
    768                             break Flushing;
    769                         }
    770
    771                         // Try to prepare more to write.
    772                         prepareBuffers();
    773                     }
    774                 }
    775
    776
    777                 // break If we failed to flush
    778                 if (len > 0)
    779                     total+=len;
    780                 else
    781                     break Flushing;
    782
    783                 last_len = len;
    784             }
    785
    786             return total;
    787         }
    788         catch (IOException e)
    789         {
    790             Log.ignore(e);
    791             throw (e instanceof EofException) ? e:new EofException(e);
    792         }
    793     }
    794
    795     /* ------------------------------------------------------------ */
    796     private void prepareBuffers()
    797     {
    798         // if we are not flushing an existing chunk
    799         if (!_bufferChunked)
    800         {
    801             // Refill buffer if possible
    802             if (_content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
    803             {
    804                 int len = _buffer.put(_content);
    805                 _content.skip(len);
    806                 if (_content.length() == 0)
    807                     _content = null;
    808             }
    809
    810             // Chunk buffer if need be
    811             if (_contentLength == HttpTokens.CHUNKED_CONTENT)
    812             {
    813                 int size = _buffer == null ? 0 : _buffer.length();
    814                 if (size > 0)
    815                 {
    816                     // Prepare a chunk!
    817                     _bufferChunked = true;
    818
    819                     // Did we leave space at the start of the buffer.
    820                     if (_buffer.getIndex() == CHUNK_SPACE)
    821                     {
    822                         // Oh yes, goodie! let's use it then!
    823                         _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
    824                         _buffer.setGetIndex(_buffer.getIndex() - 2);
    825                         BufferUtil.prependHexInt(_buffer, size);
    826
    827                         if (_needCRLF)
    828                         {
    829                             _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
    830                             _buffer.setGetIndex(_buffer.getIndex() - 2);
    831                             _needCRLF = false;
    832                         }
    833                     }
    834                     else
    835                     {
    836                         // No space so lets use the header buffer.
    837                         if (_needCRLF)
    838                         {
    839                             if (_header.length() > 0) throw new IllegalStateException("EOC");
    840                             _header.put(HttpTokens.CRLF);
    841                             _needCRLF = false;
    842                         }
    843                         BufferUtil.putHexInt(_header, size);
    844                         _header.put(HttpTokens.CRLF);
    845                     }
    846
    847                     // Add end chunk trailer.
    848                     if (_buffer.space() >= 2)
    849                         _buffer.put(HttpTokens.CRLF);
    850                     else
    851                         _needCRLF = true;
    852                 }
    853
    854                 // If we need EOC and everything written
    855                 if (_needEOC && (_content == null || _content.length() == 0))
    856                 {
    857                     if (_needCRLF)
    858                     {
    859                         if (_buffer == null && _header.space() >= 2)
    860                         {
    861                             _header.put(HttpTokens.CRLF);
    862                             _needCRLF = false;
    863                         }
    864                         else if (_buffer!=null && _buffer.space() >= 2)
    865                         {
    866                             _buffer.put(HttpTokens.CRLF);
    867                             _needCRLF = false;
    868                         }
    869                     }
    870
    871                     if (!_needCRLF && _needEOC)
    872                     {
    873                         if (_buffer == null && _header.space() >= LAST_CHUNK.length)
    874                         {
    875                             if (!_head)
    876                             {
    877                                 _header.put(LAST_CHUNK);
    878                                 _bufferChunked=true;
    879                             }
    880                             _needEOC = false;
    881                         }
    882                         else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length)
    883                         {
    884                             if (!_head)
    885                             {
    886                                 _buffer.put(LAST_CHUNK);
    887                                 _bufferChunked=true;
    888                             }
    889                             _needEOC = false;
    890                         }
    891                     }
    892                 }
    893             }
    894         }
    895
    896         if (_content != null && _content.length() == 0)
    897             _content = null;
    898
    899     }
    900
    901     public int getBytesBuffered()
    902     {
    903         return(_header==null?0:_header.length())+
    904         (_buffer==null?0:_buffer.length())+
    905         (_content==null?0:_content.length());
    906     }
    907
    908     public boolean isEmpty()
    909     {
    910         return (_header==null||_header.length()==0) &&
    911         (_buffer==null||_buffer.length()==0) &&
    912         (_content==null||_content.length()==0);
    913     }
    914
    915     public String toString()
    916     {
    917         return "HttpGenerator s="+_state+
    918         " h="+(_header==null?"null":(""+_header.length()))+
    919         " b="+(_buffer==null?"null":(""+_buffer.length()))+
    920         " c="+(_content==null?"null":(""+_content.length()));
    921     }
    922 }