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