View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.http;
20  
21  import java.io.IOException;
22  import java.nio.BufferOverflowException;
23  import java.nio.ByteBuffer;
24  
25  import org.eclipse.jetty.http.HttpTokens.EndOfContent;
26  import org.eclipse.jetty.util.BufferUtil;
27  import org.eclipse.jetty.util.StringUtil;
28  import org.eclipse.jetty.util.log.Log;
29  import org.eclipse.jetty.util.log.Logger;
30  
31  /* ------------------------------------------------------------ */
32  /**
33   * HttpGenerator. Builds HTTP Messages.
34   *
35   */
36  public class HttpGenerator
37  {
38      private static final Logger LOG = Log.getLogger(HttpGenerator.class);
39  
40      public static final ResponseInfo CONTINUE_100_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,100,null,false);
41      public static final ResponseInfo PROGRESS_102_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,102,null,false);
42      public final static ResponseInfo RESPONSE_500_INFO =
43          new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0,HttpStatus.INTERNAL_SERVER_ERROR_500,null,false);
44  
45      // states
46      public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END }
47      public enum Result { NEED_CHUNK,NEED_INFO,NEED_HEADER,FLUSH,CONTINUE,SHUTDOWN_OUT,DONE}
48  
49      // other statics
50      public static final int CHUNK_SIZE = 12;
51  
52      private State _state = State.START;
53      private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;
54  
55      private long _contentPrepared = 0;
56      private boolean _noContent = false;
57      private Boolean _persistent = null;
58      private boolean _sendServerVersion;
59  
60  
61      /* ------------------------------------------------------------------------------- */
62      public static void setServerVersion(String version)
63      {
64          SERVER=StringUtil.getBytes("Server: Jetty("+version+")\015\012");
65      }
66  
67      /* ------------------------------------------------------------------------------- */
68      // data
69      private boolean _needCRLF = false;
70  
71      /* ------------------------------------------------------------------------------- */
72      public HttpGenerator()
73      {
74      }
75  
76      /* ------------------------------------------------------------------------------- */
77      public void reset()
78      {
79          _state = State.START;
80          _endOfContent = EndOfContent.UNKNOWN_CONTENT;
81          _noContent=false;
82          _persistent = null;
83          _contentPrepared = 0;
84          _needCRLF = false;
85          _noContent=false;
86      }
87  
88      /* ------------------------------------------------------------ */
89      public boolean getSendServerVersion ()
90      {
91          return _sendServerVersion;
92      }
93  
94      /* ------------------------------------------------------------ */
95      public void setSendServerVersion (boolean sendServerVersion)
96      {
97          _sendServerVersion = sendServerVersion;
98      }
99  
100     /* ------------------------------------------------------------ */
101     public State getState()
102     {
103         return _state;
104     }
105 
106     /* ------------------------------------------------------------ */
107     public boolean isState(State state)
108     {
109         return _state == state;
110     }
111 
112     /* ------------------------------------------------------------ */
113     public boolean isIdle()
114     {
115         return _state == State.START;
116     }
117 
118     /* ------------------------------------------------------------ */
119     public boolean isEnd()
120     {
121         return _state == State.END;
122     }
123 
124     /* ------------------------------------------------------------ */
125     public boolean isCommitted()
126     {
127         return _state.ordinal() >= State.COMMITTED.ordinal();
128     }
129 
130     /* ------------------------------------------------------------ */
131     public boolean isChunking()
132     {
133         return _endOfContent==EndOfContent.CHUNKED_CONTENT;
134     }
135 
136     /* ------------------------------------------------------------ */
137     public void setPersistent(boolean persistent)
138     {
139         _persistent=persistent;
140     }
141 
142     /* ------------------------------------------------------------ */
143     /**
144      * @return true if known to be persistent
145      */
146     public boolean isPersistent()
147     {
148         return Boolean.TRUE.equals(_persistent);
149     }
150 
151     /* ------------------------------------------------------------ */
152     public boolean isWritten()
153     {
154         return _contentPrepared>0;
155     }
156 
157     /* ------------------------------------------------------------ */
158     public long getContentPrepared()
159     {
160         return _contentPrepared;
161     }
162 
163     /* ------------------------------------------------------------ */
164     public void abort()
165     {
166         _persistent=false;
167         _state=State.END;
168         _endOfContent=null;
169     }
170 
171     /* ------------------------------------------------------------ */
172     public Result generateRequest(RequestInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
173     {
174         switch(_state)
175         {
176             case START:
177             {
178                 if (info==null)
179                     return Result.NEED_INFO;
180 
181                 // Do we need a request header
182                 if (header==null)
183                     return Result.NEED_HEADER;
184 
185                 // If we have not been told our persistence, set the default
186                 if (_persistent==null)
187                     _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
188 
189                 // prepare the header
190                 int pos=BufferUtil.flipToFill(header);
191                 try
192                 {
193                     // generate ResponseLine
194                     generateRequestLine(info,header);
195 
196                     if (info.getHttpVersion()==HttpVersion.HTTP_0_9)
197                         _noContent=true;
198                     else
199                         generateHeaders(info,header,content,last);
200 
201                     boolean expect100 = info.getHttpFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
202 
203                     if (expect100)
204                     {
205                         _state = State.COMMITTED;
206                     }
207                     else
208                     {
209                         // handle the content.
210                         int len = BufferUtil.length(content);
211                         if (len>0)
212                         {
213                             _contentPrepared+=len;
214                             if (isChunking())
215                                 prepareChunk(header,len);
216                         }
217                         _state = last?State.COMPLETING:State.COMMITTED;
218                     }
219 
220                     return Result.FLUSH;
221                 }
222                 catch(Exception e)
223                 {
224                     String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
225                     throw new IOException(message,e);
226                 }
227                 finally
228                 {
229                     BufferUtil.flipToFlush(header,pos);
230                 }
231             }
232 
233             case COMMITTED:
234             {
235                 int len = BufferUtil.length(content);
236 
237                 if (len>0)
238                 {
239                     // Do we need a chunk buffer?
240                     if (isChunking())
241                     {
242                         // Do we need a chunk buffer?
243                         if (chunk==null)
244                             return Result.NEED_CHUNK;
245                         BufferUtil.clearToFill(chunk);
246                         prepareChunk(chunk,len);
247                         BufferUtil.flipToFlush(chunk,0);
248                     }
249                     _contentPrepared+=len;
250                 }
251 
252                 if (last)
253                 {
254                     _state=State.COMPLETING;
255                     return len>0?Result.FLUSH:Result.CONTINUE;
256                 }
257 
258                 return Result.FLUSH;
259             }
260 
261             case COMPLETING:
262             {
263                 if (BufferUtil.hasContent(content))
264                 {
265                     LOG.debug("discarding content in COMPLETING");
266                     BufferUtil.clear(content);
267                 }
268 
269                 if (isChunking())
270                 {
271                     // Do we need a chunk buffer?
272                     if (chunk==null)
273                         return Result.NEED_CHUNK;
274                     BufferUtil.clearToFill(chunk);
275                     prepareChunk(chunk,0);
276                     BufferUtil.flipToFlush(chunk,0);
277                     _endOfContent=EndOfContent.UNKNOWN_CONTENT;
278                     return Result.FLUSH;
279                 }
280 
281                 _state=State.END;
282                return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
283             }
284 
285             case END:
286                 if (BufferUtil.hasContent(content))
287                 {
288                     LOG.debug("discarding content in COMPLETING");
289                     BufferUtil.clear(content);
290                 }
291                 return Result.DONE;
292 
293             default:
294                 throw new IllegalStateException();
295         }
296     }
297 
298     /* ------------------------------------------------------------ */
299     public Result generateResponse(ResponseInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
300     {
301         switch(_state)
302         {
303             case START:
304             {
305                 if (info==null)
306                     return Result.NEED_INFO;
307 
308                 // Handle 0.9
309                 if (info.getHttpVersion() == HttpVersion.HTTP_0_9)
310                 {
311                     _persistent = false;
312                     _endOfContent=EndOfContent.EOF_CONTENT;
313                     if (BufferUtil.hasContent(content))
314                         _contentPrepared+=content.remaining();
315                     _state = last?State.COMPLETING:State.COMMITTED;
316                     return Result.FLUSH;
317                 }
318 
319                 // Do we need a response header
320                 if (header==null)
321                     return Result.NEED_HEADER;
322 
323                 // If we have not been told our persistence, set the default
324                 if (_persistent==null)
325                     _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
326 
327                 // prepare the header
328                 int pos=BufferUtil.flipToFill(header);
329                 try
330                 {
331                     // generate ResponseLine
332                     generateResponseLine(info,header);
333 
334                     // Handle 1xx and no content responses
335                     int status=info.getStatus();
336                     if (status>=100 && status<200 )
337                     {
338                         _noContent=true;
339 
340                         if (status!=HttpStatus.SWITCHING_PROTOCOLS_101 )
341                         {
342                             header.put(HttpTokens.CRLF);
343                             _state=State.COMPLETING_1XX;
344                             return Result.FLUSH;
345                         }
346                     }
347                     else if (status==HttpStatus.NO_CONTENT_204 || status==HttpStatus.NOT_MODIFIED_304)
348                     {
349                         _noContent=true;
350                     }
351 
352                     generateHeaders(info,header,content,last);
353 
354                     // handle the content.
355                     int len = BufferUtil.length(content);
356                     if (len>0)
357                     {
358                         _contentPrepared+=len;
359                         if (isChunking() && !info.isHead())
360                             prepareChunk(header,len);
361                     }
362                     _state = last?State.COMPLETING:State.COMMITTED;
363                 }
364                 catch(Exception e)
365                 {
366                     String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
367                     throw new IOException(message,e);
368                 }
369                 finally
370                 {
371                     BufferUtil.flipToFlush(header,pos);
372                 }
373 
374                 return Result.FLUSH;
375             }
376 
377             case COMMITTED:
378             {
379                 int len = BufferUtil.length(content);
380 
381                 // handle the content.
382                 if (len>0)
383                 {
384                     if (isChunking())
385                     {
386                         if (chunk==null)
387                             return Result.NEED_CHUNK;
388                         BufferUtil.clearToFill(chunk);
389                         prepareChunk(chunk,len);
390                         BufferUtil.flipToFlush(chunk,0);
391                     }
392                     _contentPrepared+=len;
393                 }
394 
395                 if (last)
396                 {
397                     _state=State.COMPLETING;
398                     return len>0?Result.FLUSH:Result.CONTINUE;
399                 }
400                 return len>0?Result.FLUSH:Result.DONE;
401 
402             }
403 
404             case COMPLETING_1XX:
405             {
406                 reset();
407                 return Result.DONE;
408             }
409 
410             case COMPLETING:
411             {
412                 if (BufferUtil.hasContent(content))
413                 {
414                     LOG.debug("discarding content in COMPLETING");
415                     BufferUtil.clear(content);
416                 }
417 
418                 if (isChunking())
419                 {
420                     // Do we need a chunk buffer?
421                     if (chunk==null)
422                         return Result.NEED_CHUNK;
423 
424                     // Write the last chunk
425                     BufferUtil.clearToFill(chunk);
426                     prepareChunk(chunk,0);
427                     BufferUtil.flipToFlush(chunk,0);
428                     _endOfContent=EndOfContent.UNKNOWN_CONTENT;
429                     return Result.FLUSH;
430                 }
431 
432                 _state=State.END;
433 
434                return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
435             }
436 
437             case END:
438                 if (BufferUtil.hasContent(content))
439                 {
440                     LOG.debug("discarding content in COMPLETING");
441                     BufferUtil.clear(content);
442                 }
443                 return Result.DONE;
444 
445             default:
446                 throw new IllegalStateException();
447         }
448     }
449 
450     /* ------------------------------------------------------------ */
451     private void prepareChunk(ByteBuffer chunk, int remaining)
452     {
453         // if we need CRLF add this to header
454         if (_needCRLF)
455             BufferUtil.putCRLF(chunk);
456 
457         // Add the chunk size to the header
458         if (remaining>0)
459         {
460             BufferUtil.putHexInt(chunk, remaining);
461             BufferUtil.putCRLF(chunk);
462             _needCRLF=true;
463         }
464         else
465         {
466             chunk.put(LAST_CHUNK);
467             _needCRLF=false;
468         }
469     }
470 
471     /* ------------------------------------------------------------ */
472     private void generateRequestLine(RequestInfo request,ByteBuffer header)
473     {
474         header.put(StringUtil.getBytes(request.getMethod()));
475         header.put((byte)' ');
476         header.put(StringUtil.getBytes(request.getUri()));
477         switch(request.getHttpVersion())
478         {
479             case HTTP_1_0:
480             case HTTP_1_1:
481                 header.put((byte)' ');
482                 header.put(request.getHttpVersion().toBytes());
483         }
484         header.put(HttpTokens.CRLF);
485     }
486 
487     /* ------------------------------------------------------------ */
488     private void generateResponseLine(ResponseInfo response, ByteBuffer header)
489     {
490         // Look for prepared response line
491         int status=response.getStatus();
492         PreparedResponse preprepared = status<__preprepared.length?__preprepared[status]:null;
493         String reason=response.getReason();
494         if (preprepared!=null)
495         {
496             if (reason==null)
497                 header.put(preprepared._responseLine);
498             else
499             {
500                 header.put(preprepared._schemeCode);
501                 header.put(getReasonBytes(reason));
502                 header.put(HttpTokens.CRLF);
503             }
504         }
505         else // generate response line
506         {
507             header.put(HTTP_1_1_SPACE);
508             header.put((byte) ('0' + status / 100));
509             header.put((byte) ('0' + (status % 100) / 10));
510             header.put((byte) ('0' + (status % 10)));
511             header.put((byte) ' ');
512             if (reason==null)
513             {
514                 header.put((byte) ('0' + status / 100));
515                 header.put((byte) ('0' + (status % 100) / 10));
516                 header.put((byte) ('0' + (status % 10)));
517             }
518             else
519                 header.put(getReasonBytes(reason));
520             header.put(HttpTokens.CRLF);
521         }
522     }
523 
524     /* ------------------------------------------------------------ */
525     private byte[] getReasonBytes(String reason)
526     {
527         if (reason.length()>1024)
528             reason=reason.substring(0,1024);
529         byte[] _bytes = StringUtil.getBytes(reason);
530 
531         for (int i=_bytes.length;i-->0;)
532             if (_bytes[i]=='\r' || _bytes[i]=='\n')
533                 _bytes[i]='?';
534         return _bytes;
535     }
536 
537     /* ------------------------------------------------------------ */
538     private void generateHeaders(Info _info,ByteBuffer header,ByteBuffer content,boolean last)
539     {
540         final RequestInfo _request=(_info instanceof RequestInfo)?(RequestInfo)_info:null;
541         final ResponseInfo _response=(_info instanceof ResponseInfo)?(ResponseInfo)_info:null;
542 
543         // default field values
544         boolean has_server = false;
545         HttpField transfer_encoding=null;
546         boolean keep_alive=false;
547         boolean close=false;
548         boolean content_type=false;
549         StringBuilder connection = null;
550 
551         // Generate fields
552         if (_info.getHttpFields() != null)
553         {
554             for (HttpField field : _info.getHttpFields())
555             {
556                 HttpHeader h = field.getHeader();
557 
558                 switch (h==null?HttpHeader.UNKNOWN:h)
559                 {
560                     case CONTENT_LENGTH:
561                         // handle specially below
562                         if (_info.getContentLength()>=0)
563                             _endOfContent=EndOfContent.CONTENT_LENGTH;
564                         break;
565 
566                     case CONTENT_TYPE:
567                     {
568                         if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString()))
569                             _endOfContent=EndOfContent.SELF_DEFINING_CONTENT;
570 
571                         // write the field to the header
572                         content_type=true;
573                         field.putTo(header);
574                         break;
575                     }
576 
577                     case TRANSFER_ENCODING:
578                     {
579                         if (_info.getHttpVersion() == HttpVersion.HTTP_1_1)
580                             transfer_encoding = field;
581                         // Do NOT add yet!
582                         break;
583                     }
584 
585                     case CONNECTION:
586                     {
587                         if (_request!=null)
588                             field.putTo(header);
589 
590                         // Lookup and/or split connection value field
591                         HttpHeaderValue[] values = new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
592                         String[] split = null;
593 
594                         if (values[0]==null)
595                         {
596                             split = field.getValue().split("\\s*,\\s*");
597                             if (split.length>0)
598                             {
599                                 values=new HttpHeaderValue[split.length];
600                                 for (int i=0;i<split.length;i++)
601                                     values[i]=HttpHeaderValue.CACHE.get(split[i]);
602                             }
603                         }
604 
605                         // Handle connection values
606                         for (int i=0;i<values.length;i++)
607                         {
608                             HttpHeaderValue value=values[i];
609                             switch (value==null?HttpHeaderValue.UNKNOWN:value)
610                             {
611                                 case UPGRADE:
612                                 {
613                                     // special case for websocket connection ordering
614                                     header.put(HttpHeader.CONNECTION.getBytesColonSpace()).put(HttpHeader.UPGRADE.getBytes());
615                                     header.put(CRLF);
616                                     break;
617                                 }
618 
619                                 case CLOSE:
620                                 {
621                                     close=true;
622                                     if (_response!=null)
623                                     {
624                                         _persistent=false;
625                                         if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
626                                             _endOfContent=EndOfContent.EOF_CONTENT;
627                                     }
628                                     break;
629                                 }
630 
631                                 case KEEP_ALIVE:
632                                 {
633                                     if (_info.getHttpVersion() == HttpVersion.HTTP_1_0)
634                                     {
635                                         keep_alive = true;
636                                         if (_response!=null)
637                                             _persistent=true;
638                                     }
639                                     break;
640                                 }
641 
642                                 default:
643                                 {
644                                     if (connection==null)
645                                         connection=new StringBuilder();
646                                     else
647                                         connection.append(',');
648                                     connection.append(split==null?field.getValue():split[i]);
649                                 }
650                             }
651                         }
652 
653                         // Do NOT add yet!
654                         break;
655                     }
656 
657                     case SERVER:
658                     {
659                         if (getSendServerVersion())
660                         {
661                             has_server=true;
662                             field.putTo(header);
663                         }
664                         break;
665                     }
666 
667                     default:
668                         field.putTo(header);
669                 }
670             }
671         }
672 
673 
674         // Calculate how to end _content and connection, _content length and transfer encoding
675         // settings.
676         // From RFC 2616 4.4:
677         // 1. No body for 1xx, 204, 304 & HEAD response
678         // 2. Force _content-length?
679         // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
680         // 4. Content-Length
681         // 5. multipart/byteranges
682         // 6. close
683         int status=_response!=null?_response.getStatus():-1;
684         switch (_endOfContent)
685         {
686             case UNKNOWN_CONTENT:
687                 // It may be that we have no _content, or perhaps _content just has not been
688                 // written yet?
689 
690                 // Response known not to have a body
691                 if (_contentPrepared == 0 && _response!=null && (status < 200 || status == 204 || status == 304))
692                     _endOfContent=EndOfContent.NO_CONTENT;
693                 else if (_info.getContentLength()>0)
694                 {
695                     // we have been given a content length
696                     _endOfContent=EndOfContent.CONTENT_LENGTH;
697                     long content_length = _info.getContentLength();
698                     if ((_response!=null || content_length>0 || content_type ) && !_noContent)
699                     {
700                         // known length but not actually set.
701                         header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
702                         BufferUtil.putDecLong(header, content_length);
703                         header.put(HttpTokens.CRLF);
704                     }
705                 }
706                 else if (last)
707                 {
708                     // we have seen all the _content there is, so we can be content-length limited.
709                     _endOfContent=EndOfContent.CONTENT_LENGTH;
710                     long content_length = _contentPrepared+BufferUtil.length(content);
711 
712                     // Do we need to tell the headers about it
713                     if ((_response!=null || content_length>0 || content_type ) && !_noContent)
714                     {
715                         header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
716                         BufferUtil.putDecLong(header, content_length);
717                         header.put(HttpTokens.CRLF);
718                     }
719                 }
720                 else
721                 {
722                     // No idea, so we must assume that a body is coming
723                     _endOfContent = (!isPersistent() || _info.getHttpVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal() ) ? EndOfContent.EOF_CONTENT : EndOfContent.CHUNKED_CONTENT;
724                     if (_response!=null && _endOfContent==EndOfContent.EOF_CONTENT)
725                     {
726                         _endOfContent=EndOfContent.NO_CONTENT;
727                         _noContent=true;
728                     }
729                 }
730                 break;
731 
732             case CONTENT_LENGTH:
733                 long content_length = _info.getContentLength();
734                 if ((_response!=null || content_length>0 || content_type ) && !_noContent)
735                 {
736                     // known length but not actually set.
737                     header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
738                     BufferUtil.putDecLong(header, content_length);
739                     header.put(HttpTokens.CRLF);
740                 }
741                 break;
742 
743             case NO_CONTENT:
744                 if (_response!=null && status >= 200 && status != 204 && status != 304)
745                     header.put(CONTENT_LENGTH_0);
746                 break;
747 
748             case EOF_CONTENT:
749                 _persistent = _request!=null;
750                 break;
751 
752             case CHUNKED_CONTENT:
753                 break;
754 
755             default:
756                 break;
757         }
758 
759         // Add transfer_encoding if needed
760         if (isChunking())
761         {
762             // try to use user supplied encoding as it may have other values.
763             if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue()))
764             {
765                 String c = transfer_encoding.getValue();
766                 if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
767                     transfer_encoding.putTo(header);
768                 else
769                     throw new IllegalArgumentException("BAD TE");
770             }
771             else
772                 header.put(TRANSFER_ENCODING_CHUNKED);
773         }
774 
775         // Handle connection if need be
776         if (_endOfContent==EndOfContent.EOF_CONTENT)
777         {
778             keep_alive=false;
779             _persistent=false;
780         }
781 
782         // If this is a response, work out persistence
783         if (_response!=null)
784         {
785             if (!isPersistent() && (close || _info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()))
786             {
787                 if (connection==null)
788                     header.put(CONNECTION_CLOSE);
789                 else
790                 {
791                     header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2);
792                     header.put((byte)',');
793                     header.put(StringUtil.getBytes(connection.toString()));
794                     header.put(CRLF);
795                 }
796             }
797             else if (keep_alive)
798             {
799                 if (connection==null)
800                     header.put(CONNECTION_KEEP_ALIVE);
801                 else
802                 {
803                     header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_CLOSE.length-2);
804                     header.put((byte)',');
805                     header.put(StringUtil.getBytes(connection.toString()));
806                     header.put(CRLF);
807                 }
808             }
809             else if (connection!=null)
810             {
811                 header.put(CONNECTION_);
812                 header.put(StringUtil.getBytes(connection.toString()));
813                 header.put(CRLF);
814             }
815         }
816 
817         if (!has_server && status>199 && getSendServerVersion())
818             header.put(SERVER);
819 
820         // end the header.
821         header.put(HttpTokens.CRLF);
822     }
823 
824     /* ------------------------------------------------------------------------------- */
825     public static byte[] getReasonBuffer(int code)
826     {
827         PreparedResponse status = code<__preprepared.length?__preprepared[code]:null;
828         if (status!=null)
829             return status._reason;
830         return null;
831     }
832 
833     /* ------------------------------------------------------------------------------- */
834     @Override
835     public String toString()
836     {
837         return String.format("%s{s=%s}",
838                 getClass().getSimpleName(),
839                 _state);
840     }
841 
842     /* ------------------------------------------------------------------------------- */
843     /* ------------------------------------------------------------------------------- */
844     /* ------------------------------------------------------------------------------- */
845     // common _content
846     private static final byte[] LAST_CHUNK =    { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
847     private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
848     private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
849     private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
850     private static final byte[] CONNECTION_ = StringUtil.getBytes("Connection: ");
851     private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
852     private static final byte[] CRLF = StringUtil.getBytes("\015\012");
853     private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
854     private static byte[] SERVER = StringUtil.getBytes("Server: Jetty(7.0.x)\015\012");
855 
856     /* ------------------------------------------------------------------------------- */
857     /* ------------------------------------------------------------------------------- */
858     /* ------------------------------------------------------------------------------- */
859     // Build cache of response lines for status
860     private static class PreparedResponse
861     {
862         byte[] _reason;
863         byte[] _schemeCode;
864         byte[] _responseLine;
865     }
866     private static final PreparedResponse[] __preprepared = new PreparedResponse[HttpStatus.MAX_CODE+1];
867     static
868     {
869         int versionLength=HttpVersion.HTTP_1_1.toString().length();
870 
871         for (int i=0;i<__preprepared.length;i++)
872         {
873             HttpStatus.Code code = HttpStatus.getCode(i);
874             if (code==null)
875                 continue;
876             String reason=code.getMessage();
877             byte[] line=new byte[versionLength+5+reason.length()+2];
878             HttpVersion.HTTP_1_1.toBuffer().get(line,0,versionLength);
879             line[versionLength+0]=' ';
880             line[versionLength+1]=(byte)('0'+i/100);
881             line[versionLength+2]=(byte)('0'+(i%100)/10);
882             line[versionLength+3]=(byte)('0'+(i%10));
883             line[versionLength+4]=' ';
884             for (int j=0;j<reason.length();j++)
885                 line[versionLength+5+j]=(byte)reason.charAt(j);
886             line[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN;
887             line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
888 
889             __preprepared[i] = new PreparedResponse();
890             __preprepared[i]._reason=new byte[line.length-versionLength-7] ;
891             System.arraycopy(line,versionLength+5,__preprepared[i]._reason,0,line.length-versionLength-7);
892             __preprepared[i]._schemeCode=new byte[versionLength+5];
893             System.arraycopy(line,0,__preprepared[i]._schemeCode,0,versionLength+5);
894             __preprepared[i]._responseLine=line;
895         }
896     }
897 
898     public static class Info
899     {
900         final HttpVersion _httpVersion;
901         final HttpFields _httpFields;
902         final long _contentLength;
903 
904         private Info(HttpVersion httpVersion, HttpFields httpFields, long contentLength)
905         {
906             _httpVersion = httpVersion;
907             _httpFields = httpFields;
908             _contentLength = contentLength;
909         }
910 
911         public HttpVersion getHttpVersion()
912         {
913             return _httpVersion;
914         }
915         public HttpFields getHttpFields()
916         {
917             return _httpFields;
918         }
919         public long getContentLength()
920         {
921             return _contentLength;
922         }
923     }
924 
925     public static class RequestInfo extends Info
926     {
927         private final String _method;
928         private final String _uri;
929 
930         public RequestInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, String method, String uri)
931         {
932             super(httpVersion,httpFields,contentLength);
933             _method = method;
934             _uri = uri;
935         }
936 
937         public String getMethod()
938         {
939             return _method;
940         }
941 
942         public String getUri()
943         {
944             return _uri;
945         }
946 
947         @Override
948         public String toString()
949         {
950             return String.format("RequestInfo{%s %s %s,%d}",_method,_uri,_httpVersion,_contentLength);
951         }
952     }
953 
954     public static class ResponseInfo extends Info
955     {
956         private final int _status;
957         private final String _reason;
958         private final boolean _head;
959 
960         public ResponseInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, int status, String reason, boolean head)
961         {
962             super(httpVersion,httpFields,contentLength);
963             _status = status;
964             _reason = reason;
965             _head = head;
966         }
967 
968         public boolean isInformational()
969         {
970             return _status>=100 && _status<200;
971         }
972 
973         public int getStatus()
974         {
975             return _status;
976         }
977 
978         public String getReason()
979         {
980             return _reason;
981         }
982 
983         public boolean isHead()
984         {
985             return _head;
986         }
987 
988         @Override
989         public String toString()
990         {
991             return String.format("ResponseInfo{%s %s %s,%d,%b}",_httpVersion,_status,_reason,_contentLength,_head);
992         }
993     }
994 }