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 }