]> WPIA git - gigi.git/blobdiff - lib/jetty/org/eclipse/jetty/http/HttpGenerator.java
Importing upstream Jetty jetty-9.2.1.v20140609
[gigi.git] / lib / jetty / org / eclipse / jetty / http / HttpGenerator.java
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpGenerator.java b/lib/jetty/org/eclipse/jetty/http/HttpGenerator.java
new file mode 100644 (file)
index 0000000..a51e4ba
--- /dev/null
@@ -0,0 +1,1104 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.http.HttpTokens.EndOfContent;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * HttpGenerator. Builds HTTP Messages.
+ *
+ * If the system property "org.eclipse.jetty.http.HttpGenerator.STRICT" is set to true,
+ * then the generator will strictly pass on the exact strings received from methods and header
+ * fields.  Otherwise a fast case insensitive string lookup is used that may alter the
+ * case and white space of some methods/headers
+ * </p>
+ */
+public class HttpGenerator
+{
+    private final static Logger LOG = Log.getLogger(HttpGenerator.class);
+
+    public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT"); 
+
+    private final static byte[] __colon_space = new byte[] {':',' '};
+    private final static HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE};
+    public static final ResponseInfo CONTINUE_100_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,100,null,false);
+    public static final ResponseInfo PROGRESS_102_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,102,null,false);
+    public final static ResponseInfo RESPONSE_500_INFO =
+        new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0,HttpStatus.INTERNAL_SERVER_ERROR_500,null,false);
+
+    // states
+    public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END }
+    public enum Result { NEED_CHUNK,NEED_INFO,NEED_HEADER,FLUSH,CONTINUE,SHUTDOWN_OUT,DONE}
+
+    // other statics
+    public static final int CHUNK_SIZE = 12;
+
+    private State _state = State.START;
+    private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;
+
+    private long _contentPrepared = 0;
+    private boolean _noContent = false;
+    private Boolean _persistent = null;
+
+    private final int _send;
+    private final static int SEND_SERVER = 0x01;
+    private final static int SEND_XPOWEREDBY = 0x02;
+
+
+    /* ------------------------------------------------------------------------------- */
+    public static void setJettyVersion(String serverVersion)
+    {
+        SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\015\012");
+        SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\015\012");
+        SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtil.getBytes("Server: " + serverVersion + "\015\012X-Powered-By: " +
+                serverVersion + "\015\012");
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    // data
+    private boolean _needCRLF = false;
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpGenerator()
+    {
+        this(false,false);
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    public HttpGenerator(boolean sendServerVersion,boolean sendXPoweredBy)
+    {
+        _send=(sendServerVersion?SEND_SERVER:0) | (sendXPoweredBy?SEND_XPOWEREDBY:0);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public void reset()
+    {
+        _state = State.START;
+        _endOfContent = EndOfContent.UNKNOWN_CONTENT;
+        _noContent=false;
+        _persistent = null;
+        _contentPrepared = 0;
+        _needCRLF = false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public boolean getSendServerVersion ()
+    {
+        return (_send&SEND_SERVER)!=0;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setSendServerVersion (boolean sendServerVersion)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /* ------------------------------------------------------------ */
+    public State getState()
+    {
+        return _state;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isState(State state)
+    {
+        return _state == state;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIdle()
+    {
+        return _state == State.START;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isEnd()
+    {
+        return _state == State.END;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isCommitted()
+    {
+        return _state.ordinal() >= State.COMMITTED.ordinal();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isChunking()
+    {
+        return _endOfContent==EndOfContent.CHUNKED_CONTENT;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setPersistent(boolean persistent)
+    {
+        _persistent=persistent;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if known to be persistent
+     */
+    public boolean isPersistent()
+    {
+        return Boolean.TRUE.equals(_persistent);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isWritten()
+    {
+        return _contentPrepared>0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getContentPrepared()
+    {
+        return _contentPrepared;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void abort()
+    {
+        _persistent=false;
+        _state=State.END;
+        _endOfContent=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Result generateRequest(RequestInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
+    {
+        switch(_state)
+        {
+            case START:
+            {
+                if (info==null)
+                    return Result.NEED_INFO;
+
+                // Do we need a request header
+                if (header==null)
+                    return Result.NEED_HEADER;
+
+                // If we have not been told our persistence, set the default
+                if (_persistent==null)
+                    _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
+
+                // prepare the header
+                int pos=BufferUtil.flipToFill(header);
+                try
+                {
+                    // generate ResponseLine
+                    generateRequestLine(info,header);
+
+                    if (info.getHttpVersion()==HttpVersion.HTTP_0_9)
+                        _noContent=true;
+                    else
+                        generateHeaders(info,header,content,last);
+
+                    boolean expect100 = info.getHttpFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
+
+                    if (expect100)
+                    {
+                        _state = State.COMMITTED;
+                    }
+                    else
+                    {
+                        // handle the content.
+                        int len = BufferUtil.length(content);
+                        if (len>0)
+                        {
+                            _contentPrepared+=len;
+                            if (isChunking())
+                                prepareChunk(header,len);
+                        }
+                        _state = last?State.COMPLETING:State.COMMITTED;
+                    }
+
+                    return Result.FLUSH;
+                }
+                catch(Exception e)
+                {
+                    String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
+                    throw new IOException(message,e);
+                }
+                finally
+                {
+                    BufferUtil.flipToFlush(header,pos);
+                }
+            }
+
+            case COMMITTED:
+            {
+                int len = BufferUtil.length(content);
+
+                if (len>0)
+                {
+                    // Do we need a chunk buffer?
+                    if (isChunking())
+                    {
+                        // Do we need a chunk buffer?
+                        if (chunk==null)
+                            return Result.NEED_CHUNK;
+                        BufferUtil.clearToFill(chunk);
+                        prepareChunk(chunk,len);
+                        BufferUtil.flipToFlush(chunk,0);
+                    }
+                    _contentPrepared+=len;
+                }
+
+                if (last)
+                {
+                    _state=State.COMPLETING;
+                    return len>0?Result.FLUSH:Result.CONTINUE;
+                }
+
+                return Result.FLUSH;
+            }
+
+            case COMPLETING:
+            {
+                if (BufferUtil.hasContent(content))
+                {
+                    LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+
+                if (isChunking())
+                {
+                    // Do we need a chunk buffer?
+                    if (chunk==null)
+                        return Result.NEED_CHUNK;
+                    BufferUtil.clearToFill(chunk);
+                    prepareChunk(chunk,0);
+                    BufferUtil.flipToFlush(chunk,0);
+                    _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+                    return Result.FLUSH;
+                }
+
+                _state=State.END;
+               return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
+            }
+
+            case END:
+                if (BufferUtil.hasContent(content))
+                {
+                    LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+                return Result.DONE;
+
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public Result generateResponse(ResponseInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
+    {
+        switch(_state)
+        {
+            case START:
+            {
+                if (info==null)
+                    return Result.NEED_INFO;
+
+                // Handle 0.9
+                if (info.getHttpVersion() == HttpVersion.HTTP_0_9)
+                {
+                    _persistent = false;
+                    _endOfContent=EndOfContent.EOF_CONTENT;
+                    if (BufferUtil.hasContent(content))
+                        _contentPrepared+=content.remaining();
+                    _state = last?State.COMPLETING:State.COMMITTED;
+                    return Result.FLUSH;
+                }
+
+                // Do we need a response header
+                if (header==null)
+                    return Result.NEED_HEADER;
+
+                // If we have not been told our persistence, set the default
+                if (_persistent==null)
+                    _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
+
+                // prepare the header
+                int pos=BufferUtil.flipToFill(header);
+                try
+                {
+                    // generate ResponseLine
+                    generateResponseLine(info,header);
+
+                    // Handle 1xx and no content responses
+                    int status=info.getStatus();
+                    if (status>=100 && status<200 )
+                    {
+                        _noContent=true;
+
+                        if (status!=HttpStatus.SWITCHING_PROTOCOLS_101 )
+                        {
+                            header.put(HttpTokens.CRLF);
+                            _state=State.COMPLETING_1XX;
+                            return Result.FLUSH;
+                        }
+                    }
+                    else if (status==HttpStatus.NO_CONTENT_204 || status==HttpStatus.NOT_MODIFIED_304)
+                    {
+                        _noContent=true;
+                    }
+
+                    generateHeaders(info,header,content,last);
+
+                    // handle the content.
+                    int len = BufferUtil.length(content);
+                    if (len>0)
+                    {
+                        _contentPrepared+=len;
+                        if (isChunking() && !info.isHead())
+                            prepareChunk(header,len);
+                    }
+                    _state = last?State.COMPLETING:State.COMMITTED;
+                }
+                catch(Exception e)
+                {
+                    String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
+                    throw new IOException(message,e);
+                }
+                finally
+                {
+                    BufferUtil.flipToFlush(header,pos);
+                }
+
+                return Result.FLUSH;
+            }
+
+            case COMMITTED:
+            {
+                int len = BufferUtil.length(content);
+
+                // handle the content.
+                if (len>0)
+                {
+                    if (isChunking())
+                    {
+                        if (chunk==null)
+                            return Result.NEED_CHUNK;
+                        BufferUtil.clearToFill(chunk);
+                        prepareChunk(chunk,len);
+                        BufferUtil.flipToFlush(chunk,0);
+                    }
+                    _contentPrepared+=len;
+                }
+
+                if (last)
+                {
+                    _state=State.COMPLETING;
+                    return len>0?Result.FLUSH:Result.CONTINUE;
+                }
+                return len>0?Result.FLUSH:Result.DONE;
+
+            }
+
+            case COMPLETING_1XX:
+            {
+                reset();
+                return Result.DONE;
+            }
+
+            case COMPLETING:
+            {
+                if (BufferUtil.hasContent(content))
+                {
+                    LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+
+                if (isChunking())
+                {
+                    // Do we need a chunk buffer?
+                    if (chunk==null)
+                        return Result.NEED_CHUNK;
+
+                    // Write the last chunk
+                    BufferUtil.clearToFill(chunk);
+                    prepareChunk(chunk,0);
+                    BufferUtil.flipToFlush(chunk,0);
+                    _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+                    return Result.FLUSH;
+                }
+
+                _state=State.END;
+
+               return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
+            }
+
+            case END:
+                if (BufferUtil.hasContent(content))
+                {
+                    LOG.debug("discarding content in COMPLETING");
+                    BufferUtil.clear(content);
+                }
+                return Result.DONE;
+
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void prepareChunk(ByteBuffer chunk, int remaining)
+    {
+        // if we need CRLF add this to header
+        if (_needCRLF)
+            BufferUtil.putCRLF(chunk);
+
+        // Add the chunk size to the header
+        if (remaining>0)
+        {
+            BufferUtil.putHexInt(chunk, remaining);
+            BufferUtil.putCRLF(chunk);
+            _needCRLF=true;
+        }
+        else
+        {
+            chunk.put(LAST_CHUNK);
+            _needCRLF=false;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void generateRequestLine(RequestInfo request,ByteBuffer header)
+    {
+        header.put(StringUtil.getBytes(request.getMethod()));
+        header.put((byte)' ');
+        header.put(StringUtil.getBytes(request.getUri()));
+        switch(request.getHttpVersion())
+        {
+            case HTTP_1_0:
+            case HTTP_1_1:
+                header.put((byte)' ');
+                header.put(request.getHttpVersion().toBytes());
+                break;
+            default:
+                throw new IllegalStateException();
+        }
+        header.put(HttpTokens.CRLF);
+    }
+
+    /* ------------------------------------------------------------ */
+    private void generateResponseLine(ResponseInfo response, ByteBuffer header)
+    {
+        // Look for prepared response line
+        int status=response.getStatus();
+        PreparedResponse preprepared = status<__preprepared.length?__preprepared[status]:null;
+        String reason=response.getReason();
+        if (preprepared!=null)
+        {
+            if (reason==null)
+                header.put(preprepared._responseLine);
+            else
+            {
+                header.put(preprepared._schemeCode);
+                header.put(getReasonBytes(reason));
+                header.put(HttpTokens.CRLF);
+            }
+        }
+        else // generate response line
+        {
+            header.put(HTTP_1_1_SPACE);
+            header.put((byte) ('0' + status / 100));
+            header.put((byte) ('0' + (status % 100) / 10));
+            header.put((byte) ('0' + (status % 10)));
+            header.put((byte) ' ');
+            if (reason==null)
+            {
+                header.put((byte) ('0' + status / 100));
+                header.put((byte) ('0' + (status % 100) / 10));
+                header.put((byte) ('0' + (status % 10)));
+            }
+            else
+                header.put(getReasonBytes(reason));
+            header.put(HttpTokens.CRLF);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private byte[] getReasonBytes(String reason)
+    {
+        if (reason.length()>1024)
+            reason=reason.substring(0,1024);
+        byte[] _bytes = StringUtil.getBytes(reason);
+
+        for (int i=_bytes.length;i-->0;)
+            if (_bytes[i]=='\r' || _bytes[i]=='\n')
+                _bytes[i]='?';
+        return _bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    private void generateHeaders(Info _info,ByteBuffer header,ByteBuffer content,boolean last)
+    {
+        final RequestInfo request=(_info instanceof RequestInfo)?(RequestInfo)_info:null;
+        final ResponseInfo response=(_info instanceof ResponseInfo)?(ResponseInfo)_info:null;
+
+        // default field values
+        int send=_send;
+        HttpField transfer_encoding=null;
+        boolean keep_alive=false;
+        boolean close=false;
+        boolean content_type=false;
+        StringBuilder connection = null;
+
+        // Generate fields
+        if (_info.getHttpFields() != null)
+        {
+            for (HttpField field : _info.getHttpFields())
+            {
+                HttpHeader h = field.getHeader();
+
+                switch (h==null?HttpHeader.UNKNOWN:h)
+                {
+                    case CONTENT_LENGTH:
+                        // handle specially below
+                        if (_info.getContentLength()>=0)
+                            _endOfContent=EndOfContent.CONTENT_LENGTH;
+                        break;
+
+                    case CONTENT_TYPE:
+                    {
+                        if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString()))
+                            _endOfContent=EndOfContent.SELF_DEFINING_CONTENT;
+
+                        // write the field to the header
+                        content_type=true;
+                        putTo(field,header);
+                        break;
+                    }
+
+                    case TRANSFER_ENCODING:
+                    {
+                        if (_info.getHttpVersion() == HttpVersion.HTTP_1_1)
+                            transfer_encoding = field;
+                        // Do NOT add yet!
+                        break;
+                    }
+
+                    case CONNECTION:
+                    {
+                        if (request!=null)
+                            putTo(field,header);
+
+                        // Lookup and/or split connection value field
+                        HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue())?CLOSE:new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
+                        String[] split = null;
+
+                        if (values[0]==null)
+                        {
+                            split = field.getValue().split("\\s*,\\s*");
+                            if (split.length>0)
+                            {
+                                values=new HttpHeaderValue[split.length];
+                                for (int i=0;i<split.length;i++)
+                                    values[i]=HttpHeaderValue.CACHE.get(split[i]);
+                            }
+                        }
+
+                        // Handle connection values
+                        for (int i=0;i<values.length;i++)
+                        {
+                            HttpHeaderValue value=values[i];
+                            switch (value==null?HttpHeaderValue.UNKNOWN:value)
+                            {
+                                case UPGRADE:
+                                {
+                                    // special case for websocket connection ordering
+                                    header.put(HttpHeader.CONNECTION.getBytesColonSpace()).put(HttpHeader.UPGRADE.getBytes());
+                                    header.put(CRLF);
+                                    break;
+                                }
+
+                                case CLOSE:
+                                {
+                                    close=true;
+                                    if (response!=null)
+                                    {
+                                        _persistent=false;
+                                        if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
+                                            _endOfContent=EndOfContent.EOF_CONTENT;
+                                    }
+                                    break;
+                                }
+
+                                case KEEP_ALIVE:
+                                {
+                                    if (_info.getHttpVersion() == HttpVersion.HTTP_1_0)
+                                    {
+                                        keep_alive = true;
+                                        if (response!=null)
+                                            _persistent=true;
+                                    }
+                                    break;
+                                }
+
+                                default:
+                                {
+                                    if (connection==null)
+                                        connection=new StringBuilder();
+                                    else
+                                        connection.append(',');
+                                    connection.append(split==null?field.getValue():split[i]);
+                                }
+                            }
+                        }
+
+                        // Do NOT add yet!
+                        break;
+                    }
+
+                    case SERVER:
+                    {
+                        send=send&~SEND_SERVER;
+                        putTo(field,header);
+                        break;
+                    }
+
+                    default:
+                        putTo(field,header);
+                }
+            }
+        }
+
+
+        // Calculate how to end _content and connection, _content length and transfer encoding
+        // settings.
+        // From RFC 2616 4.4:
+        // 1. No body for 1xx, 204, 304 & HEAD response
+        // 2. Force _content-length?
+        // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
+        // 4. Content-Length
+        // 5. multipart/byteranges
+        // 6. close
+        int status=response!=null?response.getStatus():-1;
+        switch (_endOfContent)
+        {
+            case UNKNOWN_CONTENT:
+                // It may be that we have no _content, or perhaps _content just has not been
+                // written yet?
+
+                // Response known not to have a body
+                if (_contentPrepared == 0 && response!=null && (status < 200 || status == 204 || status == 304))
+                    _endOfContent=EndOfContent.NO_CONTENT;
+                else if (_info.getContentLength()>0)
+                {
+                    // we have been given a content length
+                    _endOfContent=EndOfContent.CONTENT_LENGTH;
+                    long content_length = _info.getContentLength();
+                    if ((response!=null || content_length>0 || content_type ) && !_noContent)
+                    {
+                        // known length but not actually set.
+                        header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+                        BufferUtil.putDecLong(header, content_length);
+                        header.put(HttpTokens.CRLF);
+                    }
+                }
+                else if (last)
+                {
+                    // we have seen all the _content there is, so we can be content-length limited.
+                    _endOfContent=EndOfContent.CONTENT_LENGTH;
+                    long content_length = _contentPrepared+BufferUtil.length(content);
+
+                    // Do we need to tell the headers about it
+                    if ((response!=null || content_length>0 || content_type ) && !_noContent)
+                    {
+                        header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+                        BufferUtil.putDecLong(header, content_length);
+                        header.put(HttpTokens.CRLF);
+                    }
+                }
+                else
+                {
+                    // No idea, so we must assume that a body is coming.
+                    _endOfContent = EndOfContent.CHUNKED_CONTENT;
+                    // HTTP 1.0 does not understand chunked content, so we must use EOF content.
+                    // For a request with HTTP 1.0 & Connection: keep-alive
+                    // we *must* close the connection, otherwise the client
+                    // has no way to detect the end of the content.
+                    if (!isPersistent() || _info.getHttpVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
+                        _endOfContent = EndOfContent.EOF_CONTENT;
+                }
+                break;
+
+            case CONTENT_LENGTH:
+                long content_length = _info.getContentLength();
+                if ((response!=null || content_length>0 || content_type ) && !_noContent)
+                {
+                    // known length but not actually set.
+                    header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+                    BufferUtil.putDecLong(header, content_length);
+                    header.put(HttpTokens.CRLF);
+                }
+                break;
+
+            case NO_CONTENT:
+                if (response!=null && status >= 200 && status != 204 && status != 304)
+                    header.put(CONTENT_LENGTH_0);
+                break;
+
+            case EOF_CONTENT:
+                _persistent = request!=null;
+                break;
+
+            case CHUNKED_CONTENT:
+                break;
+
+            default:
+                break;
+        }
+
+        // Add transfer_encoding if needed
+        if (isChunking())
+        {
+            // try to use user supplied encoding as it may have other values.
+            if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue()))
+            {
+                String c = transfer_encoding.getValue();
+                if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
+                    putTo(transfer_encoding,header);
+                else
+                    throw new IllegalArgumentException("BAD TE");
+            }
+            else
+                header.put(TRANSFER_ENCODING_CHUNKED);
+        }
+
+        // Handle connection if need be
+        if (_endOfContent==EndOfContent.EOF_CONTENT)
+        {
+            keep_alive=false;
+            _persistent=false;
+        }
+
+        // If this is a response, work out persistence
+        if (response!=null)
+        {
+            if (!isPersistent() && (close || _info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()))
+            {
+                if (connection==null)
+                    header.put(CONNECTION_CLOSE);
+                else
+                {
+                    header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2);
+                    header.put((byte)',');
+                    header.put(StringUtil.getBytes(connection.toString()));
+                    header.put(CRLF);
+                }
+            }
+            else if (keep_alive)
+            {
+                if (connection==null)
+                    header.put(CONNECTION_KEEP_ALIVE);
+                else
+                {
+                    header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_KEEP_ALIVE.length-2);
+                    header.put((byte)',');
+                    header.put(StringUtil.getBytes(connection.toString()));
+                    header.put(CRLF);
+                }
+            }
+            else if (connection!=null)
+            {
+                header.put(HttpHeader.CONNECTION.getBytesColonSpace());
+                header.put(StringUtil.getBytes(connection.toString()));
+                header.put(CRLF);
+            }
+        }
+
+        if (status>199)
+            header.put(SEND[send]);
+
+        // end the header.
+        header.put(HttpTokens.CRLF);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public static byte[] getReasonBuffer(int code)
+    {
+        PreparedResponse status = code<__preprepared.length?__preprepared[code]:null;
+        if (status!=null)
+            return status._reason;
+        return null;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    public String toString()
+    {
+        return String.format("%s{s=%s}",
+                getClass().getSimpleName(),
+                _state);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    // common _content
+    private static final byte[] LAST_CHUNK =    { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
+    private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
+    private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
+    private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
+    private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
+    private static final byte[] CRLF = StringUtil.getBytes("\015\012");
+    private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
+    private static final byte[][] SEND = new byte[][]{
+            new byte[0],
+            StringUtil.getBytes("Server: Jetty(9.x.x)\015\012"),
+        StringUtil.getBytes("X-Powered-By: Jetty(9.x.x)\015\012"),
+        StringUtil.getBytes("Server: Jetty(9.x.x)\015\012X-Powered-By: Jetty(9.x.x)\015\012")
+    };
+
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    // Build cache of response lines for status
+    private static class PreparedResponse
+    {
+        byte[] _reason;
+        byte[] _schemeCode;
+        byte[] _responseLine;
+    }
+    private static final PreparedResponse[] __preprepared = new PreparedResponse[HttpStatus.MAX_CODE+1];
+    static
+    {
+        int versionLength=HttpVersion.HTTP_1_1.toString().length();
+
+        for (int i=0;i<__preprepared.length;i++)
+        {
+            HttpStatus.Code code = HttpStatus.getCode(i);
+            if (code==null)
+                continue;
+            String reason=code.getMessage();
+            byte[] line=new byte[versionLength+5+reason.length()+2];
+            HttpVersion.HTTP_1_1.toBuffer().get(line,0,versionLength);
+            line[versionLength+0]=' ';
+            line[versionLength+1]=(byte)('0'+i/100);
+            line[versionLength+2]=(byte)('0'+(i%100)/10);
+            line[versionLength+3]=(byte)('0'+(i%10));
+            line[versionLength+4]=' ';
+            for (int j=0;j<reason.length();j++)
+                line[versionLength+5+j]=(byte)reason.charAt(j);
+            line[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN;
+            line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
+
+            __preprepared[i] = new PreparedResponse();
+            __preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0,versionLength+5);
+            __preprepared[i]._reason = Arrays.copyOfRange(line, versionLength+5, line.length-2);
+            __preprepared[i]._responseLine=line;
+        }
+    }
+
+    public static class Info
+    {
+        final HttpVersion _httpVersion;
+        final HttpFields _httpFields;
+        final long _contentLength;
+
+        private Info(HttpVersion httpVersion, HttpFields httpFields, long contentLength)
+        {
+            _httpVersion = httpVersion;
+            _httpFields = httpFields;
+            _contentLength = contentLength;
+        }
+
+        public HttpVersion getHttpVersion()
+        {
+            return _httpVersion;
+        }
+        public HttpFields getHttpFields()
+        {
+            return _httpFields;
+        }
+        public long getContentLength()
+        {
+            return _contentLength;
+        }
+    }
+
+    public static class RequestInfo extends Info
+    {
+        private final String _method;
+        private final String _uri;
+
+        public RequestInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, String method, String uri)
+        {
+            super(httpVersion,httpFields,contentLength);
+            _method = method;
+            _uri = uri;
+        }
+
+        public String getMethod()
+        {
+            return _method;
+        }
+
+        public String getUri()
+        {
+            return _uri;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("RequestInfo{%s %s %s,%d}",_method,_uri,_httpVersion,_contentLength);
+        }
+    }
+
+    public static class ResponseInfo extends Info
+    {
+        private final int _status;
+        private final String _reason;
+        private final boolean _head;
+
+        public ResponseInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, int status, String reason, boolean head)
+        {
+            super(httpVersion,httpFields,contentLength);
+            _status = status;
+            _reason = reason;
+            _head = head;
+        }
+
+        public boolean isInformational()
+        {
+            return _status>=100 && _status<200;
+        }
+
+        public int getStatus()
+        {
+            return _status;
+        }
+
+        public String getReason()
+        {
+            return _reason;
+        }
+
+        public boolean isHead()
+        {
+            return _head;
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("ResponseInfo{%s %s %s,%d,%b}",_httpVersion,_status,_reason,_contentLength,_head);
+        }
+    } 
+
+    private static void putSanitisedName(String s,ByteBuffer buffer)
+    {
+        int l=s.length();
+        for (int i=0;i<l;i++)
+        {
+            char c=s.charAt(i);
+            
+            if (c<0 || c>0xff || c=='\r' || c=='\n'|| c==':')
+                buffer.put((byte)'?');
+            else
+                buffer.put((byte)(0xff&c));
+        }
+    }
+
+    private static void putSanitisedValue(String s,ByteBuffer buffer)
+    {
+        int l=s.length();
+        for (int i=0;i<l;i++)
+        {
+            char c=s.charAt(i);
+            
+            if (c<0 || c>0xff || c=='\r' || c=='\n')
+                buffer.put((byte)'?');
+            else
+                buffer.put((byte)(0xff&c));
+        }
+    }
+
+    public static void putTo(HttpField field, ByteBuffer bufferInFillMode)
+    {
+        if (field instanceof CachedHttpField)
+        {
+            ((CachedHttpField)field).putTo(bufferInFillMode);
+        }
+        else
+        {
+            HttpHeader header=field.getHeader();
+            if (header!=null)
+            {
+                bufferInFillMode.put(header.getBytesColonSpace());
+                putSanitisedValue(field.getValue(),bufferInFillMode);
+            }
+            else
+            {
+                putSanitisedName(field.getName(),bufferInFillMode);
+                bufferInFillMode.put(__colon_space);
+                putSanitisedValue(field.getValue(),bufferInFillMode);
+            }
+
+            BufferUtil.putCRLF(bufferInFillMode);
+        }
+    }
+
+    public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode) 
+    {
+        for (HttpField field : fields)
+        {
+            if (field != null)
+                putTo(field,bufferInFillMode);
+        }
+        BufferUtil.putCRLF(bufferInFillMode);
+    }
+    
+    public static class CachedHttpField extends HttpField
+    {
+        private final byte[] _bytes;
+        public CachedHttpField(HttpHeader header,String value)
+        {
+            super(header,value);
+            int cbl=header.getBytesColonSpace().length;
+            _bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2);
+            System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length());
+            _bytes[_bytes.length-2]=(byte)'\r';
+            _bytes[_bytes.length-1]=(byte)'\n';
+        }
+        
+        public void putTo(ByteBuffer bufferInFillMode)
+        {
+            bufferInFillMode.put(_bytes);
+        }
+    }
+}