diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java deleted file mode 100644 index 9ddb356eb76..00000000000 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/AbstractGenerator.java +++ /dev/null @@ -1,522 +0,0 @@ -// ======================================================================== -// Copyright (c) 2004-2009 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.ByteBuffer; - -import javax.swing.text.View; - -import org.eclipse.jetty.io.Buffers; -import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.io.EofException; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; - -/* ------------------------------------------------------------ */ -/** - * Abstract Generator. Builds HTTP Messages. - * - * Currently this class uses a system parameter "jetty.direct.writers" to control - * two optional writer to byte conversions. buffer.writers=true will probably be - * faster, but will consume more memory. This option is just for testing and tuning. - * - */ -public abstract class AbstractGenerator implements Generator -{ - private static final Logger LOG = Log.getLogger(AbstractGenerator.class); - - // states - public final static int STATE_HEADER = 0; - public final static int STATE_CONTENT = 2; - public final static int STATE_FLUSHING = 3; - public final static int STATE_END = 4; - - public static final byte[] NO_BYTES = {}; - - // data - - protected final Buffers _buffers; // source of buffers - protected final EndPoint _endp; - - protected int _state = STATE_HEADER; - - protected int _status = 0; - protected HttpVersion _version = HttpVersion.HTTP_1_1; - protected ByteBuffer _reason; - protected ByteBuffer _method; - protected String _uri; - - protected long _contentWritten = 0; - protected long _contentLength = HttpTokens.UNKNOWN_CONTENT; - protected boolean _last = false; - protected boolean _head = false; - protected boolean _noContent = false; - protected Boolean _persistent = null; - - protected ByteBuffer _header; // Buffer for HTTP header (and maybe small _content) - protected ByteBuffer _buffer; // Buffer for copy of passed _content - protected ByteBuffer _content; // Buffer passed to addContent - - protected ByteBuffer _date; - - private boolean _sendServerVersion; - - - /* ------------------------------------------------------------------------------- */ - /** - * Constructor. - * - * @param buffers buffer pool - * @param io the end point - */ - public AbstractGenerator(Buffers buffers, EndPoint io) - { - this._buffers = buffers; - this._endp = io; - } - - /* ------------------------------------------------------------------------------- */ - public abstract boolean isRequest(); - - /* ------------------------------------------------------------------------------- */ - public abstract boolean isResponse(); - - /* ------------------------------------------------------------------------------- */ - public boolean isOpen() - { - return _endp.isOpen(); - } - - /* ------------------------------------------------------------------------------- */ - public void reset() - { - _state = STATE_HEADER; - _status = 0; - _version = HttpVersion.HTTP_1_1; - _reason = null; - _last = false; - _head = false; - _noContent=false; - _persistent = null; - _contentWritten = 0; - _contentLength = HttpTokens.UNKNOWN_CONTENT; - _date = null; - - _content = null; - _method=null; - } - - /* ------------------------------------------------------------------------------- */ - public void returnBuffers() - { - if (_buffer!=null && _buffer.length()==0) - { - _buffers.returnBuffer(_buffer); - _buffer=null; - } - - if (_header!=null && _header.length()==0) - { - _buffers.returnBuffer(_header); - _header=null; - } - } - - /* ------------------------------------------------------------------------------- */ - public void resetBuffer() - { - if(_state>=STATE_FLUSHING) - throw new IllegalStateException("Flushed"); - - _last = false; - _persistent=null; - _contentWritten = 0; - _contentLength = HttpTokens.UNKNOWN_CONTENT; - _content=null; - if (_buffer!=null) - _buffer.clear(); - } - - /* ------------------------------------------------------------ */ - /** - * @return Returns the contentBufferSize. - */ - public int getContentBufferSize() - { - if (_buffer==null) - _buffer=_buffers.getBuffer(); - return _buffer.capacity(); - } - - /* ------------------------------------------------------------ */ - /** - * @param contentBufferSize The contentBufferSize to set. - */ - public void increaseContentBufferSize(int contentBufferSize) - { - if (_buffer==null) - _buffer=_buffers.getBuffer(); - if (contentBufferSize > _buffer.capacity()) - { - ByteBuffer nb = _buffers.getBuffer(contentBufferSize); - nb.put(_buffer); - _buffers.returnBuffer(_buffer); - _buffer = nb; - } - } - - /* ------------------------------------------------------------ */ - public ByteBuffer getUncheckedBuffer() - { - return _buffer; - } - - /* ------------------------------------------------------------ */ - public boolean getSendServerVersion () - { - return _sendServerVersion; - } - - /* ------------------------------------------------------------ */ - public void setSendServerVersion (boolean sendServerVersion) - { - _sendServerVersion = sendServerVersion; - } - - /* ------------------------------------------------------------ */ - public int getState() - { - return _state; - } - - /* ------------------------------------------------------------ */ - public boolean isState(int state) - { - return _state == state; - } - - /* ------------------------------------------------------------ */ - public boolean isComplete() - { - return _state == STATE_END; - } - - /* ------------------------------------------------------------ */ - public boolean isIdle() - { - return _state == STATE_HEADER && _method==null && _status==0; - } - - /* ------------------------------------------------------------ */ - public boolean isCommitted() - { - return _state != STATE_HEADER; - } - - /* ------------------------------------------------------------ */ - /** - * @return Returns the head. - */ - public boolean isHead() - { - return _head; - } - - /* ------------------------------------------------------------ */ - public void setContentLength(long value) - { - if (value<0) - _contentLength=HttpTokens.UNKNOWN_CONTENT; - else - _contentLength=value; - } - - /* ------------------------------------------------------------ */ - /** - * @param head The head to set. - */ - public void setHead(boolean head) - { - _head = head; - } - - /* ------------------------------------------------------------ */ - /** - * @return false if the connection should be closed after a request has been read, - * true if it should be used for additional requests. - */ - public boolean isPersistent() - { - return _persistent!=null - ?_persistent.booleanValue() - :(isRequest()?true:_version>HttpVersion.HTTP_1_0_ORDINAL); - } - - /* ------------------------------------------------------------ */ - public void setPersistent(boolean persistent) - { - _persistent=persistent; - } - - /* ------------------------------------------------------------ */ - /** - * @param version The version of the client the response is being sent to (NB. Not the version - * in the response, which is the version of the server). - */ - public void setVersion(int version) - { - if (_state != STATE_HEADER) - throw new IllegalStateException("STATE!=START "+_state); - _version = version; - if (_version==HttpVersion.HTTP_0_9_ORDINAL && _method!=null) - _noContent=true; - } - - /* ------------------------------------------------------------ */ - public int getVersion() - { - return _version; - } - - /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.http.Generator#setDate(org.eclipse.jetty.io.ByteBuffer) - */ - public void setDate(ByteBuffer timeStampBuffer) - { - _date=timeStampBuffer; - } - - /* ------------------------------------------------------------ */ - /** - */ - public void setRequest(String method, String uri) - { - if (method==null || HttpMethod.GET.equals(method) ) - _method=HttpMethod.GET_BUFFER; - else - _method=HttpMethod.CACHE.lookup(method); - _uri=uri; - if (_version==HttpVersion.HTTP_0_9_ORDINAL) - _noContent=true; - } - - /* ------------------------------------------------------------ */ - /** - * @param status The status code to send. - * @param reason the status message to send. - */ - public void setResponse(int status, String reason) - { - if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START"); - _method=null; - _status = status; - if (reason!=null) - { - int len=reason.length(); - - // TODO don't hard code - if (len>1024) - len=1024; - _reason=BufferUtil.allocate(len); - for (int i=0;i0; - } - - /* ------------------------------------------------------------ */ - public boolean isWritten() - { - return _contentWritten>0; - } - - /* ------------------------------------------------------------ */ - public boolean isAllContentWritten() - { - return _contentLength>=0 && _contentWritten>=_contentLength; - } - - /* ------------------------------------------------------------ */ - public abstract void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException; - - /* ------------------------------------------------------------ */ - /** - * Complete the message. - * - * @throws IOException - */ - public void complete() throws IOException - { - if (_state == STATE_HEADER) - { - throw new IllegalStateException("State==HEADER"); - } - - if (_contentLength >= 0 && _contentLength != _contentWritten && !_head) - { - if (LOG.isDebugEnabled()) - LOG.debug("ContentLength written=="+_contentWritten+" != contentLength=="+_contentLength); - _persistent = false; - } - } - - /* ------------------------------------------------------------ */ - public abstract int flushBuffer() throws IOException; - - - /* ------------------------------------------------------------ */ - public void flush(long maxIdleTime) throws IOException - { - // block until everything is flushed - long now=System.currentTimeMillis(); - long end=now+maxIdleTime; - ByteBuffer content = _content; - ByteBuffer buffer = _buffer; - if (content!=null && content.length()>0 || buffer!=null && buffer.length()>0 || isBufferFull()) - { - flushBuffer(); - - while (now0 ||buffer!=null && buffer.length()>0) && _endp.isOpen()&& !_endp.isOutputShutdown()) - { - blockForOutput(end-now); - now=System.currentTimeMillis(); - } - } - } - - /* ------------------------------------------------------------ */ - /** - * Utility method to send an error response. If the builder is not committed, this call is - * equivalent to a setResponse, addContent and complete call. - * - * @param code The error code - * @param reason The error reason - * @param content Contents of the error page - * @param close True if the connection should be closed - * @throws IOException if there is a problem flushing the response - */ - public void sendError(int code, String reason, String content, boolean close) throws IOException - { - if (close) - _persistent=false; - if (isCommitted()) - { - LOG.debug("sendError on committed: {} {}",code,reason); - } - else - { - LOG.debug("sendError: {} {}",code,reason); - setResponse(code, reason); - if (content != null) - { - completeHeader(null, false); - addContent(new View(BufferUtil.allocate(content)), Generator.LAST); - } - else - { - completeHeader(null, true); - } - complete(); - } - } - - /* ------------------------------------------------------------ */ - /** - * @return Returns the contentWritten. - */ - public long getContentWritten() - { - return _contentWritten; - } - - - - /* ------------------------------------------------------------ */ - public void blockForOutput(long maxIdleTime) throws IOException - { - if (_endp.isBlocking()) - { - try - { - flushBuffer(); - } - catch(IOException e) - { - _endp.close(); - throw e; - } - } - else - { - if (!_endp.blockWritable(maxIdleTime)) - { - _endp.close(); - throw new EofException("timeout"); - } - - flushBuffer(); - } - } - -} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/Generator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/Generator.java deleted file mode 100644 index 503bba6c391..00000000000 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/Generator.java +++ /dev/null @@ -1,101 +0,0 @@ -// ======================================================================== -// Copyright (c) 2006-2009 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.ByteBuffer; - - -public interface Generator -{ - public static final boolean LAST=true; - public static final boolean MORE=false; - - /* ------------------------------------------------------------ */ - /** - * Add content. - * - * @param content - * @param last - * @throws IllegalArgumentException if content is {@link ByteBuffer#isImmutable immutable}. - * @throws IllegalStateException If the request is not expecting any more content, - * or if the buffers are full and cannot be flushed. - * @throws IOException if there is a problem flushing the buffers. - */ - void addContent(ByteBuffer content, boolean last) throws IOException; - - /* ------------------------------------------------------------ */ - /** - * Add content. - * - * @param b byte - * @return true if the buffers are full - * @throws IOException - */ - boolean addContent(byte b) throws IOException; - - void complete() throws IOException; - - void completeHeader(HttpFields responseFields, boolean last) throws IOException; - - int flushBuffer() throws IOException; - - int getContentBufferSize(); - - long getContentWritten(); - - boolean isWritten(); - - boolean isAllContentWritten(); - - void increaseContentBufferSize(int size); - - boolean isBufferFull(); - - boolean isCommitted(); - - boolean isComplete(); - - boolean isPersistent(); - - void reset(); - - void resetBuffer(); - - void returnBuffers(); - - void sendError(int code, String reason, String content, boolean close) throws IOException; - - void setHead(boolean head); - - void setRequest(String method, String uri); - - void setResponse(int status, String reason); - - - void setSendServerVersion(boolean sendServerVersion); - - void setVersion(int version); - - boolean isIdle(); - - void setContentLength(long length); - - void setPersistent(boolean persistent); - - void setDate(ByteBuffer timeStampBuffer); - - -} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index 131ad3ea8e7..f09f272d290 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -1048,6 +1048,7 @@ public class HttpFields _next = null; } + /* ------------------------------------------------------------ */ private byte[] toSanitisedBytes(String s) { byte[] bytes = s.getBytes(StringUtil.__ISO_8859_1_CHARSET); @@ -1091,6 +1092,12 @@ public class HttpFields BufferUtil.putCRLF(buffer); } + + /* ------------------------------------------------------------ */ + public void putValueTo(ByteBuffer buffer) throws IOException + { + buffer.put(toSanitisedBytes(_value)); + } /* ------------------------------------------------------------ */ public String getName() diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java index aa2e0ae7240..9b1415ec934 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java @@ -17,6 +17,8 @@ import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; +import javax.swing.text.View; + import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; @@ -32,9 +34,8 @@ import org.eclipse.jetty.util.log.Logger; * * */ -public class HttpGenerator extends AbstractGenerator +public class HttpGenerator { - private static final Logger LOG = Log.getLogger(HttpGenerator.class); // Build cache of response lines for status private static class Status @@ -75,14 +76,37 @@ public class HttpGenerator extends AbstractGenerator } } - /* ------------------------------------------------------------------------------- */ - public static byte[] getReasonBuffer(int code) - { - Status status = code<__status.length?__status[code]:null; - if (status!=null) - return status._reason; - return null; - } + + private static final Logger LOG = Log.getLogger(HttpGenerator.class); + + // states + + enum State { HEADER, CONTENT, FLUSHING, END }; + + public static final byte[] NO_BYTES = {}; + + // data + + protected State _state = State.HEADER; + + protected int _status = 0; + protected HttpVersion _version = HttpVersion.HTTP_1_1; + protected String _reason; + protected String _method; + protected String _uri; + + protected long _contentWritten = 0; + protected long _contentLength = HttpTokens.UNKNOWN_CONTENT; + protected boolean _last = false; + protected boolean _head = false; + protected boolean _noContent = false; + protected Boolean _persistent = null; + + + protected ByteBuffer _date; + + private boolean _sendServerVersion; + // common _content @@ -92,6 +116,7 @@ public class HttpGenerator extends AbstractGenerator 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[] CONNECTION_ = StringUtil.getBytes("Connection: "); + 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 byte[] SERVER = StringUtil.getBytes("Server: Jetty(7.0.x)\015\012"); @@ -111,41 +136,26 @@ public class HttpGenerator extends AbstractGenerator private boolean _bufferChunked = false; + + + /* ------------------------------------------------------------------------------- */ - /** - * Constructor. - * - * @param buffers buffer pool - * @param io the end point to use - */ - public HttpGenerator(Buffers buffers, EndPoint io) - { - super(buffers,io); - } - - /* ------------------------------------------------------------------------------- */ - @Override public void reset() { - if (_persistent!=null && !_persistent && _endp!=null && !_endp.isOutputShutdown()) - { - try - { - _endp.shutdownOutput(); - } - catch(IOException e) - { - LOG.ignore(e); - } - } - super.reset(); - if (_buffer!=null) - _buffer.clear(); - if (_header!=null) - _header.clear(); - if (_content!=null) - _content=null; - _bypass = false; + _state = State.HEADER; + _status = 0; + _version = HttpVersion.HTTP_1_1; + _reason = null; + _last = false; + _head = false; + _noContent=false; + _persistent = null; + _contentWritten = 0; + _contentLength = HttpTokens.UNKNOWN_CONTENT; + _date = null; + + _method=null; + _needCRLF = false; _needEOC = false; _bufferChunked=false; @@ -153,6 +163,264 @@ public class HttpGenerator extends AbstractGenerator _uri=null; _noContent=false; } + + /* ------------------------------------------------------------------------------- */ + public void resetBuffer() + { + if(_state.ordinal()>=State.FLUSHING.ordinal()) + throw new IllegalStateException("Flushed"); + + _last = false; + _persistent=null; + _contentWritten = 0; + _contentLength = HttpTokens.UNKNOWN_CONTENT; + } + + /* ------------------------------------------------------------ */ + public boolean getSendServerVersion () + { + return _sendServerVersion; + } + + /* ------------------------------------------------------------ */ + public void setSendServerVersion (boolean sendServerVersion) + { + _sendServerVersion = sendServerVersion; + } + + /* ------------------------------------------------------------ */ + public State getState() + { + return _state; + } + + /* ------------------------------------------------------------ */ + public boolean isState(State state) + { + return _state == state; + } + + /* ------------------------------------------------------------ */ + public boolean isComplete() + { + return _state == State.END; + } + + /* ------------------------------------------------------------ */ + public boolean isIdle() + { + return _state == State.HEADER && _method==null && _status==0; + } + + /* ------------------------------------------------------------ */ + public boolean isCommitted() + { + return _state != State.HEADER; + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the head. + */ + public boolean isHead() + { + return _head; + } + + /* ------------------------------------------------------------ */ + public void setContentLength(long value) + { + if (value<0) + _contentLength=HttpTokens.UNKNOWN_CONTENT; + else + _contentLength=value; + } + + /* ------------------------------------------------------------ */ + /** + * @param head The head to set. + */ + public void setHead(boolean head) + { + _head = head; + } + + /* ------------------------------------------------------------ */ + /** + * @return false if the connection should be closed after a request has been read, + * true if it should be used for additional requests. + */ + public boolean isPersistent() + { + return _persistent!=null + ?_persistent.booleanValue() + :(isRequest()?true:_version.ordinal()>HttpVersion.HTTP_1_0.ordinal()); + } + + /* ------------------------------------------------------------ */ + public void setPersistent(boolean persistent) + { + _persistent=persistent; + } + + /* ------------------------------------------------------------ */ + /** + * @param version The version of the client the response is being sent to (NB. Not the version + * in the response, which is the version of the server). + */ + public void setVersion(HttpVersion version) + { + if (_state != State.HEADER) + throw new IllegalStateException("STATE!=START "+_state); + _version = version; + if (_version==HttpVersion.HTTP_0_9 && _method!=null) + _noContent=true; + } + + /* ------------------------------------------------------------ */ + public HttpVersion getVersion() + { + return _version; + } + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.http.Generator#setDate(org.eclipse.jetty.io.ByteBuffer) + */ + public void setDate(ByteBuffer timeStampBuffer) + { + _date=timeStampBuffer; + } + + /* ------------------------------------------------------------ */ + /** + */ + public void setRequest(String method, String uri) + { + _method=method; + _uri=uri; + if (_version==HttpVersion.HTTP_0_9) + _noContent=true; + } + + /* ------------------------------------------------------------ */ + /** + * @param status The status code to send. + * @param reason the status message to send. + */ + public void setResponse(int status, String reason) + { + if (_state != State.HEADER) throw new IllegalStateException("STATE!=START"); + _method=null; + _status = status; + _reason=reason; + if (_reason!=null) + { + if (_reason.length()>1024) + _reason=reason.substring(0,1024); + _reason=_reason.replace('\r','?').replace('\n','?'); + } + } + + /* ------------------------------------------------------------ */ + public boolean isWritten() + { + return _contentWritten>0; + } + + /* ------------------------------------------------------------ */ + public boolean isAllContentWritten() + { + return _contentLength>=0 && _contentWritten>=_contentLength; + } + + /* ------------------------------------------------------------ */ + /** + * Complete the message. + * + * @throws IOException + */ + public void complete() throws IOException + { + if (_state == State.END) + return; + + if (_state == State.HEADER) + { + throw new IllegalStateException("State==HEADER"); + } + + if (_contentLength >= 0 && _contentLength != _contentWritten && !_head) + { + if (LOG.isDebugEnabled()) + LOG.debug("ContentLength written=="+_contentWritten+" != contentLength=="+_contentLength); + _persistent = false; + } + + if (_state.ordinal() < State.FLUSHING.ordinal()) + { + _state = State.FLUSHING; + if (_contentLength == HttpTokens.CHUNKED_CONTENT) + _needEOC = true; + } + } + + + /* ------------------------------------------------------------ */ + /** + * Utility method to send an error response. If the builder is not committed, this call is + * equivalent to a setResponse, addContent and complete call. + * + * @param code The error code + * @param reason The error reason + * @param content Contents of the error page + * @param close True if the connection should be closed + * @throws IOException if there is a problem flushing the response + */ + public void sendError(int code, String reason, String content, boolean close) throws IOException + { + if (close) + _persistent=false; + if (isCommitted()) + { + LOG.debug("sendError on committed: {} {}",code,reason); + } + else + { + LOG.debug("sendError: {} {}",code,reason); + setResponse(code, reason); + if (content != null) + { + completeHeader(null, false); + addContent(new View(BufferUtil.allocate(content)), Generator.LAST); + } + else + { + completeHeader(null, true); + } + complete(); + } + } + + /* ------------------------------------------------------------ */ + /** + * @return Returns the contentWritten. + */ + public long getContentWritten() + { + return _contentWritten; + } + + + /* ------------------------------------------------------------------------------- */ + public static byte[] getReasonBuffer(int code) + { + Status status = code<__status.length?__status[code]:null; + if (status!=null) + return status._reason; + return null; + } + /* ------------------------------------------------------------ */ /** @@ -170,7 +438,7 @@ public class HttpGenerator extends AbstractGenerator if (_noContent) throw new IllegalStateException("NO CONTENT"); - if (_last || _state==STATE_END) + if (_last || _state==State.END) { LOG.warn("Ignoring extra content {}",content); content.clear(); @@ -186,7 +454,7 @@ public class HttpGenerator extends AbstractGenerator flushBuffer(); if (_content != null && _content.hasRemaining()) { - ByteBuffer nc=_buffers.getBuffer(_content.remaining()+content.length()); + ByteBuffer nc=_buffers.getBuffer(_content.remaining()+content.remaining()); nc.put(_content); nc.put(content); content=nc; @@ -194,7 +462,7 @@ public class HttpGenerator extends AbstractGenerator } _content = content; - _contentWritten += content.length(); + _contentWritten += content.remaining(); // Handle the _content if (_head) @@ -228,14 +496,14 @@ public class HttpGenerator extends AbstractGenerator */ public void sendResponse(ByteBuffer response) throws IOException { - if (_noContent || _state!=STATE_HEADER || _content!=null && _content.hasRemaining() || _bufferChunked || _head ) + if (_noContent || _state!=State.HEADER || _content!=null && _content.hasRemaining() || _bufferChunked || _head ) throw new IllegalStateException(); _last = true; _content = response; _bypass = true; - _state = STATE_FLUSHING; + _state = State.FLUSHING; // TODO this is not exactly right, but should do. _contentLength =_contentWritten = response.length(); @@ -255,7 +523,7 @@ public class HttpGenerator extends AbstractGenerator if (_noContent) throw new IllegalStateException("NO CONTENT"); - if (_last || _state==STATE_END) + if (_last || _state==State.END) { LOG.warn("Ignoring extra content {}",Byte.valueOf(b)); return false; @@ -285,55 +553,10 @@ public class HttpGenerator extends AbstractGenerator return _buffer.space()<=(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0); } - /* ------------------------------------------------------------ */ - /** Prepare buffer for unchecked writes. - * Prepare the generator buffer to receive unchecked writes - * @return the available space in the buffer. - * @throws IOException - */ - @Override - public int prepareUncheckedAddContent() throws IOException - { - if (_noContent) - return -1; - - if (_last || _state==STATE_END) - return -1; - - // Handle any unfinished business? - ByteBuffer content = _content; - if (content != null && content.length()>0 || _bufferChunked) - { - flushBuffer(); - if (content != null && content.length()>0 || _bufferChunked) - throw new IllegalStateException("FULL"); - } - - // we better check we have a buffer - if (_buffer == null) - _buffer = _buffers.getBuffer(); - - _contentWritten-=_buffer.remaining(); - - // Handle the _content - if (_head) - return Integer.MAX_VALUE; - - return _buffer.space()-(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0); - } - - /* ------------------------------------------------------------ */ - @Override - public boolean isBufferFull() - { - // Should we flush the buffers? - return super.isBufferFull() || _bufferChunked || _bypass || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE); - } - /* ------------------------------------------------------------ */ public void send1xx(int code) throws IOException { - if (_state != STATE_HEADER) + if (_state != State.HEADER) return; if (code<100||code>199) @@ -369,24 +592,21 @@ public class HttpGenerator extends AbstractGenerator } /* ------------------------------------------------------------ */ - @Override public boolean isRequest() { return _method!=null; } /* ------------------------------------------------------------ */ - @Override public boolean isResponse() { return _method==null; } /* ------------------------------------------------------------ */ - @Override - public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException + public void completeHeader(ByteBuffer buffer, HttpFields fields, boolean allContentAdded) throws IOException { - if (_state != STATE_HEADER) + if (_state != State.HEADER) return; // handle a reset @@ -397,9 +617,6 @@ public class HttpGenerator extends AbstractGenerator throw new IllegalStateException("last?"); _last = _last | allContentAdded; - // get a header buffer - if (_header == null) - _header = _buffers.getHeader(); boolean has_server = false; @@ -409,72 +626,71 @@ public class HttpGenerator extends AbstractGenerator { _persistent=true; - if (_version == HttpVersion.HTTP_0_9_ORDINAL) + if (_version == HttpVersion.HTTP_0_9) { _contentLength = HttpTokens.NO_CONTENT; - _header.put(_method); - _header.put((byte)' '); - _header.put(_uri.getBytes("UTF-8")); // TODO check - _header.put(HttpTokens.CRLF); - _state = STATE_FLUSHING; + buffer.put(_method); + buffer.put((byte)' '); + buffer.put(_uri.getBytes("UTF-8")); // TODO check + buffer.put(HttpTokens.CRLF); + _state = State.FLUSHING; _noContent=true; return; } else { - _header.put(_method); - _header.put((byte)' '); - _header.put(_uri.getBytes("UTF-8")); // TODO check - _header.put((byte)' '); - _header.put(_version==HttpVersion.HTTP_1_0_ORDINAL?HttpVersion.HTTP_1_0_BUFFER:HttpVersion.HTTP_1_1_BUFFER); - _header.put(HttpTokens.CRLF); + buffer.put(_method); + buffer.put((byte)' '); + buffer.put(_uri.getBytes("UTF-8")); // TODO check + buffer.put((byte)' '); + buffer.put((_version==HttpVersion.HTTP_1_0?HttpVersion.HTTP_1_0:HttpVersion.HTTP_1_1).toBytes()); + buffer.put(HttpTokens.CRLF); } } else { // Responses - if (_version == HttpVersion.HTTP_0_9_ORDINAL) + if (_version == HttpVersion.HTTP_0_9) { _persistent = false; _contentLength = HttpTokens.EOF_CONTENT; - _state = STATE_CONTENT; + _state = State.CONTENT; return; } else { if (_persistent==null) - _persistent= (_version > HttpVersion.HTTP_1_0_ORDINAL); + _persistent= (_version.ordinal() > HttpVersion.HTTP_1_0.ordinal()); // add response line Status status = _status<__status.length?__status[_status]:null; if (status==null) { - _header.put(HttpVersion.HTTP_1_1_BUFFER); - _header.put((byte) ' '); - _header.put((byte) ('0' + _status / 100)); - _header.put((byte) ('0' + (_status % 100) / 10)); - _header.put((byte) ('0' + (_status % 10))); - _header.put((byte) ' '); + buffer.put(HTTP_1_1_SPACE); + buffer.put((byte) ('0' + _status / 100)); + buffer.put((byte) ('0' + (_status % 100) / 10)); + buffer.put((byte) ('0' + (_status % 10))); + buffer.put((byte) ' '); if (_reason==null) { - _header.put((byte) ('0' + _status / 100)); - _header.put((byte) ('0' + (_status % 100) / 10)); - _header.put((byte) ('0' + (_status % 10))); + buffer.put((byte) ('0' + _status / 100)); + buffer.put((byte) ('0' + (_status % 100) / 10)); + buffer.put((byte) ('0' + (_status % 10))); } else - _header.put(_reason); - _header.put(HttpTokens.CRLF); + buffer.put(_reason); + buffer.put(HttpTokens.CRLF); } else { if (_reason==null) - _header.put(status._responseLine); + buffer.put(status._responseLine); else { - _header.put(status._schemeCode); - _header.put(_reason); - _header.put(HttpTokens.CRLF); + buffer.put(status._schemeCode); + buffer.put(_reason); + buffer.put(HttpTokens.CRLF); } } @@ -488,8 +704,8 @@ public class HttpGenerator extends AbstractGenerator if (_status!=101 ) { - _header.put(HttpTokens.CRLF); - _state = STATE_CONTENT; + buffer.put(HttpTokens.CRLF); + _state = State.CONTENT; return; } } @@ -506,11 +722,9 @@ public class HttpGenerator extends AbstractGenerator // Add headers if (_status>=200 && _date!=null) { - _header.put(HttpHeader.DATE_BUFFER); - _header.put((byte)':'); - _header.put((byte)' '); - _header.put(_date); - _header.put(CRLF); + buffer.put(HttpHeader.DATE.toBytesColonSpace()); + buffer.put(_date); + buffer.put(CRLF); } // key field values @@ -530,9 +744,12 @@ public class HttpGenerator extends AbstractGenerator if (field==null) continue; - switch (field.getNameOrdinal()) - { - case HttpHeader.CONTENT_LENGTH_ORDINAL: + HttpHeader header = HttpHeader.CACHE.get(field.getName()); + HttpHeaderValue value = null; + + switch (header==null?HttpHeader.UNKNOWN:header) + { + case CONTENT_LENGTH: content_length = field; _contentLength = field.getLongValue(); @@ -540,42 +757,72 @@ public class HttpGenerator extends AbstractGenerator content_length = null; // write the field to the header buffer - field.putTo(_header); + field.putTo(buffer); break; - case HttpHeader.CONTENT_TYPE_ORDINAL: - if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT; + case CONTENT_TYPE: + if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString())) + _contentLength = HttpTokens.SELF_DEFINING_CONTENT; // write the field to the header buffer content_type=true; - field.putTo(_header); + field.putTo(buffer); break; - case HttpHeader.TRANSFER_ENCODING_ORDINAL: - if (_version == HttpVersion.HTTP_1_1_ORDINAL) + case TRANSFER_ENCODING: + if (_version == HttpVersion.HTTP_1_1) transfer_encoding = field; // Do NOT add yet! break; - case HttpHeader.CONNECTION_ORDINAL: + case CONNECTION: if (isRequest()) - field.putTo(_header); + field.putTo(buffer); - int connection_value = field.getValueOrdinal(); - switch (connection_value) + value = HttpHeaderValue.CACHE.get(field.getValue()); + + switch (value==null?HttpHeaderValue.UNKNOWN:value) { - case -1: + case UPGRADE: + { + // special case for websocket connection ordering + if (isResponse()) + { + field.putTo(buffer); + continue; + } + } + case CLOSE: + { + close=true; + if (isResponse()) + _persistent=false; + if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT) + _contentLength = HttpTokens.EOF_CONTENT; + break; + } + case KEEP_ALIVE: + { + if (_version == HttpVersion.HTTP_1_0) + { + keep_alive = true; + if (isResponse()) + _persistent=true; + } + break; + } + + default: { String[] values = field.getValue().split(","); for (int i=0;values!=null && i0 || content_type ) && !_noContent) { // known length but not actually set. - _header.put(HttpHeader.CONTENT_LENGTH_BUFFER); - _header.put(HttpTokens.COLON); - _header.put((byte) ' '); - BufferUtil.putDecLong(_header, _contentLength); - _header.put(HttpTokens.CRLF); + buffer.put(HttpHeader.CONTENT_LENGTH.toBytes()); + buffer.put(HttpTokens.COLON); + buffer.put((byte) ' '); + BufferUtil.putDecLong(buffer, _contentLength); + buffer.put(HttpTokens.CRLF); } } else @@ -715,7 +933,7 @@ public class HttpGenerator extends AbstractGenerator case HttpTokens.NO_CONTENT: if (content_length == null && isResponse() && _status >= 200 && _status != 204 && _status != 304) - _header.put(CONTENT_LENGTH_0); + buffer.put(CONTENT_LENGTH_0); break; case HttpTokens.EOF_CONTENT: @@ -738,12 +956,12 @@ public class HttpGenerator extends AbstractGenerator { String c = transfer_encoding.getValue(); if (c.endsWith(HttpHeaderValue.CHUNKED)) - transfer_encoding.putTo(_header); + transfer_encoding.putTo(buffer); else throw new IllegalArgumentException("BAD TE"); } else - _header.put(TRANSFER_ENCODING_CHUNKED); + buffer.put(TRANSFER_ENCODING_CHUNKED); } // Handle connection if need be @@ -757,197 +975,47 @@ public class HttpGenerator extends AbstractGenerator { if (!_persistent && (close || _version > HttpVersion.HTTP_1_0_ORDINAL)) { - _header.put(CONNECTION_CLOSE); + buffer.put(CONNECTION_CLOSE); if (connection!=null) { - _header.setPutIndex(_header.putIndex()-2); - _header.put((byte)','); - _header.put(connection.toString().getBytes()); - _header.put(CRLF); + buffer.setPutIndex(buffer.putIndex()-2); + buffer.put((byte)','); + buffer.put(connection.toString().getBytes()); + buffer.put(CRLF); } } else if (keep_alive) { - _header.put(CONNECTION_KEEP_ALIVE); + buffer.put(CONNECTION_KEEP_ALIVE); if (connection!=null) { - _header.setPutIndex(_header.putIndex()-2); - _header.put((byte)','); - _header.put(connection.toString().getBytes()); - _header.put(CRLF); + buffer.setPutIndex(buffer.putIndex()-2); + buffer.put((byte)','); + buffer.put(connection.toString().getBytes()); + buffer.put(CRLF); } } else if (connection!=null) { - _header.put(CONNECTION_); - _header.put(connection.toString().getBytes()); - _header.put(CRLF); + buffer.put(CONNECTION_); + buffer.put(connection.toString().getBytes()); + buffer.put(CRLF); } } if (!has_server && _status>199 && getSendServerVersion()) - _header.put(SERVER); + buffer.put(SERVER); // end the header. - _header.put(HttpTokens.CRLF); - _state = STATE_CONTENT; + buffer.put(HttpTokens.CRLF); + _state = State.CONTENT; } catch(ArrayIndexOutOfBoundsException e) { - throw new RuntimeException("Header>"+_header.capacity(),e); + throw new RuntimeException("Header>"+buffer.capacity(),e); } } - - /* ------------------------------------------------------------ */ - /** - * Complete the message. - * - * @throws IOException - */ - @Override - public void complete() throws IOException - { - if (_state == STATE_END) - return; - - super.complete(); - - if (_state < STATE_FLUSHING) - { - _state = STATE_FLUSHING; - if (_contentLength == HttpTokens.CHUNKED_CONTENT) - _needEOC = true; - } - - flushBuffer(); - } - - /* ------------------------------------------------------------ */ - @Override - public int flushBuffer() throws IOException - { - try - { - - if (_state == STATE_HEADER) - throw new IllegalStateException("State==HEADER"); - - prepareBuffers(); - - if (_endp == null) - { - if (_needCRLF && _buffer!=null) - _buffer.put(HttpTokens.CRLF); - if (_needEOC && _buffer!=null && !_head) - _buffer.put(LAST_CHUNK); - _needCRLF=false; - _needEOC=false; - return 0; - } - - int total= 0; - - int len = -1; - int to_flush = flushMask(); - int last_flush; - - do - { - last_flush=to_flush; - switch (to_flush) - { - case 7: - throw new IllegalStateException(); // should never happen! - case 6: - len = _endp.flush(_header, _buffer, null); - break; - case 5: - len = _endp.flush(_header, _content, null); - break; - case 4: - len = _endp.flush(_header); - break; - case 3: - len = _endp.flush(_buffer, _content, null); - break; - case 2: - len = _endp.flush(_buffer); - break; - case 1: - len = _endp.flush(_content); - break; - case 0: - { - len=0; - // Nothing more we can write now. - if (_header != null) - _header.clear(); - - _bypass = false; - _bufferChunked = false; - - if (_buffer != null) - { - _buffer.clear(); - if (_contentLength == HttpTokens.CHUNKED_CONTENT) - { - // reserve some space for the chunk header - _buffer.setPutIndex(CHUNK_SPACE); - _buffer.setGetIndex(CHUNK_SPACE); - - // Special case handling for small left over buffer from - // an addContent that caused a buffer flush. - if (_content != null && _content.remaining() < _buffer.space() && _state != STATE_FLUSHING) - { - _buffer.put(_content); - _content.clear(); - _content=null; - } - } - } - - // Are we completely finished for now? - if (!_needCRLF && !_needEOC && (_content==null || _content.remaining()==0)) - { - if (_state == STATE_FLUSHING) - _state = STATE_END; - - if (_state==STATE_END && _persistent != null && !_persistent && _status!=100 && _method==null) - _endp.shutdownOutput(); - } - else - // Try to prepare more to write. - prepareBuffers(); - } - - } - - if (len > 0) - total+=len; - - to_flush = flushMask(); - } - // loop while progress is being made (OR we have prepared some buffers that might make progress) - while (len>0 || (to_flush!=0 && last_flush==0)); - - return total; - } - catch (IOException e) - { - LOG.ignore(e); - throw (e instanceof EofException) ? e:new EofException(e); - } - } - - /* ------------------------------------------------------------ */ - private int flushMask() - { - return ((_header != null && _header.remaining() > 0)?4:0) - | ((_buffer != null && _buffer.remaining() > 0)?2:0) - | ((_bypass && _content != null && _content.remaining() > 0)?1:0); - } - /* ------------------------------------------------------------ */ private void prepareBuffers() { @@ -1084,28 +1152,11 @@ public class HttpGenerator extends AbstractGenerator } - public int getBytesBuffered() - { - return(_header==null?0:_header.remaining())+ - (_buffer==null?0:_buffer.remaining())+ - (_content==null?0:_content.remaining()); - } - - public boolean isEmpty() - { - return (_header==null||_header.remaining()==0) && - (_buffer==null||_buffer.remaining()==0) && - (_content==null||_content.remaining()==0); - } - @Override public String toString() { - return String.format("%s{s=%d,h=%d,b=%d,c=%d}", + return String.format("%s{s=%d}", getClass().getSimpleName(), - _state, - _header == null ? -1 : _header.remaining(), - _buffer == null ? -1 : _buffer.remaining(), - _content == null ? -1 : _content.remaining()); + _state); } } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java index b8d92c6ba1b..ad81252a403 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java @@ -17,6 +17,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringMap; +import org.eclipse.jetty.util.StringUtil; public enum HttpHeader @@ -101,7 +102,9 @@ public enum HttpHeader SET_COOKIE("Set-Cookie"), SET_COOKIE2("Set-Cookie2"), MIME_VERSION("MIME-Version"), - IDENTITY("identity"); + IDENTITY("identity"), + + UNKNOWN("::UNKNOWN::"); /* ------------------------------------------------------------ */ @@ -109,17 +112,22 @@ public enum HttpHeader static { for (HttpHeader header : HttpHeader.values()) - CACHE.put(header.toString(),header); + if (header!=UNKNOWN) + CACHE.put(header.toString(),header); } private final String _string; + private final byte[] _bytes; + private final byte[] _bytesColonSpace; private final ByteBuffer _buffer; /* ------------------------------------------------------------ */ HttpHeader(String s) { _string=s; - _buffer=BufferUtil.toBuffer(s); + _bytes=StringUtil.getBytes(s); + _bytesColonSpace=StringUtil.getBytes(s+": "); + _buffer=ByteBuffer.wrap(_bytes); } /* ------------------------------------------------------------ */ @@ -128,6 +136,18 @@ public enum HttpHeader return _buffer.asReadOnlyBuffer(); } + /* ------------------------------------------------------------ */ + public byte[] toBytes() + { + return _bytes; + } + + /* ------------------------------------------------------------ */ + public byte[] toBytesColonSpace() + { + return _bytesColonSpace; + } + /* ------------------------------------------------------------ */ public String toString() { diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java index 8e68cdbe335..d502d9c9b41 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java @@ -35,14 +35,16 @@ public enum HttpHeaderValue TE("TE"), BYTES("bytes"), NO_CACHE("no-cache"), - UPGRADE("Upgrade"); + UPGRADE("Upgrade"), + UNKNOWN("::UNKNOWN::"); /* ------------------------------------------------------------ */ public final static StringMap CACHE= new StringMap(true); static { for (HttpHeaderValue value : HttpHeaderValue.values()) - CACHE.put(value.toString(),value); + if (value!=UNKNOWN) + CACHE.put(value.toString(),value); } private final String _string; diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java index 97f6b7f779f..4a809deea45 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java @@ -17,6 +17,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringMap; +import org.eclipse.jetty.util.StringUtil; /* ------------------------------------------------------------------------------- */ @@ -35,6 +36,7 @@ public enum HttpVersion } private final String _string; + private final byte[] _bytes; private final ByteBuffer _buffer; private final int _version; @@ -42,10 +44,17 @@ public enum HttpVersion HttpVersion(String s,int version) { _string=s; - _buffer=BufferUtil.toBuffer(s); + _bytes=StringUtil.getBytes(s); + _buffer=ByteBuffer.wrap(_bytes); _version=version; } + /* ------------------------------------------------------------ */ + public byte[] toBytes() + { + return _bytes; + } + /* ------------------------------------------------------------ */ public ByteBuffer toBuffer() { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java index de79ee258c9..75fec91a823 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java @@ -352,14 +352,7 @@ public class StringUtil /* ------------------------------------------------------------ */ public static String toUTF8String(byte[] b,int offset,int length) { - try - { - return new String(b,offset,length,__UTF8); - } - catch (UnsupportedEncodingException e) - { - throw new IllegalArgumentException(e); - } + return new String(b,offset,length,__UTF8_CHARSET); } /* ------------------------------------------------------------ */ @@ -418,15 +411,7 @@ public class StringUtil public static byte[] getBytes(String s) { - try - { - return s.getBytes(__ISO_8859_1); - } - catch(Exception e) - { - LOG.warn(e); - return s.getBytes(); - } + return s.getBytes(__ISO_8859_1_CHARSET); } public static byte[] getBytes(String s,String charset)