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 }