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