Jetty9 - Refactored error handling logic.
This commit is contained in:
parent
dc140f85b7
commit
71e7f519c7
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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("&", "&");
|
||||
reason = reason.replaceAll("<", "<");
|
||||
reason = reason.replaceAll(">", ">");
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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, "&", "&");
|
||||
message= StringUtil.replace(message, "<", "<");
|
||||
message= StringUtil.replace(message, ">", ">");
|
||||
}
|
||||
String uri= request.getRequestURI();
|
||||
if (uri!=null)
|
||||
{
|
||||
uri= StringUtil.replace(uri, "&", "&");
|
||||
uri= StringUtil.replace(uri, "<", "<");
|
||||
uri= StringUtil.replace(uri, ">", ">");
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue