View Javadoc

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