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