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