All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.http.HttpGenerator Maven / Gradle / Ivy

There is a newer version: 11.0.0.beta1
Show newest version
//
//  ========================================================================
//  Copyright (c) 1995-2015 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
 * 

*/ 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 boolean isNoContent() { return _noContent; } /* ------------------------------------------------------------ */ 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)) { if (LOG.isDebugEnabled()) 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)) { if (LOG.isDebugEnabled()) 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)) { if (LOG.isDebugEnabled()) 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)) { if (LOG.isDebugEnabled()) 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;i0) { // 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=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;i0xff || 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;i0xff || 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); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy