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