* Issue #3806 async sendError Avoid using isHandled as a test withing sendError as this can be called asynchronously and is in a race with the normal dispatch of the request, which could also be setting handled status. The ErrorHandler was dispatching directly to a context from within sendError. This meant that an async thread can call sendError and be dispatched to within the servlet container at the same time that the original thread was still dispatched to the container. This commit fixes that problem by using an async dispatch for error pages within the ErrorHandler. However, this introduces a new problem that a well behaved async app will call complete after calling sendError. Thus we have ignore complete ISEs for the remainder of the current async cycle. Fixed the closing of the output after calling sendError. Do not close if the request was async (and thus might be dispatched to an async error) or if it is now async because the error page itself is async. * updates from review * better tests * revert ignore complete * added some TODOs * more TODOs * fixed rename * cleanup ISE and more TODOs * refactored to call sendError for uncaught exceptions rather than onError * more of the refactor * extra tests for sendError from completing state Reworked HttpChannelState and sendError so that sendError is now just a change of state. All the work is done in the ErrorDispatch action, including calling the ErrorHandler. Async not yet working. Additional tests Converted ERRORED state to a separate boolean so it can be used for both Sync and Async dispatches. Removed ASYNC_IO state as it was just the same as DISPATCHED The async onError listener handling is now most likely broken. WIP making sendError simpler and more tests pass WIP handling async and thrown exceptions WIP passing tests Improved thread handling removed bad test Implemented error dispatch on complete properly more fixed tests sendError state looks committed - Added resetContent method to leave more non-content headers during sendError - Fixed security tests - simplified the non dispatch error page writing. Moved towards being able to write async * fixed gzipHandlerTest * Updated handling of timeout errors. According to servlet spec, exceptions thrown from onTimeout should not be passed to onError, but just logged and ignored: If an exception is thrown while invoking methods in an AsyncListener, it is logged and will not affect the invocation of any other AsyncListeners. * This changes several tests. * Dispatcher/ContextHandler changes for new ERROR dispatch handling. Feels a bit fragile! * Fixed tests in jetty-servlets * Fixed tests in jetty-proxy * more test fixes * Fixed head handling reverted unnecessary changes Improved reason handling WIP on fully async error handling. Simplified HttpChannelState state machines to allow for async actions during completing more WIP on fully async error handling. sendError and completion are not both non-blocking, without using a startAsync operation. However we are lacking unit tests that actually exercise those code paths. * Simplified name of states Added test for async completion * Cleanups and javadoc * Cleanups and javadoc * remove snake case * feedback from review * Write error page into fixed pooled buffer Use the response to get/release a pooled buffer into which the error page can be written. Make it a fixed sized buffer and if it overflows then no error page is generated (first overflow turns off showstacks to save space). The ErrorHandler badly needs to be refactored, but we cannot change API in jetty-9 * More test fixes for different error page format * minor cleanups * Cleanup from Review * Fixed javadoc * cleanups and simplifications * Cleanup from Review * renaming and some TODOs * Cleanup from Review * Checkstyle fixes * Cleanup from Review * Code cleanups and simplifications * fixed debug * Cleanup from Review * Ensure response sent before server shutdown * removed unnecessary optimisation * fixed duplicate from merge * Updates from review Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
parent
0f8230c05b
commit
bde86467f4
|
@ -320,6 +320,20 @@ public class HttpStatus
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean hasNoBody(int status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case NO_CONTENT_204:
|
||||
case NOT_MODIFIED_304:
|
||||
case PARTIAL_CONTENT_206:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return status < OK_200;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test against an code to determine if it falls into the
|
||||
* <code>Informational</code> message category as defined in the <a
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.io;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
||||
/**
|
||||
* Simple wrapper of a ByteBuffer as an OutputStream.
|
||||
* The buffer does not grow and this class will throw an
|
||||
* {@link java.nio.BufferOverflowException} if the buffer capacity is exceeded.
|
||||
*/
|
||||
public class ByteBufferOutputStream extends OutputStream
|
||||
{
|
||||
final ByteBuffer _buffer;
|
||||
|
||||
public ByteBufferOutputStream(ByteBuffer buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
}
|
||||
|
||||
public void flush()
|
||||
{
|
||||
}
|
||||
|
||||
public void write(byte[] b)
|
||||
{
|
||||
write(b, 0, b.length);
|
||||
}
|
||||
|
||||
public void write(byte[] b, int off, int len)
|
||||
{
|
||||
BufferUtil.append(_buffer, b, off, len);
|
||||
}
|
||||
|
||||
public void write(int b)
|
||||
{
|
||||
BufferUtil.append(_buffer, (byte)b);
|
||||
}
|
||||
}
|
|
@ -680,6 +680,14 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
catch (Exception e)
|
||||
{
|
||||
_log.ignore(e);
|
||||
try
|
||||
{
|
||||
proxyResponse.sendError(-1);
|
||||
}
|
||||
catch (Exception e2)
|
||||
{
|
||||
_log.ignore(e2);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -22,6 +22,8 @@ import java.io.IOException;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.annotation.Name;
|
||||
|
||||
/**
|
||||
|
@ -79,11 +81,20 @@ public class ResponsePatternRule extends PatternRule
|
|||
// status code 400 and up are error codes
|
||||
if (code >= 400)
|
||||
{
|
||||
if (!StringUtil.isBlank(_reason))
|
||||
{
|
||||
// use both setStatusWithReason (to set the reason) and sendError to set the message
|
||||
Request.getBaseRequest(request).getResponse().setStatusWithReason(code, _reason);
|
||||
response.sendError(code, _reason);
|
||||
}
|
||||
else
|
||||
{
|
||||
response.setStatus(code);
|
||||
response.sendError(code);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
response.setStatus(code, _reason);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ import java.io.IOException;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -88,8 +90,14 @@ public class ValidUrlRule extends Rule
|
|||
// status code 400 and up are error codes so include a reason
|
||||
if (code >= 400)
|
||||
{
|
||||
if (StringUtil.isBlank(_reason))
|
||||
response.sendError(code);
|
||||
else
|
||||
{
|
||||
Request.getBaseRequest(request).getResponse().setStatusWithReason(code, _reason);
|
||||
response.sendError(code, _reason);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
response.setStatus(code);
|
||||
|
|
|
@ -20,6 +20,8 @@ package org.eclipse.jetty.rewrite.handler;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.server.Dispatcher;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -59,7 +61,7 @@ public class ResponsePatternRuleTest extends AbstractRuleTestCase
|
|||
_rule.apply(null, _request, _response);
|
||||
|
||||
assertEquals(i, _response.getStatus());
|
||||
assertEquals(null, _response.getReason());
|
||||
assertEquals("reason" + i, _response.getReason());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +74,7 @@ public class ResponsePatternRuleTest extends AbstractRuleTestCase
|
|||
_rule.apply(null, _request, _response);
|
||||
|
||||
assertEquals(i, _response.getStatus());
|
||||
assertEquals("", _response.getReason());
|
||||
assertEquals(HttpStatus.getMessage(i), _request.getAttribute(Dispatcher.ERROR_MESSAGE));
|
||||
super.reset();
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +89,7 @@ public class ResponsePatternRuleTest extends AbstractRuleTestCase
|
|||
_rule.apply(null, _request, _response);
|
||||
|
||||
assertEquals(i, _response.getStatus());
|
||||
assertEquals("reason-" + i, _response.getReason());
|
||||
assertEquals("reason-" + i, _request.getAttribute(Dispatcher.ERROR_MESSAGE));
|
||||
super.reset();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -321,7 +322,8 @@ public class SpecExampleConstraintTest
|
|||
response = _connector.getResponse("POST /ctx/acme/wholesale/index.html HTTP/1.0\r\n" +
|
||||
"Authorization: Basic " + encodedChris + "\r\n" +
|
||||
"\r\n");
|
||||
assertThat(response, startsWith("HTTP/1.1 403 !"));
|
||||
assertThat(response, startsWith("HTTP/1.1 403 Forbidden"));
|
||||
assertThat(response, containsString("!Secure"));
|
||||
|
||||
//a user in role HOMEOWNER can do a GET
|
||||
response = _connector.getResponse("GET /ctx/acme/retail/index.html HTTP/1.0\r\n" +
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package org.eclipse.jetty.security.authentication;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
|
@ -26,6 +27,7 @@ import org.eclipse.jetty.http.HttpURI;
|
|||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.server.Authentication;
|
||||
import org.eclipse.jetty.server.HttpChannel;
|
||||
import org.eclipse.jetty.server.HttpChannelState;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpOutput;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
|
@ -34,6 +36,8 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -61,21 +65,27 @@ public class SpnegoAuthenticatorTest
|
|||
{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
Request req = new Request(channel, null);
|
||||
HttpOutput out = new HttpOutput(channel)
|
||||
|
||||
@Override
|
||||
protected HttpOutput newHttpOutput()
|
||||
{
|
||||
return new HttpOutput(this)
|
||||
{
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
return;
|
||||
public void close() {}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {}
|
||||
};
|
||||
}
|
||||
};
|
||||
Response res = new Response(channel, out);
|
||||
Request req = channel.getRequest();
|
||||
Response res = channel.getResponse();
|
||||
MetaData.Request metadata = new MetaData.Request(new HttpFields());
|
||||
metadata.setURI(new HttpURI("http://localhost"));
|
||||
req.setMetaData(metadata);
|
||||
|
||||
assertThat(channel.getState().handling(), is(HttpChannelState.Action.DISPATCH));
|
||||
assertEquals(Authentication.SEND_CONTINUE, _authenticator.validateRequest(req, res, true));
|
||||
assertEquals(HttpHeader.NEGOTIATE.asString(), res.getHeader(HttpHeader.WWW_AUTHENTICATE.asString()));
|
||||
assertEquals(HttpServletResponse.SC_UNAUTHORIZED, res.getStatus());
|
||||
|
@ -91,17 +101,22 @@ public class SpnegoAuthenticatorTest
|
|||
{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
Request req = new Request(channel, null);
|
||||
HttpOutput out = new HttpOutput(channel)
|
||||
|
||||
@Override
|
||||
protected HttpOutput newHttpOutput()
|
||||
{
|
||||
return new HttpOutput(this)
|
||||
{
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
return;
|
||||
public void close() {}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {}
|
||||
};
|
||||
}
|
||||
};
|
||||
Response res = new Response(channel, out);
|
||||
Request req = channel.getRequest();
|
||||
Response res = channel.getResponse();
|
||||
HttpFields http_fields = new HttpFields();
|
||||
// Create a bogus Authorization header. We don't care about the actual credentials.
|
||||
http_fields.add(HttpHeader.AUTHORIZATION, "Basic asdf");
|
||||
|
@ -109,6 +124,7 @@ public class SpnegoAuthenticatorTest
|
|||
metadata.setURI(new HttpURI("http://localhost"));
|
||||
req.setMetaData(metadata);
|
||||
|
||||
assertThat(channel.getState().handling(), is(HttpChannelState.Action.DISPATCH));
|
||||
assertEquals(Authentication.SEND_CONTINUE, _authenticator.validateRequest(req, res, true));
|
||||
assertEquals(HttpHeader.NEGOTIATE.asString(), res.getHeader(HttpHeader.WWW_AUTHENTICATE.asString()));
|
||||
assertEquals(HttpServletResponse.SC_UNAUTHORIZED, res.getStatus());
|
||||
|
|
|
@ -160,7 +160,7 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable
|
|||
Scheduler.Task task = _timeoutTask;
|
||||
_timeoutTask = null;
|
||||
if (task != null)
|
||||
_state.getHttpChannel().execute(() -> _state.onTimeout());
|
||||
_state.timeout();
|
||||
}
|
||||
|
||||
public void addThrowable(Throwable e)
|
||||
|
|
|
@ -42,8 +42,6 @@ public class Dispatcher implements RequestDispatcher
|
|||
{
|
||||
private static final Logger LOG = Log.getLogger(Dispatcher.class);
|
||||
|
||||
public static final String __ERROR_DISPATCH = "org.eclipse.jetty.server.Dispatcher.ERROR";
|
||||
|
||||
/**
|
||||
* Dispatch include attribute names
|
||||
*/
|
||||
|
@ -83,16 +81,8 @@ public class Dispatcher implements RequestDispatcher
|
|||
|
||||
public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
request.setAttribute(__ERROR_DISPATCH, Boolean.TRUE);
|
||||
forward(request, response, DispatcherType.ERROR);
|
||||
}
|
||||
finally
|
||||
{
|
||||
request.setAttribute(__ERROR_DISPATCH, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
|
||||
|
|
|
@ -25,7 +25,6 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
@ -33,12 +32,12 @@ import java.util.function.Function;
|
|||
import java.util.function.Supplier;
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpGenerator;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
|
@ -51,6 +50,7 @@ import org.eclipse.jetty.io.QuietException;
|
|||
import org.eclipse.jetty.server.HttpChannelState.Action;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||
import org.eclipse.jetty.server.handler.ErrorHandler.ErrorPageMapper;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
|
||||
|
@ -71,8 +71,6 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
{
|
||||
private static final Logger LOG = Log.getLogger(HttpChannel.class);
|
||||
|
||||
private final AtomicBoolean _committed = new AtomicBoolean();
|
||||
private final AtomicBoolean _responseCompleted = new AtomicBoolean();
|
||||
private final AtomicLong _requests = new AtomicLong();
|
||||
private final Connector _connector;
|
||||
private final Executor _executor;
|
||||
|
@ -121,6 +119,11 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
_state);
|
||||
}
|
||||
|
||||
public boolean isSendError()
|
||||
{
|
||||
return _state.isSendError();
|
||||
}
|
||||
|
||||
protected HttpInput newHttpInput(HttpChannelState state)
|
||||
{
|
||||
return new HttpInput(state);
|
||||
|
@ -284,8 +287,6 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
|
||||
public void recycle()
|
||||
{
|
||||
_committed.set(false);
|
||||
_responseCompleted.set(false);
|
||||
_request.recycle();
|
||||
_response.recycle();
|
||||
_committedMetaData = null;
|
||||
|
@ -320,7 +321,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
public boolean handle()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} handle {} ", this, _request.getHttpURI());
|
||||
LOG.debug("handle {} {} ", _request.getHttpURI(), this);
|
||||
|
||||
HttpChannelState.Action action = _state.handling();
|
||||
|
||||
|
@ -334,19 +335,18 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
try
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} action {}", this, action);
|
||||
LOG.debug("action {} {}", action, this);
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case TERMINATED:
|
||||
onCompleted();
|
||||
break loop;
|
||||
|
||||
case WAIT:
|
||||
// break loop without calling unhandle
|
||||
break loop;
|
||||
|
||||
case NOOP:
|
||||
// do nothing other than call unhandle
|
||||
break;
|
||||
|
||||
case DISPATCH:
|
||||
{
|
||||
if (!_request.hasMetaData())
|
||||
|
@ -354,35 +354,17 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
_request.setHandled(false);
|
||||
_response.getHttpOutput().reopen();
|
||||
|
||||
try
|
||||
dispatch(DispatcherType.REQUEST, () ->
|
||||
{
|
||||
_request.setDispatcherType(DispatcherType.REQUEST);
|
||||
notifyBeforeDispatch(_request);
|
||||
|
||||
List<HttpConfiguration.Customizer> customizers = _configuration.getCustomizers();
|
||||
if (!customizers.isEmpty())
|
||||
{
|
||||
for (HttpConfiguration.Customizer customizer : customizers)
|
||||
for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers())
|
||||
{
|
||||
customizer.customize(getConnector(), _configuration, _request);
|
||||
if (_request.isHandled())
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
getServer().handle(HttpChannel.this);
|
||||
});
|
||||
|
||||
if (!_request.isHandled())
|
||||
getServer().handle(this);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
notifyDispatchFailure(_request, x);
|
||||
throw x;
|
||||
}
|
||||
finally
|
||||
{
|
||||
notifyAfterDispatch(_request);
|
||||
_request.setDispatcherType(null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -391,70 +373,70 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
_request.setHandled(false);
|
||||
_response.getHttpOutput().reopen();
|
||||
|
||||
try
|
||||
{
|
||||
_request.setDispatcherType(DispatcherType.ASYNC);
|
||||
notifyBeforeDispatch(_request);
|
||||
getServer().handleAsync(this);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
notifyDispatchFailure(_request, x);
|
||||
throw x;
|
||||
}
|
||||
finally
|
||||
{
|
||||
notifyAfterDispatch(_request);
|
||||
_request.setDispatcherType(null);
|
||||
}
|
||||
dispatch(DispatcherType.ASYNC,() -> getServer().handleAsync(this));
|
||||
break;
|
||||
}
|
||||
|
||||
case ERROR_DISPATCH:
|
||||
case ASYNC_TIMEOUT:
|
||||
_state.onTimeout();
|
||||
break;
|
||||
|
||||
case SEND_ERROR:
|
||||
{
|
||||
try
|
||||
{
|
||||
_response.reset(true);
|
||||
Integer icode = (Integer)_request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
|
||||
int code = icode != null ? icode : HttpStatus.INTERNAL_SERVER_ERROR_500;
|
||||
_response.setStatus(code);
|
||||
_request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, code);
|
||||
// Get ready to send an error response
|
||||
_request.setHandled(false);
|
||||
_response.resetContent();
|
||||
_response.getHttpOutput().reopen();
|
||||
|
||||
try
|
||||
// the following is needed as you cannot trust the response code and reason
|
||||
// as those could have been modified after calling sendError
|
||||
Integer code = (Integer)_request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
|
||||
_response.setStatus(code != null ? code : HttpStatus.INTERNAL_SERVER_ERROR_500);
|
||||
|
||||
ContextHandler.Context context = (ContextHandler.Context)_request.getAttribute(ErrorHandler.ERROR_CONTEXT);
|
||||
ErrorHandler errorHandler = ErrorHandler.getErrorHandler(getServer(), context == null ? null : context.getContextHandler());
|
||||
|
||||
// If we can't have a body, then create a minimal error response.
|
||||
if (HttpStatus.hasNoBody(_response.getStatus()) || errorHandler == null || !errorHandler.errorPageForMethod(_request.getMethod()))
|
||||
{
|
||||
_request.setDispatcherType(DispatcherType.ERROR);
|
||||
notifyBeforeDispatch(_request);
|
||||
getServer().handle(this);
|
||||
sendResponseAndComplete();
|
||||
break;
|
||||
}
|
||||
catch (Throwable x)
|
||||
|
||||
// Look for an error page dispatcher
|
||||
String errorPage = (errorHandler instanceof ErrorPageMapper) ? ((ErrorPageMapper)errorHandler).getErrorPage(_request) : null;
|
||||
Dispatcher errorDispatcher = errorPage != null ? (Dispatcher)context.getRequestDispatcher(errorPage) : null;
|
||||
if (errorDispatcher == null)
|
||||
{
|
||||
notifyDispatchFailure(_request, x);
|
||||
throw x;
|
||||
// Allow ErrorHandler to generate response
|
||||
errorHandler.handle(null, _request, _request, _response);
|
||||
_request.setHandled(true);
|
||||
}
|
||||
finally
|
||||
else
|
||||
{
|
||||
notifyAfterDispatch(_request);
|
||||
_request.setDispatcherType(null);
|
||||
// Do the error page dispatch
|
||||
dispatch(DispatcherType.ERROR,() -> errorDispatcher.error(_request, _response));
|
||||
}
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Could not perform ERROR dispatch, aborting", x);
|
||||
Throwable failure = (Throwable)_request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
|
||||
if (failure == null)
|
||||
{
|
||||
minimalErrorResponse(x);
|
||||
}
|
||||
if (_state.isResponseCommitted())
|
||||
abort(x);
|
||||
else
|
||||
{
|
||||
if (x != failure)
|
||||
failure.addSuppressed(x);
|
||||
minimalErrorResponse(failure);
|
||||
_response.resetContent();
|
||||
sendResponseAndComplete();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// clean up the context that was set in Response.sendError
|
||||
_request.removeAttribute(ErrorHandler.ERROR_CONTEXT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -463,6 +445,12 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
throw _state.getAsyncContextEvent().getThrowable();
|
||||
}
|
||||
|
||||
case READ_REGISTER:
|
||||
{
|
||||
onAsyncWaitForContent();
|
||||
break;
|
||||
}
|
||||
|
||||
case READ_PRODUCE:
|
||||
{
|
||||
_request.getHttpInput().asyncReadProduce();
|
||||
|
@ -491,45 +479,37 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
|
||||
case COMPLETE:
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_response.isCommitted() && !_request.isHandled())
|
||||
if (!_response.isCommitted() && !_request.isHandled() && !_response.getHttpOutput().isClosed())
|
||||
{
|
||||
_response.sendError(HttpStatus.NOT_FOUND_404);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// RFC 7230, section 3.3.
|
||||
int status = _response.getStatus();
|
||||
boolean hasContent = !(_request.isHead() ||
|
||||
HttpMethod.CONNECT.is(_request.getMethod()) && status == HttpStatus.OK_200 ||
|
||||
HttpStatus.isInformational(status) ||
|
||||
status == HttpStatus.NO_CONTENT_204 ||
|
||||
status == HttpStatus.NOT_MODIFIED_304);
|
||||
if (hasContent && !_response.isContentComplete(_response.getHttpOutput().getWritten()))
|
||||
if (!_request.isHead() && !_response.isContentComplete(_response.getHttpOutput().getWritten()))
|
||||
{
|
||||
if (isCommitted())
|
||||
abort(new IOException("insufficient content written"));
|
||||
else
|
||||
_response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, "insufficient content written");
|
||||
}
|
||||
}
|
||||
_response.closeOutput();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_request.setHandled(true);
|
||||
_state.onComplete();
|
||||
onCompleted();
|
||||
_response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, "insufficient content written");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break loop;
|
||||
// TODO Currently a blocking/aborting consumeAll is done in the handling of the TERMINATED
|
||||
// TODO Action triggered by the completed callback below. It would be possible to modify the
|
||||
// TODO callback to do a non-blocking consumeAll at this point and only call completed when
|
||||
// TODO that is done.
|
||||
|
||||
// Set a close callback on the HttpOutput to make it an async callback
|
||||
_response.closeOutput(Callback.from(_state::completed));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException("state=" + _state);
|
||||
}
|
||||
throw new IllegalStateException(this.toString());
|
||||
}
|
||||
}
|
||||
catch (Throwable failure)
|
||||
|
@ -544,26 +524,29 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} handle exit, result {}", this, action);
|
||||
LOG.debug("!handle {} {}", action, this);
|
||||
|
||||
boolean suspended = action == Action.WAIT;
|
||||
return !suspended;
|
||||
}
|
||||
|
||||
protected void sendError(int code, String reason)
|
||||
private void dispatch(DispatcherType type, Dispatchable dispatchable) throws IOException, ServletException
|
||||
{
|
||||
try
|
||||
{
|
||||
_response.sendError(code, reason);
|
||||
_request.setDispatcherType(type);
|
||||
notifyBeforeDispatch(_request);
|
||||
dispatchable.dispatch();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Could not send error " + code + " " + reason, x);
|
||||
notifyDispatchFailure(_request, x);
|
||||
throw x;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_state.errorComplete();
|
||||
notifyAfterDispatch(_request);
|
||||
_request.setDispatcherType(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -591,28 +574,20 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
{
|
||||
// No stack trace unless there is debug turned on
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(_request.getRequestURI(), failure);
|
||||
LOG.warn("handleException " + _request.getRequestURI(), failure);
|
||||
else
|
||||
LOG.warn("{} {}", _request.getRequestURI(), noStack.toString());
|
||||
LOG.warn("handleException {} {}", _request.getRequestURI(), noStack.toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn(_request.getRequestURI(), failure);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (isCommitted())
|
||||
abort(failure);
|
||||
else
|
||||
_state.onError(failure);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
if (e != failure)
|
||||
failure.addSuppressed(e);
|
||||
LOG.warn("ERROR dispatch failed", failure);
|
||||
// Try to send a minimal response.
|
||||
minimalErrorResponse(failure);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap failure causes to find target class
|
||||
|
@ -635,30 +610,17 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
return null;
|
||||
}
|
||||
|
||||
private void minimalErrorResponse(Throwable failure)
|
||||
public void sendResponseAndComplete()
|
||||
{
|
||||
try
|
||||
{
|
||||
int code = 500;
|
||||
Integer status = (Integer)_request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
|
||||
if (status != null)
|
||||
code = status.intValue();
|
||||
else
|
||||
{
|
||||
Throwable cause = unwrap(failure, BadMessageException.class);
|
||||
if (cause instanceof BadMessageException)
|
||||
code = ((BadMessageException)cause).getCode();
|
||||
}
|
||||
|
||||
_response.reset(true);
|
||||
_response.setStatus(code);
|
||||
_response.flushBuffer();
|
||||
_request.setHandled(true);
|
||||
_state.completing();
|
||||
sendResponse(null, _response.getHttpOutput().getBuffer(), true, Callback.from(_state::completed));
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
if (x != failure)
|
||||
failure.addSuppressed(x);
|
||||
abort(failure);
|
||||
abort(x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -676,11 +638,11 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
public String toString()
|
||||
{
|
||||
long timeStamp = _request.getTimeStamp();
|
||||
return String.format("%s@%x{r=%s,c=%b,c=%b/%b,a=%s,uri=%s,age=%d}",
|
||||
return String.format("%s@%x{s=%s,r=%s,c=%b/%b,a=%s,uri=%s,age=%d}",
|
||||
getClass().getSimpleName(),
|
||||
hashCode(),
|
||||
_state,
|
||||
_requests,
|
||||
_committed.get(),
|
||||
isRequestCompleted(),
|
||||
isResponseCompleted(),
|
||||
_state.getState(),
|
||||
|
@ -717,7 +679,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
public boolean onContent(HttpInput.Content content)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} onContent {}", this, content);
|
||||
LOG.debug("onContent {} {}", this, content);
|
||||
notifyRequestContent(_request, content.getByteBuffer());
|
||||
return _request.getHttpInput().addContent(content);
|
||||
}
|
||||
|
@ -725,7 +687,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
public boolean onContentComplete()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} onContentComplete", this);
|
||||
LOG.debug("onContentComplete {}", this);
|
||||
notifyRequestContentEnd(_request);
|
||||
return false;
|
||||
}
|
||||
|
@ -733,7 +695,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
public void onTrailers(HttpFields trailers)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} onTrailers {}", this, trailers);
|
||||
LOG.debug("onTrailers {} {}", this, trailers);
|
||||
_trailers = trailers;
|
||||
notifyRequestTrailers(_request);
|
||||
}
|
||||
|
@ -741,7 +703,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
public boolean onRequestComplete()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} onRequestComplete", this);
|
||||
LOG.debug("onRequestComplete {}", this);
|
||||
boolean result = _request.getHttpInput().eof();
|
||||
notifyRequestEnd(_request);
|
||||
return result;
|
||||
|
@ -750,7 +712,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
public void onCompleted()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("COMPLETE for {} written={}", getRequest().getRequestURI(), getBytesWritten());
|
||||
LOG.debug("onCompleted for {} written={}", getRequest().getRequestURI(), getBytesWritten());
|
||||
|
||||
if (_requestLog != null)
|
||||
_requestLog.log(_request, _response);
|
||||
|
@ -773,7 +735,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
{
|
||||
int status = failure.getCode();
|
||||
String reason = failure.getReason();
|
||||
if (status < 400 || status > 599)
|
||||
if (status < HttpStatus.BAD_REQUEST_400 || status > 599)
|
||||
failure = new BadMessageException(HttpStatus.BAD_REQUEST_400, reason, failure);
|
||||
|
||||
notifyRequestFailure(_request, failure);
|
||||
|
@ -823,9 +785,9 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
}
|
||||
}
|
||||
|
||||
protected boolean sendResponse(MetaData.Response info, ByteBuffer content, boolean complete, final Callback callback)
|
||||
public boolean sendResponse(MetaData.Response info, ByteBuffer content, boolean complete, final Callback callback)
|
||||
{
|
||||
boolean committing = _committed.compareAndSet(false, true);
|
||||
boolean committing = _state.commitResponse();
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("sendResponse info={} content={} complete={} committing={} callback={}",
|
||||
|
@ -844,7 +806,9 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
|
||||
// wrap callback to process 100 responses
|
||||
final int status = info.getStatus();
|
||||
final Callback committed = (status < 200 && status >= 100) ? new Send100Callback(callback) : new SendCallback(callback, content, true, complete);
|
||||
final Callback committed = (status < HttpStatus.OK_200 && status >= HttpStatus.CONTINUE_100)
|
||||
? new Send100Callback(callback)
|
||||
: new SendCallback(callback, content, true, complete);
|
||||
|
||||
notifyResponseBegin(_request);
|
||||
|
||||
|
@ -891,7 +855,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
|
||||
public boolean isCommitted()
|
||||
{
|
||||
return _committed.get();
|
||||
return _state.isResponseCommitted();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -907,7 +871,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
*/
|
||||
public boolean isResponseCompleted()
|
||||
{
|
||||
return _responseCompleted.get();
|
||||
return _state.isResponseCompleted();
|
||||
}
|
||||
|
||||
public boolean isPersistent()
|
||||
|
@ -969,10 +933,13 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
* @param failure the failure that caused the abort.
|
||||
*/
|
||||
public void abort(Throwable failure)
|
||||
{
|
||||
if (_state.abortResponse())
|
||||
{
|
||||
notifyResponseFailure(_request, failure);
|
||||
_transport.abort(failure);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyRequestBegin(Request request)
|
||||
{
|
||||
|
@ -1095,6 +1062,11 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
}
|
||||
}
|
||||
|
||||
interface Dispatchable
|
||||
{
|
||||
void dispatch() throws IOException, ServletException;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Listener for {@link HttpChannel} events.</p>
|
||||
* <p>HttpChannel will emit events for the various phases it goes through while
|
||||
|
@ -1280,17 +1252,16 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
public void succeeded()
|
||||
{
|
||||
_written += _length;
|
||||
if (_complete)
|
||||
_response.getHttpOutput().closed();
|
||||
super.succeeded();
|
||||
if (_commit)
|
||||
notifyResponseCommit(_request);
|
||||
if (_length > 0)
|
||||
notifyResponseContent(_request, _content);
|
||||
if (_complete)
|
||||
{
|
||||
_responseCompleted.set(true);
|
||||
if (_complete && _state.completeResponse())
|
||||
notifyResponseEnd(_request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Throwable x)
|
||||
|
@ -1305,13 +1276,14 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
super.failed(x);
|
||||
_response.getHttpOutput().closed();
|
||||
super.failed(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable th)
|
||||
{
|
||||
_response.getHttpOutput().closed();
|
||||
abort(x);
|
||||
super.failed(x);
|
||||
}
|
||||
|
@ -1335,7 +1307,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
if (_committed.compareAndSet(true, false))
|
||||
if (_state.partialResponse())
|
||||
super.succeeded();
|
||||
else
|
||||
super.failed(new IllegalStateException());
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -278,19 +278,9 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
}
|
||||
else if (filled < 0)
|
||||
{
|
||||
switch (_channel.getState().getState())
|
||||
{
|
||||
case COMPLETING:
|
||||
case COMPLETED:
|
||||
case IDLE:
|
||||
case THROWN:
|
||||
case ASYNC_ERROR:
|
||||
if (_channel.getState().isIdle())
|
||||
getEndPoint().shutdownOutput();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -291,6 +291,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
{
|
||||
BadMessageException bad = new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408,
|
||||
String.format("Request content data rate < %d B/s", minRequestDataRate));
|
||||
if (_channelState.isResponseCommitted())
|
||||
_channelState.getHttpChannel().abort(bad);
|
||||
throw bad;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package org.eclipse.jetty.server;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
@ -62,8 +63,31 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
public class HttpOutput extends ServletOutputStream implements Runnable
|
||||
{
|
||||
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
|
||||
private static final Callback BLOCKING_CLOSE_CALLBACK = new Callback() {};
|
||||
private static ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);
|
||||
|
||||
/*
|
||||
ACTION OPEN ASYNC READY PENDING UNREADY CLOSING CLOSED
|
||||
--------------------------------------------------------------------------------------------------
|
||||
setWriteListener() READY->owp ise ise ise ise ise ise
|
||||
write() OPEN ise PENDING wpe wpe eof eof
|
||||
flush() OPEN ise PENDING wpe wpe eof eof
|
||||
close() CLOSING CLOSING CLOSING CLOSED CLOSED CLOSING CLOSED
|
||||
isReady() OPEN:true READY:true READY:true UNREADY:false UNREADY:false CLOSED:true CLOSED:true
|
||||
write completed - - - ASYNC READY->owp CLOSED -
|
||||
*/
|
||||
enum State
|
||||
{
|
||||
OPEN, // Open in blocking mode
|
||||
ASYNC, // Open in async mode
|
||||
READY, // isReady() has returned true
|
||||
PENDING, // write operating in progress
|
||||
UNREADY, // write operating in progress, isReady has returned false
|
||||
ERROR, // An error has occured
|
||||
CLOSING, // Asynchronous close in progress
|
||||
CLOSED // Closed
|
||||
}
|
||||
|
||||
/**
|
||||
* The HttpOutput.Interceptor is a single intercept point for all
|
||||
* output written to the HttpOutput: via writer; via output stream;
|
||||
|
@ -129,6 +153,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
private static Logger LOG = Log.getLogger(HttpOutput.class);
|
||||
private static final ThreadLocal<CharsetEncoder> _encoder = new ThreadLocal<>();
|
||||
|
||||
private final AtomicReference<State> _state = new AtomicReference<>(State.OPEN);
|
||||
private final HttpChannel _channel;
|
||||
private final SharedBlockingCallback _writeBlocker;
|
||||
private Interceptor _interceptor;
|
||||
|
@ -140,23 +165,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
private int _commitSize;
|
||||
private WriteListener _writeListener;
|
||||
private volatile Throwable _onError;
|
||||
|
||||
/*
|
||||
ACTION OPEN ASYNC READY PENDING UNREADY CLOSED
|
||||
-------------------------------------------------------------------------------------------
|
||||
setWriteListener() READY->owp ise ise ise ise ise
|
||||
write() OPEN ise PENDING wpe wpe eof
|
||||
flush() OPEN ise PENDING wpe wpe eof
|
||||
close() CLOSED CLOSED CLOSED CLOSED CLOSED CLOSED
|
||||
isReady() OPEN:true READY:true READY:true UNREADY:false UNREADY:false CLOSED:true
|
||||
write completed - - - ASYNC READY->owp -
|
||||
*/
|
||||
private enum OutputState
|
||||
{
|
||||
OPEN, ASYNC, READY, PENDING, UNREADY, ERROR, CLOSED
|
||||
}
|
||||
|
||||
private final AtomicReference<OutputState> _state = new AtomicReference<>(OutputState.OPEN);
|
||||
private Callback _closeCallback;
|
||||
|
||||
public HttpOutput(HttpChannel channel)
|
||||
{
|
||||
|
@ -200,7 +209,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
|
||||
public void reopen()
|
||||
{
|
||||
_state.set(OutputState.OPEN);
|
||||
_state.set(State.OPEN);
|
||||
}
|
||||
|
||||
private boolean isLastContentToWrite(int len)
|
||||
|
@ -256,16 +265,67 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
_channel.abort(failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
public void closedBySendError()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
OutputState state = _state.get();
|
||||
State state = _state.get();
|
||||
switch (state)
|
||||
{
|
||||
case OPEN:
|
||||
case READY:
|
||||
case ASYNC:
|
||||
if (!_state.compareAndSet(state, State.CLOSED))
|
||||
continue;
|
||||
return;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(state.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close(Closeable wrapper, Callback callback)
|
||||
{
|
||||
_closeCallback = callback;
|
||||
try
|
||||
{
|
||||
if (wrapper != null)
|
||||
wrapper.close();
|
||||
if (!isClosed())
|
||||
close();
|
||||
}
|
||||
catch (Throwable th)
|
||||
{
|
||||
closed();
|
||||
if (_closeCallback == null)
|
||||
LOG.ignore(th);
|
||||
else
|
||||
callback.failed(th);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_closeCallback != null)
|
||||
callback.succeeded();
|
||||
_closeCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
Callback closeCallback = _closeCallback == null ? BLOCKING_CLOSE_CALLBACK : _closeCallback;
|
||||
|
||||
while (true)
|
||||
{
|
||||
State state = _state.get();
|
||||
switch (state)
|
||||
{
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
{
|
||||
_closeCallback = null;
|
||||
closeCallback.succeeded();
|
||||
return;
|
||||
}
|
||||
case ASYNC:
|
||||
|
@ -273,11 +333,10 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
// A close call implies a write operation, thus in asynchronous mode
|
||||
// a call to isReady() that returned true should have been made.
|
||||
// However it is desirable to allow a close at any time, specially if
|
||||
// complete is called. Thus we simulate a call to isReady here, assuming
|
||||
// that we can transition to READY.
|
||||
if (!_state.compareAndSet(state, OutputState.READY))
|
||||
// complete is called. Thus we simulate a call to isReady here, by
|
||||
// trying to move to READY state. Either way we continue.
|
||||
_state.compareAndSet(state, State.READY);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case UNREADY:
|
||||
case PENDING:
|
||||
|
@ -288,34 +347,45 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
// complete is called. Because the prior write has not yet completed
|
||||
// and/or isReady has not been called, this close is allowed, but will
|
||||
// abort the response.
|
||||
if (!_state.compareAndSet(state, OutputState.CLOSED))
|
||||
if (!_state.compareAndSet(state, State.CLOSED))
|
||||
continue;
|
||||
IOException ex = new IOException("Closed while Pending/Unready");
|
||||
LOG.warn(ex.toString());
|
||||
LOG.debug(ex);
|
||||
abort(ex);
|
||||
_closeCallback = null;
|
||||
closeCallback.failed(ex);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!_state.compareAndSet(state, OutputState.CLOSED))
|
||||
if (!_state.compareAndSet(state, State.CLOSING))
|
||||
continue;
|
||||
|
||||
// Do a normal close by writing the aggregate buffer or an empty buffer. If we are
|
||||
// not including, then indicate this is the last write.
|
||||
try
|
||||
{
|
||||
write(BufferUtil.hasContent(_aggregate) ? _aggregate : BufferUtil.EMPTY_BUFFER, !_channel.getResponse().isIncluding());
|
||||
ByteBuffer content = BufferUtil.hasContent(_aggregate) ? _aggregate : BufferUtil.EMPTY_BUFFER;
|
||||
if (closeCallback == BLOCKING_CLOSE_CALLBACK)
|
||||
{
|
||||
// Do a blocking close
|
||||
write(content, !_channel.getResponse().isIncluding());
|
||||
_closeCallback = null;
|
||||
closeCallback.succeeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
_closeCallback = null;
|
||||
write(content, !_channel.getResponse().isIncluding(), closeCallback);
|
||||
}
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
LOG.ignore(x); // Ignore it, it's been already logged in write().
|
||||
_closeCallback = null;
|
||||
closeCallback.failed(x);
|
||||
}
|
||||
finally
|
||||
{
|
||||
releaseBuffer();
|
||||
}
|
||||
// Return even if an exception is thrown by write().
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -326,11 +396,11 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
* Called to indicate that the last write has been performed.
|
||||
* It updates the state and performs cleanup operations.
|
||||
*/
|
||||
void closed()
|
||||
public void closed()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
OutputState state = _state.get();
|
||||
State state = _state.get();
|
||||
switch (state)
|
||||
{
|
||||
case CLOSED:
|
||||
|
@ -339,15 +409,16 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
}
|
||||
case UNREADY:
|
||||
{
|
||||
if (_state.compareAndSet(state, OutputState.ERROR))
|
||||
if (_state.compareAndSet(state, State.ERROR))
|
||||
_writeListener.onError(_onError == null ? new EofException("Async closed") : _onError);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!_state.compareAndSet(state, OutputState.CLOSED))
|
||||
if (!_state.compareAndSet(state, State.CLOSED))
|
||||
break;
|
||||
|
||||
// Just make sure write and output stream really are closed
|
||||
try
|
||||
{
|
||||
_channel.getResponse().closeOutput();
|
||||
|
@ -369,6 +440,18 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
}
|
||||
}
|
||||
|
||||
public ByteBuffer getBuffer()
|
||||
{
|
||||
return _aggregate;
|
||||
}
|
||||
|
||||
public ByteBuffer acquireBuffer()
|
||||
{
|
||||
if (_aggregate == null)
|
||||
_aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), _interceptor.isOptimizedForDirectBuffers());
|
||||
return _aggregate;
|
||||
}
|
||||
|
||||
private void releaseBuffer()
|
||||
{
|
||||
if (_aggregate != null)
|
||||
|
@ -380,7 +463,14 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
|
||||
public boolean isClosed()
|
||||
{
|
||||
return _state.get() == OutputState.CLOSED;
|
||||
switch (_state.get())
|
||||
{
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAsync()
|
||||
|
@ -402,7 +492,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
{
|
||||
while (true)
|
||||
{
|
||||
switch (_state.get())
|
||||
State state = _state.get();
|
||||
switch (state)
|
||||
{
|
||||
case OPEN:
|
||||
write(BufferUtil.hasContent(_aggregate) ? _aggregate : BufferUtil.EMPTY_BUFFER, false);
|
||||
|
@ -412,25 +503,24 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
throw new IllegalStateException("isReady() not called");
|
||||
|
||||
case READY:
|
||||
if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
|
||||
if (!_state.compareAndSet(state, State.PENDING))
|
||||
continue;
|
||||
new AsyncFlush().iterate();
|
||||
return;
|
||||
|
||||
case PENDING:
|
||||
return;
|
||||
|
||||
case UNREADY:
|
||||
throw new WritePendingException();
|
||||
|
||||
case ERROR:
|
||||
throw new EofException(_onError);
|
||||
|
||||
case PENDING:
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
return;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
throw new IllegalStateException(state.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -441,7 +531,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
// Async or Blocking ?
|
||||
while (true)
|
||||
{
|
||||
switch (_state.get())
|
||||
State state = _state.get();
|
||||
switch (state)
|
||||
{
|
||||
case OPEN:
|
||||
// process blocking below
|
||||
|
@ -451,15 +542,14 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
throw new IllegalStateException("isReady() not called");
|
||||
|
||||
case READY:
|
||||
if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
|
||||
if (!_state.compareAndSet(state, State.PENDING))
|
||||
continue;
|
||||
|
||||
// Should we aggregate?
|
||||
boolean last = isLastContentToWrite(len);
|
||||
if (!last && len <= _commitSize)
|
||||
{
|
||||
if (_aggregate == null)
|
||||
_aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), _interceptor.isOptimizedForDirectBuffers());
|
||||
acquireBuffer();
|
||||
|
||||
// YES - fill the aggregate with content from the buffer
|
||||
int filled = BufferUtil.fill(_aggregate, b, off, len);
|
||||
|
@ -467,8 +557,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
// return if we are not complete, not full and filled all the content
|
||||
if (filled == len && !BufferUtil.isFull(_aggregate))
|
||||
{
|
||||
if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
|
||||
throw new IllegalStateException();
|
||||
if (!_state.compareAndSet(State.PENDING, State.ASYNC))
|
||||
throw new IllegalStateException(_state.get().toString());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -488,11 +578,12 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
case ERROR:
|
||||
throw new EofException(_onError);
|
||||
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
throw new EofException("Closed");
|
||||
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
throw new IllegalStateException(state.toString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -504,8 +595,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
boolean last = isLastContentToWrite(len);
|
||||
if (!last && len <= _commitSize)
|
||||
{
|
||||
if (_aggregate == null)
|
||||
_aggregate = _channel.getByteBufferPool().acquire(capacity, _interceptor.isOptimizedForDirectBuffers());
|
||||
acquireBuffer();
|
||||
|
||||
// YES - fill the aggregate with content from the buffer
|
||||
int filled = BufferUtil.fill(_aggregate, b, off, len);
|
||||
|
@ -554,9 +644,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
{
|
||||
write(BufferUtil.EMPTY_BUFFER, true);
|
||||
}
|
||||
|
||||
if (last)
|
||||
closed();
|
||||
}
|
||||
|
||||
public void write(ByteBuffer buffer) throws IOException
|
||||
|
@ -566,7 +653,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
// Async or Blocking ?
|
||||
while (true)
|
||||
{
|
||||
switch (_state.get())
|
||||
State state = _state.get();
|
||||
switch (state)
|
||||
{
|
||||
case OPEN:
|
||||
// process blocking below
|
||||
|
@ -576,7 +664,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
throw new IllegalStateException("isReady() not called");
|
||||
|
||||
case READY:
|
||||
if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
|
||||
if (!_state.compareAndSet(state, State.PENDING))
|
||||
continue;
|
||||
|
||||
// Do the asynchronous writing from the callback
|
||||
|
@ -591,11 +679,12 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
case ERROR:
|
||||
throw new EofException(_onError);
|
||||
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
throw new EofException("Closed");
|
||||
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
throw new IllegalStateException(state.toString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -613,9 +702,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
write(buffer, last);
|
||||
else if (last)
|
||||
write(BufferUtil.EMPTY_BUFFER, true);
|
||||
|
||||
if (last)
|
||||
closed();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -630,34 +716,28 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
switch (_state.get())
|
||||
{
|
||||
case OPEN:
|
||||
if (_aggregate == null)
|
||||
_aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), _interceptor.isOptimizedForDirectBuffers());
|
||||
acquireBuffer();
|
||||
BufferUtil.append(_aggregate, (byte)b);
|
||||
|
||||
// Check if all written or full
|
||||
if (complete || BufferUtil.isFull(_aggregate))
|
||||
{
|
||||
write(_aggregate, complete);
|
||||
if (complete)
|
||||
closed();
|
||||
}
|
||||
break;
|
||||
|
||||
case ASYNC:
|
||||
throw new IllegalStateException("isReady() not called");
|
||||
|
||||
case READY:
|
||||
if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
|
||||
if (!_state.compareAndSet(State.READY, State.PENDING))
|
||||
continue;
|
||||
|
||||
if (_aggregate == null)
|
||||
_aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), _interceptor.isOptimizedForDirectBuffers());
|
||||
acquireBuffer();
|
||||
BufferUtil.append(_aggregate, (byte)b);
|
||||
|
||||
// Check if all written or full
|
||||
if (!complete && !BufferUtil.isFull(_aggregate))
|
||||
{
|
||||
if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
|
||||
if (!_state.compareAndSet(State.PENDING, State.ASYNC))
|
||||
throw new IllegalStateException();
|
||||
return;
|
||||
}
|
||||
|
@ -673,6 +753,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
case ERROR:
|
||||
throw new EofException(_onError);
|
||||
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
throw new EofException("Closed");
|
||||
|
||||
|
@ -810,7 +891,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
|
||||
_written += content.remaining();
|
||||
write(content, true);
|
||||
closed();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -966,7 +1046,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
switch (_state.get())
|
||||
{
|
||||
case OPEN:
|
||||
if (!_state.compareAndSet(OutputState.OPEN, OutputState.PENDING))
|
||||
if (!_state.compareAndSet(State.OPEN, State.PENDING))
|
||||
continue;
|
||||
break;
|
||||
|
||||
|
@ -974,6 +1054,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
callback.failed(new EofException(_onError));
|
||||
return;
|
||||
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
callback.failed(new EofException("Closed"));
|
||||
return;
|
||||
|
@ -1073,6 +1154,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
_onError = null;
|
||||
_firstByteTimeStamp = -1;
|
||||
_flushed = 0;
|
||||
_closeCallback = null;
|
||||
reopen();
|
||||
}
|
||||
|
||||
|
@ -1082,7 +1164,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
if (BufferUtil.hasContent(_aggregate))
|
||||
BufferUtil.clear(_aggregate);
|
||||
_written = 0;
|
||||
reopen();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1091,7 +1172,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
if (!_channel.getState().isAsync())
|
||||
throw new IllegalStateException("!ASYNC");
|
||||
|
||||
if (_state.compareAndSet(OutputState.OPEN, OutputState.READY))
|
||||
if (_state.compareAndSet(State.OPEN, State.READY))
|
||||
{
|
||||
_writeListener = writeListener;
|
||||
if (_channel.getState().onWritePossible())
|
||||
|
@ -1109,30 +1190,25 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
switch (_state.get())
|
||||
{
|
||||
case OPEN:
|
||||
case READY:
|
||||
case ERROR:
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
return true;
|
||||
|
||||
case ASYNC:
|
||||
if (!_state.compareAndSet(OutputState.ASYNC, OutputState.READY))
|
||||
if (!_state.compareAndSet(State.ASYNC, State.READY))
|
||||
continue;
|
||||
return true;
|
||||
|
||||
case READY:
|
||||
return true;
|
||||
|
||||
case PENDING:
|
||||
if (!_state.compareAndSet(OutputState.PENDING, OutputState.UNREADY))
|
||||
if (!_state.compareAndSet(State.PENDING, State.UNREADY))
|
||||
continue;
|
||||
return false;
|
||||
|
||||
case UNREADY:
|
||||
return false;
|
||||
|
||||
case ERROR:
|
||||
return true;
|
||||
|
||||
case CLOSED:
|
||||
return true;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
@ -1144,12 +1220,13 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
{
|
||||
while (true)
|
||||
{
|
||||
OutputState state = _state.get();
|
||||
State state = _state.get();
|
||||
|
||||
if (_onError != null)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
case ERROR:
|
||||
{
|
||||
|
@ -1158,7 +1235,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
}
|
||||
default:
|
||||
{
|
||||
if (_state.compareAndSet(state, OutputState.ERROR))
|
||||
if (_state.compareAndSet(state, State.ERROR))
|
||||
{
|
||||
Throwable th = _onError;
|
||||
_onError = null;
|
||||
|
@ -1234,16 +1311,16 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
{
|
||||
while (true)
|
||||
{
|
||||
OutputState last = _state.get();
|
||||
State last = _state.get();
|
||||
switch (last)
|
||||
{
|
||||
case PENDING:
|
||||
if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
|
||||
if (!_state.compareAndSet(State.PENDING, State.ASYNC))
|
||||
continue;
|
||||
break;
|
||||
|
||||
case UNREADY:
|
||||
if (!_state.compareAndSet(OutputState.UNREADY, OutputState.READY))
|
||||
if (!_state.compareAndSet(State.UNREADY, State.READY))
|
||||
continue;
|
||||
if (_last)
|
||||
closed();
|
||||
|
|
|
@ -759,10 +759,18 @@ public class Request implements HttpServletRequest
|
|||
}
|
||||
|
||||
/**
|
||||
* @return The current {@link Context context} used for this request, or <code>null</code> if {@link #setContext} has not yet been called.
|
||||
* @return The current {@link Context context} used for this error handling for this request. If the request is asynchronous,
|
||||
* then it is the context that called async. Otherwise it is the last non-null context passed to #setContext
|
||||
*/
|
||||
public Context getErrorContext()
|
||||
{
|
||||
if (isAsyncStarted())
|
||||
{
|
||||
ContextHandler handler = _channel.getState().getContextHandler();
|
||||
if (handler != null)
|
||||
return handler.getServletContext();
|
||||
}
|
||||
|
||||
return _errorContext;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,18 +18,18 @@
|
|||
|
||||
package org.eclipse.jetty.server;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.channels.IllegalSelectorException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.ServletResponseWrapper;
|
||||
|
@ -57,9 +57,8 @@ import org.eclipse.jetty.http.MetaData;
|
|||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.http.PreEncodedHttpField;
|
||||
import org.eclipse.jetty.io.RuntimeIOException;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||
import org.eclipse.jetty.server.session.SessionHandler;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
@ -378,70 +377,35 @@ public class Response implements HttpServletResponse
|
|||
sendError(sc, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an error response.
|
||||
* <p>In addition to the servlet standard handling, this method supports some additional codes:</p>
|
||||
* <dl>
|
||||
* <dt>102</dt><dd>Send a partial PROCESSING response and allow additional responses</dd>
|
||||
* <dt>-1</dt><dd>Abort the HttpChannel and close the connection/stream</dd>
|
||||
* </dl>
|
||||
* @param code The error code
|
||||
* @param message The message
|
||||
* @throws IOException If an IO problem occurred sending the error response.
|
||||
*/
|
||||
@Override
|
||||
public void sendError(int code, String message) throws IOException
|
||||
{
|
||||
if (isIncluding())
|
||||
return;
|
||||
|
||||
if (isCommitted())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Aborting on sendError on committed response {} {}", code, message);
|
||||
code = -1;
|
||||
}
|
||||
else
|
||||
resetBuffer();
|
||||
|
||||
switch (code)
|
||||
{
|
||||
case -1:
|
||||
_channel.abort(new IOException());
|
||||
return;
|
||||
case 102:
|
||||
_channel.abort(new IOException(message));
|
||||
break;
|
||||
case HttpStatus.PROCESSING_102:
|
||||
sendProcessing();
|
||||
return;
|
||||
break;
|
||||
default:
|
||||
_channel.getState().sendError(code, message);
|
||||
break;
|
||||
}
|
||||
|
||||
_outputType = OutputType.NONE;
|
||||
setContentType(null);
|
||||
setCharacterEncoding(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);
|
||||
|
||||
setStatus(code);
|
||||
|
||||
Request request = _channel.getRequest();
|
||||
Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
|
||||
if (message == null)
|
||||
{
|
||||
_reason = HttpStatus.getMessage(code);
|
||||
message = cause == null ? _reason : cause.toString();
|
||||
}
|
||||
else
|
||||
_reason = message;
|
||||
|
||||
// If we are allowed to have a body, then produce the error page.
|
||||
if (code != SC_NO_CONTENT && code != SC_NOT_MODIFIED &&
|
||||
code != SC_PARTIAL_CONTENT && code >= SC_OK)
|
||||
{
|
||||
ContextHandler.Context context = request.getErrorContext();
|
||||
ContextHandler contextHandler = context == null ? _channel.getState().getContextHandler() : context.getContextHandler();
|
||||
request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, code);
|
||||
request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
|
||||
request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
|
||||
request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, request.getServletName());
|
||||
ErrorHandler errorHandler = ErrorHandler.getErrorHandler(_channel.getServer(), contextHandler);
|
||||
if (errorHandler != null)
|
||||
errorHandler.handle(null, request, request, this);
|
||||
}
|
||||
if (!request.isAsyncStarted())
|
||||
closeOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -653,8 +617,11 @@ public class Response implements HttpServletResponse
|
|||
throw new IllegalArgumentException();
|
||||
if (!isIncluding())
|
||||
{
|
||||
_status = sc;
|
||||
// Null the reason only if the status is different. This allows
|
||||
// a specific reason to be sent with setStatusWithReason followed by sendError.
|
||||
if (_status != sc)
|
||||
_reason = null;
|
||||
_status = sc;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -717,6 +684,11 @@ public class Response implements HttpServletResponse
|
|||
return _outputType == OutputType.STREAM;
|
||||
}
|
||||
|
||||
public boolean isWritingOrStreaming()
|
||||
{
|
||||
return isWriting() || isStreaming();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter getWriter() throws IOException
|
||||
{
|
||||
|
@ -825,21 +797,15 @@ public class Response implements HttpServletResponse
|
|||
|
||||
public void closeOutput() throws IOException
|
||||
{
|
||||
switch (_outputType)
|
||||
{
|
||||
case WRITER:
|
||||
if (_outputType == OutputType.WRITER)
|
||||
_writer.close();
|
||||
if (!_out.isClosed())
|
||||
_out.close();
|
||||
break;
|
||||
case STREAM:
|
||||
if (!_out.isClosed())
|
||||
getOutputStream().close();
|
||||
break;
|
||||
default:
|
||||
if (!_out.isClosed())
|
||||
_out.close();
|
||||
}
|
||||
|
||||
public void closeOutput(Callback callback)
|
||||
{
|
||||
_out.close((_outputType == OutputType.WRITER) ? _writer : _out, callback);
|
||||
}
|
||||
|
||||
public long getLongContentLength()
|
||||
|
@ -1029,19 +995,20 @@ public class Response implements HttpServletResponse
|
|||
@Override
|
||||
public void reset()
|
||||
{
|
||||
reset(false);
|
||||
}
|
||||
|
||||
public void reset(boolean preserveCookies)
|
||||
{
|
||||
resetForForward();
|
||||
_status = 200;
|
||||
_reason = null;
|
||||
_out.resetBuffer();
|
||||
_outputType = OutputType.NONE;
|
||||
_contentLength = -1;
|
||||
_contentType = null;
|
||||
_mimeType = null;
|
||||
_characterEncoding = null;
|
||||
_encodingFrom = EncodingFrom.NOT_SET;
|
||||
|
||||
List<HttpField> cookies = preserveCookies ? _fields.getFields(HttpHeader.SET_COOKIE) : null;
|
||||
// Clear all response headers
|
||||
_fields.clear();
|
||||
|
||||
// recreate necessary connection related fields
|
||||
for (String value : _channel.getRequest().getHttpFields().getCSV(HttpHeader.CONNECTION, false))
|
||||
{
|
||||
HttpHeaderValue cb = HttpHeaderValue.CACHE.get(value);
|
||||
|
@ -1064,10 +1031,7 @@ public class Response implements HttpServletResponse
|
|||
}
|
||||
}
|
||||
|
||||
if (preserveCookies)
|
||||
cookies.forEach(_fields::add);
|
||||
else
|
||||
{
|
||||
// recreate session cookies
|
||||
Request request = getHttpChannel().getRequest();
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null && session.isNew())
|
||||
|
@ -1081,6 +1045,45 @@ public class Response implements HttpServletResponse
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void resetContent()
|
||||
{
|
||||
_out.resetBuffer();
|
||||
_outputType = OutputType.NONE;
|
||||
_contentLength = -1;
|
||||
_contentType = null;
|
||||
_mimeType = null;
|
||||
_characterEncoding = null;
|
||||
_encodingFrom = EncodingFrom.NOT_SET;
|
||||
|
||||
// remove the content related response headers and keep all others
|
||||
for (Iterator<HttpField> i = getHttpFields().iterator(); i.hasNext(); )
|
||||
{
|
||||
HttpField field = i.next();
|
||||
if (field.getHeader() == null)
|
||||
continue;
|
||||
|
||||
switch (field.getHeader())
|
||||
{
|
||||
case CONTENT_TYPE:
|
||||
case CONTENT_LENGTH:
|
||||
case CONTENT_ENCODING:
|
||||
case CONTENT_LANGUAGE:
|
||||
case CONTENT_RANGE:
|
||||
case CONTENT_MD5:
|
||||
case CONTENT_LOCATION:
|
||||
case TRANSFER_ENCODING:
|
||||
case CACHE_CONTROL:
|
||||
case LAST_MODIFIED:
|
||||
case EXPIRES:
|
||||
case ETAG:
|
||||
case DATE:
|
||||
case VARY:
|
||||
i.remove();
|
||||
continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void resetForForward()
|
||||
|
@ -1093,6 +1096,7 @@ public class Response implements HttpServletResponse
|
|||
public void resetBuffer()
|
||||
{
|
||||
_out.resetBuffer();
|
||||
_out.reopen();
|
||||
}
|
||||
|
||||
public void setTrailers(Supplier<HttpFields> trailers)
|
||||
|
@ -1133,6 +1137,9 @@ public class Response implements HttpServletResponse
|
|||
@Override
|
||||
public boolean isCommitted()
|
||||
{
|
||||
// If we are in sendError state, we pretend to be committed
|
||||
if (_channel.isSendError())
|
||||
return true;
|
||||
return _channel.isCommitted();
|
||||
}
|
||||
|
||||
|
|
|
@ -485,11 +485,17 @@ public class Server extends HandlerWrapper implements Attributes
|
|||
if (HttpMethod.OPTIONS.is(request.getMethod()) || "*".equals(target))
|
||||
{
|
||||
if (!HttpMethod.OPTIONS.is(request.getMethod()))
|
||||
{
|
||||
request.setHandled(true);
|
||||
response.sendError(HttpStatus.BAD_REQUEST_400);
|
||||
}
|
||||
else
|
||||
{
|
||||
handleOptions(request, response);
|
||||
if (!request.isHandled())
|
||||
handle(target, request, request, response);
|
||||
}
|
||||
}
|
||||
else
|
||||
handle(target, request, request, response);
|
||||
|
||||
|
|
|
@ -1106,7 +1106,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
case UNAVAILABLE:
|
||||
baseRequest.setHandled(true);
|
||||
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
|
||||
return true;
|
||||
return false;
|
||||
default:
|
||||
if ((DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled()))
|
||||
return false;
|
||||
|
@ -1141,8 +1141,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
if (oldContext != _scontext)
|
||||
{
|
||||
// check the target.
|
||||
if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch) ||
|
||||
DispatcherType.ERROR.equals(dispatch) && baseRequest.getHttpChannelState().isAsync())
|
||||
if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch))
|
||||
{
|
||||
if (_compactPath)
|
||||
target = URIUtil.compactPath(target);
|
||||
|
@ -1279,30 +1278,12 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
if (new_context)
|
||||
requestInitialized(baseRequest, request);
|
||||
|
||||
switch (dispatch)
|
||||
if (dispatch == DispatcherType.REQUEST && isProtectedTarget(target))
|
||||
{
|
||||
case REQUEST:
|
||||
if (isProtectedTarget(target))
|
||||
{
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
baseRequest.setHandled(true);
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ERROR:
|
||||
// If this is already a dispatch to an error page, proceed normally
|
||||
if (Boolean.TRUE.equals(baseRequest.getAttribute(Dispatcher.__ERROR_DISPATCH)))
|
||||
break;
|
||||
|
||||
// We can just call doError here. If there is no error page, then one will
|
||||
// be generated. If there is an error page, then a RequestDispatcher will be
|
||||
// used to route the request through appropriate filters etc.
|
||||
doError(target, baseRequest, request, response);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
nextHandle(target, baseRequest, request, response);
|
||||
}
|
||||
|
|
|
@ -19,28 +19,29 @@
|
|||
package org.eclipse.jetty.server.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.http.QuotedQualityCSV;
|
||||
import org.eclipse.jetty.io.ByteBufferOutputStream;
|
||||
import org.eclipse.jetty.server.Dispatcher;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -54,10 +55,13 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
*/
|
||||
public class ErrorHandler extends AbstractHandler
|
||||
{
|
||||
// TODO This classes API needs to be majorly refactored/cleanup in jetty-10
|
||||
private static final Logger LOG = Log.getLogger(ErrorHandler.class);
|
||||
public static final String ERROR_PAGE = "org.eclipse.jetty.server.error_page";
|
||||
public static final String ERROR_CONTEXT = "org.eclipse.jetty.server.error_context";
|
||||
|
||||
boolean _showStacks = true;
|
||||
boolean _disableStacks = false;
|
||||
boolean _showMessageInTitle = true;
|
||||
String _cacheControl = "must-revalidate,no-cache,no-store";
|
||||
|
||||
|
@ -65,6 +69,19 @@ public class ErrorHandler extends AbstractHandler
|
|||
{
|
||||
}
|
||||
|
||||
public boolean errorPageForMethod(String method)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case "GET":
|
||||
case "POST":
|
||||
case "HEAD":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
|
||||
*/
|
||||
|
@ -77,65 +94,14 @@ public class ErrorHandler extends AbstractHandler
|
|||
@Override
|
||||
public void doError(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
String method = request.getMethod();
|
||||
if (!HttpMethod.GET.is(method) && !HttpMethod.POST.is(method) && !HttpMethod.HEAD.is(method))
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
}
|
||||
String cacheControl = getCacheControl();
|
||||
if (cacheControl != null)
|
||||
response.setHeader(HttpHeader.CACHE_CONTROL.asString(), cacheControl);
|
||||
|
||||
if (this instanceof ErrorPageMapper)
|
||||
{
|
||||
String errorPage = ((ErrorPageMapper)this).getErrorPage(request);
|
||||
if (errorPage != null)
|
||||
{
|
||||
String oldErrorPage = (String)request.getAttribute(ERROR_PAGE);
|
||||
ContextHandler.Context context = baseRequest.getErrorContext();
|
||||
if (context == null)
|
||||
context = ContextHandler.getCurrentContext();
|
||||
if (context == null)
|
||||
{
|
||||
LOG.warn("No ServletContext for error page {}", errorPage);
|
||||
}
|
||||
else if (oldErrorPage != null && oldErrorPage.equals(errorPage))
|
||||
{
|
||||
LOG.warn("Error page loop {}", errorPage);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.setAttribute(ERROR_PAGE, errorPage);
|
||||
|
||||
Dispatcher dispatcher = (Dispatcher)context.getRequestDispatcher(errorPage);
|
||||
try
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("error page dispatch {}->{}", errorPage, dispatcher);
|
||||
if (dispatcher != null)
|
||||
{
|
||||
dispatcher.error(request, response);
|
||||
return;
|
||||
}
|
||||
LOG.warn("No error page found " + errorPage);
|
||||
}
|
||||
catch (ServletException e)
|
||||
{
|
||||
LOG.warn(Log.EXCEPTION, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("No Error Page mapping for request({} {}) (using default)", request.getMethod(), request.getRequestURI());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_cacheControl != null)
|
||||
response.setHeader(HttpHeader.CACHE_CONTROL.asString(), _cacheControl);
|
||||
generateAcceptableResponse(baseRequest, request, response, response.getStatus(), baseRequest.getResponse().getReason());
|
||||
String message = (String)request.getAttribute(Dispatcher.ERROR_MESSAGE);
|
||||
if (message == null)
|
||||
message = baseRequest.getResponse().getReason();
|
||||
generateAcceptableResponse(baseRequest, request, response, response.getStatus(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,7 +110,7 @@ public class ErrorHandler extends AbstractHandler
|
|||
* acceptable to the user-agent. The Accept header is evaluated in
|
||||
* quality order and the method
|
||||
* {@link #generateAcceptableResponse(Request, HttpServletRequest, HttpServletResponse, int, String, String)}
|
||||
* is called for each mimetype until {@link Request#isHandled()} is true.</p>
|
||||
* is called for each mimetype until the response is written to or committed.</p>
|
||||
*
|
||||
* @param baseRequest The base request
|
||||
* @param request The servlet request (may be wrapped)
|
||||
|
@ -167,11 +133,10 @@ public class ErrorHandler extends AbstractHandler
|
|||
for (String mimeType : acceptable)
|
||||
{
|
||||
generateAcceptableResponse(baseRequest, request, response, code, message, mimeType);
|
||||
if (response.isCommitted() || baseRequest.getResponse().isWriting() || baseRequest.getResponse().isStreaming())
|
||||
if (response.isCommitted() || baseRequest.getResponse().isWritingOrStreaming())
|
||||
break;
|
||||
}
|
||||
}
|
||||
baseRequest.getResponse().closeOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -192,6 +157,7 @@ public class ErrorHandler extends AbstractHandler
|
|||
* @return A {@link Writer} if there is a known acceptable charset or null
|
||||
* @throws IOException if a Writer cannot be returned
|
||||
*/
|
||||
@Deprecated
|
||||
protected Writer getAcceptableWriter(Request baseRequest, HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException
|
||||
{
|
||||
|
@ -225,33 +191,132 @@ public class ErrorHandler extends AbstractHandler
|
|||
* <p>This method is called for each mime type in the users agent's
|
||||
* <code>Accept</code> header, until {@link Request#isHandled()} is true and a
|
||||
* response of the appropriate type is generated.
|
||||
* </p>
|
||||
* <p>The default implementation handles "text/html", "text/*" and "*/*".
|
||||
* The method can be overridden to handle other types. Implementations must
|
||||
* immediate produce a response and may not be async.
|
||||
* </p>
|
||||
*
|
||||
* @param baseRequest The base request
|
||||
* @param request The servlet request (may be wrapped)
|
||||
* @param response The response (may be wrapped)
|
||||
* @param code the http error code
|
||||
* @param message the http error message
|
||||
* @param mimeType The mimetype to generate (may be */*or other wildcard)
|
||||
* @param contentType The mimetype to generate (may be */*or other wildcard)
|
||||
* @throws IOException if a response cannot be generated
|
||||
*/
|
||||
protected void generateAcceptableResponse(Request baseRequest, HttpServletRequest request, HttpServletResponse response, int code, String message, String mimeType)
|
||||
protected void generateAcceptableResponse(Request baseRequest, HttpServletRequest request, HttpServletResponse response, int code, String message, String contentType)
|
||||
throws IOException
|
||||
{
|
||||
switch (mimeType)
|
||||
// We can generate an acceptable contentType, but can we generate an acceptable charset?
|
||||
// TODO refactor this in jetty-10 to be done in the other calling loop
|
||||
Charset charset = null;
|
||||
List<String> acceptable = baseRequest.getHttpFields().getQualityCSV(HttpHeader.ACCEPT_CHARSET);
|
||||
if (!acceptable.isEmpty())
|
||||
{
|
||||
for (String name : acceptable)
|
||||
{
|
||||
if ("*".equals(name))
|
||||
{
|
||||
charset = StandardCharsets.UTF_8;
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
charset = Charset.forName(name);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
}
|
||||
}
|
||||
if (charset == null)
|
||||
return;
|
||||
}
|
||||
|
||||
MimeTypes.Type type;
|
||||
switch (contentType)
|
||||
{
|
||||
case "text/html":
|
||||
case "text/*":
|
||||
case "*/*":
|
||||
type = MimeTypes.Type.TEXT_HTML;
|
||||
if (charset == null)
|
||||
charset = StandardCharsets.ISO_8859_1;
|
||||
break;
|
||||
|
||||
case "text/json":
|
||||
case "application/json":
|
||||
type = MimeTypes.Type.TEXT_JSON;
|
||||
if (charset == null)
|
||||
charset = StandardCharsets.UTF_8;
|
||||
break;
|
||||
|
||||
case "text/plain":
|
||||
type = MimeTypes.Type.TEXT_PLAIN;
|
||||
if (charset == null)
|
||||
charset = StandardCharsets.ISO_8859_1;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// write into the response aggregate buffer and flush it asynchronously.
|
||||
while (true)
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
Writer writer = getAcceptableWriter(baseRequest, request, response);
|
||||
if (writer != null)
|
||||
try
|
||||
{
|
||||
// TODO currently the writer used here is of fixed size, so a large
|
||||
// TODO error page may cause a BufferOverflow. In which case we try
|
||||
// TODO again with stacks disabled. If it still overflows, it is
|
||||
// TODO written without a body.
|
||||
ByteBuffer buffer = baseRequest.getResponse().getHttpOutput().acquireBuffer();
|
||||
ByteBufferOutputStream out = new ByteBufferOutputStream(buffer);
|
||||
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, charset));
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case TEXT_HTML:
|
||||
response.setContentType(MimeTypes.Type.TEXT_HTML.asString());
|
||||
response.setCharacterEncoding(charset.name());
|
||||
handleErrorPage(request, writer, code, message);
|
||||
break;
|
||||
case TEXT_JSON:
|
||||
response.setContentType(contentType);
|
||||
writeErrorJson(request, writer, code, message);
|
||||
break;
|
||||
case TEXT_PLAIN:
|
||||
response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString());
|
||||
response.setCharacterEncoding(charset.name());
|
||||
writeErrorPlain(request, writer, code, message);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
writer.flush();
|
||||
break;
|
||||
}
|
||||
catch (BufferOverflowException e)
|
||||
{
|
||||
LOG.warn("Error page too large: {} {} {}", code, message, request);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.warn(e);
|
||||
baseRequest.getResponse().resetContent();
|
||||
if (!_disableStacks)
|
||||
{
|
||||
LOG.info("Disabling showsStacks for " + this);
|
||||
_disableStacks = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do an asynchronous completion.
|
||||
baseRequest.getHttpChannel().sendResponseAndComplete();
|
||||
}
|
||||
|
||||
protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message)
|
||||
|
@ -278,12 +343,13 @@ public class ErrorHandler extends AbstractHandler
|
|||
{
|
||||
writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\n");
|
||||
writer.write("<title>Error ");
|
||||
writer.write(Integer.toString(code));
|
||||
|
||||
if (_showMessageInTitle)
|
||||
// TODO this code is duplicated in writeErrorPageMessage
|
||||
String status = Integer.toString(code);
|
||||
writer.write(status);
|
||||
if (message != null && !message.equals(status))
|
||||
{
|
||||
writer.write(' ');
|
||||
write(writer, message);
|
||||
writer.write(StringUtil.sanitizeXmlString(message));
|
||||
}
|
||||
writer.write("</title>\n");
|
||||
}
|
||||
|
@ -294,7 +360,7 @@ public class ErrorHandler extends AbstractHandler
|
|||
String uri = request.getRequestURI();
|
||||
|
||||
writeErrorPageMessage(request, writer, code, message, uri);
|
||||
if (showStacks)
|
||||
if (showStacks && !_disableStacks)
|
||||
writeErrorPageStacks(request, writer);
|
||||
|
||||
Request.getBaseRequest(request).getHttpChannel().getHttpConfiguration()
|
||||
|
@ -305,30 +371,98 @@ public class ErrorHandler extends AbstractHandler
|
|||
throws IOException
|
||||
{
|
||||
writer.write("<h2>HTTP ERROR ");
|
||||
String status = Integer.toString(code);
|
||||
writer.write(status);
|
||||
if (message != null && !message.equals(status))
|
||||
{
|
||||
writer.write(' ');
|
||||
writer.write(StringUtil.sanitizeXmlString(message));
|
||||
}
|
||||
writer.write("</h2>\n");
|
||||
writer.write("<table>\n");
|
||||
htmlRow(writer, "URI", uri);
|
||||
htmlRow(writer, "STATUS", status);
|
||||
htmlRow(writer, "MESSAGE", message);
|
||||
htmlRow(writer, "SERVLET", request.getAttribute(Dispatcher.ERROR_SERVLET_NAME));
|
||||
Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
|
||||
while (cause != null)
|
||||
{
|
||||
htmlRow(writer, "CAUSED BY", cause);
|
||||
cause = cause.getCause();
|
||||
}
|
||||
writer.write("</table>\n");
|
||||
}
|
||||
|
||||
private void htmlRow(Writer writer, String tag, Object value)
|
||||
throws IOException
|
||||
{
|
||||
writer.write("<tr><th>");
|
||||
writer.write(tag);
|
||||
writer.write(":</th><td>");
|
||||
if (value == null)
|
||||
writer.write("-");
|
||||
else
|
||||
writer.write(StringUtil.sanitizeXmlString(value.toString()));
|
||||
writer.write("</td></tr>\n");
|
||||
}
|
||||
|
||||
private void writeErrorPlain(HttpServletRequest request, PrintWriter writer, int code, String message)
|
||||
{
|
||||
writer.write("HTTP ERROR ");
|
||||
writer.write(Integer.toString(code));
|
||||
writer.write("</h2>\n<p>Problem accessing ");
|
||||
write(writer, uri);
|
||||
writer.write(". Reason:\n<pre> ");
|
||||
write(writer, message);
|
||||
writer.write("</pre></p>");
|
||||
writer.write(' ');
|
||||
writer.write(StringUtil.sanitizeXmlString(message));
|
||||
writer.write("\n");
|
||||
writer.printf("URI: %s%n", request.getRequestURI());
|
||||
writer.printf("STATUS: %s%n", code);
|
||||
writer.printf("MESSAGE: %s%n", message);
|
||||
writer.printf("SERVLET: %s%n", request.getAttribute(Dispatcher.ERROR_SERVLET_NAME));
|
||||
Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
|
||||
while (cause != null)
|
||||
{
|
||||
writer.printf("CAUSED BY %s%n", cause);
|
||||
if (_showStacks && !_disableStacks)
|
||||
cause.printStackTrace(writer);
|
||||
cause = cause.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeErrorJson(HttpServletRequest request, PrintWriter writer, int code, String message)
|
||||
{
|
||||
writer
|
||||
.append("{\n")
|
||||
.append(" url: \"").append(request.getRequestURI()).append("\",\n")
|
||||
.append(" status: \"").append(Integer.toString(code)).append("\",\n")
|
||||
.append(" message: ").append(QuotedStringTokenizer.quote(message)).append(",\n");
|
||||
Object servlet = request.getAttribute(Dispatcher.ERROR_SERVLET_NAME);
|
||||
if (servlet != null)
|
||||
writer.append("servlet: \"").append(servlet.toString()).append("\",\n");
|
||||
Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
|
||||
int c = 0;
|
||||
while (cause != null)
|
||||
{
|
||||
writer.append(" cause").append(Integer.toString(c++)).append(": ")
|
||||
.append(QuotedStringTokenizer.quote(cause.toString())).append(",\n");
|
||||
cause = cause.getCause();
|
||||
}
|
||||
writer.append("}");
|
||||
}
|
||||
|
||||
protected void writeErrorPageStacks(HttpServletRequest request, Writer writer)
|
||||
throws IOException
|
||||
{
|
||||
Throwable th = (Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
|
||||
if (_showStacks && th != null)
|
||||
{
|
||||
PrintWriter pw = writer instanceof PrintWriter ? (PrintWriter)writer : new PrintWriter(writer);
|
||||
pw.write("<pre>");
|
||||
while (th != null)
|
||||
{
|
||||
writer.write("<h3>Caused by:</h3><pre>");
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
th.printStackTrace(pw);
|
||||
pw.flush();
|
||||
write(writer, sw.getBuffer().toString());
|
||||
writer.write("</pre>\n");
|
||||
|
||||
th = th.getCause();
|
||||
}
|
||||
writer.write("</pre>\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -197,8 +197,9 @@ public class ShutdownHandler extends HandlerWrapper
|
|||
connector.shutdown();
|
||||
}
|
||||
|
||||
response.sendError(200, "Connectors closed, commencing full shutdown");
|
||||
baseRequest.setHandled(true);
|
||||
response.setStatus(200);
|
||||
response.flushBuffer();
|
||||
|
||||
final Server server = getServer();
|
||||
new Thread()
|
||||
|
|
|
@ -43,9 +43,7 @@ import static org.hamcrest.Matchers.is;
|
|||
|
||||
public abstract class AbstractHttpTest
|
||||
{
|
||||
private static final Set<String> __noBodyCodes = new HashSet<>(Arrays.asList(new String[]{
|
||||
"100", "101", "102", "204", "304"
|
||||
}));
|
||||
private static final Set<String> __noBodyCodes = new HashSet<>(Arrays.asList("100", "101", "102", "204", "304"));
|
||||
|
||||
protected static Server server;
|
||||
protected static ServerConnector connector;
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Exchanger;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.http.HttpCompliance;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.io.ChannelEndPoint;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.ManagedSelector;
|
||||
import org.eclipse.jetty.io.SocketChannelEndPoint;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
* Extended Server Tester.
|
||||
*/
|
||||
public class AsyncCompletionTest extends HttpServerTestFixture
|
||||
{
|
||||
private static final Exchanger<DelayedCallback> X = new Exchanger<>();
|
||||
private static final AtomicBoolean COMPLETE = new AtomicBoolean();
|
||||
|
||||
private static class DelayedCallback extends Callback.Nested
|
||||
{
|
||||
private CompletableFuture<Void> _delay = new CompletableFuture<>();
|
||||
|
||||
public DelayedCallback(Callback callback)
|
||||
{
|
||||
super(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
_delay.complete(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
_delay.completeExceptionally(x);
|
||||
}
|
||||
|
||||
public void proceed()
|
||||
{
|
||||
try
|
||||
{
|
||||
_delay.get(10, TimeUnit.SECONDS);
|
||||
getCallback().succeeded();
|
||||
}
|
||||
catch(Throwable th)
|
||||
{
|
||||
th.printStackTrace();
|
||||
getCallback().failed(th);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void init() throws Exception
|
||||
{
|
||||
COMPLETE.set(false);
|
||||
|
||||
startServer(new ServerConnector(_server, new HttpConnectionFactory()
|
||||
{
|
||||
@Override
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||
{
|
||||
return configure(new ExtendedHttpConnection(getHttpConfiguration(), connector, endPoint), connector, endPoint);
|
||||
}
|
||||
})
|
||||
{
|
||||
@Override
|
||||
protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
|
||||
{
|
||||
return new ExtendedEndPoint(channel, selectSet, key, getScheduler());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class ExtendedEndPoint extends SocketChannelEndPoint
|
||||
{
|
||||
public ExtendedEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler)
|
||||
{
|
||||
super(channel, selector, key, scheduler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Callback callback, ByteBuffer... buffers) throws IllegalStateException
|
||||
{
|
||||
DelayedCallback delay = new DelayedCallback(callback);
|
||||
super.write(delay, buffers);
|
||||
try
|
||||
{
|
||||
X.exchange(delay);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExtendedHttpConnection extends HttpConnection
|
||||
{
|
||||
public ExtendedHttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint)
|
||||
{
|
||||
super(config, connector, endPoint, HttpCompliance.RFC7230_LEGACY, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted()
|
||||
{
|
||||
COMPLETE.compareAndSet(false,true);
|
||||
super.onCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
// Tests from here use these parameters
|
||||
public static Stream<Arguments> tests()
|
||||
{
|
||||
List<Object[]> tests = new ArrayList<>();
|
||||
tests.add(new Object[]{new HelloWorldHandler(), 200, "Hello world"});
|
||||
tests.add(new Object[]{new SendErrorHandler(499,"Test async sendError"), 499, "Test async sendError"});
|
||||
return tests.stream().map(Arguments::of);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("tests")
|
||||
public void testAsyncCompletion(Handler handler, int status, String message) throws Exception
|
||||
{
|
||||
configureServer(handler);
|
||||
|
||||
int base = _threadPool.getBusyThreads();
|
||||
try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
|
||||
{
|
||||
OutputStream os = client.getOutputStream();
|
||||
|
||||
// write the request
|
||||
os.write("GET / HTTP/1.0\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1));
|
||||
os.flush();
|
||||
|
||||
// The write should happen but the callback is delayed
|
||||
HttpTester.Response response = HttpTester.parseResponse(client.getInputStream());
|
||||
assertThat(response, Matchers.notNullValue());
|
||||
assertThat(response.getStatus(), is(status));
|
||||
String content = response.getContent();
|
||||
assertThat(content, containsString(message));
|
||||
|
||||
// Check that a thread is held busy in write
|
||||
assertThat(_threadPool.getBusyThreads(), Matchers.greaterThan(base));
|
||||
|
||||
// Getting the Delayed callback will free the thread
|
||||
DelayedCallback delay = X.exchange(null, 10, TimeUnit.SECONDS);
|
||||
|
||||
// wait for threads to return to base level
|
||||
long end = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
|
||||
while(_threadPool.getBusyThreads() != base)
|
||||
{
|
||||
if (System.nanoTime() > end)
|
||||
throw new TimeoutException();
|
||||
Thread.sleep(10);
|
||||
}
|
||||
|
||||
// We are now asynchronously waiting!
|
||||
assertThat(COMPLETE.get(), is(false));
|
||||
|
||||
// proceed with the completion
|
||||
delay.proceed();
|
||||
|
||||
while(!COMPLETE.get())
|
||||
{
|
||||
if (System.nanoTime() > end)
|
||||
throw new TimeoutException();
|
||||
Thread.sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,7 +30,6 @@ import org.eclipse.jetty.http.HttpField;
|
|||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -53,43 +52,6 @@ public class ErrorHandlerTest
|
|||
server = new Server();
|
||||
connector = new LocalConnector(server);
|
||||
server.addConnector(connector);
|
||||
server.addBean(new ErrorHandler()
|
||||
{
|
||||
@Override
|
||||
protected void generateAcceptableResponse(
|
||||
Request baseRequest,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
int code,
|
||||
String message,
|
||||
String mimeType) throws IOException
|
||||
{
|
||||
switch (mimeType)
|
||||
{
|
||||
case "text/json":
|
||||
case "application/json":
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
response.setContentType(mimeType);
|
||||
response.getWriter()
|
||||
.append("{")
|
||||
.append("code: \"").append(Integer.toString(code)).append("\",")
|
||||
.append("message: \"").append(message).append('"')
|
||||
.append("}");
|
||||
break;
|
||||
}
|
||||
case "text/plain":
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
response.setContentType("text/plain");
|
||||
response.getOutputStream().print(response.getContentType());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
super.generateAcceptableResponse(baseRequest, request, response, code, message, mimeType);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
server.setHandler(new AbstractHandler()
|
||||
{
|
||||
|
|
|
@ -190,6 +190,10 @@ public class HttpInputAsyncStateTest
|
|||
__history.add("COMPLETE");
|
||||
break;
|
||||
|
||||
case READ_REGISTER:
|
||||
_state.getHttpChannel().onAsyncWaitForContent();
|
||||
break;
|
||||
|
||||
default:
|
||||
fail("Bad Action: " + action);
|
||||
}
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BrokenBarrierException;
|
||||
import java.util.concurrent.CyclicBarrier;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Stream;
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
//TODO: reset buffer tests
|
||||
//TODO: add protocol specific tests for connection: close and/or chunking
|
||||
|
||||
public class HttpManyWaysToAsyncCommitBadBehaviourTest extends AbstractHttpTest
|
||||
{
|
||||
private final String contextAttribute = getClass().getName() + ".asyncContext";
|
||||
|
||||
public static Stream<Arguments> httpVersions()
|
||||
{
|
||||
// boolean dispatch - if true we dispatch, otherwise we complete
|
||||
final boolean DISPATCH = true;
|
||||
final boolean COMPLETE = false;
|
||||
|
||||
List<Arguments> ret = new ArrayList<>();
|
||||
ret.add(Arguments.of(HttpVersion.HTTP_1_0, DISPATCH));
|
||||
ret.add(Arguments.of(HttpVersion.HTTP_1_0, COMPLETE));
|
||||
ret.add(Arguments.of(HttpVersion.HTTP_1_1, DISPATCH));
|
||||
ret.add(Arguments.of(HttpVersion.HTTP_1_1, COMPLETE));
|
||||
return ret.stream();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("httpVersions")
|
||||
public void testHandlerSetsHandledAndWritesSomeContent(HttpVersion httpVersion, boolean dispatch) throws Exception
|
||||
{
|
||||
server.setHandler(new SetHandledWriteSomeDataHandler(false, dispatch));
|
||||
server.start();
|
||||
|
||||
HttpTester.Response response = executeRequest(httpVersion);
|
||||
|
||||
assertThat("response code is 500", response.getStatus(), is(500));
|
||||
}
|
||||
|
||||
private class SetHandledWriteSomeDataHandler extends ThrowExceptionOnDemandHandler
|
||||
{
|
||||
private final boolean dispatch;
|
||||
|
||||
private SetHandledWriteSomeDataHandler(boolean throwException, boolean dispatch)
|
||||
{
|
||||
super(throwException);
|
||||
this.dispatch = dispatch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doNonErrorHandle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
final CyclicBarrier resumeBarrier = new CyclicBarrier(1);
|
||||
|
||||
if (baseRequest.getDispatcherType() == DispatcherType.ERROR)
|
||||
{
|
||||
response.sendError(500);
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.getAttribute(contextAttribute) == null)
|
||||
{
|
||||
final AsyncContext asyncContext = baseRequest.startAsync();
|
||||
new Thread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
asyncContext.getResponse().getWriter().write("foobar");
|
||||
if (dispatch)
|
||||
asyncContext.dispatch();
|
||||
else
|
||||
asyncContext.complete();
|
||||
resumeBarrier.await(5, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (IOException | TimeoutException | InterruptedException | BrokenBarrierException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).run();
|
||||
}
|
||||
try
|
||||
{
|
||||
resumeBarrier.await(5, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException | BrokenBarrierException | TimeoutException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
throw new TestCommitException();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -65,13 +65,22 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
|
||||
public abstract class HttpServerTestBase extends HttpServerTestFixture
|
||||
{
|
||||
private static final String REQUEST1_HEADER = "POST / HTTP/1.0\n" + "Host: localhost\n" + "Content-Type: text/xml; charset=utf-8\n" + "Connection: close\n" + "Content-Length: ";
|
||||
private static final String REQUEST1_HEADER = "POST / HTTP/1.0\n" +
|
||||
"Host: localhost\n" +
|
||||
"Content-Type: text/xml; charset=utf-8\n" +
|
||||
"Connection: close\n" +
|
||||
"Content-Length: ";
|
||||
private static final String REQUEST1_CONTENT = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
|
||||
"<nimbus xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" + " xsi:noNamespaceSchemaLocation=\"nimbus.xsd\" version=\"1.0\">\n" +
|
||||
"<nimbus xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
|
||||
" xsi:noNamespaceSchemaLocation=\"nimbus.xsd\" version=\"1.0\">\n" +
|
||||
"</nimbus>";
|
||||
private static final String REQUEST1 = REQUEST1_HEADER + REQUEST1_CONTENT.getBytes().length + "\n\n" + REQUEST1_CONTENT;
|
||||
|
||||
private static final String RESPONSE1 = "HTTP/1.1 200 OK\n" + "Content-Length: 13\n" + "Server: Jetty(" + Server.getVersion() + ")\n" + "\n" + "Hello world\n";
|
||||
private static final String RESPONSE1 = "HTTP/1.1 200 OK\n" +
|
||||
"Content-Length: 13\n" +
|
||||
"Server: Jetty(" + Server.getVersion() + ")\n" +
|
||||
"\n" +
|
||||
"Hello world\n";
|
||||
|
||||
// Break the request up into three pieces, splitting the header.
|
||||
private static final String FRAGMENT1 = REQUEST1.substring(0, 16);
|
||||
|
@ -154,7 +163,12 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
assertThat(response, Matchers.containsString("HTTP/1.1 200 OK"));
|
||||
assertThat(response, Matchers.containsString("Allow: GET"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGETStar() throws Exception
|
||||
{
|
||||
configureServer(new OptionsHandler());
|
||||
try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
|
||||
{
|
||||
OutputStream os = client.getOutputStream();
|
||||
|
@ -434,27 +448,22 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
|
||||
{
|
||||
OutputStream os = client.getOutputStream();
|
||||
//@checkstyle-disable-check : IllegalTokenText
|
||||
os.write(("GET /R2 HTTP/1.1\015\012" +
|
||||
"Host: localhost\015\012" +
|
||||
"Transfer-Encoding: chunked\015\012" +
|
||||
"Content-Type: text/plain\015\012" +
|
||||
"Connection: close\015\012" +
|
||||
"\015\012").getBytes());
|
||||
//@checkstyle-enable-check : IllegalTokenText
|
||||
|
||||
os.write(("GET /R2 HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Transfer-Encoding: chunked\r\n" +
|
||||
"Content-Type: text/plain\r\n" +
|
||||
"Connection: close\r\n" +
|
||||
"\r\n").getBytes());
|
||||
os.flush();
|
||||
Thread.sleep(1000);
|
||||
os.write(("5").getBytes());
|
||||
Thread.sleep(1000);
|
||||
//@checkstyle-disable-check : IllegalTokenText
|
||||
os.write(("\015\012").getBytes());
|
||||
//@checkstyle-enable-check : IllegalTokenText
|
||||
os.write(("\r\n").getBytes());
|
||||
os.flush();
|
||||
Thread.sleep(1000);
|
||||
//@checkstyle-disable-check : IllegalTokenText
|
||||
os.write(("ABCDE\015\012" +
|
||||
"0;\015\012\015\012").getBytes());
|
||||
//@checkstyle-enable-check : IllegalTokenText
|
||||
os.write(("ABCDE\r\n" +
|
||||
"0;\r\n\r\n").getBytes());
|
||||
os.flush();
|
||||
|
||||
// Read the response.
|
||||
|
@ -472,14 +481,14 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
{
|
||||
OutputStream os = client.getOutputStream();
|
||||
//@checkstyle-disable-check : IllegalTokenText
|
||||
os.write(("GET /R2 HTTP/1.1\015\012" +
|
||||
"Host: localhost\015\012" +
|
||||
"Content-Length: 5\015\012" +
|
||||
"Content-Type: text/plain\015\012" +
|
||||
"Connection: close\015\012" +
|
||||
"\015\012" +
|
||||
"ABCDE\015\012" +
|
||||
"\015\012"
|
||||
os.write(("GET /R2 HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Content-Length: 5\r\n" +
|
||||
"Content-Type: text/plain\r\n" +
|
||||
"Connection: close\r\n" +
|
||||
"\r\n" +
|
||||
"ABCDE\r\n" +
|
||||
"\r\n"
|
||||
//@checkstyle-enable-check : IllegalTokenText
|
||||
).getBytes());
|
||||
os.flush();
|
||||
|
@ -1136,26 +1145,26 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
|
||||
//@checkstyle-disable-check : IllegalTokenText
|
||||
os.write((
|
||||
"POST /R1 HTTP/1.1\015\012" +
|
||||
"POST /R1 HTTP/1.1\r\n" +
|
||||
"Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
|
||||
"content-type: text/plain; charset=utf-8\r\n" +
|
||||
"content-length: 10\r\n" +
|
||||
"\015\012" +
|
||||
"\r\n" +
|
||||
"123456789\n" +
|
||||
|
||||
"HEAD /R2 HTTP/1.1\015\012" +
|
||||
"Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\015\012" +
|
||||
"HEAD /R2 HTTP/1.1\r\n" +
|
||||
"Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
|
||||
"content-type: text/plain; charset=utf-8\r\n" +
|
||||
"content-length: 10\r\n" +
|
||||
"\015\012" +
|
||||
"\r\n" +
|
||||
"ABCDEFGHI\n" +
|
||||
|
||||
"POST /R3 HTTP/1.1\015\012" +
|
||||
"Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\015\012" +
|
||||
"POST /R3 HTTP/1.1\r\n" +
|
||||
"Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
|
||||
"content-type: text/plain; charset=utf-8\r\n" +
|
||||
"content-length: 10\r\n" +
|
||||
"Connection: close\015\012" +
|
||||
"\015\012" +
|
||||
"Connection: close\r\n" +
|
||||
"\r\n" +
|
||||
"abcdefghi\n"
|
||||
//@checkstyle-enable-check : IllegalTokenText
|
||||
).getBytes(StandardCharsets.ISO_8859_1));
|
||||
|
@ -1553,8 +1562,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
{
|
||||
try
|
||||
{
|
||||
byte[] bytes = (
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
byte[] bytes = ("GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Content-Length: " + cl + "\r\n" +
|
||||
"\r\n" +
|
||||
|
|
|
@ -40,7 +40,8 @@ import org.junit.jupiter.api.AfterEach;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
public class HttpServerTestFixture
|
||||
{ // Useful constants
|
||||
{
|
||||
// Useful constants
|
||||
protected static final long PAUSE = 10L;
|
||||
protected static final int LOOPS = 50;
|
||||
|
||||
|
@ -186,6 +187,31 @@ public class HttpServerTestFixture
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
protected static class SendErrorHandler extends AbstractHandler
|
||||
{
|
||||
private final int code;
|
||||
private final String message;
|
||||
|
||||
public SendErrorHandler()
|
||||
{
|
||||
this(500, null);
|
||||
}
|
||||
|
||||
public SendErrorHandler(int code, String message)
|
||||
{
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
response.sendError(code, message);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class ReadExactHandler extends AbstractHandler.ErrorDispatchHandler
|
||||
{
|
||||
private int expected;
|
||||
|
|
|
@ -32,6 +32,8 @@ import javax.servlet.http.HttpServletResponse;
|
|||
|
||||
import org.eclipse.jetty.server.handler.HandlerWrapper;
|
||||
import org.eclipse.jetty.server.session.SessionHandler;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -42,6 +44,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
|
||||
public class LocalAsyncContextTest
|
||||
{
|
||||
public static final Logger LOG = Log.getLogger(LocalAsyncContextTest.class);
|
||||
protected Server _server;
|
||||
protected SuspendHandler _handler;
|
||||
protected Connector _connector;
|
||||
|
@ -232,6 +235,7 @@ public class LocalAsyncContextTest
|
|||
|
||||
private synchronized String process(String content) throws Exception
|
||||
{
|
||||
LOG.debug("TEST process: {}", content);
|
||||
reset();
|
||||
String request = "GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
|
@ -305,6 +309,7 @@ public class LocalAsyncContextTest
|
|||
@Override
|
||||
public void handle(String target, final Request baseRequest, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
LOG.debug("handle {} {}", baseRequest.getDispatcherType(), baseRequest);
|
||||
if (DispatcherType.REQUEST.equals(baseRequest.getDispatcherType()))
|
||||
{
|
||||
if (_read > 0)
|
||||
|
|
|
@ -35,6 +35,8 @@ import java.util.Enumeration;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Stream;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.Cookie;
|
||||
|
@ -70,6 +72,8 @@ import org.hamcrest.Matchers;
|
|||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
@ -664,65 +668,41 @@ public class ResponseTest
|
|||
assertEquals("foo/bar; other=pq charset=utf-8 other=xyz;charset=utf-16", response.getContentType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatusCodes() throws Exception
|
||||
public static Stream<Object[]> sendErrorTestCodes()
|
||||
{
|
||||
Response response = getResponse();
|
||||
|
||||
response.sendError(404);
|
||||
assertEquals(404, response.getStatus());
|
||||
assertEquals("Not Found", response.getReason());
|
||||
|
||||
response = getResponse();
|
||||
|
||||
response.sendError(500, "Database Error");
|
||||
assertEquals(500, response.getStatus());
|
||||
assertEquals("Database Error", response.getReason());
|
||||
assertEquals("must-revalidate,no-cache,no-store", response.getHeader(HttpHeader.CACHE_CONTROL.asString()));
|
||||
|
||||
response = getResponse();
|
||||
|
||||
response.setStatus(200);
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals(null, response.getReason());
|
||||
|
||||
response = getResponse();
|
||||
|
||||
response.sendError(406, "Super Nanny");
|
||||
assertEquals(406, response.getStatus());
|
||||
assertEquals("Super Nanny", response.getReason());
|
||||
assertEquals("must-revalidate,no-cache,no-store", response.getHeader(HttpHeader.CACHE_CONTROL.asString()));
|
||||
List<Object[]> data = new ArrayList<>();
|
||||
data.add(new Object[]{404, null, "Not Found"});
|
||||
data.add(new Object[]{500, "Database Error", "Database Error"});
|
||||
data.add(new Object[]{406, "Super Nanny", "Super Nanny"});
|
||||
return data.stream();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatusCodesNoErrorHandler() throws Exception
|
||||
@ParameterizedTest
|
||||
@MethodSource(value = "sendErrorTestCodes")
|
||||
public void testStatusCodes(int code, String message, String expectedMessage) throws Exception
|
||||
{
|
||||
Response response = getResponse();
|
||||
assertThat(response.getHttpChannel().getState().handling(), is(HttpChannelState.Action.DISPATCH));
|
||||
|
||||
if (message == null)
|
||||
response.sendError(code);
|
||||
else
|
||||
response.sendError(code, message);
|
||||
|
||||
assertTrue(response.getHttpOutput().isClosed());
|
||||
assertEquals(code, response.getStatus());
|
||||
assertEquals(null, response.getReason());
|
||||
assertEquals(expectedMessage, response.getHttpChannel().getRequest().getAttribute(RequestDispatcher.ERROR_MESSAGE));
|
||||
assertThat(response.getHttpChannel().getState().unhandle(), is(HttpChannelState.Action.SEND_ERROR));
|
||||
assertThat(response.getHttpChannel().getState().unhandle(), is(HttpChannelState.Action.COMPLETE));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource(value = "sendErrorTestCodes")
|
||||
public void testStatusCodesNoErrorHandler(int code, String message, String expectedMessage) throws Exception
|
||||
{
|
||||
_server.removeBean(_server.getBean(ErrorHandler.class));
|
||||
Response response = getResponse();
|
||||
|
||||
response.sendError(404);
|
||||
assertEquals(404, response.getStatus());
|
||||
assertEquals("Not Found", response.getReason());
|
||||
|
||||
response = getResponse();
|
||||
|
||||
response.sendError(500, "Database Error");
|
||||
assertEquals(500, response.getStatus());
|
||||
assertEquals("Database Error", response.getReason());
|
||||
assertThat(response.getHeader(HttpHeader.CACHE_CONTROL.asString()), Matchers.nullValue());
|
||||
|
||||
response = getResponse();
|
||||
|
||||
response.setStatus(200);
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals(null, response.getReason());
|
||||
|
||||
response = getResponse();
|
||||
|
||||
response.sendError(406, "Super Nanny");
|
||||
assertEquals(406, response.getStatus());
|
||||
assertEquals("Super Nanny", response.getReason());
|
||||
assertThat(response.getHeader(HttpHeader.CACHE_CONTROL.asString()), Matchers.nullValue());
|
||||
testStatusCodes(code, message, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -898,7 +878,7 @@ public class ResponseTest
|
|||
assertTrue(!response.isCommitted());
|
||||
assertTrue(!writer.checkError());
|
||||
writer.print("");
|
||||
assertTrue(!writer.checkError());
|
||||
// assertTrue(!writer.checkError()); // TODO check if this is correct? checkout does an open check and the print above closes
|
||||
assertTrue(response.isCommitted());
|
||||
}
|
||||
|
||||
|
@ -1032,7 +1012,7 @@ public class ResponseTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testCookiesWithReset() throws Exception
|
||||
public void testResetContent() throws Exception
|
||||
{
|
||||
Response response = getResponse();
|
||||
|
||||
|
@ -1048,9 +1028,27 @@ public class ResponseTest
|
|||
cookie2.setPath("/path");
|
||||
response.addCookie(cookie2);
|
||||
|
||||
//keep the cookies
|
||||
response.reset(true);
|
||||
response.setContentType("some/type");
|
||||
response.setContentLength(3);
|
||||
response.setHeader(HttpHeader.EXPIRES,"never");
|
||||
|
||||
response.setHeader("SomeHeader", "SomeValue");
|
||||
|
||||
response.getOutputStream();
|
||||
|
||||
// reset the content
|
||||
response.resetContent();
|
||||
|
||||
// check content is nulled
|
||||
assertThat(response.getContentType(), nullValue());
|
||||
assertThat(response.getContentLength(), is(-1L));
|
||||
assertThat(response.getHeader(HttpHeader.EXPIRES.asString()), nullValue());
|
||||
response.getWriter();
|
||||
|
||||
// check arbitrary header still set
|
||||
assertThat(response.getHeader("SomeHeader"), is("SomeValue"));
|
||||
|
||||
// check cookies are still there
|
||||
Enumeration<String> set = response.getHttpFields().getValues("Set-Cookie");
|
||||
|
||||
assertNotNull(set);
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.eclipse.jetty.server.AbstractNCSARequestLog;
|
|||
import org.eclipse.jetty.server.CustomRequestLog;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpChannel;
|
||||
import org.eclipse.jetty.server.HttpChannelState;
|
||||
import org.eclipse.jetty.server.LocalConnector;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.RequestLog;
|
||||
|
@ -44,6 +45,7 @@ import org.eclipse.jetty.server.Response;
|
|||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.StacklessLogging;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
@ -390,7 +392,7 @@ public class NcsaRequestLogTest
|
|||
data.add(new Object[]{logType, new IOExceptionPartialHandler(), "/ioex", "\"GET /ioex HTTP/1.0\" 200"});
|
||||
data.add(new Object[]{logType, new RuntimeExceptionHandler(), "/rtex", "\"GET /rtex HTTP/1.0\" 500"});
|
||||
data.add(new Object[]{logType, new BadMessageHandler(), "/bad", "\"GET /bad HTTP/1.0\" 499"});
|
||||
data.add(new Object[]{logType, new AbortHandler(), "/bad", "\"GET /bad HTTP/1.0\" 488"});
|
||||
data.add(new Object[]{logType, new AbortHandler(), "/bad", "\"GET /bad HTTP/1.0\" 500"});
|
||||
data.add(new Object[]{logType, new AbortPartialHandler(), "/bad", "\"GET /bad HTTP/1.0\" 200"});
|
||||
}
|
||||
|
||||
|
@ -517,7 +519,9 @@ public class NcsaRequestLogTest
|
|||
startServer();
|
||||
makeRequest(requestPath);
|
||||
|
||||
expectedLogEntry = "\"GET " + requestPath + " HTTP/1.0\" 200";
|
||||
// If we abort, we can't write a 200 error page
|
||||
if (!(testHandler instanceof AbortHandler))
|
||||
expectedLogEntry = expectedLogEntry.replaceFirst(" [1-9][0-9][0-9]", " 200");
|
||||
assertRequestLog(expectedLogEntry, _log);
|
||||
}
|
||||
|
||||
|
@ -577,6 +581,10 @@ public class NcsaRequestLogTest
|
|||
{
|
||||
try
|
||||
{
|
||||
while (baseRequest.getHttpChannel().getState().getState() != HttpChannelState.State.WAITING)
|
||||
{
|
||||
Thread.sleep(10);
|
||||
}
|
||||
baseRequest.setHandled(false);
|
||||
testHandler.handle(target, baseRequest, request, response);
|
||||
if (!baseRequest.isHandled())
|
||||
|
@ -584,18 +592,21 @@ public class NcsaRequestLogTest
|
|||
}
|
||||
catch (BadMessageException bad)
|
||||
{
|
||||
response.sendError(bad.getCode());
|
||||
response.sendError(bad.getCode(), bad.getReason());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
response.sendError(500);
|
||||
response.sendError(500, e.toString());
|
||||
}
|
||||
}
|
||||
catch (Throwable th)
|
||||
catch (IOException | IllegalStateException th)
|
||||
{
|
||||
throw new RuntimeException(th);
|
||||
Log.getLog().ignore(th);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ac.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -266,6 +266,7 @@ public class SecuredRedirectHandlerTest
|
|||
{
|
||||
if (!"/".equals(target))
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
response.sendError(404);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ package org.eclipse.jetty.server.ssl;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
@ -42,6 +41,7 @@ import javax.net.ssl.SSLSocketFactory;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
|
@ -55,8 +55,8 @@ import org.eclipse.jetty.server.ServerConnector;
|
|||
import org.eclipse.jetty.server.SocketCustomizationListener;
|
||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
@ -65,9 +65,8 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class SniSslConnectionFactoryTest
|
||||
{
|
||||
|
@ -81,23 +80,23 @@ public class SniSslConnectionFactoryTest
|
|||
{
|
||||
_server = new Server();
|
||||
|
||||
HttpConfiguration http_config = new HttpConfiguration();
|
||||
http_config.setSecureScheme("https");
|
||||
http_config.setSecurePort(8443);
|
||||
http_config.setOutputBufferSize(32768);
|
||||
_httpsConfiguration = new HttpConfiguration(http_config);
|
||||
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||
httpConfig.setSecureScheme("https");
|
||||
httpConfig.setSecurePort(8443);
|
||||
httpConfig.setOutputBufferSize(32768);
|
||||
_httpsConfiguration = new HttpConfiguration(httpConfig);
|
||||
SecureRequestCustomizer src = new SecureRequestCustomizer();
|
||||
src.setSniHostCheck(true);
|
||||
_httpsConfiguration.addCustomizer(src);
|
||||
_httpsConfiguration.addCustomizer((connector, httpConfig, request) ->
|
||||
_httpsConfiguration.addCustomizer((connector, hc, request) ->
|
||||
{
|
||||
EndPoint endp = request.getHttpChannel().getEndPoint();
|
||||
if (endp instanceof SslConnection.DecryptedEndPoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
SslConnection.DecryptedEndPoint ssl_endp = (SslConnection.DecryptedEndPoint)endp;
|
||||
SslConnection sslConnection = ssl_endp.getSslConnection();
|
||||
SslConnection.DecryptedEndPoint sslEndp = (SslConnection.DecryptedEndPoint)endp;
|
||||
SslConnection sslConnection = sslEndp.getSslConnection();
|
||||
SSLEngine sslEngine = sslConnection.getSSLEngine();
|
||||
SSLSession session = sslEngine.getSession();
|
||||
for (Certificate c : session.getLocalCertificates())
|
||||
|
@ -224,6 +223,7 @@ public class SniSslConnectionFactoryTest
|
|||
public void testSameConnectionRequestsForManyDomains() throws Exception
|
||||
{
|
||||
start("src/test/resources/keystore_sni.p12");
|
||||
_server.setErrorHandler(new ErrorHandler());
|
||||
|
||||
SslContextFactory clientContextFactory = new SslContextFactory.Client(true);
|
||||
clientContextFactory.start();
|
||||
|
@ -246,8 +246,8 @@ public class SniSslConnectionFactoryTest
|
|||
output.flush();
|
||||
|
||||
InputStream input = sslSocket.getInputStream();
|
||||
String response = response(input);
|
||||
assertTrue(response.startsWith("HTTP/1.1 200 "));
|
||||
HttpTester.Response response = HttpTester.parseResponse(input);
|
||||
assertThat(response.getStatus(), is(200));
|
||||
|
||||
// Same socket, send a request for a different domain but same alias.
|
||||
request =
|
||||
|
@ -256,9 +256,8 @@ public class SniSslConnectionFactoryTest
|
|||
"\r\n";
|
||||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
response = response(input);
|
||||
assertTrue(response.startsWith("HTTP/1.1 200 "));
|
||||
response = HttpTester.parseResponse(input);
|
||||
assertThat(response.getStatus(), is(200));
|
||||
|
||||
// Same socket, send a request for a different domain but different alias.
|
||||
request =
|
||||
|
@ -268,9 +267,9 @@ public class SniSslConnectionFactoryTest
|
|||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
response = response(input);
|
||||
assertThat(response, startsWith("HTTP/1.1 400 "));
|
||||
assertThat(response, containsString("Host does not match SNI"));
|
||||
response = HttpTester.parseResponse(input);
|
||||
assertThat(response.getStatus(), is(400));
|
||||
assertThat(response.getContent(), containsString("Host does not match SNI"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -303,8 +302,8 @@ public class SniSslConnectionFactoryTest
|
|||
output.flush();
|
||||
|
||||
InputStream input = sslSocket.getInputStream();
|
||||
String response = response(input);
|
||||
assertTrue(response.startsWith("HTTP/1.1 200 "));
|
||||
HttpTester.Response response = HttpTester.parseResponse(input);
|
||||
assertThat(response.getStatus(), is(200));
|
||||
|
||||
// Now, on the same socket, send a request for a different valid domain.
|
||||
request =
|
||||
|
@ -314,8 +313,8 @@ public class SniSslConnectionFactoryTest
|
|||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
response = response(input);
|
||||
assertTrue(response.startsWith("HTTP/1.1 200 "));
|
||||
response = HttpTester.parseResponse(input);
|
||||
assertThat(response.getStatus(), is(200));
|
||||
|
||||
// Now make a request for an invalid domain for this connection.
|
||||
request =
|
||||
|
@ -325,9 +324,9 @@ public class SniSslConnectionFactoryTest
|
|||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
response = response(input);
|
||||
assertTrue(response.startsWith("HTTP/1.1 400 "));
|
||||
assertThat(response, Matchers.containsString("Host does not match SNI"));
|
||||
response = HttpTester.parseResponse(input);
|
||||
assertThat(response.getStatus(), is(400));
|
||||
assertThat(response.getContent(), containsString("Host does not match SNI"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -335,22 +334,6 @@ public class SniSslConnectionFactoryTest
|
|||
}
|
||||
}
|
||||
|
||||
private String response(InputStream input) throws IOException
|
||||
{
|
||||
Utf8StringBuilder buffer = new Utf8StringBuilder();
|
||||
int crlfs = 0;
|
||||
while (true)
|
||||
{
|
||||
int read = input.read();
|
||||
assertTrue(read >= 0);
|
||||
buffer.append((byte)read);
|
||||
crlfs = (read == '\r' || read == '\n') ? crlfs + 1 : 0;
|
||||
if (crlfs == 4)
|
||||
break;
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
private String getResponse(String host, String cn) throws Exception
|
||||
{
|
||||
String response = getResponse(host, host, cn);
|
||||
|
|
|
@ -50,6 +50,7 @@ import org.junit.jupiter.api.Test;
|
|||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
|
@ -478,7 +479,7 @@ public class AsyncContextTest
|
|||
|
||||
assertThat("error servlet", responseBody, containsString("ERROR: /error"));
|
||||
assertThat("error servlet", responseBody, containsString("PathInfo= /500"));
|
||||
assertThat("error servlet", responseBody, containsString("EXCEPTION: java.lang.RuntimeException: TEST"));
|
||||
assertThat("error servlet", responseBody, not(containsString("EXCEPTION: ")));
|
||||
}
|
||||
|
||||
private class DispatchingRunnable implements Runnable
|
||||
|
@ -552,7 +553,7 @@ public class AsyncContextTest
|
|||
@Override
|
||||
public void onTimeout(AsyncEvent event) throws IOException
|
||||
{
|
||||
throw new RuntimeException("TEST");
|
||||
throw new RuntimeException("BAD EXPIRE");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.io.QuietException;
|
||||
import org.eclipse.jetty.server.LocalConnector;
|
||||
import org.eclipse.jetty.server.QuietServletException;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
|
@ -42,6 +43,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class AsyncListenerTest
|
||||
|
@ -140,7 +142,7 @@ public class AsyncListenerTest
|
|||
test_StartAsync_Throw_OnError(event ->
|
||||
{
|
||||
HttpServletResponse response = (HttpServletResponse)event.getAsyncContext().getResponse();
|
||||
response.sendError(HttpStatus.BAD_GATEWAY_502);
|
||||
response.sendError(HttpStatus.BAD_GATEWAY_502, "Message!!!");
|
||||
});
|
||||
String httpResponse = connector.getResponse(
|
||||
"GET /ctx/path HTTP/1.1\r\n" +
|
||||
|
@ -148,7 +150,8 @@ public class AsyncListenerTest
|
|||
"Connection: close\r\n" +
|
||||
"\r\n");
|
||||
assertThat(httpResponse, containsString("HTTP/1.1 502 "));
|
||||
assertThat(httpResponse, containsString(TestRuntimeException.class.getName()));
|
||||
assertThat(httpResponse, containsString("Message!!!"));
|
||||
assertThat(httpResponse, not(containsString(TestRuntimeException.class.getName())));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -191,7 +194,7 @@ public class AsyncListenerTest
|
|||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
AsyncContext asyncContext = request.startAsync();
|
||||
asyncContext.setTimeout(0);
|
||||
asyncContext.setTimeout(10000);
|
||||
asyncContext.addListener(new AsyncListenerAdapter()
|
||||
{
|
||||
@Override
|
||||
|
@ -268,7 +271,8 @@ public class AsyncListenerTest
|
|||
"Connection: close\r\n" +
|
||||
"\r\n");
|
||||
assertThat(httpResponse, containsString("HTTP/1.1 500 "));
|
||||
assertThat(httpResponse, containsString(TestRuntimeException.class.getName()));
|
||||
assertThat(httpResponse, containsString("AsyncContext timeout"));
|
||||
assertThat(httpResponse, not(containsString(TestRuntimeException.class.getName())));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -292,6 +296,7 @@ public class AsyncListenerTest
|
|||
{
|
||||
HttpServletResponse response = (HttpServletResponse)event.getAsyncContext().getResponse();
|
||||
response.sendError(HttpStatus.BAD_GATEWAY_502);
|
||||
event.getAsyncContext().complete();
|
||||
});
|
||||
String httpResponse = connector.getResponse(
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
|
@ -384,7 +389,7 @@ public class AsyncListenerTest
|
|||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
AsyncContext asyncContext = request.startAsync();
|
||||
asyncContext.setTimeout(0);
|
||||
asyncContext.setTimeout(10000);
|
||||
asyncContext.addListener(new AsyncListenerAdapter()
|
||||
{
|
||||
@Override
|
||||
|
@ -447,7 +452,7 @@ public class AsyncListenerTest
|
|||
}
|
||||
|
||||
// Unique named RuntimeException to help during debugging / assertions.
|
||||
public static class TestRuntimeException extends RuntimeException
|
||||
public static class TestRuntimeException extends RuntimeException implements QuietException
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ import org.eclipse.jetty.server.HttpConfiguration;
|
|||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
|
@ -305,6 +306,7 @@ public class AsyncServletIOTest
|
|||
request.append(s).append("w=").append(w);
|
||||
s = '&';
|
||||
}
|
||||
LOG.debug("process {} {}", request.toString(), BufferUtil.toDetailString(BufferUtil.toBuffer(content)));
|
||||
|
||||
request.append(" HTTP/1.1\r\n")
|
||||
.append("Host: localhost\r\n")
|
||||
|
@ -816,13 +818,15 @@ public class AsyncServletIOTest
|
|||
// wait until server is ready
|
||||
_servletStolenAsyncRead.ready.await();
|
||||
final CountDownLatch wait = new CountDownLatch(1);
|
||||
|
||||
final CountDownLatch held = new CountDownLatch(1);
|
||||
// Stop any dispatches until we want them
|
||||
|
||||
UnaryOperator<Runnable> old = _wQTP.wrapper.getAndSet(r ->
|
||||
() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
held.countDown();
|
||||
wait.await();
|
||||
r.run();
|
||||
}
|
||||
|
@ -836,7 +840,9 @@ public class AsyncServletIOTest
|
|||
// We are an unrelated thread, let's mess with the input stream
|
||||
ServletInputStream sin = _servletStolenAsyncRead.listener.in;
|
||||
sin.setReadListener(_servletStolenAsyncRead.listener);
|
||||
|
||||
// thread should be dispatched to handle, but held by our wQTP wait.
|
||||
assertTrue(held.await(10, TimeUnit.SECONDS));
|
||||
|
||||
// Let's steal our read
|
||||
assertTrue(sin.isReady());
|
||||
|
|
|
@ -265,7 +265,6 @@ public class AsyncServletTest
|
|||
"start",
|
||||
"onTimeout",
|
||||
"error",
|
||||
"onError",
|
||||
"ERROR /ctx/error/custom",
|
||||
"!initial",
|
||||
"onComplete"));
|
||||
|
@ -273,44 +272,6 @@ public class AsyncServletTest
|
|||
assertContains("ERROR DISPATCH", response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartOnTimeoutErrorComplete() throws Exception
|
||||
{
|
||||
String response = process("start=200&timeout=error&error=complete", null);
|
||||
assertThat(response, startsWith("HTTP/1.1 200 OK"));
|
||||
assertThat(__history, contains(
|
||||
"REQUEST /ctx/path/info",
|
||||
"initial",
|
||||
"start",
|
||||
"onTimeout",
|
||||
"error",
|
||||
"onError",
|
||||
"complete",
|
||||
"onComplete"));
|
||||
|
||||
assertContains("COMPLETED", response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartOnTimeoutErrorDispatch() throws Exception
|
||||
{
|
||||
String response = process("start=200&timeout=error&error=dispatch", null);
|
||||
assertThat(response, startsWith("HTTP/1.1 200 OK"));
|
||||
assertThat(__history, contains(
|
||||
"REQUEST /ctx/path/info",
|
||||
"initial",
|
||||
"start",
|
||||
"onTimeout",
|
||||
"error",
|
||||
"onError",
|
||||
"dispatch",
|
||||
"ASYNC /ctx/path/info",
|
||||
"!initial",
|
||||
"onComplete"));
|
||||
|
||||
assertContains("DISPATCHED", response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartOnTimeoutComplete() throws Exception
|
||||
{
|
||||
|
@ -526,8 +487,10 @@ public class AsyncServletTest
|
|||
"onStartAsync",
|
||||
"start",
|
||||
"onTimeout",
|
||||
"ERROR /ctx/path/error",
|
||||
"!initial",
|
||||
"onComplete")); // Error Page Loop!
|
||||
assertContains("HTTP ERROR 500", response);
|
||||
assertContains("AsyncContext timeout", response);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -85,7 +85,8 @@ public class CustomRequestLogTest
|
|||
|
||||
_connector.getResponse("GET /context/servlet/info HTTP/1.0\n\n");
|
||||
String log = _entries.poll(5, TimeUnit.SECONDS);
|
||||
assertThat(log, is("Filename: " + _tmpDir + File.separator + "servlet" + File.separator + "info"));
|
||||
String expected = new File(_tmpDir + File.separator + "servlet" + File.separator + "info").getCanonicalPath();
|
||||
assertThat(log, is("Filename: " + expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -20,8 +20,22 @@ package org.eclipse.jetty.servlet;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.EnumSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.Servlet;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -29,8 +43,12 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.server.Dispatcher;
|
||||
import org.eclipse.jetty.server.HttpChannel;
|
||||
import org.eclipse.jetty.server.HttpChannelState;
|
||||
import org.eclipse.jetty.server.LocalConnector;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.handler.HandlerWrapper;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.StacklessLogging;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
@ -40,43 +58,71 @@ import org.junit.jupiter.api.Test;
|
|||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class ErrorPageTest
|
||||
{
|
||||
private Server _server;
|
||||
private LocalConnector _connector;
|
||||
private StacklessLogging _stackless;
|
||||
private static CountDownLatch __asyncSendErrorCompleted;
|
||||
private ErrorPageErrorHandler _errorPageErrorHandler;
|
||||
|
||||
@BeforeEach
|
||||
public void init() throws Exception
|
||||
{
|
||||
_server = new Server();
|
||||
_connector = new LocalConnector(_server);
|
||||
_server.addConnector(_connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
|
||||
|
||||
_server.addConnector(_connector);
|
||||
_server.setHandler(context);
|
||||
|
||||
context.setContextPath("/");
|
||||
|
||||
context.addFilter(SingleDispatchFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
|
||||
|
||||
context.addServlet(DefaultServlet.class, "/");
|
||||
context.addServlet(FailServlet.class, "/fail/*");
|
||||
context.addServlet(FailClosedServlet.class, "/fail-closed/*");
|
||||
context.addServlet(ErrorServlet.class, "/error/*");
|
||||
context.addServlet(AppServlet.class, "/app/*");
|
||||
context.addServlet(LongerAppServlet.class, "/longer.app/*");
|
||||
context.addServlet(SyncSendErrorServlet.class, "/sync/*");
|
||||
context.addServlet(AsyncSendErrorServlet.class, "/async/*");
|
||||
context.addServlet(NotEnoughServlet.class, "/notenough/*");
|
||||
context.addServlet(DeleteServlet.class, "/delete/*");
|
||||
context.addServlet(ErrorAndStatusServlet.class, "/error-and-status/*");
|
||||
|
||||
ErrorPageErrorHandler error = new ErrorPageErrorHandler();
|
||||
context.setErrorHandler(error);
|
||||
error.addErrorPage(599, "/error/599");
|
||||
error.addErrorPage(400, "/error/400");
|
||||
HandlerWrapper noopHandler = new HandlerWrapper()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
if (target.startsWith("/noop"))
|
||||
return;
|
||||
else
|
||||
super.handle(target, baseRequest, request, response);
|
||||
}
|
||||
};
|
||||
context.insertHandler(noopHandler);
|
||||
|
||||
_errorPageErrorHandler = new ErrorPageErrorHandler();
|
||||
context.setErrorHandler(_errorPageErrorHandler);
|
||||
_errorPageErrorHandler.addErrorPage(595, "/error/595");
|
||||
_errorPageErrorHandler.addErrorPage(597, "/sync");
|
||||
_errorPageErrorHandler.addErrorPage(599, "/error/599");
|
||||
_errorPageErrorHandler.addErrorPage(400, "/error/400");
|
||||
// error.addErrorPage(500,"/error/500");
|
||||
error.addErrorPage(IllegalStateException.class.getCanonicalName(), "/error/TestException");
|
||||
error.addErrorPage(BadMessageException.class, "/error/BadMessageException");
|
||||
error.addErrorPage(ErrorPageErrorHandler.GLOBAL_ERROR_PAGE, "/error/GlobalErrorPage");
|
||||
_errorPageErrorHandler.addErrorPage(IllegalStateException.class.getCanonicalName(), "/error/TestException");
|
||||
_errorPageErrorHandler.addErrorPage(BadMessageException.class, "/error/BadMessageException");
|
||||
_errorPageErrorHandler.addErrorPage(ErrorPageErrorHandler.GLOBAL_ERROR_PAGE, "/error/GlobalErrorPage");
|
||||
|
||||
_server.start();
|
||||
_stackless = new StacklessLogging(ServletHandler.class);
|
||||
|
||||
__asyncSendErrorCompleted = new CountDownLatch(1);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
|
@ -87,6 +133,101 @@ public class ErrorPageTest
|
|||
_server.join();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testErrorOverridesStatus() throws Exception
|
||||
{
|
||||
String response = _connector.getResponse("GET /error-and-status/anything HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 594 594"));
|
||||
assertThat(response, Matchers.containsString("ERROR_PAGE: /GlobalErrorPage"));
|
||||
assertThat(response, Matchers.containsString("ERROR_MESSAGE: custom get error"));
|
||||
assertThat(response, Matchers.containsString("ERROR_CODE: 594"));
|
||||
assertThat(response, Matchers.containsString("ERROR_EXCEPTION: null"));
|
||||
assertThat(response, Matchers.containsString("ERROR_EXCEPTION_TYPE: null"));
|
||||
assertThat(response, Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$ErrorAndStatusServlet-"));
|
||||
assertThat(response, Matchers.containsString("ERROR_REQUEST_URI: /error-and-status/anything"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHttp204CannotHaveBody() throws Exception
|
||||
{
|
||||
String response = _connector.getResponse("GET /fail/code?code=204 HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 204 No Content"));
|
||||
assertThat(response, not(Matchers.containsString("DISPATCH: ")));
|
||||
assertThat(response, not(Matchers.containsString("ERROR_PAGE: ")));
|
||||
assertThat(response, not(Matchers.containsString("ERROR_CODE: ")));
|
||||
assertThat(response, not(Matchers.containsString("ERROR_EXCEPTION: ")));
|
||||
assertThat(response, not(Matchers.containsString("ERROR_EXCEPTION_TYPE: ")));
|
||||
assertThat(response, not(Matchers.containsString("ERROR_SERVLET: ")));
|
||||
assertThat(response, not(Matchers.containsString("ERROR_REQUEST_URI: ")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeleteCannotHaveBody() throws Exception
|
||||
{
|
||||
String response = _connector.getResponse("DELETE /delete/anything HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 595 595"));
|
||||
assertThat(response, not(Matchers.containsString("DISPATCH: ")));
|
||||
assertThat(response, not(Matchers.containsString("ERROR_PAGE: ")));
|
||||
assertThat(response, not(Matchers.containsString("ERROR_MESSAGE: ")));
|
||||
assertThat(response, not(Matchers.containsString("ERROR_CODE: ")));
|
||||
assertThat(response, not(Matchers.containsString("ERROR_EXCEPTION: ")));
|
||||
assertThat(response, not(Matchers.containsString("ERROR_EXCEPTION_TYPE: ")));
|
||||
assertThat(response, not(Matchers.containsString("ERROR_SERVLET: ")));
|
||||
assertThat(response, not(Matchers.containsString("ERROR_REQUEST_URI: ")));
|
||||
|
||||
assertThat(response, not(containsString("This shouldn't be seen")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateAcceptableResponse_noAcceptHeader() throws Exception
|
||||
{
|
||||
// no global error page here
|
||||
_errorPageErrorHandler.getErrorPages().remove(ErrorPageErrorHandler.GLOBAL_ERROR_PAGE);
|
||||
|
||||
String response = _connector.getResponse("GET /fail/code?code=598 HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 598 598"));
|
||||
assertThat(response, Matchers.containsString("<title>Error 598"));
|
||||
assertThat(response, Matchers.containsString("<h2>HTTP ERROR 598"));
|
||||
assertThat(response, Matchers.containsString("/fail/code"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateAcceptableResponse_htmlAcceptHeader() throws Exception
|
||||
{
|
||||
// no global error page here
|
||||
_errorPageErrorHandler.getErrorPages().remove(ErrorPageErrorHandler.GLOBAL_ERROR_PAGE);
|
||||
|
||||
// even when text/html is not the 1st content type, a html error page should still be generated
|
||||
String response = _connector.getResponse("GET /fail/code?code=598 HTTP/1.0\r\n" +
|
||||
"Accept: application/bytes,text/html\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 598 598"));
|
||||
assertThat(response, Matchers.containsString("<title>Error 598"));
|
||||
assertThat(response, Matchers.containsString("<h2>HTTP ERROR 598"));
|
||||
assertThat(response, Matchers.containsString("/fail/code"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateAcceptableResponse_noHtmlAcceptHeader() throws Exception
|
||||
{
|
||||
// no global error page here
|
||||
_errorPageErrorHandler.getErrorPages().remove(ErrorPageErrorHandler.GLOBAL_ERROR_PAGE);
|
||||
|
||||
String response = _connector.getResponse("GET /fail/code?code=598 HTTP/1.0\r\n" +
|
||||
"Accept: application/bytes\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 598 598"));
|
||||
assertThat(response, not(Matchers.containsString("<title>Error 598")));
|
||||
assertThat(response, not(Matchers.containsString("<h2>HTTP ERROR 598")));
|
||||
assertThat(response, not(Matchers.containsString("/fail/code")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNestedSendErrorDoesNotLoop() throws Exception
|
||||
{
|
||||
String response = _connector.getResponse("GET /fail/code?code=597 HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 597 597"));
|
||||
assertThat(response, not(Matchers.containsString("time this error page is being accessed")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendErrorClosedResponse() throws Exception
|
||||
{
|
||||
|
@ -167,7 +308,7 @@ public class ErrorPageTest
|
|||
try (StacklessLogging ignore = new StacklessLogging(Dispatcher.class))
|
||||
{
|
||||
String response = _connector.getResponse("GET /app?baa=%88%A4 HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 400 Bad query encoding"));
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 400 Bad Request"));
|
||||
assertThat(response, Matchers.containsString("ERROR_PAGE: /BadMessageException"));
|
||||
assertThat(response, Matchers.containsString("ERROR_MESSAGE: Bad query encoding"));
|
||||
assertThat(response, Matchers.containsString("ERROR_CODE: 400"));
|
||||
|
@ -179,6 +320,94 @@ public class ErrorPageTest
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncErrorPageDSC() throws Exception
|
||||
{
|
||||
try (StacklessLogging ignore = new StacklessLogging(Dispatcher.class))
|
||||
{
|
||||
String response = _connector.getResponse("GET /async/info?mode=DSC HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 599 599"));
|
||||
assertThat(response, Matchers.containsString("ERROR_PAGE: /599"));
|
||||
assertThat(response, Matchers.containsString("ERROR_CODE: 599"));
|
||||
assertThat(response, Matchers.containsString("ERROR_EXCEPTION: null"));
|
||||
assertThat(response, Matchers.containsString("ERROR_EXCEPTION_TYPE: null"));
|
||||
assertThat(response, Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$AsyncSendErrorServlet-"));
|
||||
assertThat(response, Matchers.containsString("ERROR_REQUEST_URI: /async/info"));
|
||||
assertTrue(__asyncSendErrorCompleted.await(10, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncErrorPageSDC() throws Exception
|
||||
{
|
||||
try (StacklessLogging ignore = new StacklessLogging(Dispatcher.class))
|
||||
{
|
||||
String response = _connector.getResponse("GET /async/info?mode=SDC HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 599 599"));
|
||||
assertThat(response, Matchers.containsString("ERROR_PAGE: /599"));
|
||||
assertThat(response, Matchers.containsString("ERROR_CODE: 599"));
|
||||
assertThat(response, Matchers.containsString("ERROR_EXCEPTION: null"));
|
||||
assertThat(response, Matchers.containsString("ERROR_EXCEPTION_TYPE: null"));
|
||||
assertThat(response, Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$AsyncSendErrorServlet-"));
|
||||
assertThat(response, Matchers.containsString("ERROR_REQUEST_URI: /async/info"));
|
||||
assertTrue(__asyncSendErrorCompleted.await(10, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncErrorPageSCD() throws Exception
|
||||
{
|
||||
try (StacklessLogging ignore = new StacklessLogging(Dispatcher.class))
|
||||
{
|
||||
String response = _connector.getResponse("GET /async/info?mode=SCD HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 599 599"));
|
||||
assertThat(response, Matchers.containsString("ERROR_PAGE: /599"));
|
||||
assertThat(response, Matchers.containsString("ERROR_CODE: 599"));
|
||||
assertThat(response, Matchers.containsString("ERROR_EXCEPTION: null"));
|
||||
assertThat(response, Matchers.containsString("ERROR_EXCEPTION_TYPE: null"));
|
||||
assertThat(response, Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$AsyncSendErrorServlet-"));
|
||||
assertThat(response, Matchers.containsString("ERROR_REQUEST_URI: /async/info"));
|
||||
assertTrue(__asyncSendErrorCompleted.await(10, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoop() throws Exception
|
||||
{
|
||||
String response = _connector.getResponse("GET /noop/info HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
|
||||
assertThat(response, Matchers.containsString("DISPATCH: ERROR"));
|
||||
assertThat(response, Matchers.containsString("ERROR_PAGE: /GlobalErrorPage"));
|
||||
assertThat(response, Matchers.containsString("ERROR_CODE: 404"));
|
||||
assertThat(response, Matchers.containsString("ERROR_EXCEPTION: null"));
|
||||
assertThat(response, Matchers.containsString("ERROR_EXCEPTION_TYPE: null"));
|
||||
assertThat(response, Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.DefaultServlet-"));
|
||||
assertThat(response, Matchers.containsString("ERROR_REQUEST_URI: /noop/info"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotEnough() throws Exception
|
||||
{
|
||||
String response = _connector.getResponse("GET /notenough/info HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 500 Server Error"));
|
||||
assertThat(response, Matchers.containsString("DISPATCH: ERROR"));
|
||||
assertThat(response, Matchers.containsString("ERROR_PAGE: /GlobalErrorPage"));
|
||||
assertThat(response, Matchers.containsString("ERROR_CODE: 500"));
|
||||
assertThat(response, Matchers.containsString("ERROR_EXCEPTION: null"));
|
||||
assertThat(response, Matchers.containsString("ERROR_EXCEPTION_TYPE: null"));
|
||||
assertThat(response, Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$NotEnoughServlet-"));
|
||||
assertThat(response, Matchers.containsString("ERROR_REQUEST_URI: /notenough/info"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotEnoughCommitted() throws Exception
|
||||
{
|
||||
String response = _connector.getResponse("GET /notenough/info?commit=true HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 200 OK"));
|
||||
assertThat(response, Matchers.containsString("Content-Length: 1000"));
|
||||
assertThat(response, Matchers.endsWith("SomeBytes"));
|
||||
}
|
||||
|
||||
public static class AppServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
@Override
|
||||
|
@ -198,6 +427,112 @@ public class ErrorPageTest
|
|||
}
|
||||
}
|
||||
|
||||
public static class SyncSendErrorServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
public static final AtomicInteger COUNTER = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
int count = COUNTER.incrementAndGet();
|
||||
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.println("this is the " + count + " time this error page is being accessed");
|
||||
response.sendError(597, "loop #" + count);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AsyncSendErrorServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
final CountDownLatch hold = new CountDownLatch(1);
|
||||
final String mode = request.getParameter("mode");
|
||||
switch(mode)
|
||||
{
|
||||
case "DSC":
|
||||
case "SDC":
|
||||
case "SCD":
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(mode);
|
||||
}
|
||||
|
||||
final boolean lateComplete = "true".equals(request.getParameter("latecomplete"));
|
||||
AsyncContext async = request.startAsync();
|
||||
async.start(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
switch(mode)
|
||||
{
|
||||
case "SDC":
|
||||
response.sendError(599);
|
||||
break;
|
||||
case "SCD":
|
||||
response.sendError(599);
|
||||
async.complete();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Complete after original servlet
|
||||
hold.countDown();
|
||||
|
||||
// Wait until request async waiting
|
||||
while (Request.getBaseRequest(request).getHttpChannelState().getState() == HttpChannelState.State.HANDLING)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(10);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case "DSC":
|
||||
response.sendError(599);
|
||||
async.complete();
|
||||
break;
|
||||
case "SDC":
|
||||
async.complete();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(IllegalStateException e)
|
||||
{
|
||||
Log.getLog().ignore(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
__asyncSendErrorCompleted.countDown();
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Log.getLog().warn(e);
|
||||
}
|
||||
});
|
||||
hold.await();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class FailServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
@Override
|
||||
|
@ -225,16 +560,51 @@ public class ErrorPageTest
|
|||
}
|
||||
catch (Throwable ignore)
|
||||
{
|
||||
// no opEchoSocket
|
||||
Log.getLog().ignore(ignore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ErrorAndStatusServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
response.sendError(594, "custom get error");
|
||||
response.setStatus(200);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DeleteServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
@Override
|
||||
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
response.getWriter().append("This shouldn't be seen");
|
||||
response.sendError(595, "custom delete");
|
||||
}
|
||||
}
|
||||
|
||||
public static class NotEnoughServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
response.setContentLength(1000);
|
||||
response.getOutputStream().write("SomeBytes".getBytes(StandardCharsets.UTF_8));
|
||||
if (Boolean.parseBoolean(request.getParameter("commit")))
|
||||
response.flushBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ErrorServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
if (request.getDispatcherType() != DispatcherType.ERROR && request.getDispatcherType() != DispatcherType.ASYNC)
|
||||
throw new IllegalStateException("Bad Dispatcher Type " + request.getDispatcherType());
|
||||
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.println("DISPATCH: " + request.getDispatcherType().name());
|
||||
writer.println("ERROR_PAGE: " + request.getPathInfo());
|
||||
|
@ -247,4 +617,55 @@ public class ErrorPageTest
|
|||
writer.println("getParameterMap()= " + request.getParameterMap());
|
||||
}
|
||||
}
|
||||
|
||||
public static class SingleDispatchFilter implements Filter
|
||||
{
|
||||
ConcurrentMap<Integer, Thread> dispatches = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
|
||||
{
|
||||
final Integer key = request.hashCode();
|
||||
Thread current = Thread.currentThread();
|
||||
final Thread existing = dispatches.putIfAbsent(key, current);
|
||||
if (existing != null && existing != current)
|
||||
{
|
||||
System.err.println("DOUBLE DISPATCH OF REQUEST!!!!!!!!!!!!!!!!!!");
|
||||
System.err.println("Thread " + existing + " :");
|
||||
for (StackTraceElement element : existing.getStackTrace())
|
||||
{
|
||||
System.err.println("\tat " + element);
|
||||
}
|
||||
IllegalStateException ex = new IllegalStateException();
|
||||
ex.printStackTrace();
|
||||
response.flushBuffer();
|
||||
throw ex;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (existing == null)
|
||||
{
|
||||
if (!dispatches.remove(key, current))
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -153,7 +153,10 @@ public class GzipHandlerTest
|
|||
response.setHeader("ETag", __contentETag);
|
||||
String ifnm = req.getHeader("If-None-Match");
|
||||
if (ifnm != null && ifnm.equals(__contentETag))
|
||||
response.sendError(304);
|
||||
{
|
||||
response.setStatus(304);
|
||||
response.flushBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintWriter writer = response.getWriter();
|
||||
|
|
|
@ -501,8 +501,17 @@ public class DoSFilter implements Filter
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Timing out {}", request);
|
||||
try
|
||||
{
|
||||
response.sendError(HttpStatus.SERVICE_UNAVAILABLE_503);
|
||||
}
|
||||
catch (IllegalStateException ise)
|
||||
{
|
||||
LOG.ignore(ise);
|
||||
// abort instead
|
||||
response.sendError(-1);
|
||||
}
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.info(x);
|
||||
|
|
|
@ -440,6 +440,7 @@ public class BufferUtil
|
|||
*
|
||||
* @param to Buffer is flush mode
|
||||
* @param b byte to append
|
||||
* @throws BufferOverflowException if unable to append buffer due to space limits
|
||||
*/
|
||||
public static void append(ByteBuffer to, byte b)
|
||||
{
|
||||
|
@ -1103,20 +1104,20 @@ public class BufferUtil
|
|||
for (int i = 0; i < buffer.position(); i++)
|
||||
{
|
||||
appendContentChar(buf, buffer.get(i));
|
||||
if (i == 16 && buffer.position() > 32)
|
||||
if (i == 8 && buffer.position() > 16)
|
||||
{
|
||||
buf.append("...");
|
||||
i = buffer.position() - 16;
|
||||
i = buffer.position() - 8;
|
||||
}
|
||||
}
|
||||
buf.append("<<<");
|
||||
for (int i = buffer.position(); i < buffer.limit(); i++)
|
||||
{
|
||||
appendContentChar(buf, buffer.get(i));
|
||||
if (i == buffer.position() + 16 && buffer.limit() > buffer.position() + 32)
|
||||
if (i == buffer.position() + 24 && buffer.limit() > buffer.position() + 48)
|
||||
{
|
||||
buf.append("...");
|
||||
i = buffer.limit() - 16;
|
||||
i = buffer.limit() - 24;
|
||||
}
|
||||
}
|
||||
buf.append(">>>");
|
||||
|
@ -1125,10 +1126,10 @@ public class BufferUtil
|
|||
for (int i = limit; i < buffer.capacity(); i++)
|
||||
{
|
||||
appendContentChar(buf, buffer.get(i));
|
||||
if (i == limit + 16 && buffer.capacity() > limit + 32)
|
||||
if (i == limit + 8 && buffer.capacity() > limit + 16)
|
||||
{
|
||||
buf.append("...");
|
||||
i = buffer.capacity() - 16;
|
||||
i = buffer.capacity() - 8;
|
||||
}
|
||||
}
|
||||
buffer.limit(limit);
|
||||
|
|
|
@ -171,10 +171,11 @@ public class QueuedThreadPool extends ContainerLifeCycle implements SizedThreadP
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Stopping {}", this);
|
||||
|
||||
super.doStop();
|
||||
|
||||
removeBean(_tryExecutor);
|
||||
_tryExecutor = TryExecutor.NO_TRY;
|
||||
|
||||
super.doStop();
|
||||
|
||||
// Signal the Runner threads that we are stopping
|
||||
int threads = _counts.getAndSetHi(Integer.MIN_VALUE);
|
||||
|
|
|
@ -115,7 +115,7 @@ public class WebSocketNegotiationTest
|
|||
client.getOutputStream().write(upgradeRequest.getBytes(ISO_8859_1));
|
||||
String response = getUpgradeResponse(client.getInputStream());
|
||||
|
||||
assertThat(response, containsString("400 Missing request header 'Sec-WebSocket-Key'"));
|
||||
assertThat(response, containsString("400 "));
|
||||
}
|
||||
|
||||
protected static HttpFields newUpgradeRequest(String extensions)
|
||||
|
|
|
@ -89,6 +89,6 @@ public class WebSocketInvalidVersionTest
|
|||
connFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT);
|
||||
});
|
||||
assertThat(x.getCause(), instanceOf(UpgradeException.class));
|
||||
assertThat(x.getMessage(), containsString("400 Unsupported websocket version specification"));
|
||||
assertThat(x.getMessage(), containsString("400 "));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,8 +108,8 @@ public class BadAppTests extends AbstractDistributionTest
|
|||
startHttpClient();
|
||||
ContentResponse response = client.GET("http://localhost:" + port + "/badapp/");
|
||||
assertEquals(HttpStatus.SERVICE_UNAVAILABLE_503, response.getStatus());
|
||||
assertThat(response.getContentAsString(), containsString("Unavailable"));
|
||||
assertThat(response.getContentAsString(), containsString("Problem accessing /badapp/"));
|
||||
assertThat(response.getContentAsString(), containsString("<h2>HTTP ERROR 503 Service Unavailable</h2>"));
|
||||
assertThat(response.getContentAsString(), containsString("<tr><th>URI:</th><td>/badapp/</td></tr>"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,8 +148,8 @@ public class BadAppTests extends AbstractDistributionTest
|
|||
startHttpClient();
|
||||
ContentResponse response = client.GET("http://localhost:" + port + "/badapp/");
|
||||
assertEquals(HttpStatus.SERVICE_UNAVAILABLE_503, response.getStatus());
|
||||
assertThat(response.getContentAsString(), containsString("Unavailable"));
|
||||
assertThat(response.getContentAsString(), containsString("Problem accessing /badapp/"));
|
||||
assertThat(response.getContentAsString(), containsString("<h2>HTTP ERROR 503 Service Unavailable</h2>"));
|
||||
assertThat(response.getContentAsString(), containsString("<tr><th>URI:</th><td>/badapp/</td></tr>"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue