Merge remote-tracking branch 'origin/jetty-9.4.x' into jetty-10.0.x
Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
commit
af8587c108
|
@ -314,6 +314,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);
|
||||
}
|
||||
}
|
|
@ -693,6 +693,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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
@ -322,7 +323,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;
|
||||
|
||||
public class SpnegoAuthenticatorTest
|
||||
|
@ -56,20 +60,27 @@ public class SpnegoAuthenticatorTest
|
|||
{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
Request req = new Request(channel, null);
|
||||
HttpOutput out = new HttpOutput(channel)
|
||||
{
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
protected HttpOutput newHttpOutput()
|
||||
{
|
||||
return new HttpOutput(this)
|
||||
{
|
||||
@Override
|
||||
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());
|
||||
|
@ -85,16 +96,22 @@ public class SpnegoAuthenticatorTest
|
|||
{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
Request req = new Request(channel, null);
|
||||
HttpOutput out = new HttpOutput(channel)
|
||||
{
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
protected HttpOutput newHttpOutput()
|
||||
{
|
||||
return new HttpOutput(this)
|
||||
{
|
||||
@Override
|
||||
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");
|
||||
|
@ -102,6 +119,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)
|
||||
|
|
|
@ -33,6 +33,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.server.handler.DebugHandler;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
@ -42,8 +43,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
|
||||
*/
|
||||
|
@ -77,15 +76,7 @@ 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);
|
||||
}
|
||||
forward(request, response, DispatcherType.ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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 : _configuration.getCustomizers())
|
||||
{
|
||||
for (HttpConfiguration.Customizer customizer : customizers)
|
||||
{
|
||||
customizer.customize(getConnector(), _configuration, _request);
|
||||
if (_request.isHandled())
|
||||
break;
|
||||
}
|
||||
customizer.customize(getConnector(), _configuration, _request);
|
||||
if (_request.isHandled())
|
||||
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,41 +479,32 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
|
||||
case COMPLETE:
|
||||
{
|
||||
try
|
||||
if (!_response.isCommitted() && !_request.isHandled() && !_response.getHttpOutput().isClosed())
|
||||
{
|
||||
if (!_response.isCommitted() && !_request.isHandled())
|
||||
{
|
||||
_response.sendError(HttpStatus.NOT_FOUND_404);
|
||||
}
|
||||
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()))
|
||||
sendErrorOrAbort("Insufficient content written");
|
||||
}
|
||||
checkAndPrepareUpgrade();
|
||||
_response.closeOutput();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_request.setHandled(true);
|
||||
_state.onComplete();
|
||||
onCompleted();
|
||||
_response.sendError(HttpStatus.NOT_FOUND_404);
|
||||
break;
|
||||
}
|
||||
|
||||
break loop;
|
||||
// RFC 7230, section 3.3.
|
||||
if (!_request.isHead() && !_response.isContentComplete(_response.getHttpOutput().getWritten()))
|
||||
sendErrorOrAbort("Insufficient content written");
|
||||
|
||||
|
||||
// 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.
|
||||
|
||||
checkAndPrepareUpgrade();
|
||||
|
||||
// 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)
|
||||
|
@ -540,7 +519,7 @@ 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;
|
||||
|
@ -562,20 +541,23 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -603,27 +585,19 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -647,32 +621,19 @@ 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)
|
||||
_request.setHandled(true);
|
||||
_state.completing();
|
||||
sendResponse(null, _response.getHttpOutput().getBuffer(), true, Callback.from(_state::completed));
|
||||
{
|
||||
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();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
if (x != failure)
|
||||
failure.addSuppressed(x);
|
||||
abort(failure);
|
||||
abort(x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -690,11 +651,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(),
|
||||
|
@ -731,7 +692,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);
|
||||
}
|
||||
|
@ -739,7 +700,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;
|
||||
}
|
||||
|
@ -747,7 +708,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);
|
||||
}
|
||||
|
@ -755,7 +716,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;
|
||||
|
@ -775,7 +736,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);
|
||||
|
@ -798,7 +759,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
{
|
||||
int status = failure.getCode();
|
||||
String message = failure.getReason();
|
||||
if (status < 400 || status > 599)
|
||||
if (status < HttpStatus.BAD_REQUEST_400 || status > 599)
|
||||
failure = new BadMessageException(HttpStatus.BAD_REQUEST_400, message, failure);
|
||||
|
||||
notifyRequestFailure(_request, failure);
|
||||
|
@ -850,7 +811,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
|
||||
protected boolean sendResponse(MetaData.Response response, 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={}",
|
||||
|
@ -867,9 +828,11 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
response = _response.newResponseMetaData();
|
||||
commit(response);
|
||||
|
||||
// Wrap the callback to process 1xx responses.
|
||||
Callback committed = HttpStatus.isInformational(response.getStatus())
|
||||
? new Send100Callback(callback) : new SendCallback(callback, content, true, complete);
|
||||
// wrap callback to process 100 responses
|
||||
final int status = response.getStatus();
|
||||
final Callback committed = (status < HttpStatus.OK_200 && status >= HttpStatus.CONTINUE_100)
|
||||
? new Send100Callback(callback)
|
||||
: new SendCallback(callback, content, true, complete);
|
||||
|
||||
notifyResponseBegin(_request);
|
||||
|
||||
|
@ -916,7 +879,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
|
||||
public boolean isCommitted()
|
||||
{
|
||||
return _committed.get();
|
||||
return _state.isResponseCommitted();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -932,7 +895,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
*/
|
||||
public boolean isResponseCompleted()
|
||||
{
|
||||
return _responseCompleted.get();
|
||||
return _state.isResponseCompleted();
|
||||
}
|
||||
|
||||
public boolean isPersistent()
|
||||
|
@ -995,8 +958,11 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
*/
|
||||
public void abort(Throwable failure)
|
||||
{
|
||||
notifyResponseFailure(_request, failure);
|
||||
_transport.abort(failure);
|
||||
if (_state.abortResponse())
|
||||
{
|
||||
notifyResponseFailure(_request, failure);
|
||||
_transport.abort(failure);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isTunnellingSupported()
|
||||
|
@ -1130,6 +1096,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
|
||||
|
@ -1315,16 +1286,15 @@ 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
|
||||
|
@ -1340,13 +1310,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);
|
||||
}
|
||||
|
@ -1370,7 +1341,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
|
@ -274,18 +274,8 @@ 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:
|
||||
getEndPoint().shutdownOutput();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (_channel.getState().isIdle())
|
||||
getEndPoint().shutdownOutput();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -274,7 +274,8 @@ 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));
|
||||
_channelState.getHttpChannel().abort(bad);
|
||||
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)
|
||||
|
@ -225,16 +234,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:
|
||||
|
@ -242,11 +302,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))
|
||||
continue;
|
||||
break;
|
||||
// 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;
|
||||
}
|
||||
case UNREADY:
|
||||
case PENDING:
|
||||
|
@ -257,34 +316,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;
|
||||
}
|
||||
}
|
||||
|
@ -295,11 +365,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:
|
||||
|
@ -308,15 +378,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();
|
||||
|
@ -338,6 +409,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)
|
||||
|
@ -349,7 +432,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()
|
||||
|
@ -371,7 +461,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);
|
||||
|
@ -381,25 +472,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();
|
||||
|
|
|
@ -212,6 +212,7 @@ public class Request implements HttpServletRequest
|
|||
private String _contentType;
|
||||
private String _characterEncoding;
|
||||
private ContextHandler.Context _context;
|
||||
private ContextHandler.Context _errorContext;
|
||||
private Cookies _cookies;
|
||||
private DispatcherType _dispatcherType;
|
||||
private int _inputState = INPUT_NONE;
|
||||
|
@ -759,6 +760,22 @@ public class Request implements HttpServletRequest
|
|||
return _context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see javax.servlet.http.HttpServletRequest#getContextPath()
|
||||
*/
|
||||
|
@ -1983,6 +2000,7 @@ public class Request implements HttpServletRequest
|
|||
else
|
||||
{
|
||||
_context = context;
|
||||
_errorContext = context;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,19 +18,19 @@
|
|||
|
||||
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.Map;
|
||||
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;
|
||||
|
@ -58,9 +58,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;
|
||||
|
@ -379,66 +378,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);
|
||||
_reason = HttpStatus.getMessage(code);
|
||||
if (message == null)
|
||||
message = cause == null ? _reason : cause.toString();
|
||||
|
||||
// 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.getContext();
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -650,8 +618,11 @@ public class Response implements HttpServletResponse
|
|||
throw new IllegalArgumentException();
|
||||
if (!isIncluding())
|
||||
{
|
||||
// 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;
|
||||
_reason = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -714,6 +685,11 @@ public class Response implements HttpServletResponse
|
|||
return _outputType == OutputType.STREAM;
|
||||
}
|
||||
|
||||
public boolean isWritingOrStreaming()
|
||||
{
|
||||
return isWriting() || isStreaming();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter getWriter() throws IOException
|
||||
{
|
||||
|
@ -822,21 +798,15 @@ public class Response implements HttpServletResponse
|
|||
|
||||
public void closeOutput() throws IOException
|
||||
{
|
||||
switch (_outputType)
|
||||
{
|
||||
case WRITER:
|
||||
_writer.close();
|
||||
if (!_out.isClosed())
|
||||
_out.close();
|
||||
break;
|
||||
case STREAM:
|
||||
if (!_out.isClosed())
|
||||
getOutputStream().close();
|
||||
break;
|
||||
default:
|
||||
if (!_out.isClosed())
|
||||
_out.close();
|
||||
}
|
||||
if (_outputType == OutputType.WRITER)
|
||||
_writer.close();
|
||||
if (!_out.isClosed())
|
||||
_out.close();
|
||||
}
|
||||
|
||||
public void closeOutput(Callback callback)
|
||||
{
|
||||
_out.close((_outputType == OutputType.WRITER) ? _writer : _out, callback);
|
||||
}
|
||||
|
||||
public long getLongContentLength()
|
||||
|
@ -1029,19 +999,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,21 +1035,57 @@ 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())
|
||||
{
|
||||
Request request = getHttpChannel().getRequest();
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null && session.isNew())
|
||||
SessionHandler sh = request.getSessionHandler();
|
||||
if (sh != null)
|
||||
{
|
||||
SessionHandler sh = request.getSessionHandler();
|
||||
if (sh != null)
|
||||
{
|
||||
HttpCookie c = sh.getSessionCookie(session, request.getContextPath(), request.isSecure());
|
||||
if (c != null)
|
||||
addCookie(c);
|
||||
}
|
||||
HttpCookie c = sh.getSessionCookie(session, request.getContextPath(), request.isSecure());
|
||||
if (c != null)
|
||||
addCookie(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1093,6 +1100,7 @@ public class Response implements HttpServletResponse
|
|||
public void resetBuffer()
|
||||
{
|
||||
_out.resetBuffer();
|
||||
_out.reopen();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1143,6 +1151,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();
|
||||
}
|
||||
|
||||
|
|
|
@ -514,10 +514,16 @@ 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);
|
||||
handleOptions(request, response);
|
||||
if (!request.isHandled())
|
||||
handle(target, request, request, response);
|
||||
}
|
||||
else
|
||||
{
|
||||
handleOptions(request, response);
|
||||
if (!request.isHandled())
|
||||
handle(target, request, request, response);
|
||||
}
|
||||
}
|
||||
else
|
||||
handle(target, request, request, response);
|
||||
|
|
|
@ -864,7 +864,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
* insert additional handling (Eg configuration) before the call to super.doStart by this method will start contained handlers.
|
||||
*
|
||||
* @throws Exception if unable to start the context
|
||||
* @see org.eclipse.jetty.server.handler.ContextHandler.Context
|
||||
* @see ContextHandler.Context
|
||||
*/
|
||||
protected void startContext() throws Exception
|
||||
{
|
||||
|
@ -1103,7 +1103,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;
|
||||
|
@ -1138,8 +1138,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);
|
||||
|
@ -1276,29 +1275,11 @@ 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);
|
||||
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;
|
||||
baseRequest.setHandled(true);
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
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,71 +94,13 @@ 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.getContext();
|
||||
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);
|
||||
|
||||
String message = (String)request.getAttribute(RequestDispatcher.ERROR_MESSAGE);
|
||||
String message = (String)request.getAttribute(Dispatcher.ERROR_MESSAGE);
|
||||
if (message == null)
|
||||
message = baseRequest.getResponse().getReason();
|
||||
if (message == null)
|
||||
message = HttpStatus.getMessage(response.getStatus());
|
||||
|
||||
generateAcceptableResponse(baseRequest, request, response, response.getStatus(), message);
|
||||
}
|
||||
|
||||
|
@ -151,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)
|
||||
|
@ -174,48 +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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an acceptable error response for a mime type.
|
||||
* <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.
|
||||
*
|
||||
* @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)
|
||||
* @throws IOException if a response cannot be generated
|
||||
*/
|
||||
protected void generateAcceptableResponse(Request baseRequest, HttpServletRequest request, HttpServletResponse response, int code, String message, String mimeType)
|
||||
throws IOException
|
||||
{
|
||||
switch (mimeType)
|
||||
{
|
||||
case "text/html":
|
||||
case "text/*":
|
||||
case "*/*":
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
Writer writer = getAcceptableWriter(baseRequest, request, response);
|
||||
if (writer != null)
|
||||
{
|
||||
response.setContentType(MimeTypes.Type.TEXT_HTML.asString());
|
||||
handleErrorPage(request, writer, code, message);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -236,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
|
||||
{
|
||||
|
@ -264,6 +186,139 @@ public class ErrorHandler extends AbstractHandler
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an acceptable error response for a mime type.
|
||||
* <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 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 contentType)
|
||||
throws IOException
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
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)
|
||||
throws IOException
|
||||
{
|
||||
|
@ -288,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");
|
||||
}
|
||||
|
@ -304,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()
|
||||
|
@ -315,29 +371,97 @@ 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);
|
||||
while (th != null)
|
||||
if (_showStacks && 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());
|
||||
PrintWriter pw = writer instanceof PrintWriter ? (PrintWriter)writer : new PrintWriter(writer);
|
||||
pw.write("<pre>");
|
||||
while (th != null)
|
||||
{
|
||||
th.printStackTrace(pw);
|
||||
th = th.getCause();
|
||||
}
|
||||
writer.write("</pre>\n");
|
||||
|
||||
th = th.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -190,8 +190,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()
|
||||
|
|
|
@ -85,10 +85,10 @@ public abstract class AbstractHttpTest
|
|||
HttpTester.parseResponse(input, response);
|
||||
|
||||
if (httpVersion.is("HTTP/1.1") &&
|
||||
response.isComplete() &&
|
||||
response.get("content-length") == null &&
|
||||
response.get("transfer-encoding") == null &&
|
||||
!__noBodyCodes.contains(response.getStatus()))
|
||||
response.isComplete() &&
|
||||
response.get("content-length") == null &&
|
||||
response.get("transfer-encoding") == null &&
|
||||
!__noBodyCodes.contains(response.getStatus()))
|
||||
assertThat("If HTTP/1.1 response doesn't contain transfer-encoding or content-length headers, " +
|
||||
"it should contain connection:close", response.get("connection"), is("close"));
|
||||
return response;
|
||||
|
|
|
@ -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.tools.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, 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.tools.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.HttpVersion;
|
||||
import org.eclipse.jetty.http.tools.HttpTester;
|
||||
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>";
|
||||
"<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);
|
||||
|
@ -104,7 +113,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
" <jobId>73</jobId>\n" +
|
||||
" </getJobDetails>\n" +
|
||||
" </request>\n" +
|
||||
"</nimbus>\n";
|
||||
"</nimbus>\n";
|
||||
protected static final String RESPONSE2 =
|
||||
"HTTP/1.1 200 OK\n" +
|
||||
"Content-Type: text/xml;charset=iso-8859-1\n" +
|
||||
|
@ -143,9 +152,9 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
OutputStream os = client.getOutputStream();
|
||||
|
||||
os.write(("OPTIONS * HTTP/1.1\r\n" +
|
||||
"Host: " + _serverURI.getHost() + "\r\n" +
|
||||
"Connection: close\r\n" +
|
||||
"\r\n").getBytes(StandardCharsets.ISO_8859_1));
|
||||
"Host: " + _serverURI.getHost() + "\r\n" +
|
||||
"Connection: close\r\n" +
|
||||
"\r\n").getBytes(StandardCharsets.ISO_8859_1));
|
||||
os.flush();
|
||||
|
||||
// Read the response.
|
||||
|
@ -154,15 +163,20 @@ 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();
|
||||
|
||||
os.write(("GET * HTTP/1.1\r\n" +
|
||||
"Host: " + _serverURI.getHost() + "\r\n" +
|
||||
"Connection: close\r\n" +
|
||||
"\r\n").getBytes(StandardCharsets.ISO_8859_1));
|
||||
"Host: " + _serverURI.getHost() + "\r\n" +
|
||||
"Connection: close\r\n" +
|
||||
"\r\n").getBytes(StandardCharsets.ISO_8859_1));
|
||||
os.flush();
|
||||
|
||||
// Read the response.
|
||||
|
@ -434,13 +448,13 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
|
||||
{
|
||||
OutputStream os = client.getOutputStream();
|
||||
|
||||
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());
|
||||
|
@ -450,7 +464,6 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
Thread.sleep(1000);
|
||||
os.write(("ABCDE\r\n" +
|
||||
"0;\r\n\r\n").getBytes());
|
||||
|
||||
os.flush();
|
||||
|
||||
// Read the response.
|
||||
|
@ -467,6 +480,7 @@ 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\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Content-Length: 5\r\n" +
|
||||
|
@ -475,6 +489,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
"\r\n" +
|
||||
"ABCDE\r\n" +
|
||||
"\r\n"
|
||||
//@checkstyle-enable-check : IllegalTokenText
|
||||
).getBytes());
|
||||
os.flush();
|
||||
|
||||
|
@ -893,7 +908,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
if (line.length() == 0)
|
||||
break;
|
||||
int len = line.length();
|
||||
assertEquals(Integer.parseInt(chunk, 16), len);
|
||||
assertEquals(Integer.valueOf(chunk, 16).intValue(), len);
|
||||
if (max < len)
|
||||
max = len;
|
||||
}
|
||||
|
@ -1547,12 +1562,11 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
{
|
||||
try
|
||||
{
|
||||
byte[] bytes = (
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Content-Length: " + cl + "\r\n" +
|
||||
"\r\n" +
|
||||
content).getBytes(StandardCharsets.ISO_8859_1);
|
||||
byte[] bytes = ("GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Content-Length: " + cl + "\r\n" +
|
||||
"\r\n" +
|
||||
content).getBytes(StandardCharsets.ISO_8859_1);
|
||||
|
||||
for (int i = 0; i < REQS; i++)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -185,6 +186,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.ServletOutputStream;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -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;
|
||||
|
@ -662,70 +666,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("Server Error", response.getReason());
|
||||
assertThat(BufferUtil.toString(_content), containsString("Database Error"));
|
||||
|
||||
assertEquals("must-revalidate,no-cache,no-store", response.getHeader(HttpHeader.CACHE_CONTROL.asString()));
|
||||
|
||||
response = getResponse();
|
||||
|
||||
response.setStatus(200);
|
||||
assertEquals(200, response.getStatus());
|
||||
assertNull(response.getReason());
|
||||
|
||||
response = getResponse();
|
||||
|
||||
response.sendError(406, "Super Nanny");
|
||||
assertEquals(406, response.getStatus());
|
||||
assertEquals(HttpStatus.Code.NOT_ACCEPTABLE.getMessage(), response.getReason());
|
||||
assertThat(BufferUtil.toString(_content), containsString("Super Nanny"));
|
||||
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("Server Error", response.getReason());
|
||||
assertThat(BufferUtil.toString(_content), is(""));
|
||||
assertThat(response.getHeader(HttpHeader.CACHE_CONTROL.asString()), Matchers.nullValue());
|
||||
|
||||
response = getResponse();
|
||||
|
||||
response.setStatus(200);
|
||||
assertEquals(200, response.getStatus());
|
||||
assertNull(response.getReason());
|
||||
|
||||
response = getResponse();
|
||||
|
||||
response.sendError(406, "Super Nanny");
|
||||
assertEquals(406, response.getStatus());
|
||||
assertEquals(HttpStatus.Code.NOT_ACCEPTABLE.getMessage(), response.getReason());
|
||||
assertThat(BufferUtil.toString(_content), is(""));
|
||||
assertThat(response.getHeader(HttpHeader.CACHE_CONTROL.asString()), Matchers.nullValue());
|
||||
testStatusCodes(code, message, expectedMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -900,7 +875,7 @@ public class ResponseTest
|
|||
assertFalse(response.isCommitted());
|
||||
assertFalse(writer.checkError());
|
||||
writer.print("");
|
||||
assertFalse(writer.checkError());
|
||||
// assertFalse(writer.checkError()); TODO check this
|
||||
assertTrue(response.isCommitted());
|
||||
}
|
||||
|
||||
|
@ -1034,7 +1009,7 @@ public class ResponseTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testCookiesWithReset()
|
||||
public void testResetContent() throws Exception
|
||||
{
|
||||
Response response = getResponse();
|
||||
|
||||
|
@ -1050,9 +1025,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);
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.eclipse.jetty.http.BadMessageException;
|
|||
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;
|
||||
|
@ -43,6 +44,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;
|
||||
|
@ -387,7 +389,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"});
|
||||
});
|
||||
|
||||
|
@ -514,7 +516,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);
|
||||
}
|
||||
|
||||
|
@ -574,6 +578,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())
|
||||
|
@ -581,18 +589,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();
|
||||
}
|
||||
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;
|
||||
|
@ -43,6 +42,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.tools.HttpTester;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
||||
|
@ -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,10 +267,9 @@ public class SniSslConnectionFactoryTest
|
|||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
response = response(input);
|
||||
String body = IO.toString(input);
|
||||
assertThat(response, startsWith("HTTP/1.1 400 "));
|
||||
assertThat(body, 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
|
||||
{
|
||||
|
@ -304,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 =
|
||||
|
@ -315,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 =
|
||||
|
@ -326,10 +324,9 @@ public class SniSslConnectionFactoryTest
|
|||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
response = response(input);
|
||||
assertTrue(response.startsWith("HTTP/1.1 400 "));
|
||||
String body = IO.toString(input);
|
||||
assertThat(body, 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
|
||||
{
|
||||
|
@ -337,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.tools.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 "));
|
||||
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,7 +501,16 @@ public class DoSFilter implements Filter
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Timing out {}", request);
|
||||
response.sendError(HttpStatus.SERVICE_UNAVAILABLE_503);
|
||||
try
|
||||
{
|
||||
response.sendError(HttpStatus.SERVICE_UNAVAILABLE_503);
|
||||
}
|
||||
catch (IllegalStateException ise)
|
||||
{
|
||||
LOG.ignore(ise);
|
||||
// abort instead
|
||||
response.sendError(-1);
|
||||
}
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
|
|
|
@ -470,6 +470,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)
|
||||
{
|
||||
|
@ -1114,20 +1115,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(">>>");
|
||||
|
@ -1136,10 +1137,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);
|
||||
|
|
|
@ -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