blob: 8df3ebe2a26e91c42548d831e49eb1ed123aa064 [file] [log] [blame]
//
// ========================================================================
// 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 org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.io.View;
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 int _version = HttpVersions.HTTP_1_1_ORDINAL;
protected Buffer _reason;
protected Buffer _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 Buffer _header; // Buffer for HTTP header (and maybe small _content)
protected Buffer _buffer; // Buffer for copy of passed _content
protected Buffer _content; // Buffer passed to addContent
protected Buffer _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 = HttpVersions.HTTP_1_1_ORDINAL;
_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())
{
Buffer nb = _buffers.getBuffer(contentBufferSize);
nb.put(_buffer);
_buffers.returnBuffer(_buffer);
_buffer = nb;
}
}
/* ------------------------------------------------------------ */
public Buffer 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 <code>false</code> if the connection should be closed after a request has been read,
* <code>true</code> if it should be used for additional requests.
*/
public boolean isPersistent()
{
return _persistent!=null
?_persistent.booleanValue()
:(isRequest()?true:_version>HttpVersions.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==HttpVersions.HTTP_0_9_ORDINAL && _method!=null)
_noContent=true;
}
/* ------------------------------------------------------------ */
public int getVersion()
{
return _version;
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.http.Generator#setDate(org.eclipse.jetty.io.Buffer)
*/
public void setDate(Buffer timeStampBuffer)
{
_date=timeStampBuffer;
}
/* ------------------------------------------------------------ */
/**
*/
public void setRequest(String method, String uri)
{
if (method==null || HttpMethods.GET.equals(method) )
_method=HttpMethods.GET_BUFFER;
else
_method=HttpMethods.CACHE.lookup(method);
_uri=uri;
if (_version==HttpVersions.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=new ByteArrayBuffer(len);
for (int i=0;i<len;i++)
{
char ch = reason.charAt(i);
if (ch!='\r'&&ch!='\n')
_reason.put((byte)ch);
else
_reason.put((byte)' ');
}
}
}
/* ------------------------------------------------------------ */
/** Prepare buffer for unchecked writes.
* Prepare the generator buffer to receive unchecked writes
* @return the available space in the buffer.
* @throws IOException
*/
public abstract int prepareUncheckedAddContent() throws IOException;
/* ------------------------------------------------------------ */
void uncheckedAddContent(int b)
{
_buffer.put((byte)b);
}
/* ------------------------------------------------------------ */
public void completeUncheckedAddContent()
{
if (_noContent)
{
if(_buffer!=null)
_buffer.clear();
}
else
{
_contentWritten+=_buffer.length();
if (_head)
_buffer.clear();
}
}
/* ------------------------------------------------------------ */
public boolean isBufferFull()
{
if (_buffer != null && _buffer.space()==0)
{
if (_buffer.length()==0 && !_buffer.isImmutable())
_buffer.compact();
return _buffer.space()==0;
}
return _content!=null && _content.length()>0;
}
/* ------------------------------------------------------------ */
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;
Buffer content = _content;
Buffer buffer = _buffer;
if (content!=null && content.length()>0 || buffer!=null && buffer.length()>0 || isBufferFull())
{
flushBuffer();
while (now<end && (content!=null && content.length()>0 ||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(new ByteArrayBuffer(content)), Generator.LAST);
}
else if (code>=400)
{
completeHeader(null, false);
addContent(new View(new ByteArrayBuffer("Error: "+(reason==null?(""+code):reason))), 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();
}
}
}