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