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.BufferUtil;
21  import org.eclipse.jetty.io.Buffers;
22  import org.eclipse.jetty.io.ByteArrayBuffer;
23  import org.eclipse.jetty.io.EndPoint;
24  import org.eclipse.jetty.io.EofException;
25  import org.eclipse.jetty.io.BufferCache.CachedBuffer;
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 && content.length() > 0 && _last)
188         {
189             // TODO - use bypass in more cases.
190             // Make _content a direct buffer
191             _bypass = true;
192         }
193         else if (!_bufferChunked)
194         {
195             // Yes - so we better check we have a buffer
196             if (_buffer == null) 
197                 _buffer = _buffers.getBuffer();
198 
199             // Copy _content to buffer;
200             int len=_buffer.put(_content);
201             _content.skip(len);
202             if (_content.length() == 0) 
203                 _content = null;
204         }
205     }
206 
207     /* ------------------------------------------------------------ */
208     /**
209      * send complete response.
210      * 
211      * @param response
212      */
213     public void sendResponse(Buffer response) throws IOException
214     {
215         if (_noContent || _state!=STATE_HEADER || _content!=null && _content.length()>0 || _bufferChunked || _head )
216             throw new IllegalStateException();
217 
218         _last = true;
219 
220         _content = response;
221         _bypass = true;
222         _state = STATE_FLUSHING;
223 
224         // TODO this is not exactly right, but should do.
225         _contentLength =_contentWritten = response.length();
226         
227     }
228     
229     /* ------------------------------------------------------------ */
230     /**
231      * Add content.
232      * 
233      * @param b byte
234      * @return true if the buffers are full
235      * @throws IOException
236      */
237     public boolean addContent(byte b) throws IOException
238     {
239         if (_noContent)
240             throw new IllegalStateException("NO CONTENT");
241         
242         if (_last || _state==STATE_END) 
243         {
244             Log.debug("Ignoring extra content {}",Byte.valueOf(b));
245             return false;
246         }
247 
248         // Handle any unfinished business?
249         if (_content != null && _content.length()>0 || _bufferChunked)
250         {
251             flushBuffer();
252             if (_content != null && _content.length()>0 || _bufferChunked) 
253                 throw new IllegalStateException("FULL");
254         }
255 
256         _contentWritten++;
257         
258         // Handle the _content
259         if (_head)
260             return false;
261         
262         // we better check we have a buffer
263         if (_buffer == null) 
264             _buffer = _buffers.getBuffer();
265         
266         // Copy _content to buffer;
267         _buffer.put(b);
268         
269         return _buffer.space()<=(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0);
270     }
271 
272     /* ------------------------------------------------------------ */
273     /** Prepare buffer for unchecked writes.
274      * Prepare the generator buffer to receive unchecked writes
275      * @return the available space in the buffer.
276      * @throws IOException
277      */
278     @Override
279     public int prepareUncheckedAddContent() throws IOException
280     {
281         if (_noContent)
282             return -1;
283         
284         if (_last || _state==STATE_END) 
285             return -1;
286 
287         // Handle any unfinished business?
288         Buffer content = _content;
289         if (content != null && content.length()>0 || _bufferChunked)
290         {
291             flushBuffer();
292             if (content != null && content.length()>0 || _bufferChunked) 
293                 throw new IllegalStateException("FULL");
294         }
295 
296         // we better check we have a buffer
297         if (_buffer == null) 
298             _buffer = _buffers.getBuffer();
299 
300         _contentWritten-=_buffer.length();
301         
302         // Handle the _content
303         if (_head)
304             return Integer.MAX_VALUE;
305         
306         return _buffer.space()-(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0);
307     }
308     
309     /* ------------------------------------------------------------ */
310     @Override
311     public boolean isBufferFull()
312     {
313         // Should we flush the buffers?
314         return super.isBufferFull() || _bufferChunked || _bypass  || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE);
315     }
316 
317     /* ------------------------------------------------------------ */
318     public void send1xx(int code) throws IOException
319     {
320         if (_state != STATE_HEADER) 
321             return;
322         
323         if (code<100||code>199)
324             throw new IllegalArgumentException("!1xx");
325         Status status=__status[code];
326         if (status==null)
327             throw new IllegalArgumentException(code+"?");
328         
329         // get a header buffer
330         if (_header == null) 
331             _header = _buffers.getHeader();
332         
333         _header.put(status._responseLine);
334         _header.put(HttpTokens.CRLF);
335         
336         try
337         {
338             // nasty semi busy flush!
339             while(_header.length()>0)
340             {
341                 int len = _endp.flush(_header);
342                 if (len<0)
343                     throw new EofException();
344                 if (len==0)
345                     Thread.sleep(100);
346             }
347         }
348         catch(InterruptedException e)
349         {
350             Log.debug(e);
351             throw new InterruptedIOException(e.toString());
352         }
353     }
354     
355     /* ------------------------------------------------------------ */
356     @Override
357     public boolean isRequest()
358     {
359         return _method!=null;
360     }
361     
362     /* ------------------------------------------------------------ */
363     @Override
364     public boolean isResponse()
365     {
366         return _method==null;
367     }
368     
369     /* ------------------------------------------------------------ */
370     @Override
371     public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
372     {
373         if (_state != STATE_HEADER) 
374             return;
375         
376         // handle a reset 
377         if (isResponse() && _status==0)
378             throw new EofException();
379 
380         if (_last && !allContentAdded) 
381             throw new IllegalStateException("last?");
382         _last = _last | allContentAdded;
383 
384         // get a header buffer
385         if (_header == null) 
386             _header = _buffers.getHeader();
387         
388         boolean has_server = false;
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     
781 
782 
783     /* ------------------------------------------------------------ */
784     /**
785      * Complete the message.
786      * 
787      * @throws IOException
788      */
789     @Override
790     public void complete() throws IOException
791     {
792         if (_state == STATE_END) 
793             return;
794         
795         super.complete();
796         
797         if (_state < STATE_FLUSHING)
798         {
799             _state = STATE_FLUSHING;
800             if (_contentLength == HttpTokens.CHUNKED_CONTENT) 
801                 _needEOC = true;
802         }
803         
804         flushBuffer();
805     }
806 
807     /* ------------------------------------------------------------ */
808     @Override
809     public long flushBuffer() throws IOException
810     {
811         try
812         {   
813             if (_state == STATE_HEADER) 
814                 throw new IllegalStateException("State==HEADER");
815             
816             prepareBuffers();
817             
818             if (_endp == null)
819             {
820                 if (_needCRLF && _buffer!=null) 
821                     _buffer.put(HttpTokens.CRLF);
822                 if (_needEOC && _buffer!=null && !_head) 
823                     _buffer.put(LAST_CHUNK);
824                 _needCRLF=false;
825                 _needEOC=false;
826                 return 0;
827             }
828 
829             int total= 0;
830 
831             int len = -1;
832             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);
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                     throw new IllegalStateException(); // should never happen!
848                 case 2:
849                     len = _endp.flush(_buffer);
850                     break;
851                 case 1:
852                     len = _endp.flush(_content);
853                     break;
854                 case 0:
855                 {
856                     // Nothing more we can write now.
857                     if (_header != null) 
858                         _header.clear();
859 
860                     _bypass = false;
861                     _bufferChunked = false;
862 
863                     if (_buffer != null)
864                     {
865                         _buffer.clear();
866                         if (_contentLength == HttpTokens.CHUNKED_CONTENT)
867                         {
868                             // reserve some space for the chunk header
869                             _buffer.setPutIndex(CHUNK_SPACE);
870                             _buffer.setGetIndex(CHUNK_SPACE);
871 
872                             // Special case handling for small left over buffer from
873                             // an addContent that caused a buffer flush.
874                             if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING)
875                             {
876                                 _buffer.put(_content);
877                                 _content.clear();
878                                 _content = null;
879                             }
880                         }
881                     }
882 
883                     // Are we completely finished for now?
884                     if (!_needCRLF && !_needEOC && (_content == null || _content.length() == 0))
885                     {
886                         if (_state == STATE_FLUSHING)
887                             _state = STATE_END;
888                         if (_state==STATE_END && !_persistent && _status!=100) 
889                             _endp.close();
890                     }
891                     else
892                         // Try to prepare more to write.
893                         prepareBuffers();
894                 }
895             }
896 
897             if (len > 0)
898                 total+=len;
899 
900             return total;
901         }
902         catch (IOException e)
903         {
904             Log.ignore(e);
905             throw (e instanceof EofException) ? e:new EofException(e);
906         }
907     }
908 
909     /* ------------------------------------------------------------ */
910     private void prepareBuffers()
911     {
912         // if we are not flushing an existing chunk
913         if (!_bufferChunked)
914         {
915             // Refill buffer if possible
916             if (_content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
917             {
918                 int len = _buffer.put(_content);
919                 _content.skip(len);
920                 if (_content.length() == 0) 
921                     _content = null;
922             }
923 
924             // Chunk buffer if need be
925             if (_contentLength == HttpTokens.CHUNKED_CONTENT)
926             {
927                 int size = _buffer == null ? 0 : _buffer.length();
928                 if (size > 0)
929                 {
930                     // Prepare a chunk!
931                     _bufferChunked = true;
932 
933                     // Did we leave space at the start of the buffer.
934                     //noinspection ConstantConditions
935                     if (_buffer.getIndex() == CHUNK_SPACE)
936                     {
937                         // Oh yes, goodie! let's use it then!
938                         _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
939                         _buffer.setGetIndex(_buffer.getIndex() - 2);
940                         BufferUtil.prependHexInt(_buffer, size);
941 
942                         if (_needCRLF)
943                         {
944                             _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
945                             _buffer.setGetIndex(_buffer.getIndex() - 2);
946                             _needCRLF = false;
947                         }
948                     }
949                     else
950                     {
951                         // No space so lets use the header buffer.
952                         if (_needCRLF)
953                         {
954                             if (_header.length() > 0) throw new IllegalStateException("EOC");
955                             _header.put(HttpTokens.CRLF);
956                             _needCRLF = false;
957                         }
958                         BufferUtil.putHexInt(_header, size);
959                         _header.put(HttpTokens.CRLF);
960                     }
961 
962                     // Add end chunk trailer.
963                     if (_buffer.space() >= 2)
964                         _buffer.put(HttpTokens.CRLF);
965                     else
966                         _needCRLF = true;
967                 }
968 
969                 // If we need EOC and everything written
970                 if (_needEOC && (_content == null || _content.length() == 0))
971                 {
972                     if (_needCRLF)
973                     {
974                         if (_buffer == null && _header.space() >= 2)
975                         {
976                             _header.put(HttpTokens.CRLF);
977                             _needCRLF = false;
978                         }
979                         else if (_buffer!=null && _buffer.space() >= 2)
980                         {
981                             _buffer.put(HttpTokens.CRLF);
982                             _needCRLF = false;
983                         }
984                     }
985 
986                     if (!_needCRLF && _needEOC)
987                     {
988                         if (_buffer == null && _header.space() >= LAST_CHUNK.length)
989                         {
990                             if (!_head)
991                             {
992                                 _header.put(LAST_CHUNK);
993                                 _bufferChunked=true;
994                             }
995                             _needEOC = false;
996                         }
997                         else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length)
998                         {
999                             if (!_head)
1000                             {
1001                                 _buffer.put(LAST_CHUNK);
1002                                 _bufferChunked=true;
1003                             }
1004                             _needEOC = false;
1005                         }
1006                     }
1007                 }
1008             }
1009         }
1010 
1011         if (_content != null && _content.length() == 0) 
1012             _content = null;
1013 
1014     }
1015 
1016     public int getBytesBuffered()
1017     {
1018         return(_header==null?0:_header.length())+
1019         (_buffer==null?0:_buffer.length())+
1020         (_content==null?0:_content.length());
1021     }
1022 
1023     public boolean isEmpty()
1024     {
1025         return (_header==null||_header.length()==0) &&
1026         (_buffer==null||_buffer.length()==0) &&
1027         (_content==null||_content.length()==0);
1028     }
1029     
1030     @Override
1031     public String toString()
1032     {
1033         return "HttpGenerator s="+_state+
1034         " h="+(_header==null?"null":_header.length())+
1035         " b="+(_buffer==null?"null":_buffer.length())+
1036         " c="+(_content==null?"null":_content.length());
1037     }
1038 }