View Javadoc

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