Jetty9 - Refactored error handling logic.

This commit is contained in:
Simone Bordet 2012-08-21 23:40:08 +02:00
parent dc140f85b7
commit 71e7f519c7
10 changed files with 248 additions and 231 deletions

View File

@ -384,11 +384,10 @@ public class HttpGenerator
if (len>0)
{
// Do we need a chunk buffer?
if (isChunking() && (headerOrChunk==null || headerOrChunk.capacity()>CHUNK_SIZE))
if (isChunking() && BufferUtil.space(headerOrChunk) < CHUNK_SIZE)
return Result.NEED_CHUNK;
ByteBuffer chunk = headerOrChunk;
_contentPrepared+=len;
if (isChunking())
{
@ -421,11 +420,11 @@ public class HttpGenerator
if (isChunking())
{
// Do we need a chunk buffer?
if (headerOrChunk==null || headerOrChunk.capacity()>CHUNK_SIZE)
if (BufferUtil.space(headerOrChunk) < CHUNK_SIZE)
return Result.NEED_CHUNK;
ByteBuffer chunk=headerOrChunk;
// Write the last chunk
ByteBuffer chunk=headerOrChunk;
BufferUtil.clearToFill(chunk);
prepareChunk(chunk,0);
BufferUtil.flipToFlush(chunk,0);
@ -448,8 +447,6 @@ public class HttpGenerator
}
}
/* ------------------------------------------------------------ */
private void prepareChunk(ByteBuffer chunk, int remaining)
{
@ -471,7 +468,6 @@ public class HttpGenerator
}
}
/* ------------------------------------------------------------ */
private void generateRequestLine(RequestInfo request,ByteBuffer header)
{

View File

@ -24,8 +24,10 @@ import java.nio.ByteBuffer;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import org.eclipse.jetty.continuation.ContinuationThrowable;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
import org.eclipse.jetty.http.HttpHeader;
@ -38,6 +40,8 @@ import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
@ -170,7 +174,7 @@ public class HttpChannel
throw new IOException("Committed before 100 Continues");
// TODO: break this dependency with HttpGenerator
boolean committed = commit(HttpGenerator.CONTINUE_100_INFO, null, false);
boolean committed = commitResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
if (!committed)
throw new IOException("Concurrent commit while trying to send 100-Continue"); // TODO: better message
}
@ -243,7 +247,7 @@ public class HttpChannel
LOG.warn(String.valueOf(_uri), e);
_state.error(e);
_request.setHandled(true);
commitError(500, null, e.toString());
handleError(e);
}
finally
{
@ -251,7 +255,7 @@ public class HttpChannel
}
}
return complete1();
return complete();
}
finally
{
@ -264,7 +268,7 @@ public class HttpChannel
}
}
protected boolean complete1()
protected boolean complete()
{
LOG.debug("{} complete", this);
@ -287,7 +291,7 @@ public class HttpChannel
}
if (!_response.isCommitted() && !_request.isHandled())
commitError(404, null, null); // TODO: this should call the ErrorHandler
_response.sendError(Response.SC_NOT_FOUND, null, null);
_request.setHandled(true);
_request.getHttpInput().consumeAll();
@ -380,62 +384,130 @@ public class HttpChannel
*/
}
protected boolean commitError(final int status, final String reason, String content)
/**
* <p>Sends an error 500, performing a special logic to detect whether the request is suspended,
* to avoid concurrent writes from the application.</p>
* <p>It may happen that the application suspends, and then throws an exception, while an application
* spawned thread writes the response content; in such case, we attempt to commit the error directly
* bypassing the {@link ErrorHandler} mechanisms and the response OutputStream.</p>
* @param x the Throwable that caused the problem
*/
private void handleError(Throwable x)
{
return true; // TODO
/*
LOG.debug("{} sendError {} {}", this, status, reason);
if (_response.isCommitted())
return false;
try
if (_state.isSuspended())
{
_response.setStatus(status, reason);
_response.getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
ByteBuffer buffer = null;
if (content != null)
{
buffer = BufferUtil.toBuffer(content, StringUtil.__UTF8_CHARSET);
_response.setContentLength(buffer.remaining());
}
HttpGenerator.ResponseInfo info = _handler.commit();
try
{
write(info, buffer).get();
HttpFields fields = new HttpFields();
ResponseInfo info = new ResponseInfo(_request.getHttpVersion(), fields, 0, Response.SC_INTERNAL_SERVER_ERROR, null, _request.isHead());
boolean committed = commitResponse(info, null, true);
if (!committed)
LOG.warn("Could not send response error 500, response is already committed");
}
catch (final InterruptedException e)
catch (IOException e)
{
throw new InterruptedIOException()
{{
this.initCause(e);
}};
// We tried our best, just log
LOG.debug("Could not commit response error 500", e);
}
catch (final ExecutionException e)
}
else
{
_response.sendError(500, null, x.getMessage());
}
}
protected void sendError(ResponseInfo info, String extraContent)
{
int status = info.getStatus();
try
{
String reason = info.getReason();
if (reason == null)
reason = HttpStatus.getMessage(status);
// If we are allowed to have a body
if (status != Response.SC_NO_CONTENT &&
status != Response.SC_NOT_MODIFIED &&
status != Response.SC_PARTIAL_CONTENT &&
status >= Response.SC_OK)
{
throw new IOException()
{{
this.initCause(e);
}};
ErrorHandler errorHandler = null;
ContextHandler.Context context = _request.getContext();
if (context != null)
errorHandler = context.getContextHandler().getErrorHandler();
if (errorHandler == null)
errorHandler = getServer().getBean(ErrorHandler.class);
if (errorHandler != null)
{
_request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, new Integer(status));
_request.setAttribute(RequestDispatcher.ERROR_MESSAGE, reason);
_request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, _request.getRequestURI());
_request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, _request.getServletName());
errorHandler.handle(null, _request, _request, _response);
}
else
{
HttpFields fields = info.getHttpFields();
fields.put(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
fields.put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.TEXT_HTML_8859_1.toString());
reason = escape(reason);
String uri = escape(_request.getRequestURI());
extraContent = escape(extraContent);
StringBuilder writer = new StringBuilder(2048);
writer.append("<html>\n");
writer.append("<head>\n");
writer.append("<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
writer.append("<title>Error ").append(Integer.toString(status)).append(' ').append(reason).append("</title>\n");
writer.append("</head>\n");
writer.append("<body>\n");
writer.append("<h2>HTTP ERROR: ").append(Integer.toString(status)).append("</h2>\n");
writer.append("<p>Problem accessing ").append(uri).append(". Reason:\n");
writer.append("<pre>").append(reason).append("</pre></p>");
if (extraContent != null)
writer.append("<p>").append(extraContent).append("</p>");
writer.append("<hr /><i><small>Powered by Jetty://</small></i>\n");
writer.append("</body>\n");
writer.append("</html>");
byte[] bytes = writer.toString().getBytes(StringUtil.__ISO_8859_1);
fields.put(HttpHeader.CONTENT_LENGTH, String.valueOf(bytes.length));
_response.getOutputStream().write(bytes);
}
}
else if (status != Response.SC_PARTIAL_CONTENT)
{
// TODO: not sure why we need to modify the request when writing an error ?
// TODO: or modify the response if the error code cannot have a body ?
// _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_TYPE);
// _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_LENGTH);
// _characterEncoding = null;
// _mimeType = null;
}
return true;
}
catch (Exception e)
{
e.printStackTrace();
LOG.debug("failed to sendError {} {}", status, reason, e);
}
finally
{
complete();
// TODO: is this needed ?
if (_state.isIdle())
_state.complete();
_request.getHttpInput().shutdownInput();
}
return false;
*/
catch (IOException x)
{
// We failed to write the error, bail out
LOG.debug("Could not write error response " + status, x);
}
}
private String escape(String reason)
{
if (reason != null)
{
reason = reason.replaceAll("&", "&amp;");
reason = reason.replaceAll("<", "&lt;");
reason = reason.replaceAll(">", "&gt;");
}
return reason;
}
public boolean isSuspended()
@ -604,13 +676,13 @@ public class HttpChannel
if (!_host)
{
commitError(HttpStatus.BAD_REQUEST_400, "No Host Header", null);
_response.sendError(Response.SC_BAD_REQUEST, "No Host Header", null);
return true;
}
if (_expect)
{
commitError(HttpStatus.EXPECTATION_FAILED_417, null, null);
_response.sendError(Response.SC_EXPECTATION_FAILED, null, null);
return true;
}
@ -658,7 +730,7 @@ public class HttpChannel
{
if (status < 400 || status > 599)
status = HttpStatus.BAD_REQUEST_400;
commitError(status, null, null);
_response.sendError(status, null, null);
}
@Override
@ -680,7 +752,7 @@ public class HttpChannel
}
}
protected boolean commit(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
protected boolean commitResponse(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
{
boolean committed = _committed.compareAndSet(false, true);
if (committed)
@ -710,7 +782,7 @@ public class HttpChannel
else
{
ResponseInfo info = _response.newResponseInfo();
boolean committed = commit(info, content, complete);
boolean committed = commitResponse(info, content, complete);
if (!committed)
throw new IOException("Concurrent commit"); // TODO: better message
}

View File

@ -337,7 +337,8 @@ public class HttpConnection extends AbstractConnection
{
_parser.reset();
_parser.close();
_channel.getEventHandler().badMessage(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413,null);
_channel.getResponse().sendError(Response.SC_REQUEST_ENTITY_TOO_LARGE, null, null);
// TODO: close the connection !
}
}
}
@ -397,10 +398,10 @@ public class HttpConnection extends AbstractConnection
return _httpConfig;
}
@Override
// @Override
protected boolean commitError(int status, String reason, String content)
{
if (!super.commitError(status,reason,content))
// if (!super.commitError(status,reason,content))
{
// TODO - should this just be a close and we don't worry about a RST overtaking a flushed response?
@ -408,7 +409,7 @@ public class HttpConnection extends AbstractConnection
// the client something is wrong
getEndPoint().shutdownOutput();
_generator.abort();
return false;
// return false;
}
return true;
}

View File

@ -49,10 +49,12 @@ public class HttpOutput extends ServletOutputStream
private boolean _closed;
private long _written;
private ByteBuffer _aggregate;
private int _bufferSize;
public HttpOutput(HttpChannel channel)
{
_channel = channel;
_bufferSize = _channel.getHttpConfiguration().getResponseBufferSize();
}
public boolean isWritten()
@ -126,7 +128,7 @@ public class HttpOutput extends ServletOutputStream
if (_aggregate == null)
{
// What size should the aggregate be ?
int size = _channel.getHttpConfiguration().getResponseBufferSize();
int size = getBufferSize();
// If this write would fill more than half the aggregate, just write it directly
if (len > size / 2)
@ -176,7 +178,7 @@ public class HttpOutput extends ServletOutputStream
throw new EOFException();
if (_aggregate == null)
_aggregate = _channel.getConnector().getByteBufferPool().acquire(_channel.getHttpConfiguration().getResponseBufferSize(), false);
_aggregate = _channel.getConnector().getByteBufferPool().acquire(getBufferSize(), false);
BufferUtil.append(_aggregate, (byte)b);
_written++;
@ -199,7 +201,6 @@ public class HttpOutput extends ServletOutputStream
if (isClosed())
throw new IOException("Closed");
// Convert HTTP content to contentl
if (content instanceof HttpContent)
{
HttpContent httpContent = (HttpContent)content;
@ -247,24 +248,14 @@ public class HttpOutput extends ServletOutputStream
throw new IllegalArgumentException("unknown content type?");
}
public int getContentBufferSize()
public int getBufferSize()
{
if (_aggregate != null)
return _aggregate.capacity();
return _channel.getHttpConfiguration().getResponseBufferSize();
return _bufferSize;
}
public void increaseContentBufferSize(int size)
public void setBufferSize(int size)
{
if (_aggregate == null || size <= getContentBufferSize())
return;
ByteBuffer r = _channel.getConnector().getByteBufferPool().acquire(size, false);
if (BufferUtil.hasContent(_aggregate))
BufferUtil.flipPutFlip(_aggregate, r);
if (_aggregate != null)
_channel.getConnector().getByteBufferPool().release(_aggregate);
_aggregate = r;
this._bufferSize = size;
}
public void resetBuffer()

View File

@ -9,5 +9,5 @@ public interface HttpTransport
{
public void commit(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean complete) throws IOException;
public int write(ByteBuffer content, boolean complete) throws IOException;
public void write(ByteBuffer content, boolean complete) throws IOException;
}

View File

@ -38,6 +38,7 @@ public class HttpTransportOverHttp implements HttpTransport
private final ByteBufferPool _bufferPool;
private final HttpConfiguration _configuration;
private final EndPoint _endPoint;
private HttpGenerator.ResponseInfo _info;
public HttpTransportOverHttp(ByteBufferPool _bufferPool, HttpConfiguration _configuration, EndPoint _endPoint)
{
@ -48,6 +49,23 @@ public class HttpTransportOverHttp implements HttpTransport
@Override
public void commit(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
{
generate(info, content, complete);
// TODO: Trick only needed by the current HttpGenerator, that always require a ResponseInfo object
if (!complete)
_info = info;
}
@Override
public void write(ByteBuffer content, boolean complete) throws IOException
{
generate(_info, content, complete);
// TODO: Trick only needed by the current HttpGenerator, that always require a ResponseInfo object
if (complete)
_info = null;
}
private void generate(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
{
ByteBuffer header = null;
out: while (true)
@ -87,7 +105,7 @@ public class HttpTransportOverHttp implements HttpTransport
}
else
{
write(header, content);
write(header == null ? BufferUtil.EMPTY_BUFFER : header, content);
}
continue;
}
@ -100,6 +118,10 @@ public class HttpTransportOverHttp implements HttpTransport
{
break out;
}
case CONTINUE:
{
break;
}
default:
{
throw new IllegalStateException();
@ -131,10 +153,4 @@ public class HttpTransportOverHttp implements HttpTransport
throw (Error)cause;
}
}
@Override
public int write(ByteBuffer content, boolean complete) throws IOException
{
throw new UnsupportedOperationException();
}
}

View File

@ -1081,8 +1081,9 @@ public class Request implements HttpServletRequest
}
catch (NumberFormatException e)
{
// TODO: this should be moved to the parser !
if (_channel != null)
_channel.commitError(HttpStatus.BAD_REQUEST_400,"Bad Host header",null);
getResponse().sendError(Response.SC_BAD_REQUEST, "Bad Host header", null);
}
return _serverName;
}

View File

@ -26,7 +26,6 @@ import java.util.Collections;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
@ -43,9 +42,6 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.util.ByteArrayISO8859Writer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.log.Log;
@ -60,7 +56,10 @@ public class Response implements HttpServletResponse
{
private static final Logger LOG = Log.getLogger(Response.class);
public enum OutputState {NONE,STREAM,WRITER}
public enum OutputType
{
NONE,STREAM,WRITER
}
/**
* If a header name starts with this string, the header (stripped of the prefix)
@ -86,7 +85,7 @@ public class Response implements HttpServletResponse
private MimeTypes.Type _mimeType;
private String _characterEncoding;
private String _contentType;
private OutputState _outputState=OutputState.NONE;
private OutputType _outputType = OutputType.NONE;
private PrintWriter _writer;
private long _contentLength=-1;
@ -119,7 +118,7 @@ public class Response implements HttpServletResponse
_characterEncoding=null;
_contentType=null;
_writer=null;
_outputState=OutputState.NONE;
_outputType = OutputType.NONE;
_contentLength=-1;
_committed.set(false);
_out.reset();
@ -334,99 +333,35 @@ public class Response implements HttpServletResponse
@Override
public void sendError(int code, String message) throws IOException
{
if (isIncluding())
return;
sendError(code, message, null);
}
protected void sendError(int code, String message, String content)
{
if (isIncluding())
return;
if (isCommitted())
LOG.warn("Committed before "+code+" "+message);
{
LOG.warn("Could not commit error {} {}, response already committed", code, message);
return;
}
resetBuffer();
_characterEncoding=null;
setHeader(HttpHeader.EXPIRES,null);
setHeader(HttpHeader.LAST_MODIFIED,null);
setHeader(HttpHeader.CACHE_CONTROL,null);
setHeader(HttpHeader.CONTENT_TYPE,null);
setHeader(HttpHeader.CONTENT_LENGTH,null);
_characterEncoding = null;
setHeader(HttpHeader.EXPIRES, null);
setHeader(HttpHeader.LAST_MODIFIED, null);
setHeader(HttpHeader.CACHE_CONTROL, null);
setHeader(HttpHeader.CONTENT_TYPE, null);
setHeader(HttpHeader.CONTENT_LENGTH, null);
_outputState=OutputState.NONE;
setStatus(code,message);
if (message == null)
message = HttpStatus.getMessage(code);
setStatus(code, message);
if (message==null)
message=HttpStatus.getMessage(code);
_outputType = OutputType.NONE;
// If we are allowed to have a body
if (code!=SC_NO_CONTENT &&
code!=SC_NOT_MODIFIED &&
code!=SC_PARTIAL_CONTENT &&
code>=SC_OK)
{
Request request = _channel.getRequest();
ErrorHandler error_handler = null;
ContextHandler.Context context = request.getContext();
if (context!=null)
error_handler=context.getContextHandler().getErrorHandler();
if (error_handler==null)
error_handler = _channel.getServer().getBean(ErrorHandler.class);
if (error_handler!=null)
{
request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(code));
request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,request.getServletName());
error_handler.handle(null,_channel.getRequest(),_channel.getRequest(),this );
}
else
{
setHeader(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString());
ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);
if (message != null)
{
message= StringUtil.replace(message, "&", "&amp;");
message= StringUtil.replace(message, "<", "&lt;");
message= StringUtil.replace(message, ">", "&gt;");
}
String uri= request.getRequestURI();
if (uri!=null)
{
uri= StringUtil.replace(uri, "&", "&amp;");
uri= StringUtil.replace(uri, "<", "&lt;");
uri= StringUtil.replace(uri, ">", "&gt;");
}
writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
writer.write("<title>Error ");
writer.write(Integer.toString(code));
writer.write(' ');
if (message==null)
message=HttpStatus.getMessage(code);
writer.write(message);
writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
writer.write(Integer.toString(code));
writer.write("</h2>\n<p>Problem accessing ");
writer.write(uri);
writer.write(". Reason:\n<pre> ");
writer.write(message);
writer.write("</pre>");
writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>");
writer.write("\n</body>\n</html>\n");
writer.flush();
setContentLength(writer.size());
writer.writeTo(getOutputStream());
writer.destroy();
}
}
else if (code!=SC_PARTIAL_CONTENT)
{
_channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_TYPE);
_channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_LENGTH);
_characterEncoding=null;
_mimeType=null;
}
complete();
_channel.sendError(newResponseInfo(), content);
}
/* ------------------------------------------------------------ */
@ -454,7 +389,7 @@ public class Response implements HttpServletResponse
{
if (_channel.isExpecting102Processing() && !isCommitted())
{
_channel.commit(HttpGenerator.PROGRESS_102_INFO,null,true);
_channel.commitResponse(HttpGenerator.PROGRESS_102_INFO, null, true);
}
}
@ -760,22 +695,22 @@ public class Response implements HttpServletResponse
@Override
public ServletOutputStream getOutputStream() throws IOException
{
if (_outputState==OutputState.WRITER)
if (_outputType == OutputType.WRITER)
throw new IllegalStateException("WRITER");
_outputState=OutputState.STREAM;
_outputType = OutputType.STREAM;
return _out;
}
/* ------------------------------------------------------------ */
public boolean isWriting()
{
return _outputState==OutputState.WRITER;
return _outputType == OutputType.WRITER;
}
/* ------------------------------------------------------------ */
public boolean isOutputing()
{
return _outputState!=OutputState.NONE;
return _outputType != OutputType.NONE;
}
/* ------------------------------------------------------------ */
@ -785,7 +720,7 @@ public class Response implements HttpServletResponse
@Override
public PrintWriter getWriter() throws IOException
{
if (_outputState==OutputState.STREAM)
if (_outputType == OutputType.STREAM)
throw new IllegalStateException("STREAM");
/* if there is no writer yet */
@ -817,7 +752,7 @@ public class Response implements HttpServletResponse
}
}
_outputState=OutputState.WRITER;
_outputType = OutputType.WRITER;
return _writer;
}
@ -854,7 +789,7 @@ public class Response implements HttpServletResponse
{
try
{
switch(_outputState)
switch(_outputType)
{
case WRITER:
_writer.close();
@ -904,7 +839,7 @@ public class Response implements HttpServletResponse
if (isIncluding())
return;
if (_outputState==OutputState.NONE && !isCommitted())
if (_outputType == OutputType.NONE && !isCommitted())
{
if (encoding==null)
{
@ -1010,7 +945,7 @@ public class Response implements HttpServletResponse
{
if (isCommitted() || getContentCount()>0 )
throw new IllegalStateException("Committed or content written");
_out.increaseContentBufferSize(size);
_out.setBufferSize(size);
}
/* ------------------------------------------------------------ */
@ -1020,7 +955,7 @@ public class Response implements HttpServletResponse
@Override
public int getBufferSize()
{
return _out.getContentBufferSize();
return _out.getBufferSize();
}
/* ------------------------------------------------------------ */
@ -1087,7 +1022,7 @@ public class Response implements HttpServletResponse
resetBuffer();
_writer=null;
_outputState=OutputState.NONE;
_outputType = OutputType.NONE;
}
/* ------------------------------------------------------------ */
@ -1100,7 +1035,7 @@ public class Response implements HttpServletResponse
if (isCommitted())
throw new IllegalStateException("Committed");
switch(_outputState)
switch(_outputType)
{
case STREAM:
case WRITER:
@ -1134,7 +1069,7 @@ public class Response implements HttpServletResponse
@Override
public boolean isCommitted()
{
return _committed.get();
return _channel.isCommitted();
}
/* ------------------------------------------------------------ */
@ -1150,7 +1085,7 @@ public class Response implements HttpServletResponse
_locale = locale;
_fields.put(HttpHeader.CONTENT_LANGUAGE,locale.toString().replace('_','-'));
if (_outputState!=OutputState.NONE )
if (_outputType != OutputType.NONE )
return;
if (_channel.getRequest().getContext()==null)

View File

@ -27,13 +27,12 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.util.SimpleHttpParser;
import org.eclipse.jetty.util.log.Log;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
//TODO: reset buffer tests
@ -113,7 +112,6 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
assertThat("response code is 500", response.getCode(), is("500"));
}
private class OnlySetHandledHandler extends ThrowExceptionOnDemandHandler
{
private OnlySetHandledHandler(boolean throwException)
@ -138,6 +136,7 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
SimpleHttpParser.TestHttpResponse response = executeRequest();
assertThat("response code is 200", response.getCode(), is("200"));
assertResponseBody(response, "foobar");
assertHeader(response, "content-length", "6");
}
@ -150,6 +149,7 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
SimpleHttpParser.TestHttpResponse response = executeRequest();
assertThat("response code is 500", response.getCode(), is("500"));
assertThat("response body is not foobar", response.getBody(), not(is("foobar")));
}
private class SetHandledWriteSomeDataHandler extends ThrowExceptionOnDemandHandler
@ -176,8 +176,8 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
SimpleHttpParser.TestHttpResponse response = executeRequest();
assertThat("response code is 200", response.getCode(), is("200"));
assertResponseBody(response, "foobar");
if ("HTTP/1.1".equals(httpVersion))
assertHeader(response, "transfer-encoding", "chunked");
}
@ -190,7 +190,9 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
SimpleHttpParser.TestHttpResponse response = executeRequest();
// Since the 200 was committed, the 500 did not get the chance to be written
assertThat("response code is 200", response.getCode(), is("200"));
assertThat("response body is foobar", response.getBody(), is("foobar"));
if ("HTTP/1.1".equals(httpVersion))
assertHeader(response, "transfer-encoding", "chunked");
}
@ -255,7 +257,7 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Test
public void testWriteFlushWriteMore() throws Exception
public void testHandledWriteFlushWriteMore() throws Exception
{
server.setHandler(new WriteFlushWriteMoreHandler(false));
server.start();
@ -263,21 +265,24 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
SimpleHttpParser.TestHttpResponse response = executeRequest();
assertThat("response code is 200", response.getCode(), is("200"));
assertResponseBody(response, "foobar");
if ("HTTP/1.1".equals(httpVersion))
assertHeader(response, "transfer-encoding", "chunked"); // HTTP/1.0 does not do chunked. it will just send content and close
assertHeader(response, "transfer-encoding", "chunked");
}
@Test
public void testWriteFlushWriteMoreAndThrow() throws Exception
public void testHandledWriteFlushWriteMoreAndThrow() throws Exception
{
server.setHandler(new WriteFlushWriteMoreHandler(true));
server.start();
SimpleHttpParser.TestHttpResponse response = executeRequest();
// Since the 200 was committed, the 500 did not get the chance to be written
assertThat("response code is 200", response.getCode(), is("200"));
assertThat("response code is 200", response.getCode(), is("200"));
if ("HTTP/1.1".equals(httpVersion))
assertHeader(response, "transfer-encoding", "chunked"); // TODO HTTP/1.0 does not do chunked
assertHeader(response, "transfer-encoding", "chunked");
}
private class WriteFlushWriteMoreHandler extends ThrowExceptionOnDemandHandler
@ -299,7 +304,7 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Test
public void testBufferOverflow() throws Exception
public void testHandledBufferOverflow() throws Exception
{
server.setHandler(new OverflowHandler(false));
server.start();
@ -308,19 +313,23 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
assertThat("response code is 200", response.getCode(), is("200"));
assertResponseBody(response, "foobar");
assertHeader(response, "content-length", "6");
if ("HTTP/1.1".equals(httpVersion))
assertHeader(response, "transfer-encoding", "chunked");
}
@Test
public void testBufferOverflowAndThrow() throws Exception
public void testHandledBufferOverflowAndThrow() throws Exception
{
server.setHandler(new OverflowHandler(true));
server.start();
SimpleHttpParser.TestHttpResponse response = executeRequest();
// response not committed when we throw, so 500 expected
assertThat("response code is 500", response.getCode(), is("500"));
// Response was committed when we throw, so 200 expected
assertThat("response code is 200", response.getCode(), is("200"));
assertResponseBody(response, "foobar");
if ("HTTP/1.1".equals(httpVersion))
assertHeader(response, "transfer-encoding", "chunked");
}
private class OverflowHandler extends ThrowExceptionOnDemandHandler
@ -351,7 +360,6 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
assertThat("response code is 200", response.getCode(), is("200"));
assertThat("response body is foo", response.getBody(), is("foo"));
assertHeader(response, "content-length", "3");
}
@Test
@ -362,10 +370,8 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
SimpleHttpParser.TestHttpResponse response = executeRequest();
//TODO: should we expect 500 here?
assertThat("response code is 200", response.getCode(), is("200"));
assertThat("response body is foo", response.getBody(), is("foo"));
assertHeader(response, "content-length", "3");
assertThat("response code is 500", response.getCode(), is("500"));
assertThat("response body is not foo", response.getBody(), not(is("foo")));
}
private class SetContentLengthAndWriteThatAmountOfBytesHandler extends ThrowExceptionOnDemandHandler
@ -394,7 +400,6 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
SimpleHttpParser.TestHttpResponse response = executeRequest();
assertThat("response code is 200", response.getCode(), is("200"));
// jetty truncates the body when content-length is reached.! This is correct and desired behaviour?
assertThat("response body is foo", response.getBody(), is("foo"));
assertHeader(response, "content-length", "3");
}
@ -407,10 +412,8 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
SimpleHttpParser.TestHttpResponse response = executeRequest();
// TODO: we throw before response is committed. should we expect 500?
assertThat("response code is 200", response.getCode(), is("200"));
assertThat("response body is foo", response.getBody(), is("foo"));
assertHeader(response, "content-length", "3");
assertThat("response code is 500", response.getCode(), is("500"));
assertThat("response body is not foo", response.getBody(), not(is("foo")));
}
private class SetContentLengthAndWriteMoreBytesHandler extends ThrowExceptionOnDemandHandler
@ -425,7 +428,8 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
{
baseRequest.setHandled(true);
response.setContentLength(3);
response.getWriter().write("foobar"); // Only "foo" will get written and "bar" will be discarded
// Only "foo" will get written and "bar" will be discarded
response.getWriter().write("foobar");
super.handle(target, baseRequest, request, response);
}
}
@ -439,7 +443,8 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
SimpleHttpParser.TestHttpResponse response = executeRequest();
assertThat("response code is 200", response.getCode(), is("200"));
//TODO: jetty ignores setContentLength and sends transfer-encoding header. Correct?
assertThat("response body is foo", response.getBody(), is("foo"));
assertHeader(response, "content-length", "3");
}
@Test
@ -450,7 +455,8 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
SimpleHttpParser.TestHttpResponse response = executeRequest();
assertThat("response code is 200", response.getCode(), is("200"));
assertThat("response code is 500", response.getCode(), is("500"));
assertThat("response body is not foo", response.getBody(), not(is("foo")));
}
private class WriteAndSetContentLengthHandler extends ThrowExceptionOnDemandHandler
@ -465,13 +471,12 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
{
baseRequest.setHandled(true);
response.getWriter().write("foo");
response.setContentLength(3); // This should commit the response
response.setContentLength(3);
super.handle(target, baseRequest, request, response);
}
}
@Test
@Ignore
public void testWriteAndSetContentLengthTooSmall() throws Exception
{
server.setHandler(new WriteAndSetContentLengthTooSmallHandler(false));
@ -480,10 +485,8 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
SimpleHttpParser.TestHttpResponse response = executeRequest();
assertThat("response code is 200", response.getCode(), is("200"));
assertResponseBody(response, "foobar");
// TODO: once flushed setting contentLength is ignored and chunked is used. Correct?
if ("HTTP/1.1".equals(httpVersion))
assertHeader(response, "transfer-encoding", "chunked");
assertThat("response body is foo", response.getBody(), is("foo"));
assertHeader(response, "content-length", "3");
}
@Test
@ -494,7 +497,8 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
SimpleHttpParser.TestHttpResponse response = executeRequest();
assertThat(response.getCode(), is("500"));
assertThat("response code is 500", response.getCode(), is("500"));
assertThat("response body is not foo", response.getBody(), not(is("foo")));
}
private class WriteAndSetContentLengthTooSmallHandler extends ThrowExceptionOnDemandHandler

View File

@ -1,2 +1,3 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
# org.eclipse.jetty.LEVEL=WARN
#org.eclipse.jetty.LEVEL=DEBUG
org.eclipse.jetty.server.Server.LEVEL=WARN