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