view src/org/eclipse/jetty/http/HttpGenerator.java @ 889:5731a7707466

remove _forwarded
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 06 Oct 2016 23:24:30 -0600
parents cb78ee27b0e0
children dbecd7faa1f5
line wrap: on
line source

//
//  ========================================================================
//  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.io.InterruptedIOException;

import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.BufferCache.CachedBuffer;
import org.eclipse.jetty.io.BufferUtil;
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.util.StringUtil;
import org.eclipse.jetty.server.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/* ------------------------------------------------------------ */
/**
 * HttpGenerator. Builds HTTP Messages.
 *
 *
 *
 */
public class HttpGenerator extends AbstractGenerator
{
	private static final Logger LOG = LoggerFactory.getLogger(HttpGenerator.class);

	// Build cache of response lines for status
	private static class Status
	{
		Buffer _reason;
		Buffer _schemeCode;
		Buffer _responseLine;
	}
	private static final Status[] __status = new Status[HttpStatus.MAX_CODE+1];
	static
	{
		int versionLength=HttpVersions.HTTP_1_1_BUFFER.length();

		for (int i=0;i<__status.length;i++)
		{
			HttpStatus.Code code = HttpStatus.getCode(i);
			if (code==null)
				continue;
			String reason=code.getMessage();
			byte[] bytes=new byte[versionLength+5+reason.length()+2];
			HttpVersions.HTTP_1_1_BUFFER.peek(0,bytes, 0, versionLength);
			bytes[versionLength+0]=' ';
			bytes[versionLength+1]=(byte)('0'+i/100);
			bytes[versionLength+2]=(byte)('0'+(i%100)/10);
			bytes[versionLength+3]=(byte)('0'+(i%10));
			bytes[versionLength+4]=' ';
			for (int j=0;j<reason.length();j++)
				bytes[versionLength+5+j]=(byte)reason.charAt(j);
			bytes[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN;
			bytes[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;

			__status[i] = new Status();
			__status[i]._reason=new ByteArrayBuffer(bytes,versionLength+5,bytes.length-versionLength-7,Buffer.IMMUTABLE);
			__status[i]._schemeCode=new ByteArrayBuffer(bytes,0,versionLength+5,Buffer.IMMUTABLE);
			__status[i]._responseLine=new ByteArrayBuffer(bytes,0,bytes.length,Buffer.IMMUTABLE);
		}
	}

	/* ------------------------------------------------------------------------------- */
	public static Buffer getReasonBuffer(int code)
	{
		Status status = code<__status.length?__status[code]:null;
		if (status!=null)
			return status._reason;
		return null;
	}


	// 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[] CONNECTION_ = StringUtil.getBytes("Connection: ");
	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("+Server.version+")\015\012");

	// other statics
	private static final int CHUNK_SPACE = 12;

	// data
	protected boolean _bypass = false; // True if _content buffer can be written directly to endp and bypass the content buffer
	private boolean _needCRLF = false;
	private boolean _needEOC = false;
	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.trace("",e);
			}
		}
		super.reset();
		if (_buffer!=null)
			_buffer.clear();
		if (_header!=null)
			_header.clear();
		if (_content!=null)
			_content=null;
		_bypass = false;
		_needCRLF = false;
		_needEOC = false;
		_bufferChunked=false;
		_method=null;
		_uri=null;
		_noContent=false;
	}

	/* ------------------------------------------------------------ */
	/**
	 * Add content.
	 *
	 * @param content
	 * @param last
	 * @throws IllegalArgumentException if <code>content</code> is {@link Buffer#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.
	 */
	public void addContent(Buffer content, boolean last) throws IOException
	{
		if (_noContent)
			throw new IllegalStateException("NO CONTENT");

		if (_last || _state==STATE_END)
		{
			LOG.warn("Ignoring extra content {}",content);
			content.clear();
			return;
		}
		_last = last;

		// Handle any unfinished business?
		if (_content!=null && _content.length()>0 || _bufferChunked)
		{
			if (_endp.isOutputShutdown())
				throw new EofException();
			flushBuffer();
			if (_content != null && _content.length()>0)
			{
				if (_bufferChunked)
				{
					Buffer nc=_buffers.getBuffer(_content.length()+CHUNK_SPACE+content.length());
					nc.put(_content);
					nc.put(HttpTokens.CRLF);
					BufferUtil.putHexInt(nc, content.length());
					nc.put(HttpTokens.CRLF);
					nc.put(content);
					content=nc;
				}
				else
				{
					Buffer nc=_buffers.getBuffer(_content.length()+content.length());
					nc.put(_content);
					nc.put(content);
					content=nc;
				}
			}
		}

		_content = content;
		_contentWritten += content.length();

		// Handle the _content
		if (_head)
		{
			content.clear();
			_content=null;
		}
		else if (_endp != null && (_buffer==null || _buffer.length()==0) && _content.length() > 0 && (_last || isCommitted() && _content.length()>1024))
		{
			_bypass = true;
		}
		else if (!_bufferChunked)
		{
			// Yes - so we better check we have a buffer
			if (_buffer == null)
				_buffer = _buffers.getBuffer();

			// Copy _content to buffer;
			int len=_buffer.put(_content);
			_content.skip(len);
			if (_content.length() == 0)
				_content = null;
		}
	}

	/* ------------------------------------------------------------ */
	/**
	 * send complete response.
	 *
	 * @param response
	 */
	public void sendResponse(Buffer response) throws IOException
	{
		if (_noContent || _state!=STATE_HEADER || _content!=null && _content.length()>0 || _bufferChunked || _head )
			throw new IllegalStateException();

		_last = true;

		_content = response;
		_bypass = true;
		_state = STATE_FLUSHING;

		// TODO this is not exactly right, but should do.
		_contentLength =_contentWritten = response.length();

	}

	/* ------------------------------------------------------------ */
	/** 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?
		Buffer 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.length();

		// 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)
			return;

		if (code<100||code>199)
			throw new IllegalArgumentException("!1xx");
		Status status=__status[code];
		if (status==null)
			throw new IllegalArgumentException(code+"?");

		// get a header buffer
		if (_header == null)
			_header = _buffers.getHeader();

		_header.put(status._responseLine);
		_header.put(HttpTokens.CRLF);

		try
		{
			// nasty semi busy flush!
			while(_header.length()>0)
			{
				int len = _endp.flush(_header);
				if (len<0)
					throw new EofException();
				if (len==0)
					Thread.sleep(100);
			}
		}
		catch(InterruptedException e)
		{
			LOG.debug("",e);
			throw new InterruptedIOException(e.toString());
		}
	}

	/* ------------------------------------------------------------ */
	@Override
	public boolean isRequest()
	{
		return _method!=null;
	}

	/* ------------------------------------------------------------ */
	@Override
	public boolean isResponse()
	{
		return _method==null;
	}

	/* ------------------------------------------------------------ */
	@Override
	public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
	{
		if (_state != STATE_HEADER)
			return;

		// handle a reset
		if (isResponse() && _status==0)
			throw new EofException();

		if (_last && !allContentAdded)
			throw new IllegalStateException("last?");
		_last = _last | allContentAdded;

		// get a header buffer
		if (_header == null)
			_header = _buffers.getHeader();

		boolean has_server = false;

		try
		{
			if (isRequest())
			{
				_persistent=true;

				if (_version == HttpVersions.HTTP_0_9_ORDINAL)
				{
					_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;
					_noContent=true;
					return;
				}
				else
				{
					_header.put(_method);
					_header.put((byte)' ');
					_header.put(_uri.getBytes("UTF-8")); // TODO check
					_header.put((byte)' ');
					_header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER);
					_header.put(HttpTokens.CRLF);
				}
			}
			else
			{
				// Responses
				if (_version == HttpVersions.HTTP_0_9_ORDINAL)
				{
					_persistent = false;
					_contentLength = HttpTokens.EOF_CONTENT;
					_state = STATE_CONTENT;
					return;
				}
				else
				{
					if (_persistent==null)
						_persistent= (_version > HttpVersions.HTTP_1_0_ORDINAL);

					// add response line
					Status status = _status<__status.length?__status[_status]:null;

					if (status==null)
					{
						_header.put(HttpVersions.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) ' ');
						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(_reason);
						_header.put(HttpTokens.CRLF);
					}
					else
					{
						if (_reason==null)
							_header.put(status._responseLine);
						else
						{
							_header.put(status._schemeCode);
							_header.put(_reason);
							_header.put(HttpTokens.CRLF);
						}
					}

					if (_status<200 && _status>=100 )
					{
						_noContent=true;
						_content=null;
						if (_buffer!=null)
							_buffer.clear();
						// end the header.

						if (_status!=101 )
						{
							_header.put(HttpTokens.CRLF);
							_state = STATE_CONTENT;
							return;
						}
					}
					else if (_status==204 || _status==304)
					{
						_noContent=true;
						_content=null;
						if (_buffer!=null)
							_buffer.clear();
					}
				}
			}

			// Add headers
			if (_status>=200 && _date!=null)
			{
				_header.put(HttpHeaders.DATE_BUFFER);
				_header.put((byte)':');
				_header.put((byte)' ');
				_header.put(_date);
				_header.put(CRLF);
			}

			// key field values
			HttpFields.Field content_length = null;
			HttpFields.Field transfer_encoding = null;
			boolean keep_alive = false;
			boolean close=false;
			boolean content_type=false;
			StringBuilder connection = null;

			if (fields != null)
			{
				int s=fields.size();
				for (int f=0;f<s;f++)
				{
					HttpFields.Field field = fields.getField(f);
					if (field==null)
						continue;

					switch (field.getNameOrdinal())
					{
						case HttpHeaders.CONTENT_LENGTH_ORDINAL:
							content_length = field;
							_contentLength = field.getLongValue();

							if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten)
								content_length = null;

							// write the field to the header buffer
							field.putTo(_header);
							break;

						case HttpHeaders.CONTENT_TYPE_ORDINAL:
							if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT;

							// write the field to the header buffer
							content_type=true;
							field.putTo(_header);
							break;

						case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
							if (_version == HttpVersions.HTTP_1_1_ORDINAL)
								transfer_encoding = field;
							// Do NOT add yet!
							break;

						case HttpHeaders.CONNECTION_ORDINAL:
							if (isRequest())
								field.putTo(_header);

							int connection_value = field.getValueOrdinal();
							switch (connection_value)
							{
								case -1:
								{
									String[] values = field.getValue().split(",");
									for  (int i=0;values!=null && i<values.length;i++)
									{
										CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim());

										if (cb!=null)
										{
											switch(cb.getOrdinal())
											{
												case HttpHeaderValues.CLOSE_ORDINAL:
													close=true;
													if (isResponse())
														_persistent=false;
													keep_alive=false;
													if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT)
														_contentLength = HttpTokens.EOF_CONTENT;
													break;

												case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
													if (_version == HttpVersions.HTTP_1_0_ORDINAL)
													{
														keep_alive = true;
														if (isResponse())
															_persistent = true;
													}
													break;

												default:
													if (connection==null)
														connection=new StringBuilder();
													else
														connection.append(',');
													connection.append(values[i]);
											}
										}
										else
										{
											if (connection==null)
												connection=new StringBuilder();
											else
												connection.append(',');
											connection.append(values[i]);
										}
									}

									break;
								}
								case HttpHeaderValues.UPGRADE_ORDINAL:
								{
									// special case for websocket connection ordering
									if (isResponse())
									{
										field.putTo(_header);
										continue;
									}
								}
								case HttpHeaderValues.CLOSE_ORDINAL:
								{
									close=true;
									if (isResponse())
										_persistent=false;
									if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT)
										_contentLength = HttpTokens.EOF_CONTENT;
									break;
								}
								case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
								{
									if (_version == HttpVersions.HTTP_1_0_ORDINAL)
									{
										keep_alive = true;
										if (isResponse())
											_persistent=true;
									}
									break;
								}
								default:
								{
									if (connection==null)
										connection=new StringBuilder();
									else
										connection.append(',');
									connection.append(field.getValue());
								}
							}

							// Do NOT add yet!
							break;

						case HttpHeaders.SERVER_ORDINAL:
							has_server=true;
							field.putTo(_header);
							break;

						default:
							// write the field to the header buffer
							field.putTo(_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
			switch ((int) _contentLength)
			{
				case HttpTokens.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 (_contentWritten == 0 && isResponse() && (_status < 200 || _status == 204 || _status == 304))
						_contentLength = HttpTokens.NO_CONTENT;
					else if (_last)
					{
						// we have seen all the _content there is
						_contentLength = _contentWritten;
						if (content_length == null && (isResponse() || _contentLength>0 || content_type ) && !_noContent)
						{
							// known length but not actually set.
							_header.put(HttpHeaders.CONTENT_LENGTH_BUFFER);
							_header.put(HttpTokens.COLON);
							_header.put((byte) ' ');
							BufferUtil.putDecLong(_header, _contentLength);
							_header.put(HttpTokens.CRLF);
						}
					}
					else
					{
						// No idea, so we must assume that a body is coming
						_contentLength = (!_persistent || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT;
						if (isRequest() && _contentLength==HttpTokens.EOF_CONTENT)
						{
							_contentLength=HttpTokens.NO_CONTENT;
							_noContent=true;
						}
					}
					break;

				case HttpTokens.NO_CONTENT:
					if (content_length == null && isResponse() && _status >= 200 && _status != 204 && _status != 304)
						_header.put(CONTENT_LENGTH_0);
					break;

				case HttpTokens.EOF_CONTENT:
					_persistent = isRequest();
					break;

				case HttpTokens.CHUNKED_CONTENT:
					break;

				default:
					// TODO - maybe allow forced chunking by setting te ???
					break;
			}

			// Add transfer_encoding if needed
			if (_contentLength == HttpTokens.CHUNKED_CONTENT)
			{
				// try to use user supplied encoding as it may have other values.
				if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal())
				{
					String c = transfer_encoding.getValue();
					if (c.endsWith(HttpHeaderValues.CHUNKED))
						transfer_encoding.putTo(_header);
					else
						throw new IllegalArgumentException("BAD TE");
				}
				else
					_header.put(TRANSFER_ENCODING_CHUNKED);
			}

			// Handle connection if need be
			if (_contentLength==HttpTokens.EOF_CONTENT)
			{
				keep_alive=false;
				_persistent=false;
			}

			if (isResponse())
			{
				if (!_persistent && (close || _version > HttpVersions.HTTP_1_0_ORDINAL))
				{
					_header.put(CONNECTION_CLOSE);
					if (connection!=null)
					{
						_header.setPutIndex(_header.putIndex()-2);
						_header.put((byte)',');
						_header.put(connection.toString().getBytes());
						_header.put(CRLF);
					}
				}
				else if (keep_alive)
				{
					_header.put(CONNECTION_KEEP_ALIVE);
					if (connection!=null)
					{
						_header.setPutIndex(_header.putIndex()-2);
						_header.put((byte)',');
						_header.put(connection.toString().getBytes());
						_header.put(CRLF);
					}
				}
				else if (connection!=null)
				{
					_header.put(CONNECTION_);
					_header.put(connection.toString().getBytes());
					_header.put(CRLF);
				}
			}

			if (!has_server && _status>199)
				_header.put(SERVER);

			// end the header.
			_header.put(HttpTokens.CRLF);
			_state = STATE_CONTENT;

		}
		catch(ArrayIndexOutOfBoundsException e)
		{
			throw new RuntimeException("Header>"+_header.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.length() < _buffer.space() && _state != STATE_FLUSHING)
								{
									_buffer.put(_content);
									_content.clear();
									_content=null;
								}
							}
						}

						// Are we completely finished for now?
						if (!_needCRLF && !_needEOC && (_content==null || _content.length()==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.trace("",e);
			throw (e instanceof EofException) ? e:new EofException(e);
		}
	}

	/* ------------------------------------------------------------ */
	private int flushMask()
	{
		return  ((_header != null && _header.length() > 0)?4:0)
		| ((_buffer != null && _buffer.length() > 0)?2:0)
		| ((_bypass && _content != null && _content.length() > 0)?1:0);
	}

	/* ------------------------------------------------------------ */
	private void prepareBuffers()
	{
		// if we are not flushing an existing chunk
		if (!_bufferChunked)
		{
			// Refill buffer if possible
			if (!_bypass && _content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
			{
				int len = _buffer.put(_content);
				_content.skip(len);
				if (_content.length() == 0)
					_content = null;
			}

			// Chunk buffer if need be
			if (_contentLength == HttpTokens.CHUNKED_CONTENT)
			{
				if (_bypass && (_buffer==null||_buffer.length()==0) && _content!=null)
				{
					// this is a bypass write
					int size = _content.length();
					_bufferChunked = true;

					if (_header == null)
						_header = _buffers.getHeader();

					// if we need CRLF add this to header
					if (_needCRLF)
					{
						if (_header.length() > 0) throw new IllegalStateException("EOC");
						_header.put(HttpTokens.CRLF);
						_needCRLF = false;
					}
					// Add the chunk size to the header
					BufferUtil.putHexInt(_header, size);
					_header.put(HttpTokens.CRLF);

					// Need a CRLF after the content
					_needCRLF=true;
				}
				else if (_buffer!=null)
				{
					int size = _buffer.length();
					if (size > 0)
					{
						// Prepare a chunk!
						_bufferChunked = true;

						// Did we leave space at the start of the buffer.
						//noinspection ConstantConditions
						if (_buffer.getIndex() == CHUNK_SPACE)
						{
							// Oh yes, goodie! let's use it then!
							_buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
							_buffer.setGetIndex(_buffer.getIndex() - 2);
							BufferUtil.prependHexInt(_buffer, size);

							if (_needCRLF)
							{
								_buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
								_buffer.setGetIndex(_buffer.getIndex() - 2);
								_needCRLF = false;
							}
						}
						else
						{
							// No space so lets use a header buffer.
							if (_header == null)
								_header = _buffers.getHeader();

							if (_needCRLF)
							{
								if (_header.length() > 0) throw new IllegalStateException("EOC");
								_header.put(HttpTokens.CRLF);
								_needCRLF = false;
							}
							BufferUtil.putHexInt(_header, size);
							_header.put(HttpTokens.CRLF);
						}

						// Add end chunk trailer.
						if (_buffer.space() >= 2)
							_buffer.put(HttpTokens.CRLF);
						else
							_needCRLF = true;
					}
				}

				// If we need EOC and everything written
				if (_needEOC && (_content == null || _content.length() == 0))
				{
					if (_header == null && _buffer == null)
						_header = _buffers.getHeader();

					if (_needCRLF)
					{
						if (_buffer == null && _header != null && _header.space() >= HttpTokens.CRLF.length)
						{
							_header.put(HttpTokens.CRLF);
							_needCRLF = false;
						}
						else if (_buffer!=null && _buffer.space() >= HttpTokens.CRLF.length)
						{
							_buffer.put(HttpTokens.CRLF);
							_needCRLF = false;
						}
					}

					if (!_needCRLF && _needEOC)
					{
						if (_buffer == null && _header != null && _header.space() >= LAST_CHUNK.length)
						{
							if (!_head)
							{
								_header.put(LAST_CHUNK);
								_bufferChunked=true;
							}
							_needEOC = false;
						}
						else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length)
						{
							if (!_head)
							{
								_buffer.put(LAST_CHUNK);
								_bufferChunked=true;
							}
							_needEOC = false;
						}
					}
				}
			}
		}

		if (_content != null && _content.length() == 0)
			_content = null;

	}

	public int getBytesBuffered()
	{
		return(_header==null?0:_header.length())+
		(_buffer==null?0:_buffer.length())+
		(_content==null?0:_content.length());
	}

	public boolean isEmpty()
	{
		return (_header==null||_header.length()==0) &&
		(_buffer==null||_buffer.length()==0) &&
		(_content==null||_content.length()==0);
	}

	@Override
	public String toString()
	{
		Buffer header=_header;
		Buffer buffer=_buffer;
		Buffer content=_content;
		return String.format("%s{s=%d,h=%d,b=%d,c=%d}",
				getClass().getSimpleName(),
				_state,
				header == null ? -1 : header.length(),
				buffer == null ? -1 : buffer.length(),
				content == null ? -1 : content.length());
	}
}