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         StringBuffer connection = null;
    391
    392         if (fields != null)
    393         {
    394             Iterator iter = fields.getFields();
    395
    396             while (iter.hasNext())
    397             {
    398                 HttpFields.Field field = (HttpFields.Field) iter.next();
    399
    400                 switch (field.getNameOrdinal())
    401                 {
    402                     case HttpHeaders.CONTENT_LENGTH_ORDINAL:
    403                         content_length = field;
    404                         _contentLength = field.getLongValue();
    405
    406                         if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten)
    407                             content_length = null;
    408
    409                         // write the field to the header buffer
    410                         field.put(_header);
    411                         break;
    412
    413                     case HttpHeaders.CONTENT_TYPE_ORDINAL:
    414                         if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT;
    415
    416                         // write the field to the header buffer
    417                         field.put(_header);
    418                         break;
    419
    420                     case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
    421                         if (_version == HttpVersions.HTTP_1_1_ORDINAL) transfer_encoding = field;
    422                         // Do NOT add yet!
    423                         break;
    424
    425                     case HttpHeaders.CONNECTION_ORDINAL:
    426                         if (_method!=null)
    427                             field.put(_header);
    428
    429                         int connection_value = field.getValueOrdinal();
    430                         switch (connection_value)
    431                         {
    432                             case -1:
    433                             {
    434                                 String[] values = field.getValue().split(",");
    435                                 for  (int i=0;values!=null && i<values.length;i++)
    436                                 {
    437                                     CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim());
    438
    439                                     if (cb!=null)
    440                                     {
    441                                         switch(cb.getOrdinal())
    442                                         {
    443                                             case HttpHeaderValues.CLOSE_ORDINAL:
    444                                                 close=true;
    445                                                 if (_method==null)
    446                                                     _close=true;
    447                                                 keep_alive=false;
    448                                                 if (_close && _method==null && _contentLength == HttpTokens.UNKNOWN_CONTENT)
    449                                                     _contentLength = HttpTokens.EOF_CONTENT;
    450                                                 break;
    451
    452                                             case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
    453                                                 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
    454                                                 {
    455                                                     keep_alive = true;
    456                                                     if (_method==null)
    457                                                         _close = false;
    458                                                 }
    459                                                 break;
    460
    461                                             default:
    462                                                 if (connection==null)
    463                                                     connection=new StringBuffer();
    464                                                 else
    465                                                     connection.append(',');
    466                                                 connection.append(values[i]);
    467                                         }
    468                                     }
    469                                     else
    470                                     {
    471                                         if (connection==null)
    472                                             connection=new StringBuffer();
    473                                         else
    474                                             connection.append(',');
    475                                         connection.append(values[i]);
    476                                     }
    477                                 }
    478
    479                                 break;
    480                             }
    481                             case HttpHeaderValues.CLOSE_ORDINAL:
    482                             {
    483                                 close=true;
    484                                 if (_method==null)
    485                                     _close=true;
    486                                 if (_close && _method==null && _contentLength == HttpTokens.UNKNOWN_CONTENT)
    487                                     _contentLength = HttpTokens.EOF_CONTENT;
    488                                 break;
    489                             }
    490                             case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
    491                             {
    492                                 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
    493                                 {
    494                                     keep_alive = true;
    495                                     if (_method==null)
    496                                         _close = false;
    497                                 }
    498                                 break;
    499                             }
    500                             default:
    501                             {
    502                                 if (connection==null)
    503                                     connection=new StringBuffer();
    504                                 else
    505                                     connection.append(',');
    506                                 connection.append(field.getValue());
    507                             }
    508                         }
    509
    510                         // Do NOT add yet!
    511                         break;
    512
    513                     case HttpHeaders.SERVER_ORDINAL:
    514                         if (getSendServerVersion())
    515                         {
    516                             has_server=true;
    517                             field.put(_header);
    518                         }
    519                         break;
    520
    521                     default:
    522                         // write the field to the header buffer
    523                         field.put(_header);
    524                 }
    525             }
    526         }
    527
    528         // Calculate how to end _content and connection, _content length and transfer encoding
    529         // settings.
    530         // From RFC 2616 4.4:
    531         // 1. No body for 1xx, 204, 304 & HEAD response
    532         // 2. Force _content-length?
    533         // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
    534         // 4. Content-Length
    535         // 5. multipart/byteranges
    536         // 6. close
    537         switch ((int) _contentLength)
    538         {
    539             case HttpTokens.UNKNOWN_CONTENT:
    540                 // It may be that we have no _content, or perhaps _content just has not been
    541                 // written yet?
    542
    543                 // Response known not to have a body
    544                 if (_contentWritten == 0 && _method==null && (_status < 200 || _status == 204 || _status == 304))
    545                     _contentLength = HttpTokens.NO_CONTENT;
    546                 else if (_last)
    547                 {
    548                     // we have seen all the _content there is
    549                     _contentLength = _contentWritten;
    550                     if (content_length == null)
    551                     {
    552                         // known length but not actually set.
    553                         _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER);
    554                         _header.put(HttpTokens.COLON);
    555                         _header.put((byte) ' ');
    556                         BufferUtil.putDecLong(_header, _contentLength);
    557                         _header.put(HttpTokens.CRLF);
    558                     }
    559                 }
    560                 else
    561                 {
    562                     // No idea, so we must assume that a body is coming
    563                     _contentLength = (_close || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT;
    564                     if (_method!=null && _contentLength==HttpTokens.EOF_CONTENT)
    565                     {
    566                         _contentLength=HttpTokens.NO_CONTENT;
    567                         _noContent=true;
    568                     }
    569                 }
    570                 break;
    571
    572             case HttpTokens.NO_CONTENT:
    573                 if (content_length == null && _method==null && _status >= 200 && _status != 204 && _status != 304)
    574                     _header.put(CONTENT_LENGTH_0);
    575                 break;
    576
    577             case HttpTokens.EOF_CONTENT:
    578                 _close = _method==null;
    579                 break;
    580
    581             case HttpTokens.CHUNKED_CONTENT:
    582                 break;
    583
    584             default:
    585                 // TODO - maybe allow forced chunking by setting te ???
    586                 break;
    587         }
    588
    589         // Add transfer_encoding if needed
    590         if (_contentLength == HttpTokens.CHUNKED_CONTENT)
    591         {
    592             // try to use user supplied encoding as it may have other values.
    593             if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal())
    594             {
    595                 String c = transfer_encoding.getValue();
    596                 if (c.endsWith(HttpHeaderValues.CHUNKED))
    597                     transfer_encoding.put(_header);
    598                 else
    599                     throw new IllegalArgumentException("BAD TE");
    600             }
    601             else
    602                 _header.put(TRANSFER_ENCODING_CHUNKED);
    603         }
    604
    605         // Handle connection if need be
    606         if (_contentLength==HttpTokens.EOF_CONTENT)
    607         {
    608             keep_alive=false;
    609             _close=true;
    610         }
    611
    612         if (_method==null)
    613         {
    614             if (_close && (close || _version > HttpVersions.HTTP_1_0_ORDINAL))
    615             {
    616                 _header.put(CONNECTION_CLOSE);
    617                 if (connection!=null)
    618                 {
    619                     _header.setPutIndex(_header.putIndex()-2);
    620                     _header.put((byte)',');
    621                     _header.put(connection.toString().getBytes());
    622                     _header.put(CRLF);
    623                 }
    624             }
    625             else if (keep_alive)
    626             {
    627                 _header.put(CONNECTION_KEEP_ALIVE);
    628                 if (connection!=null)
    629                 {
    630                     _header.setPutIndex(_header.putIndex()-2);
    631                     _header.put((byte)',');
    632                     _header.put(connection.toString().getBytes());
    633                     _header.put(CRLF);
    634                 }
    635             }
    636             else if (connection!=null)
    637             {
    638                 _header.put(CONNECTION_);
    639                 _header.put(connection.toString().getBytes());
    640                 _header.put(CRLF);
    641             }
    642         }
    643
    644         if (!has_server && _status>100 && getSendServerVersion())
    645             _header.put(SERVER);
    646
    647         // end the header.
    648         _header.put(HttpTokens.CRLF);
    649
    650         _state = STATE_CONTENT;
    651
    652     }
    653
    654     /* ------------------------------------------------------------ */
    655     /**
    656      * Complete the message.
    657      *
    658      * @throws IOException
    659      */
    660     public void complete() throws IOException
    661     {
    662         if (_state == STATE_END)
    663             return;
    664
    665         super.complete();
    666
    667         if (_state < STATE_FLUSHING)
    668         {
    669             _state = STATE_FLUSHING;
    670             if (_contentLength == HttpTokens.CHUNKED_CONTENT)
    671                 _needEOC = true;
    672         }
    673
    674         flush();
    675     }
    676
    677     /* ------------------------------------------------------------ */
    678     public long flush() throws IOException
    679     {
    680         try
    681         {
    682             if (_state == STATE_HEADER)
    683                 throw new IllegalStateException("State==HEADER");
    684
    685             prepareBuffers();
    686
    687             if (_endp == null)
    688             {
    689                 if (_needCRLF && _buffer!=null)
    690                     _buffer.put(HttpTokens.CRLF);
    691                 if (_needEOC && _buffer!=null && !_head)
    692                     _buffer.put(LAST_CHUNK);
    693                 _needCRLF=false;
    694                 _needEOC=false;
    695                 return 0;
    696             }
    697
    698             // Keep flushing while there is something to flush (except break below)
    699             int total= 0;
    700             long last_len = -1;
    701             Flushing: while (true)
    702             {
    703                 int len = -1;
    704                 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);
    705                 switch (to_flush)
    706                 {
    707                     case 7:
    708                         throw new IllegalStateException(); // should never happen!
    709                     case 6:
    710                         len = _endp.flush(_header, _buffer, null);
    711                         break;
    712                     case 5:
    713                         len = _endp.flush(_header, _content, null);
    714                         break;
    715                     case 4:
    716                         len = _endp.flush(_header);
    717                         break;
    718                     case 3:
    719                         throw new IllegalStateException(); // should never happen!
    720                     case 2:
    721                         len = _endp.flush(_buffer);
    722                         break;
    723                     case 1:
    724                         len = _endp.flush(_content);
    725                         break;
    726                     case 0:
    727                     {
    728                         // Nothing more we can write now.
    729                         if (_header != null)
    730                             _header.clear();
    731
    732                         _bypass = false;
    733                         _bufferChunked = false;
    734
    735                         if (_buffer != null)
    736                         {
    737                             _buffer.clear();
    738                             if (_contentLength == HttpTokens.CHUNKED_CONTENT)
    739                             {
    740                                 // reserve some space for the chunk header
    741                                 _buffer.setPutIndex(CHUNK_SPACE);
    742                                 _buffer.setGetIndex(CHUNK_SPACE);
    743
    744                                 // Special case handling for small left over buffer from
    745                                 // an addContent that caused a buffer flush.
    746                                 if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING)
    747                                 {
    748                                     _buffer.put(_content);
    749                                     _content.clear();
    750                                     _content = null;
    751                                     break Flushing;
    752                                 }
    753                             }
    754                         }
    755
    756                         // Are we completely finished for now?
    757                         if (!_needCRLF && !_needEOC && (_content == null || _content.length() == 0))
    758                         {
    759                             if (_state == STATE_FLUSHING)
    760                                 _state = STATE_END;
    761                             if (_state==STATE_END && _close && _status!=100)
    762                                 _endp.close();
    763
    764                             break Flushing;
    765                         }
    766
    767                         // Try to prepare more to write.
    768                         prepareBuffers();
    769                     }
    770                 }
    771
    772
    773                 // break If we failed to flush
    774                 if (len > 0)
    775                     total+=len;
    776                 else
    777                     break Flushing;
    778
    779                 last_len = len;
    780             }
    781
    782             return total;
    783         }
    784         catch (IOException e)
    785         {
    786             Log.ignore(e);
    787             throw (e instanceof EofException) ? e:new EofException(e);
    788         }
    789     }
    790
    791     /* ------------------------------------------------------------ */
    792     private void prepareBuffers()
    793     {
    794         // if we are not flushing an existing chunk
    795         if (!_bufferChunked)
    796         {
    797             // Refill buffer if possible
    798             if (_content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
    799             {
    800                 int len = _buffer.put(_content);
    801                 _content.skip(len);
    802                 if (_content.length() == 0)
    803                     _content = null;
    804             }
    805
    806             // Chunk buffer if need be
    807             if (_contentLength == HttpTokens.CHUNKED_CONTENT)
    808             {
    809                 int size = _buffer == null ? 0 : _buffer.length();
    810                 if (size > 0)
    811                 {
    812                     // Prepare a chunk!
    813                     _bufferChunked = true;
    814
    815                     // Did we leave space at the start of the buffer.
    816                     if (_buffer.getIndex() == CHUNK_SPACE)
    817                     {
    818                         // Oh yes, goodie! let's use it then!
    819                         _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
    820                         _buffer.setGetIndex(_buffer.getIndex() - 2);
    821                         BufferUtil.prependHexInt(_buffer, size);
    822
    823                         if (_needCRLF)
    824                         {
    825                             _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
    826                             _buffer.setGetIndex(_buffer.getIndex() - 2);
    827                             _needCRLF = false;
    828                         }
    829                     }
    830                     else
    831                     {
    832                         // No space so lets use the header buffer.
    833                         if (_needCRLF)
    834                         {
    835                             if (_header.length() > 0) throw new IllegalStateException("EOC");
    836                             _header.put(HttpTokens.CRLF);
    837                             _needCRLF = false;
    838                         }
    839                         BufferUtil.putHexInt(_header, size);
    840                         _header.put(HttpTokens.CRLF);
    841                     }
    842
    843                     // Add end chunk trailer.
    844                     if (_buffer.space() >= 2)
    845                         _buffer.put(HttpTokens.CRLF);
    846                     else
    847                         _needCRLF = true;
    848                 }
    849
    850                 // If we need EOC and everything written
    851                 if (_needEOC && (_content == null || _content.length() == 0))
    852                 {
    853                     if (_needCRLF)
    854                     {
    855                         if (_buffer == null && _header.space() >= 2)
    856                         {
    857                             _header.put(HttpTokens.CRLF);
    858                             _needCRLF = false;
    859                         }
    860                         else if (_buffer!=null && _buffer.space() >= 2)
    861                         {
    862                             _buffer.put(HttpTokens.CRLF);
    863                             _needCRLF = false;
    864                         }
    865                     }
    866
    867                     if (!_needCRLF && _needEOC)
    868                     {
    869                         if (_buffer == null && _header.space() >= LAST_CHUNK.length)
    870                         {
    871                             if (!_head)
    872                             {
    873                                 _header.put(LAST_CHUNK);
    874                                 _bufferChunked=true;
    875                             }
    876                             _needEOC = false;
    877                         }
    878                         else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length)
    879                         {
    880                             if (!_head)
    881                             {
    882                                 _buffer.put(LAST_CHUNK);
    883                                 _bufferChunked=true;
    884                             }
    885                             _needEOC = false;
    886                         }
    887                     }
    888                 }
    889             }
    890         }
    891
    892         if (_content != null && _content.length() == 0)
    893             _content = null;
    894
    895     }
    896
    897     public int getBytesBuffered()
    898     {
    899         return(_header==null?0:_header.length())+
    900         (_buffer==null?0:_buffer.length())+
    901         (_content==null?0:_content.length());
    902     }
    903
    904     public boolean isEmpty()
    905     {
    906         return (_header==null||_header.length()==0) &&
    907         (_buffer==null||_buffer.length()==0) &&
    908         (_content==null||_content.length()==0);
    909     }
    910
    911     public String toString()
    912     {
    913         return "HttpGenerator s="+_state+
    914         " h="+(_header==null?"null":(""+_header.length()))+
    915         " b="+(_buffer==null?"null":(""+_buffer.length()))+
    916         " c="+(_content==null?"null":(""+_content.length()));
    917     }
    918 }