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