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 headerBufferSize Size of the buffer to allocate for HTTP header
117      * @param contentBufferSize Size of the buffer to allocate for HTTP content
118      */
119     public HttpGenerator(Buffers buffers, EndPoint io)
120     {
121         super(buffers,io);
122     }
123 
124     /* ------------------------------------------------------------------------------- */
125     @Override
126     public void reset(boolean returnBuffers)
127     {
128         super.reset(returnBuffers);
129         _bypass = false;
130         _needCRLF = false;
131         _needEOC = false;
132         _bufferChunked=false;
133         _method=null;
134         _uri=null;
135         _noContent=false;
136     }
137 
138 
139 
140     /* ------------------------------------------------------------ */
141     /**
142      * Add content.
143      * 
144      * @param content
145      * @param last
146      * @throws IllegalArgumentException if <code>content</code> is {@link Buffer#isImmutable immutable}.
147      * @throws IllegalStateException If the request is not expecting any more content,
148      *   or if the buffers are full and cannot be flushed.
149      * @throws IOException if there is a problem flushing the buffers.
150      */
151     public void addContent(Buffer content, boolean last) throws IOException
152     {
153         if (_noContent)
154             throw new IllegalStateException("NO CONTENT");
155 
156         if (_last || _state==STATE_END) 
157         {
158             Log.debug("Ignoring extra content {}",content);
159             content.clear();
160             return;
161         }
162         _last = last;
163 
164         // Handle any unfinished business?
165         if (_content!=null && _content.length()>0 || _bufferChunked)
166         {
167             if (!_endp.isOpen())
168                 throw new EofException();
169             flushBuffer();
170             if (_content != null && _content.length()>0) 
171             {
172                 Buffer nc=_buffers.getBuffer(_content.length()+content.length());
173                 nc.put(_content);
174                 nc.put(content);
175                 _content=nc;
176             }
177         }
178 
179         _content = content;
180         _contentWritten += content.length();
181 
182         // Handle the _content
183         if (_head)
184         {
185             content.clear();
186             _content=null;
187         }
188         else if (_endp != null && _buffer == null && content.length() > 0 && _last)
189         {
190             // TODO - use bypass in more cases.
191             // Make _content a direct buffer
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     /* ------------------------------------------------------------ */
358     @Override
359     public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
360     {
361         if (_state != STATE_HEADER) 
362             return;
363         
364         // handle a reset 
365         if (_method==null && _status==0)
366             throw new EofException();
367 
368         if (_last && !allContentAdded) 
369             throw new IllegalStateException("last?");
370         _last = _last | allContentAdded;
371 
372         // get a header buffer
373         if (_header == null) 
374             _header = _buffers.getHeader();
375         
376         boolean has_server = false;
377         
378         if (_method!=null)
379         {
380             _close = false;
381             // Request
382             if (_version == HttpVersions.HTTP_0_9_ORDINAL)
383             {
384                 _contentLength = HttpTokens.NO_CONTENT;
385                 _header.put(_method);
386                 _header.put((byte)' ');
387                 _header.put(_uri.getBytes("utf-8")); // TODO WRONG!
388                 _header.put(HttpTokens.CRLF);
389                 _state = STATE_FLUSHING;
390                 _noContent=true;
391                 return;
392             }
393             else
394             {
395                 _header.put(_method);
396                 _header.put((byte)' ');
397                 _header.put(_uri.getBytes("utf-8")); // TODO WRONG!
398                 _header.put((byte)' ');
399                 _header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER);
400                 _header.put(HttpTokens.CRLF);
401             }
402         }
403         else
404         {
405             // Response
406             if (_version == HttpVersions.HTTP_0_9_ORDINAL)
407             {
408                 _close = true;
409                 _contentLength = HttpTokens.EOF_CONTENT;
410                 _state = STATE_CONTENT;
411                 return;
412             }
413             else
414             {
415                 if (_version == HttpVersions.HTTP_1_0_ORDINAL) 
416                     _close = true;
417 
418                 // add response line
419                 Status status = _status<__status.length?__status[_status]:null;
420                 
421                 if (status==null)
422                 {
423                     _header.put(HttpVersions.HTTP_1_1_BUFFER);
424                     _header.put((byte) ' ');
425                     _header.put((byte) ('0' + _status / 100));
426                     _header.put((byte) ('0' + (_status % 100) / 10));
427                     _header.put((byte) ('0' + (_status % 10)));
428                     _header.put((byte) ' ');
429                     if (_reason==null)
430                     {
431                         _header.put((byte) ('0' + _status / 100));
432                         _header.put((byte) ('0' + (_status % 100) / 10));
433                         _header.put((byte) ('0' + (_status % 10)));
434                     }
435                     else
436                         _header.put(_reason);
437                     _header.put(HttpTokens.CRLF);
438                 }
439                 else
440                 {
441                     if (_reason==null)
442                         _header.put(status._responseLine);
443                     else
444                     {
445                         _header.put(status._schemeCode);
446                         _header.put(_reason);
447                         _header.put(HttpTokens.CRLF);
448                     }
449                 }
450 
451                 if (_status<200 && _status>=100 )
452                 {
453                     _noContent=true;
454                     _content=null;
455                     if (_buffer!=null)
456                         _buffer.clear();
457                     // end the header.
458 
459                     if (_status!=101 )
460                     {
461                         _header.put(HttpTokens.CRLF);
462                         _state = STATE_CONTENT;
463                         return;
464                     }
465                 }
466                 else if (_status==204 || _status==304)
467                 {
468                     _noContent=true;
469                     _content=null;
470                     if (_buffer!=null)
471                         _buffer.clear();
472                 }
473             }
474         }
475         
476         // Add headers
477         if (_status>=200 && _date!=null)
478         {
479             _header.put(HttpHeaders.DATE_BUFFER);
480             _header.put((byte)':');
481             _header.put((byte)' ');
482             _header.put(_date);
483             _header.put(CRLF);
484         }
485 
486         // key field values
487         HttpFields.Field content_length = null;
488         HttpFields.Field transfer_encoding = null;
489         boolean keep_alive = false;
490         boolean close=false;
491         boolean content_type=false;
492         StringBuilder connection = null;
493 
494         if (fields != null)
495         {
496             int s=fields.size();
497             for (int f=0;f<s;f++)
498             {
499                 HttpFields.Field field = fields.getField(f);
500                 if (field==null)
501                     continue;
502 
503                 switch (field.getNameOrdinal())
504                 {
505                     case HttpHeaders.CONTENT_LENGTH_ORDINAL:
506                         content_length = field;
507                         _contentLength = field.getLongValue();
508 
509                         if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten)
510                             content_length = null;
511 
512                         // write the field to the header buffer
513                         field.put(_header);
514                         break;
515 
516                     case HttpHeaders.CONTENT_TYPE_ORDINAL:
517                         if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT;
518 
519                         // write the field to the header buffer
520                         content_type=true;
521                         field.put(_header);
522                         break;
523 
524                     case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
525                         if (_version == HttpVersions.HTTP_1_1_ORDINAL) 
526                             transfer_encoding = field;
527                         // Do NOT add yet!
528                         break;
529 
530                     case HttpHeaders.CONNECTION_ORDINAL:
531                         if (_method!=null)
532                             field.put(_header);
533                         
534                         int connection_value = field.getValueOrdinal();
535                         switch (connection_value)
536                         {
537                             case -1:
538                             { 
539                                 String[] values = field.getValue().split(",");
540                                 for  (int i=0;values!=null && i<values.length;i++)
541                                 {
542                                     CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim());
543 
544                                     if (cb!=null)
545                                     {
546                                         switch(cb.getOrdinal())
547                                         {
548                                             case HttpHeaderValues.CLOSE_ORDINAL:
549                                                 close=true;
550                                                 if (_method==null)
551                                                     _close=true;
552                                                 keep_alive=false;
553                                                 if (_close && _method==null && _contentLength == HttpTokens.UNKNOWN_CONTENT) 
554                                                     _contentLength = HttpTokens.EOF_CONTENT;
555                                                 break;
556 
557                                             case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
558                                                 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
559                                                 {
560                                                     keep_alive = true;
561                                                     if (_method==null) 
562                                                         _close = false;
563                                                 }
564                                                 break;
565                                             
566                                             default:
567                                                 if (connection==null)
568                                                     connection=new StringBuilder();
569                                                 else
570                                                     connection.append(',');
571                                                 connection.append(values[i]);
572                                         }
573                                     }
574                                     else
575                                     {
576                                         if (connection==null)
577                                             connection=new StringBuilder();
578                                         else
579                                             connection.append(',');
580                                         connection.append(values[i]);
581                                     }
582                                 }
583                                 
584                                 break;
585                             }
586                             case HttpHeaderValues.UPGRADE_ORDINAL:
587                             {
588                                 // special case for websocket connection ordering
589                                 if (_method==null)
590                                 {
591                                     field.put(_header);
592                                     continue;
593                                 }
594                             }
595                             case HttpHeaderValues.CLOSE_ORDINAL:
596                             {
597                                 close=true;
598                                 if (_method==null)
599                                     _close=true;
600                                 if (_close && _method==null && _contentLength == HttpTokens.UNKNOWN_CONTENT) 
601                                     _contentLength = HttpTokens.EOF_CONTENT;
602                                 break;
603                             }
604                             case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
605                             {
606                                 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
607                                 {
608                                     keep_alive = true;
609                                     if (_method==null) 
610                                         _close = false;
611                                 }
612                                 break;
613                             }
614                             default:
615                             {
616                                 if (connection==null)
617                                     connection=new StringBuilder();
618                                 else
619                                     connection.append(',');
620                                 connection.append(field.getValue());
621                             }
622                         }
623 
624                         // Do NOT add yet!
625                         break;
626 
627                     case HttpHeaders.SERVER_ORDINAL:
628                         if (getSendServerVersion()) 
629                         {
630                             has_server=true;
631                             field.put(_header);
632                         }
633                         break;
634 
635                     default:
636                         // write the field to the header buffer
637                         field.put(_header);
638                 }
639             }
640         }
641 
642         // Calculate how to end _content and connection, _content length and transfer encoding
643         // settings.
644         // From RFC 2616 4.4:
645         // 1. No body for 1xx, 204, 304 & HEAD response
646         // 2. Force _content-length?
647         // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
648         // 4. Content-Length
649         // 5. multipart/byteranges
650         // 6. close
651         switch ((int) _contentLength)
652         {
653             case HttpTokens.UNKNOWN_CONTENT:
654                 // It may be that we have no _content, or perhaps _content just has not been
655                 // written yet?
656 
657                 // Response known not to have a body
658                 if (_contentWritten == 0 && _method==null && (_status < 200 || _status == 204 || _status == 304))
659                     _contentLength = HttpTokens.NO_CONTENT;
660                 else if (_last)
661                 {
662                     // we have seen all the _content there is
663                     _contentLength = _contentWritten;
664                     if (content_length == null && (_method==null || _contentLength>0 || content_type ))
665                     {
666                         // known length but not actually set.
667                         _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER);
668                         _header.put(HttpTokens.COLON);
669                         _header.put((byte) ' ');
670                         BufferUtil.putDecLong(_header, _contentLength);
671                         _header.put(HttpTokens.CRLF);
672                     }
673                 }
674                 else
675                 {
676                     // No idea, so we must assume that a body is coming
677                     _contentLength = (_close || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT;
678                     if (_method!=null && _contentLength==HttpTokens.EOF_CONTENT)
679                     {
680                         _contentLength=HttpTokens.NO_CONTENT;
681                         _noContent=true;
682                     }
683                 }
684                 break;
685 
686             case HttpTokens.NO_CONTENT:
687                 if (content_length == null && _method==null && _status >= 200 && _status != 204 && _status != 304) 
688                     _header.put(CONTENT_LENGTH_0);
689                 break;
690 
691             case HttpTokens.EOF_CONTENT:
692                 _close = _method==null;
693                 break;
694 
695             case HttpTokens.CHUNKED_CONTENT:
696                 break;
697 
698             default:
699                 // TODO - maybe allow forced chunking by setting te ???
700                 break;
701         }
702 
703         // Add transfer_encoding if needed
704         if (_contentLength == HttpTokens.CHUNKED_CONTENT)
705         {
706             // try to use user supplied encoding as it may have other values.
707             if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal())
708             {
709                 String c = transfer_encoding.getValue();
710                 if (c.endsWith(HttpHeaderValues.CHUNKED))
711                     transfer_encoding.put(_header);
712                 else
713                     throw new IllegalArgumentException("BAD TE");
714             }
715             else
716                 _header.put(TRANSFER_ENCODING_CHUNKED);
717         }
718 
719         // Handle connection if need be
720         if (_contentLength==HttpTokens.EOF_CONTENT)
721         {
722             keep_alive=false;
723             _close=true;
724         }
725                
726         if (_method==null)
727         {
728             if (_close && (close || _version > HttpVersions.HTTP_1_0_ORDINAL))
729             {
730                 _header.put(CONNECTION_CLOSE);
731                 if (connection!=null)
732                 {
733                     _header.setPutIndex(_header.putIndex()-2);
734                     _header.put((byte)',');
735                     _header.put(connection.toString().getBytes());
736                     _header.put(CRLF);
737                 }
738             }
739             else if (keep_alive)
740             {
741                 _header.put(CONNECTION_KEEP_ALIVE);
742                 if (connection!=null)
743                 {
744                     _header.setPutIndex(_header.putIndex()-2);
745                     _header.put((byte)',');
746                     _header.put(connection.toString().getBytes());
747                     _header.put(CRLF);
748                 }
749             }
750             else if (connection!=null)
751             {
752                 _header.put(CONNECTION_);
753                 _header.put(connection.toString().getBytes());
754                 _header.put(CRLF);
755             }
756         }
757         
758         if (!has_server && _status>199 && getSendServerVersion())
759             _header.put(SERVER);
760 
761         // end the header.
762         _header.put(HttpTokens.CRLF);
763 
764         _state = STATE_CONTENT;
765 
766     }
767     
768 
769 
770     /* ------------------------------------------------------------ */
771     /**
772      * Complete the message.
773      * 
774      * @throws IOException
775      */
776     @Override
777     public void complete() throws IOException
778     {
779         if (_state == STATE_END) 
780             return;
781         
782         super.complete();
783         
784         if (_state < STATE_FLUSHING)
785         {
786             _state = STATE_FLUSHING;
787             if (_contentLength == HttpTokens.CHUNKED_CONTENT) 
788                 _needEOC = true;
789         }
790         
791         flushBuffer();
792     }
793 
794     /* ------------------------------------------------------------ */
795     @Override
796     public long flushBuffer() throws IOException
797     {
798         try
799         {   
800             if (_state == STATE_HEADER) 
801                 throw new IllegalStateException("State==HEADER");
802             
803             prepareBuffers();
804             
805             if (_endp == null)
806             {
807                 if (_needCRLF && _buffer!=null) 
808                     _buffer.put(HttpTokens.CRLF);
809                 if (_needEOC && _buffer!=null && !_head) 
810                     _buffer.put(LAST_CHUNK);
811                 _needCRLF=false;
812                 _needEOC=false;
813                 return 0;
814             }
815 
816             int total= 0;
817 
818             int len = -1;
819             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);
820             switch (to_flush)
821             {
822                 case 7:
823                     throw new IllegalStateException(); // should never happen!
824                 case 6:
825                     len = _endp.flush(_header, _buffer, null);
826                     break;
827                 case 5:
828                     len = _endp.flush(_header, _content, null);
829                     break;
830                 case 4:
831                     len = _endp.flush(_header);
832                     break;
833                 case 3:
834                     throw new IllegalStateException(); // should never happen!
835                 case 2:
836                     len = _endp.flush(_buffer);
837                     break;
838                 case 1:
839                     len = _endp.flush(_content);
840                     break;
841                 case 0:
842                 {
843                     // Nothing more we can write now.
844                     if (_header != null) 
845                         _header.clear();
846 
847                     _bypass = false;
848                     _bufferChunked = false;
849 
850                     if (_buffer != null)
851                     {
852                         _buffer.clear();
853                         if (_contentLength == HttpTokens.CHUNKED_CONTENT)
854                         {
855                             // reserve some space for the chunk header
856                             _buffer.setPutIndex(CHUNK_SPACE);
857                             _buffer.setGetIndex(CHUNK_SPACE);
858 
859                             // Special case handling for small left over buffer from
860                             // an addContent that caused a buffer flush.
861                             if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING)
862                             {
863                                 _buffer.put(_content);
864                                 _content.clear();
865                                 _content = null;
866                             }
867                         }
868                     }
869 
870                     // Are we completely finished for now?
871                     if (!_needCRLF && !_needEOC && (_content == null || _content.length() == 0))
872                     {
873                         if (_state == STATE_FLUSHING)
874                             _state = STATE_END;
875                         if (_state==STATE_END && _close && _status!=100) 
876                             _endp.close();
877                     }
878                     else
879                         // Try to prepare more to write.
880                         prepareBuffers();
881                 }
882             }
883 
884             if (len > 0)
885                 total+=len;
886 
887             return total;
888         }
889         catch (IOException e)
890         {
891             Log.ignore(e);
892             throw (e instanceof EofException) ? e:new EofException(e);
893         }
894     }
895 
896     /* ------------------------------------------------------------ */
897     private void prepareBuffers()
898     {
899         // if we are not flushing an existing chunk
900         if (!_bufferChunked)
901         {
902             // Refill buffer if possible
903             if (_content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
904             {
905                 int len = _buffer.put(_content);
906                 _content.skip(len);
907                 if (_content.length() == 0) 
908                     _content = null;
909             }
910 
911             // Chunk buffer if need be
912             if (_contentLength == HttpTokens.CHUNKED_CONTENT)
913             {
914                 int size = _buffer == null ? 0 : _buffer.length();
915                 if (size > 0)
916                 {
917                     // Prepare a chunk!
918                     _bufferChunked = true;
919 
920                     // Did we leave space at the start of the buffer.
921                     //noinspection ConstantConditions
922                     if (_buffer.getIndex() == CHUNK_SPACE)
923                     {
924                         // Oh yes, goodie! let's use it then!
925                         _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
926                         _buffer.setGetIndex(_buffer.getIndex() - 2);
927                         BufferUtil.prependHexInt(_buffer, size);
928 
929                         if (_needCRLF)
930                         {
931                             _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
932                             _buffer.setGetIndex(_buffer.getIndex() - 2);
933                             _needCRLF = false;
934                         }
935                     }
936                     else
937                     {
938                         // No space so lets use the header buffer.
939                         if (_needCRLF)
940                         {
941                             if (_header.length() > 0) throw new IllegalStateException("EOC");
942                             _header.put(HttpTokens.CRLF);
943                             _needCRLF = false;
944                         }
945                         BufferUtil.putHexInt(_header, size);
946                         _header.put(HttpTokens.CRLF);
947                     }
948 
949                     // Add end chunk trailer.
950                     if (_buffer.space() >= 2)
951                         _buffer.put(HttpTokens.CRLF);
952                     else
953                         _needCRLF = true;
954                 }
955 
956                 // If we need EOC and everything written
957                 if (_needEOC && (_content == null || _content.length() == 0))
958                 {
959                     if (_needCRLF)
960                     {
961                         if (_buffer == null && _header.space() >= 2)
962                         {
963                             _header.put(HttpTokens.CRLF);
964                             _needCRLF = false;
965                         }
966                         else if (_buffer!=null && _buffer.space() >= 2)
967                         {
968                             _buffer.put(HttpTokens.CRLF);
969                             _needCRLF = false;
970                         }
971                     }
972 
973                     if (!_needCRLF && _needEOC)
974                     {
975                         if (_buffer == null && _header.space() >= LAST_CHUNK.length)
976                         {
977                             if (!_head)
978                             {
979                                 _header.put(LAST_CHUNK);
980                                 _bufferChunked=true;
981                             }
982                             _needEOC = false;
983                         }
984                         else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length)
985                         {
986                             if (!_head)
987                             {
988                                 _buffer.put(LAST_CHUNK);
989                                 _bufferChunked=true;
990                             }
991                             _needEOC = false;
992                         }
993                     }
994                 }
995             }
996         }
997 
998         if (_content != null && _content.length() == 0) 
999             _content = null;
1000 
1001     }
1002 
1003     public int getBytesBuffered()
1004     {
1005         return(_header==null?0:_header.length())+
1006         (_buffer==null?0:_buffer.length())+
1007         (_content==null?0:_content.length());
1008     }
1009 
1010     public boolean isEmpty()
1011     {
1012         return (_header==null||_header.length()==0) &&
1013         (_buffer==null||_buffer.length()==0) &&
1014         (_content==null||_content.length()==0);
1015     }
1016     
1017     @Override
1018     public String toString()
1019     {
1020         return "HttpGenerator s="+_state+
1021         " h="+(_header==null?"null":_header.length())+
1022         " b="+(_buffer==null?"null":_buffer.length())+
1023         " c="+(_content==null?"null":_content.length());
1024     }
1025 }