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 final 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 final byte[] LAST_CHUNK =
86      { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
87      private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
88      private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
89      private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
90      private static final byte[] CONNECTION_ = StringUtil.getBytes("Connection: ");
91      private static final byte[] CRLF = StringUtil.getBytes("\015\012");
92      private static final 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 final 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)
119     {
120         super(buffers,io);
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();
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 {}",Byte.valueOf(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();
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();
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         return super.isBufferFull() || _bufferChunked || _bypass  || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE);
307     }
308     
309     /* ------------------------------------------------------------ */
310     public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
311     {
312         if (_state != STATE_HEADER) 
313             return;
314         
315         // handle a reset 
316         if (_method==null && _status==0)
317             throw new EofException();
318 
319         if (_last && !allContentAdded) 
320             throw new IllegalStateException("last?");
321         _last = _last | allContentAdded;
322 
323         // get a header buffer
324         if (_header == null) 
325             _header = _buffers.getHeader();
326         
327         boolean has_server = false;
328         
329         if (_method!=null)
330         {
331             _close = false;
332             // Request
333             if (_version == HttpVersions.HTTP_0_9_ORDINAL)
334             {
335                 _contentLength = HttpTokens.NO_CONTENT;
336                 _header.put(_method);
337                 _header.put((byte)' ');
338                 _header.put(_uri.getBytes("utf-8")); // TODO WRONG!
339                 _header.put(HttpTokens.CRLF);
340                 _state = STATE_FLUSHING;
341                 _noContent=true;
342                 return;
343             }
344             else
345             {
346                 _header.put(_method);
347                 _header.put((byte)' ');
348                 _header.put(_uri.getBytes("utf-8")); // TODO WRONG!
349                 _header.put((byte)' ');
350                 _header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER);
351                 _header.put(HttpTokens.CRLF);
352             }
353         }
354         else
355         {
356             // Response
357             if (_version == HttpVersions.HTTP_0_9_ORDINAL)
358             {
359                 _close = true;
360                 _contentLength = HttpTokens.EOF_CONTENT;
361                 _state = STATE_CONTENT;
362                 return;
363             }
364             else
365             {
366                 if (_version == HttpVersions.HTTP_1_0_ORDINAL) 
367                     _close = true;
368 
369                 // add response line
370                 Status status = _status<__status.length?__status[_status]:null;
371                 
372                 if (status==null)
373                 {
374                     _header.put(HttpVersions.HTTP_1_1_BUFFER);
375                     _header.put((byte) ' ');
376                     _header.put((byte) ('0' + _status / 100));
377                     _header.put((byte) ('0' + (_status % 100) / 10));
378                     _header.put((byte) ('0' + (_status % 10)));
379                     _header.put((byte) ' ');
380                     if (_reason==null)
381                     {
382                         _header.put((byte) ('0' + _status / 100));
383                         _header.put((byte) ('0' + (_status % 100) / 10));
384                         _header.put((byte) ('0' + (_status % 10)));
385                     }
386                     else
387                         _header.put(_reason);
388                     _header.put(HttpTokens.CRLF);
389                 }
390                 else
391                 {
392                     if (_reason==null)
393                         _header.put(status._responseLine);
394                     else
395                     {
396                         _header.put(status._schemeCode);
397                         _header.put(_reason);
398                         _header.put(HttpTokens.CRLF);
399                     }
400                 }
401 
402                 if (_status<200 && _status>=100 )
403                 {
404                     _noContent=true;
405                     _content=null;
406                     if (_buffer!=null)
407                         _buffer.clear();
408                     // end the header.
409                     _header.put(HttpTokens.CRLF);
410                     _state = STATE_CONTENT;
411                     return;
412                 }
413 
414                 if (_status==204 || _status==304)
415                 {
416                     _noContent=true;
417                     _content=null;
418                     if (_buffer!=null)
419                         _buffer.clear();
420                 }
421             }
422         }
423         
424         // Add headers
425 
426         // key field values
427         HttpFields.Field content_length = null;
428         HttpFields.Field transfer_encoding = null;
429         boolean keep_alive = false;
430         boolean close=false;
431         StringBuilder connection = null;
432 
433         if (fields != null)
434         {
435             int s=fields.size();
436             for (int f=0;f<s;f++)
437             {
438                 HttpFields.Field field = fields.getField(f);
439                 if (field==null)
440                     continue;
441                 switch (field.getNameOrdinal())
442                 {
443                     case HttpHeaders.CONTENT_LENGTH_ORDINAL:
444                         content_length = field;
445                         _contentLength = field.getLongValue();
446 
447                         if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten)
448                             content_length = null;
449 
450                         // write the field to the header buffer
451                         field.put(_header);
452                         break;
453 
454                     case HttpHeaders.CONTENT_TYPE_ORDINAL:
455                         if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT;
456 
457                         // write the field to the header buffer
458                         field.put(_header);
459                         break;
460 
461                     case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
462                         if (_version == HttpVersions.HTTP_1_1_ORDINAL) transfer_encoding = field;
463                         // Do NOT add yet!
464                         break;
465 
466                     case HttpHeaders.CONNECTION_ORDINAL:
467                         if (_method!=null)
468                             field.put(_header);
469                         
470                         int connection_value = field.getValueOrdinal();
471                         switch (connection_value)
472                         {
473                             case -1:
474                             { 
475                                 String[] values = field.getValue().split(",");
476                                 for  (int i=0;values!=null && i<values.length;i++)
477                                 {
478                                     CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim());
479 
480                                     if (cb!=null)
481                                     {
482                                         switch(cb.getOrdinal())
483                                         {
484                                             case HttpHeaderValues.CLOSE_ORDINAL:
485                                                 close=true;
486                                                 if (_method==null)
487                                                     _close=true;
488                                                 keep_alive=false;
489                                                 if (_close && _method==null && _contentLength == HttpTokens.UNKNOWN_CONTENT) 
490                                                     _contentLength = HttpTokens.EOF_CONTENT;
491                                                 break;
492 
493                                             case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
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                                                 if (connection==null)
504                                                     connection=new StringBuilder();
505                                                 else
506                                                     connection.append(',');
507                                                 connection.append(values[i]);
508                                         }
509                                     }
510                                     else
511                                     {
512                                         if (connection==null)
513                                             connection=new StringBuilder();
514                                         else
515                                             connection.append(',');
516                                         connection.append(values[i]);
517                                     }
518                                 }
519                                 
520                                 break;
521                             }
522                             case HttpHeaderValues.CLOSE_ORDINAL:
523                             {
524                                 close=true;
525                                 if (_method==null)
526                                     _close=true;
527                                 if (_close && _method==null && _contentLength == HttpTokens.UNKNOWN_CONTENT) 
528                                     _contentLength = HttpTokens.EOF_CONTENT;
529                                 break;
530                             }
531                             case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
532                             {
533                                 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
534                                 {
535                                     keep_alive = true;
536                                     if (_method==null) 
537                                         _close = false;
538                                 }
539                                 break;
540                             }
541                             default:
542                             {
543                                 if (connection==null)
544                                     connection=new StringBuilder();
545                                 else
546                                     connection.append(',');
547                                 connection.append(field.getValue());
548                             }
549                         }
550 
551                         // Do NOT add yet!
552                         break;
553 
554                     case HttpHeaders.SERVER_ORDINAL:
555                         if (getSendServerVersion()) 
556                         {
557                             has_server=true;
558                             field.put(_header);
559                         }
560                         break;
561 
562                     default:
563                         // write the field to the header buffer
564                         field.put(_header);
565                 }
566             }
567         }
568 
569         // Calculate how to end _content and connection, _content length and transfer encoding
570         // settings.
571         // From RFC 2616 4.4:
572         // 1. No body for 1xx, 204, 304 & HEAD response
573         // 2. Force _content-length?
574         // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
575         // 4. Content-Length
576         // 5. multipart/byteranges
577         // 6. close
578         switch ((int) _contentLength)
579         {
580             case HttpTokens.UNKNOWN_CONTENT:
581                 // It may be that we have no _content, or perhaps _content just has not been
582                 // written yet?
583 
584                 // Response known not to have a body
585                 if (_contentWritten == 0 && _method==null && (_status < 200 || _status == 204 || _status == 304))
586                     _contentLength = HttpTokens.NO_CONTENT;
587                 else if (_last)
588                 {
589                     // we have seen all the _content there is
590                     _contentLength = _contentWritten;
591                     if (content_length == null)
592                     {
593                         // known length but not actually set.
594                         _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER);
595                         _header.put(HttpTokens.COLON);
596                         _header.put((byte) ' ');
597                         BufferUtil.putDecLong(_header, _contentLength);
598                         _header.put(HttpTokens.CRLF);
599                     }
600                 }
601                 else
602                 {
603                     // No idea, so we must assume that a body is coming
604                     _contentLength = (_close || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT;
605                     if (_method!=null && _contentLength==HttpTokens.EOF_CONTENT)
606                     {
607                         _contentLength=HttpTokens.NO_CONTENT;
608                         _noContent=true;
609                     }
610                 }
611                 break;
612 
613             case HttpTokens.NO_CONTENT:
614                 if (content_length == null && _method==null && _status >= 200 && _status != 204 && _status != 304) 
615                     _header.put(CONTENT_LENGTH_0);
616                 break;
617 
618             case HttpTokens.EOF_CONTENT:
619                 _close = _method==null;
620                 break;
621 
622             case HttpTokens.CHUNKED_CONTENT:
623                 break;
624 
625             default:
626                 // TODO - maybe allow forced chunking by setting te ???
627                 break;
628         }
629 
630         // Add transfer_encoding if needed
631         if (_contentLength == HttpTokens.CHUNKED_CONTENT)
632         {
633             // try to use user supplied encoding as it may have other values.
634             if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal())
635             {
636                 String c = transfer_encoding.getValue();
637                 if (c.endsWith(HttpHeaderValues.CHUNKED))
638                     transfer_encoding.put(_header);
639                 else
640                     throw new IllegalArgumentException("BAD TE");
641             }
642             else
643                 _header.put(TRANSFER_ENCODING_CHUNKED);
644         }
645 
646         // Handle connection if need be
647         if (_contentLength==HttpTokens.EOF_CONTENT)
648         {
649             keep_alive=false;
650             _close=true;
651         }
652                
653         if (_method==null)
654         {
655             if (_close && (close || _version > HttpVersions.HTTP_1_0_ORDINAL))
656             {
657                 _header.put(CONNECTION_CLOSE);
658                 if (connection!=null)
659                 {
660                     _header.setPutIndex(_header.putIndex()-2);
661                     _header.put((byte)',');
662                     _header.put(connection.toString().getBytes());
663                     _header.put(CRLF);
664                 }
665             }
666             else if (keep_alive)
667             {
668                 _header.put(CONNECTION_KEEP_ALIVE);
669                 if (connection!=null)
670                 {
671                     _header.setPutIndex(_header.putIndex()-2);
672                     _header.put((byte)',');
673                     _header.put(connection.toString().getBytes());
674                     _header.put(CRLF);
675                 }
676             }
677             else if (connection!=null)
678             {
679                 _header.put(CONNECTION_);
680                 _header.put(connection.toString().getBytes());
681                 _header.put(CRLF);
682             }
683         }
684         
685         if (!has_server && _status>100 && getSendServerVersion())
686             _header.put(SERVER);
687 
688         // end the header.
689         _header.put(HttpTokens.CRLF);
690 
691         _state = STATE_CONTENT;
692 
693     }
694 
695     /* ------------------------------------------------------------ */
696     /**
697      * Complete the message.
698      * 
699      * @throws IOException
700      */
701     public void complete() throws IOException
702     {
703         if (_state == STATE_END) 
704             return;
705         
706         super.complete();
707         
708         if (_state < STATE_FLUSHING)
709         {
710             _state = STATE_FLUSHING;
711             if (_contentLength == HttpTokens.CHUNKED_CONTENT) 
712                 _needEOC = true;
713         }
714         
715         flushBuffer();
716     }
717 
718     /* ------------------------------------------------------------ */
719     public long flushBuffer() throws IOException
720     {
721         try
722         {   
723             if (_state == STATE_HEADER) 
724                 throw new IllegalStateException("State==HEADER");
725             
726             prepareBuffers();
727             
728             if (_endp == null)
729             {
730                 if (_needCRLF && _buffer!=null) 
731                     _buffer.put(HttpTokens.CRLF);
732                 if (_needEOC && _buffer!=null && !_head) 
733                     _buffer.put(LAST_CHUNK);
734                 _needCRLF=false;
735                 _needEOC=false;
736                 return 0;
737             }
738 
739             int total= 0;
740 
741             int len = -1;
742             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);
743             switch (to_flush)
744             {
745                 case 7:
746                     throw new IllegalStateException(); // should never happen!
747                 case 6:
748                     len = _endp.flush(_header, _buffer, null);
749                     break;
750                 case 5:
751                     len = _endp.flush(_header, _content, null);
752                     break;
753                 case 4:
754                     len = _endp.flush(_header);
755                     break;
756                 case 3:
757                     throw new IllegalStateException(); // should never happen!
758                 case 2:
759                     len = _endp.flush(_buffer);
760                     break;
761                 case 1:
762                     len = _endp.flush(_content);
763                     break;
764                 case 0:
765                 {
766                     // Nothing more we can write now.
767                     if (_header != null) 
768                         _header.clear();
769 
770                     _bypass = false;
771                     _bufferChunked = false;
772 
773                     if (_buffer != null)
774                     {
775                         _buffer.clear();
776                         if (_contentLength == HttpTokens.CHUNKED_CONTENT)
777                         {
778                             // reserve some space for the chunk header
779                             _buffer.setPutIndex(CHUNK_SPACE);
780                             _buffer.setGetIndex(CHUNK_SPACE);
781 
782                             // Special case handling for small left over buffer from
783                             // an addContent that caused a buffer flush.
784                             if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING)
785                             {
786                                 _buffer.put(_content);
787                                 _content.clear();
788                                 _content = null;
789                             }
790                         }
791                     }
792 
793                     // Are we completely finished for now?
794                     if (!_needCRLF && !_needEOC && (_content == null || _content.length() == 0))
795                     {
796                         if (_state == STATE_FLUSHING)
797                             _state = STATE_END;
798                         if (_state==STATE_END && _close && _status!=100) 
799                             _endp.close();
800                     }
801                     else
802                         // Try to prepare more to write.
803                         prepareBuffers();
804                 }
805             }
806 
807             if (len > 0)
808                 total+=len;
809 
810             return total;
811         }
812         catch (IOException e)
813         {
814             Log.ignore(e);
815             throw (e instanceof EofException) ? e:new EofException(e);
816         }
817     }
818 
819     /* ------------------------------------------------------------ */
820     private void prepareBuffers()
821     {
822         // if we are not flushing an existing chunk
823         if (!_bufferChunked)
824         {
825             // Refill buffer if possible
826             if (_content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
827             {
828                 int len = _buffer.put(_content);
829                 _content.skip(len);
830                 if (_content.length() == 0) 
831                     _content = null;
832             }
833 
834             // Chunk buffer if need be
835             if (_contentLength == HttpTokens.CHUNKED_CONTENT)
836             {
837                 int size = _buffer == null ? 0 : _buffer.length();
838                 if (size > 0)
839                 {
840                     // Prepare a chunk!
841                     _bufferChunked = true;
842 
843                     // Did we leave space at the start of the buffer.
844                     //noinspection ConstantConditions
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 }