View Javadoc

1   //========================================================================
2   //$Id: HttpGenerator.java,v 1.7 2005/11/25 21:17:12 gregwilkins Exp $
3   //Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
4   //------------------------------------------------------------------------
5   //Licensed under the Apache License, Version 2.0 (the "License");
6   //you may not use this file except in compliance with the License.
7   //You may obtain a copy of the License at 
8   //http://www.apache.org/licenses/LICENSE-2.0
9   //Unless required by applicable law or agreed to in writing, software
10  //distributed under the License is distributed on an "AS IS" BASIS,
11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  //See the License for the specific language governing permissions and
13  //limitations under the License.
14  //========================================================================
15  
16  package org.mortbay.jetty;
17  
18  import java.io.IOException;
19  import java.util.Iterator;
20  
21  import org.mortbay.io.Buffer;
22  import org.mortbay.io.BufferUtil;
23  import org.mortbay.io.Buffers;
24  import org.mortbay.io.EndPoint;
25  import org.mortbay.io.Portable;
26  import org.mortbay.io.BufferCache.CachedBuffer;
27  import org.mortbay.log.Log;
28  import org.mortbay.util.QuotedStringTokenizer;
29  
30  /* ------------------------------------------------------------ */
31  /**
32   * HttpGenerator. Builds HTTP Messages.
33   * 
34   * @author gregw
35   * 
36   */
37  public class HttpGenerator extends AbstractGenerator
38  {
39      // common _content
40      private static byte[] LAST_CHUNK =
41      { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
42      private static byte[] CONTENT_LENGTH_0 = Portable.getBytes("Content-Length: 0\015\012");
43      private static byte[] CONNECTION_KEEP_ALIVE = Portable.getBytes("Connection: keep-alive\015\012");
44      private static byte[] CONNECTION_CLOSE = Portable.getBytes("Connection: close\015\012");
45      private static byte[] CONNECTION_ = Portable.getBytes("Connection: ");
46      private static byte[] CRLF = Portable.getBytes("\015\012");
47      private static byte[] TRANSFER_ENCODING_CHUNKED = Portable.getBytes("Transfer-Encoding: chunked\015\012");
48      private static byte[] SERVER = Portable.getBytes("Server: Jetty(6.0.x)\015\012");
49  
50      // other statics
51      private static int CHUNK_SPACE = 12;
52      
53      public static void setServerVersion(String version)
54      {
55          SERVER=Portable.getBytes("Server: Jetty("+version+")\015\012");
56      }
57  
58      // data
59      private boolean _bypass = false; // True if _content buffer can be written directly to endp and bypass the content buffer
60      private boolean _needCRLF = false;
61      private boolean _needEOC = false;
62      private boolean _bufferChunked = false;
63  
64      
65      /* ------------------------------------------------------------------------------- */
66      /**
67       * Constructor.
68       * 
69       * @param buffers buffer pool
70       * @param headerBufferSize Size of the buffer to allocate for HTTP header
71       * @param contentBufferSize Size of the buffer to allocate for HTTP content
72       */
73      public HttpGenerator(Buffers buffers, EndPoint io, int headerBufferSize, int contentBufferSize)
74      {
75          super(buffers,io,headerBufferSize,contentBufferSize);
76      }
77  
78      /* ------------------------------------------------------------------------------- */
79      public void reset(boolean returnBuffers)
80      {
81          super.reset(returnBuffers);
82          _bypass = false;
83          _needCRLF = false;
84          _needEOC = false;
85          _bufferChunked=false;
86          _method=null;
87          _uri=null;
88          _noContent=false;
89      }
90  
91  
92  
93      /* ------------------------------------------------------------ */
94      /**
95       * Add content.
96       * 
97       * @param content
98       * @param last
99       * @throws IllegalArgumentException if <code>content</code> is {@link Buffer#isImmutable immutable}.
100      * @throws IllegalStateException If the request is not expecting any more content,
101      *   or if the buffers are full and cannot be flushed.
102      * @throws IOException if there is a problem flushing the buffers.
103      */
104     public void addContent(Buffer content, boolean last) throws IOException
105     {
106         if (_noContent)
107             throw new IllegalStateException("NO CONTENT");
108 
109         if (_last || _state==STATE_END) 
110         {
111             Log.debug("Ignoring extra content {}",content);
112             content.clear();
113             return;
114         }
115         _last = last;
116 
117         // Handle any unfinished business?
118         if (_content!=null && _content.length()>0 || _bufferChunked)
119         {
120             if (!_endp.isOpen())
121                 throw new EofException();
122             flush();
123             if (_content != null && _content.length()>0 || _bufferChunked) 
124                 throw new IllegalStateException("FULL");
125         }
126 
127         _content = content;
128         _contentWritten += content.length();
129 
130         // Handle the _content
131         if (_head)
132         {
133             content.clear();
134             _content=null;
135         }
136         else if (_endp != null && _buffer == null && content.length() > 0 && _last)
137         {
138             // TODO - use bypass in more cases.
139             // Make _content a direct buffer
140             _bypass = true;
141         }
142         else
143         {
144             // Yes - so we better check we have a buffer
145             if (_buffer == null) 
146                 _buffer = _buffers.getBuffer(_contentBufferSize);
147 
148             // Copy _content to buffer;
149             int len=_buffer.put(_content);
150             _content.skip(len);
151             if (_content.length() == 0) 
152                 _content = null;
153         }
154     }
155 
156     /* ------------------------------------------------------------ */
157     /**
158      * send complete response.
159      * 
160      * @param response
161      */
162     public void sendResponse(Buffer response) throws IOException
163     {
164         if (_noContent || _state!=STATE_HEADER || _content!=null && _content.length()>0 || _bufferChunked || _head )
165             throw new IllegalStateException();
166 
167         _last = true;
168 
169         _content = response;
170         _bypass = true;
171         _state = STATE_FLUSHING;
172 
173         // TODO this is not exactly right, but should do.
174         _contentLength =_contentWritten = response.length();
175         
176     }
177 
178     /* ------------------------------------------------------------ */
179     /**
180      * Add content.
181      * 
182      * @param b byte
183      * @return true if the buffers are full
184      * @throws IOException
185      */
186     public boolean addContent(byte b) throws IOException
187     {
188         if (_noContent)
189             throw new IllegalStateException("NO CONTENT");
190         
191         if (_last || _state==STATE_END) 
192         {
193             Log.debug("Ignoring extra content {}",new Byte(b));
194             return false;
195         }
196 
197         // Handle any unfinished business?
198         if (_content != null && _content.length()>0 || _bufferChunked)
199         {
200             flush();
201             if (_content != null && _content.length()>0 || _bufferChunked) 
202                 throw new IllegalStateException("FULL");
203         }
204 
205         _contentWritten++;
206         
207         // Handle the _content
208         if (_head)
209             return false;
210         
211         // we better check we have a buffer
212         if (_buffer == null) 
213             _buffer = _buffers.getBuffer(_contentBufferSize);
214         
215         // Copy _content to buffer;
216         _buffer.put(b);
217         
218         return _buffer.space()<=(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0);
219     }
220 
221     /* ------------------------------------------------------------ */
222     /** Prepare buffer for unchecked writes.
223      * Prepare the generator buffer to receive unchecked writes
224      * @return the available space in the buffer.
225      * @throws IOException
226      */
227     protected int prepareUncheckedAddContent() throws IOException
228     {
229         if (_noContent)
230             return -1;
231         
232         if (_last || _state==STATE_END) 
233             return -1;
234 
235         // Handle any unfinished business?
236         Buffer content = _content;
237         if (content != null && content.length()>0 || _bufferChunked)
238         {
239             flush();
240             if (content != null && content.length()>0 || _bufferChunked) 
241                 throw new IllegalStateException("FULL");
242         }
243 
244         // we better check we have a buffer
245         if (_buffer == null) 
246             _buffer = _buffers.getBuffer(_contentBufferSize);
247 
248         _contentWritten-=_buffer.length();
249         
250         // Handle the _content
251         if (_head)
252             return Integer.MAX_VALUE;
253         
254         return _buffer.space()-(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0);
255     }
256     
257     /* ------------------------------------------------------------ */
258     public boolean isBufferFull()
259     {
260         // Should we flush the buffers?
261         boolean full = super.isBufferFull() || _bufferChunked || _bypass  || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE);
262         return full;
263     }
264     
265     /* ------------------------------------------------------------ */
266     public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
267     {
268         if (_state != STATE_HEADER) 
269             return;
270         
271         // handle a reset 
272         if (_method==null && _status==0)
273             throw new EofException();
274 
275         if (_last && !allContentAdded) 
276             throw new IllegalStateException("last?");
277         _last = _last | allContentAdded;
278 
279         // get a header buffer
280         if (_header == null) 
281             _header = _buffers.getBuffer(_headerBufferSize);
282         
283         boolean has_server = false;
284         
285         if (_method!=null)
286         {
287             _close = false;
288             // Request
289             if (_version == HttpVersions.HTTP_0_9_ORDINAL)
290             {
291                 _contentLength = HttpTokens.NO_CONTENT;
292                 _header.put(_method);
293                 _header.put((byte)' ');
294                 _header.put(_uri.getBytes("utf-8")); // TODO WRONG!
295                 _header.put(HttpTokens.CRLF);
296                 _state = STATE_FLUSHING;
297                 _noContent=true;
298                 return;
299             }
300             else
301             {
302                 _header.put(_method);
303                 _header.put((byte)' ');
304                 _header.put(_uri.getBytes("utf-8")); // TODO WRONG!
305                 _header.put((byte)' ');
306                 _header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER);
307                 _header.put(HttpTokens.CRLF);
308             }
309         }
310         else
311         {
312             // Response
313             if (_version == HttpVersions.HTTP_0_9_ORDINAL)
314             {
315                 _close = true;
316                 _contentLength = HttpTokens.EOF_CONTENT;
317                 _state = STATE_CONTENT;
318                 return;
319             }
320             else
321             {
322                 if (_version == HttpVersions.HTTP_1_0_ORDINAL) 
323                     _close = true;
324 
325                 // add response line
326                 Buffer line = HttpStatus.getResponseLine(_status);
327 
328                 
329                 if (line==null)
330                 {
331                     if (_reason==null)
332                         _reason=getReasonBuffer(_status);
333 
334                     _header.put(HttpVersions.HTTP_1_1_BUFFER);
335                     _header.put((byte) ' ');
336                     _header.put((byte) ('0' + _status / 100));
337                     _header.put((byte) ('0' + (_status % 100) / 10));
338                     _header.put((byte) ('0' + (_status % 10)));
339                     _header.put((byte) ' ');
340                     if (_reason==null)
341                     {
342                         _header.put((byte) ('0' + _status / 100));
343                         _header.put((byte) ('0' + (_status % 100) / 10));
344                         _header.put((byte) ('0' + (_status % 10)));
345                     }
346                     else
347                         _header.put(_reason);
348                     _header.put(HttpTokens.CRLF);
349                 }
350                 else
351                 {
352                     if (_reason==null)
353                         _header.put(line);
354                     else
355                     {
356                         _header.put(line.array(), 0, HttpVersions.HTTP_1_1_BUFFER.length() + 5);
357                         _header.put(_reason);
358                         _header.put(HttpTokens.CRLF);
359                     }
360                 }
361 
362                 if (_status<200 && _status>=100 )
363                 {
364                     _noContent=true;
365                     _content=null;
366                     if (_buffer!=null)
367                         _buffer.clear();
368                     // end the header.
369                     _header.put(HttpTokens.CRLF);
370                     _state = STATE_CONTENT;
371                     return;
372                 }
373 
374                 if (_status==204 || _status==304)
375                 {
376                     _noContent=true;
377                     _content=null;
378                     if (_buffer!=null)
379                         _buffer.clear();
380                 }
381             }
382         }
383         
384         // Add headers
385 
386         // key field values
387         HttpFields.Field content_length = null;
388         HttpFields.Field transfer_encoding = null;
389         boolean keep_alive = false;
390         boolean close=false;
391         boolean content_type =false;
392         StringBuffer connection = null;
393 
394         if (fields != null)
395         {
396             Iterator iter = fields.getFields();
397 
398             while (iter.hasNext())
399             {
400                 HttpFields.Field field = (HttpFields.Field) iter.next();
401 
402                 switch (field.getNameOrdinal())
403                 {
404                     case HttpHeaders.CONTENT_LENGTH_ORDINAL:
405                         content_length = field;
406                         _contentLength = field.getLongValue();
407 
408                         if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten)
409                             content_length = null;
410 
411                         // write the field to the header buffer
412                         field.put(_header);
413                         break;
414 
415                     case HttpHeaders.CONTENT_TYPE_ORDINAL:
416                         if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) 
417                             _contentLength = HttpTokens.SELF_DEFINING_CONTENT;
418                         content_type=true;
419                         // write the field to the header buffer
420                         field.put(_header);
421                         break;
422 
423                     case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
424                         if (_version == HttpVersions.HTTP_1_1_ORDINAL) transfer_encoding = field;
425                         // Do NOT add yet!
426                         break;
427 
428                     case HttpHeaders.CONNECTION_ORDINAL:
429                         if (_method!=null)
430                             field.put(_header);
431                         
432                         int connection_value = field.getValueOrdinal();
433                         switch (connection_value)
434                         {
435                             case -1:
436                             { 
437 
438                                 QuotedStringTokenizer tok = new QuotedStringTokenizer(field.getValue(), ",");
439                                 while(tok.hasMoreTokens())
440                                 {
441                                 	String token=tok.nextToken().trim();
442                                     CachedBuffer cb = HttpHeaderValues.CACHE.get(token);
443                                     if (cb!=null)
444                                     {
445                                         switch(cb.getOrdinal())
446                                         {
447                                             case HttpHeaderValues.CLOSE_ORDINAL:
448                                                 close=true;
449                                                 if (_method==null)
450                                                     _close=true;
451                                                 keep_alive=false;
452                                                 if (_close && _method==null && _contentLength == HttpTokens.UNKNOWN_CONTENT) 
453                                                     _contentLength = HttpTokens.EOF_CONTENT;
454                                                 break;
455 
456                                             case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
457                                                 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
458                                                 {
459                                                     keep_alive = true;
460                                                     if (_method==null) 
461                                                         _close = false;
462                                                 }
463                                                 break;
464                                             
465                                             default:
466                                                 if (connection==null)
467                                                     connection=new StringBuffer();
468                                                 else
469                                                     connection.append(',');
470                                                 connection.append(token);
471                                         }
472                                     }
473                                     else
474                                     {
475                                         if (connection==null)
476                                             connection=new StringBuffer();
477                                         else
478                                             connection.append(',');
479                                         connection.append(token);
480                                     }
481                                 }
482                                 
483                                 break;
484                             }
485                             case HttpHeaderValues.CLOSE_ORDINAL:
486                             {
487                                 close=true;
488                                 if (_method==null)
489                                     _close=true;
490                                 if (_close && _method==null && _contentLength == HttpTokens.UNKNOWN_CONTENT) 
491                                     _contentLength = HttpTokens.EOF_CONTENT;
492                                 break;
493                             }
494                             case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
495                             {
496                                 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
497                                 {
498                                     keep_alive = true;
499                                     if (_method==null) 
500                                         _close = false;
501                                 }
502                                 break;
503                             }
504                             default:
505                             {
506                                 if (connection==null)
507                                     connection=new StringBuffer();
508                                 else
509                                     connection.append(',');
510                                 connection.append(field.getValue());
511                             }
512                         }
513 
514                         // Do NOT add yet!
515                         break;
516 
517                     case HttpHeaders.SERVER_ORDINAL:
518                         if (getSendServerVersion()) 
519                         {
520                             has_server=true;
521                             field.put(_header);
522                         }
523                         break;
524 
525                     default:
526                         // write the field to the header buffer
527                         field.put(_header);
528                 }
529             }
530         }
531 
532         // Calculate how to end _content and connection, _content length and transfer encoding
533         // settings.
534         // From RFC 2616 4.4:
535         // 1. No body for 1xx, 204, 304 & HEAD response
536         // 2. Force _content-length?
537         // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
538         // 4. Content-Length
539         // 5. multipart/byteranges
540         // 6. close
541         switch ((int) _contentLength)
542         {
543             case HttpTokens.UNKNOWN_CONTENT:
544                 // It may be that we have no _content, or perhaps _content just has not been
545                 // written yet?
546 
547                 // Response known not to have a body
548                 if (_contentWritten == 0 && _method==null && (_status < 200 || _status == 204 || _status == 304))
549                     _contentLength = HttpTokens.NO_CONTENT;
550                 else if (_last)
551                 {
552                     // we have seen all the _content there is
553                     _contentLength = _contentWritten;
554                     if (content_length == null && (_method==null || content_type || _contentLength>0))
555                     {
556                         // known length but not actually set.
557                         _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER);
558                         _header.put(HttpTokens.COLON);
559                         _header.put((byte) ' ');
560                         BufferUtil.putDecLong(_header, _contentLength);
561                         _header.put(HttpTokens.CRLF);
562                     }
563                 }
564                 else
565                 {
566                     // No idea, so we must assume that a body is coming
567                     _contentLength = (_close || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT;
568                     if (_method!=null && _contentLength==HttpTokens.EOF_CONTENT)
569                     {
570                         _contentLength=HttpTokens.NO_CONTENT;
571                         _noContent=true;
572                     }
573                 }
574                 break;
575 
576             case HttpTokens.NO_CONTENT:
577                 if (content_length == null && _method==null && _status >= 200 && _status != 204 && _status != 304) 
578                     _header.put(CONTENT_LENGTH_0);
579                 break;
580 
581             case HttpTokens.EOF_CONTENT:
582                 _close = _method==null;
583                 break;
584 
585             case HttpTokens.CHUNKED_CONTENT:
586                 break;
587 
588             default:
589                 // TODO - maybe allow forced chunking by setting te ???
590                 break;
591         }
592 
593         // Add transfer_encoding if needed
594         if (_contentLength == HttpTokens.CHUNKED_CONTENT)
595         {
596             // try to use user supplied encoding as it may have other values.
597             if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal())
598             {
599                 String c = transfer_encoding.getValue();
600                 if (c.endsWith(HttpHeaderValues.CHUNKED))
601                     transfer_encoding.put(_header);
602                 else
603                     throw new IllegalArgumentException("BAD TE");
604             }
605             else
606                 _header.put(TRANSFER_ENCODING_CHUNKED);
607         }
608 
609         // Handle connection if need be
610         if (_contentLength==HttpTokens.EOF_CONTENT)
611         {
612             keep_alive=false;
613             _close=true;
614         }
615                
616         if (_method==null)
617         {
618             if (_close && (close || _version > HttpVersions.HTTP_1_0_ORDINAL))
619             {
620                 _header.put(CONNECTION_CLOSE);
621                 if (connection!=null)
622                 {
623                     _header.setPutIndex(_header.putIndex()-2);
624                     _header.put((byte)',');
625                     _header.put(connection.toString().getBytes());
626                     _header.put(CRLF);
627                 }
628             }
629             else if (keep_alive)
630             {
631                 _header.put(CONNECTION_KEEP_ALIVE);
632                 if (connection!=null)
633                 {
634                     _header.setPutIndex(_header.putIndex()-2);
635                     _header.put((byte)',');
636                     _header.put(connection.toString().getBytes());
637                     _header.put(CRLF);
638                 }
639             }
640             else if (connection!=null)
641             {
642                 _header.put(CONNECTION_);
643                 _header.put(connection.toString().getBytes());
644                 _header.put(CRLF);
645             }
646         }
647         
648         if (!has_server && _status>100 && getSendServerVersion())
649             _header.put(SERVER);
650 
651         // end the header.
652         _header.put(HttpTokens.CRLF);
653 
654         _state = STATE_CONTENT;
655 
656     }
657 
658     /* ------------------------------------------------------------ */
659     /**
660      * Complete the message.
661      * 
662      * @throws IOException
663      */
664     public void complete() throws IOException
665     {
666         if (_state == STATE_END) 
667             return;
668         
669         super.complete();
670         
671         if (_state < STATE_FLUSHING)
672         {
673             _state = STATE_FLUSHING;
674             if (_contentLength == HttpTokens.CHUNKED_CONTENT) 
675                 _needEOC = true;
676         }
677         
678         flush();
679     }
680 
681     /* ------------------------------------------------------------ */
682     public long flush() throws IOException
683     {
684         try
685         {   
686             if (_state == STATE_HEADER) 
687                 throw new IllegalStateException("State==HEADER");
688             
689             prepareBuffers();
690             
691             if (_endp == null)
692             {
693                 if (_needCRLF && _buffer!=null) 
694                     _buffer.put(HttpTokens.CRLF);
695                 if (_needEOC && _buffer!=null && !_head) 
696                     _buffer.put(LAST_CHUNK);
697                 _needCRLF=false;
698                 _needEOC=false;
699                 return 0;
700             }
701             
702             // Keep flushing while there is something to flush (except break below)
703             int total= 0;
704             long last_len = -1;
705             Flushing: while (true)
706             {
707                 int len = -1;
708                 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);
709                 switch (to_flush)
710                 {
711                     case 7:
712                         throw new IllegalStateException(); // should never happen!
713                     case 6:
714                         len = _endp.flush(_header, _buffer, null);
715                         break;
716                     case 5:
717                         len = _endp.flush(_header, _content, null);
718                         break;
719                     case 4:
720                         len = _endp.flush(_header);
721                         break;
722                     case 3:
723                         throw new IllegalStateException(); // should never happen!
724                     case 2:
725                         len = _endp.flush(_buffer);
726                         break;
727                     case 1:
728                         len = _endp.flush(_content);
729                         break;
730                     case 0:
731                     {
732                         // Nothing more we can write now.
733                         if (_header != null) 
734                             _header.clear();
735                         
736                         _bypass = false;
737                         _bufferChunked = false;
738                         
739                         if (_buffer != null)
740                         {
741                             _buffer.clear();
742                             if (_contentLength == HttpTokens.CHUNKED_CONTENT)
743                             {
744                                 // reserve some space for the chunk header
745                                 _buffer.setPutIndex(CHUNK_SPACE);
746                                 _buffer.setGetIndex(CHUNK_SPACE);
747                                 
748                                 // Special case handling for small left over buffer from
749                                 // an addContent that caused a buffer flush.
750                                 if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING)
751                                 {
752                                     _buffer.put(_content);
753                                     _content.clear();
754                                     _content = null;
755                                     break Flushing;
756                                 }
757                             }
758                         }
759                         
760                         // Are we completely finished for now?
761                         if (!_needCRLF && !_needEOC && (_content == null || _content.length() == 0))
762                         {
763                             if (_state == STATE_FLUSHING)
764                                 _state = STATE_END;
765                             if (_state==STATE_END && _close && _status!=100) 
766                                 _endp.shutdownOutput();
767                             
768                             break Flushing;
769                         }
770                         
771                         // Try to prepare more to write.
772                         prepareBuffers();
773                     }
774                 }
775                 
776                 
777                 // break If we failed to flush
778                 if (len > 0)
779                     total+=len;
780                 else 
781                     break Flushing;
782           
783                 last_len = len;
784             }
785             
786             return total;
787         }
788         catch (IOException e)
789         {
790             Log.ignore(e);
791             throw (e instanceof EofException) ? e:new EofException(e);
792         }
793     }
794 
795     /* ------------------------------------------------------------ */
796     private void prepareBuffers()
797     {
798         // if we are not flushing an existing chunk
799         if (!_bufferChunked)
800         {
801             // Refill buffer if possible
802             if (_content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
803             {
804                 int len = _buffer.put(_content);
805                 _content.skip(len);
806                 if (_content.length() == 0) 
807                     _content = null;
808             }
809 
810             // Chunk buffer if need be
811             if (_contentLength == HttpTokens.CHUNKED_CONTENT)
812             {
813                 int size = _buffer == null ? 0 : _buffer.length();
814                 if (size > 0)
815                 {
816                     // Prepare a chunk!
817                     _bufferChunked = true;
818 
819                     // Did we leave space at the start of the buffer.
820                     if (_buffer.getIndex() == CHUNK_SPACE)
821                     {
822                         // Oh yes, goodie! let's use it then!
823                         _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
824                         _buffer.setGetIndex(_buffer.getIndex() - 2);
825                         BufferUtil.prependHexInt(_buffer, size);
826 
827                         if (_needCRLF)
828                         {
829                             _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
830                             _buffer.setGetIndex(_buffer.getIndex() - 2);
831                             _needCRLF = false;
832                         }
833                     }
834                     else
835                     {
836                         // No space so lets use the header buffer.
837                         if (_needCRLF)
838                         {
839                             if (_header.length() > 0) throw new IllegalStateException("EOC");
840                             _header.put(HttpTokens.CRLF);
841                             _needCRLF = false;
842                         }
843                         BufferUtil.putHexInt(_header, size);
844                         _header.put(HttpTokens.CRLF);
845                     }
846 
847                     // Add end chunk trailer.
848                     if (_buffer.space() >= 2)
849                         _buffer.put(HttpTokens.CRLF);
850                     else
851                         _needCRLF = true;
852                 }
853 
854                 // If we need EOC and everything written
855                 if (_needEOC && (_content == null || _content.length() == 0))
856                 {
857                     if (_needCRLF)
858                     {
859                         if (_buffer == null && _header.space() >= 2)
860                         {
861                             _header.put(HttpTokens.CRLF);
862                             _needCRLF = false;
863                         }
864                         else if (_buffer!=null && _buffer.space() >= 2)
865                         {
866                             _buffer.put(HttpTokens.CRLF);
867                             _needCRLF = false;
868                         }
869                     }
870 
871                     if (!_needCRLF && _needEOC)
872                     {
873                         if (_buffer == null && _header.space() >= LAST_CHUNK.length)
874                         {
875                             if (!_head)
876                             {
877                                 _header.put(LAST_CHUNK);
878                                 _bufferChunked=true;
879                             }
880                             _needEOC = false;
881                         }
882                         else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length)
883                         {
884                             if (!_head)
885                             {
886                                 _buffer.put(LAST_CHUNK);
887                                 _bufferChunked=true;
888                             }
889                             _needEOC = false;
890                         }
891                     }
892                 }
893             }
894         }
895 
896         if (_content != null && _content.length() == 0) 
897             _content = null;
898 
899     }
900 
901     public int getBytesBuffered()
902     {
903         return(_header==null?0:_header.length())+
904         (_buffer==null?0:_buffer.length())+
905         (_content==null?0:_content.length());
906     }
907 
908     public boolean isEmpty()
909     {
910         return (_header==null||_header.length()==0) &&
911         (_buffer==null||_buffer.length()==0) &&
912         (_content==null||_content.length()==0);
913     }
914     
915     public String toString()
916     {
917         return "HttpGenerator s="+_state+
918         " h="+(_header==null?"null":(""+_header.length()))+
919         " b="+(_buffer==null?"null":(""+_buffer.length()))+
920         " c="+(_content==null?"null":(""+_content.length()));
921     }
922 }