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.
+ *
*
* @param baseRequest The base request
* @param request The servlet request (may be wrapped)
* @param response The response (may be wrapped)
* @param code the http error code
* @param message the http error message
- * @param mimeType The mimetype to generate (may be */*or other wildcard)
+ * @param contentType The mimetype to generate (may be */*or other wildcard)
* @throws IOException if a response cannot be generated
*/
- protected void generateAcceptableResponse(Request baseRequest, HttpServletRequest request, HttpServletResponse response, int code, String message, String mimeType)
+ protected void generateAcceptableResponse(Request baseRequest, HttpServletRequest request, HttpServletResponse response, int code, String message, String contentType)
throws IOException
{
- switch (mimeType)
+ // We can generate an acceptable contentType, but can we generate an acceptable charset?
+ // TODO refactor this in jetty-10 to be done in the other calling loop
+ Charset charset = null;
+ List 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
{
- baseRequest.setHandled(true);
- Writer writer = getAcceptableWriter(baseRequest, request, response);
- if (writer != null)
+ // 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)
{
- response.setContentType(MimeTypes.Type.TEXT_HTML.asString());
- handleErrorPage(request, writer, code, message);
+ case TEXT_HTML:
+ response.setContentType(MimeTypes.Type.TEXT_HTML.asString());
+ response.setCharacterEncoding(charset.name());
+ handleErrorPage(request, writer, code, message);
+ break;
+ case TEXT_JSON:
+ response.setContentType(contentType);
+ writeErrorJson(request, writer, code, message);
+ break;
+ case TEXT_PLAIN:
+ response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString());
+ response.setCharacterEncoding(charset.name());
+ writeErrorPlain(request, writer, code, message);
+ break;
+ default:
+ throw new IllegalStateException();
}
+
+ writer.flush();
+ break;
+ }
+ catch (BufferOverflowException e)
+ {
+ LOG.warn("Error page too large: {} {} {}", code, message, request);
+ if (LOG.isDebugEnabled())
+ LOG.warn(e);
+ baseRequest.getResponse().resetContent();
+ if (!_disableStacks)
+ {
+ LOG.info("Disabling showsStacks for " + this);
+ _disableStacks = true;
+ continue;
+ }
+ break;
}
}
+
+ // Do an asynchronous completion.
+ baseRequest.getHttpChannel().sendResponseAndComplete();
}
protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message)
@@ -278,12 +343,13 @@ public class ErrorHandler extends AbstractHandler
{
writer.write(" \n");
writer.write("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(" \n");
}
@@ -294,7 +360,7 @@ public class ErrorHandler extends AbstractHandler
String uri = request.getRequestURI();
writeErrorPageMessage(request, writer, code, message, uri);
- if (showStacks)
+ if (showStacks && !_disableStacks)
writeErrorPageStacks(request, writer);
Request.getBaseRequest(request).getHttpChannel().getHttpConfiguration()
@@ -305,29 +371,97 @@ public class ErrorHandler extends AbstractHandler
throws IOException
{
writer.write("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(" \n");
+ writer.write("\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("
\n");
+ }
+
+ private void htmlRow(Writer writer, String tag, Object value)
+ throws IOException
+ {
+ writer.write("");
+ writer.write(tag);
+ writer.write(": ");
+ if (value == null)
+ writer.write("-");
+ else
+ writer.write(StringUtil.sanitizeXmlString(value.toString()));
+ writer.write(" \n");
+ }
+
+ private void writeErrorPlain(HttpServletRequest request, PrintWriter writer, int code, String message)
+ {
+ writer.write("HTTP ERROR ");
writer.write(Integer.toString(code));
- writer.write("\nProblem accessing ");
- write(writer, uri);
- writer.write(". Reason:\n
");
- write(writer, message);
- writer.write(" ");
+ 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("Caused by: ");
- 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("");
+ while (th != null)
+ {
+ th.printStackTrace(pw);
+ th = th.getCause();
+ }
writer.write(" \n");
-
- th = th.getCause();
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java
index 24a1258b61d..ed5affa51c9 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java
@@ -197,8 +197,9 @@ public class ShutdownHandler extends HandlerWrapper
connector.shutdown();
}
- response.sendError(200, "Connectors closed, commencing full shutdown");
baseRequest.setHandled(true);
+ response.setStatus(200);
+ response.flushBuffer();
final Server server = getServer();
new Thread()
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java
index ce95a87b203..d554043205c 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java
@@ -43,9 +43,7 @@ import static org.hamcrest.Matchers.is;
public abstract class AbstractHttpTest
{
- private static final Set __noBodyCodes = new HashSet<>(Arrays.asList(new String[]{
- "100", "101", "102", "204", "304"
- }));
+ private static final Set __noBodyCodes = new HashSet<>(Arrays.asList("100", "101", "102", "204", "304"));
protected static Server server;
protected static ServerConnector connector;
@@ -87,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;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncCompletionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncCompletionTest.java
new file mode 100644
index 00000000000..a14a6bcd09f
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncCompletionTest.java
@@ -0,0 +1,221 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Exchanger;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+
+import org.eclipse.jetty.http.HttpCompliance;
+import org.eclipse.jetty.http.HttpTester;
+import org.eclipse.jetty.io.ChannelEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ManagedSelector;
+import org.eclipse.jetty.io.SocketChannelEndPoint;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Extended Server Tester.
+ */
+public class AsyncCompletionTest extends HttpServerTestFixture
+{
+ private static final Exchanger X = new Exchanger<>();
+ private static final AtomicBoolean COMPLETE = new AtomicBoolean();
+
+ private static class DelayedCallback extends Callback.Nested
+ {
+ private CompletableFuture _delay = new CompletableFuture<>();
+
+ public DelayedCallback(Callback callback)
+ {
+ super(callback);
+ }
+
+ @Override
+ public void succeeded()
+ {
+ _delay.complete(null);
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ _delay.completeExceptionally(x);
+ }
+
+ public void proceed()
+ {
+ try
+ {
+ _delay.get(10, TimeUnit.SECONDS);
+ getCallback().succeeded();
+ }
+ catch(Throwable th)
+ {
+ th.printStackTrace();
+ getCallback().failed(th);
+ }
+ }
+ }
+
+
+ @BeforeEach
+ public void init() throws Exception
+ {
+ COMPLETE.set(false);
+
+ startServer(new ServerConnector(_server, new HttpConnectionFactory()
+ {
+ @Override
+ public Connection newConnection(Connector connector, EndPoint endPoint)
+ {
+ return configure(new ExtendedHttpConnection(getHttpConfiguration(), connector, endPoint), connector, endPoint);
+ }
+ })
+ {
+ @Override
+ protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ {
+ return new ExtendedEndPoint(channel, selectSet, key, getScheduler());
+ }
+ });
+ }
+
+ private static class ExtendedEndPoint extends SocketChannelEndPoint
+ {
+ public ExtendedEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler)
+ {
+ super(channel, selector, key, scheduler);
+ }
+
+ @Override
+ public void write(Callback callback, ByteBuffer... buffers) throws IllegalStateException
+ {
+ DelayedCallback delay = new DelayedCallback(callback);
+ super.write(delay, buffers);
+ try
+ {
+ X.exchange(delay);
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private static class ExtendedHttpConnection extends HttpConnection
+ {
+ public ExtendedHttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint)
+ {
+ super(config, connector, endPoint, HttpCompliance.RFC7230_LEGACY, false);
+ }
+
+ @Override
+ public void onCompleted()
+ {
+ COMPLETE.compareAndSet(false,true);
+ super.onCompleted();
+ }
+ }
+
+ // Tests from here use these parameters
+ public static Stream tests()
+ {
+ List 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);
+ }
+ }
+ }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ErrorHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ErrorHandlerTest.java
index 0f5369036ae..7e396b072b2 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ErrorHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ErrorHandlerTest.java
@@ -30,7 +30,6 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.handler.ErrorHandler;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -53,43 +52,6 @@ public class ErrorHandlerTest
server = new Server();
connector = new LocalConnector(server);
server.addConnector(connector);
- server.addBean(new ErrorHandler()
- {
- @Override
- protected void generateAcceptableResponse(
- Request baseRequest,
- HttpServletRequest request,
- HttpServletResponse response,
- int code,
- String message,
- String mimeType) throws IOException
- {
- switch (mimeType)
- {
- case "text/json":
- case "application/json":
- {
- baseRequest.setHandled(true);
- response.setContentType(mimeType);
- response.getWriter()
- .append("{")
- .append("code: \"").append(Integer.toString(code)).append("\",")
- .append("message: \"").append(message).append('"')
- .append("}");
- break;
- }
- case "text/plain":
- {
- baseRequest.setHandled(true);
- response.setContentType("text/plain");
- response.getOutputStream().print(response.getContentType());
- break;
- }
- default:
- super.generateAcceptableResponse(baseRequest, request, response, code, message, mimeType);
- }
- }
- });
server.setHandler(new AbstractHandler()
{
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputAsyncStateTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputAsyncStateTest.java
index 9c667a9b6c8..fcabf1acfe9 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputAsyncStateTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputAsyncStateTest.java
@@ -190,6 +190,10 @@ public class HttpInputAsyncStateTest
__history.add("COMPLETE");
break;
+ case READ_REGISTER:
+ _state.getHttpChannel().onAsyncWaitForContent();
+ break;
+
default:
fail("Bad Action: " + action);
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitBadBehaviourTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitBadBehaviourTest.java
deleted file mode 100644
index 336079e4874..00000000000
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitBadBehaviourTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
-// ------------------------------------------------------------------------
-// All rights reserved. This program and the accompanying materials
-// are made available under the terms of the Eclipse Public License v1.0
-// and Apache License v2.0 which accompanies this distribution.
-//
-// The Eclipse Public License is available at
-// http://www.eclipse.org/legal/epl-v10.html
-//
-// The Apache License v2.0 is available at
-// http://www.opensource.org/licenses/apache2.0.php
-//
-// You may elect to redistribute this code under either of these licenses.
-// ========================================================================
-//
-
-package org.eclipse.jetty.server;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.BrokenBarrierException;
-import java.util.concurrent.CyclicBarrier;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.stream.Stream;
-import javax.servlet.AsyncContext;
-import javax.servlet.DispatcherType;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jetty.http.HttpTester;
-import org.eclipse.jetty.http.HttpVersion;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-//TODO: reset buffer tests
-//TODO: add protocol specific tests for connection: close and/or chunking
-
-public class HttpManyWaysToAsyncCommitBadBehaviourTest extends AbstractHttpTest
-{
- private final String contextAttribute = getClass().getName() + ".asyncContext";
-
- public static Stream httpVersions()
- {
- // boolean dispatch - if true we dispatch, otherwise we complete
- final boolean DISPATCH = true;
- final boolean COMPLETE = false;
-
- List 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();
- }
- }
-}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java
index ca5ebddc616..82d9b06bc3c 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java
@@ -21,6 +21,9 @@ package org.eclipse.jetty.server;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.stream.Stream;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
@@ -31,13 +34,16 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.util.log.StacklessLogging;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.eclipse.jetty.http.HttpFieldsMatchers.containsHeaderValue;
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.hamcrest.Matchers.nullValue;
//TODO: reset buffer tests
@@ -51,51 +57,72 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
// boolean dispatch - if true we dispatch, otherwise we complete
final boolean DISPATCH = true;
final boolean COMPLETE = false;
+ final boolean IN_WAIT = true;
+ final boolean WHILE_DISPATCHED = false;
List ret = new ArrayList<>();
- ret.add(Arguments.of(HttpVersion.HTTP_1_0, DISPATCH));
- ret.add(Arguments.of(HttpVersion.HTTP_1_1, DISPATCH));
- ret.add(Arguments.of(HttpVersion.HTTP_1_0, COMPLETE));
- ret.add(Arguments.of(HttpVersion.HTTP_1_1, COMPLETE));
+ ret.add(Arguments.of(HttpVersion.HTTP_1_0, DISPATCH, IN_WAIT));
+ ret.add(Arguments.of(HttpVersion.HTTP_1_1, DISPATCH, IN_WAIT));
+ ret.add(Arguments.of(HttpVersion.HTTP_1_0, COMPLETE, IN_WAIT));
+ ret.add(Arguments.of(HttpVersion.HTTP_1_1, COMPLETE, IN_WAIT));
+ ret.add(Arguments.of(HttpVersion.HTTP_1_0, DISPATCH, WHILE_DISPATCHED));
+ ret.add(Arguments.of(HttpVersion.HTTP_1_1, DISPATCH, WHILE_DISPATCHED));
+ ret.add(Arguments.of(HttpVersion.HTTP_1_0, COMPLETE, WHILE_DISPATCHED));
+ ret.add(Arguments.of(HttpVersion.HTTP_1_1, COMPLETE, WHILE_DISPATCHED));
return ret.stream();
}
@ParameterizedTest
@MethodSource("httpVersion")
- public void testHandlerDoesNotSetHandled(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testHandlerDoesNotSetHandled(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- DoesNotSetHandledHandler handler = new DoesNotSetHandledHandler(false, dispatch);
+ DoesNotSetHandledHandler handler = new DoesNotSetHandledHandler(false, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- assertThat("response code", response.getStatus(), is(404));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ assertThat(response.getStatus(), is(404));
+ assertThat(handler.failure(), is(nullValue()));
}
@ParameterizedTest
@MethodSource("httpVersion")
- public void testHandlerDoesNotSetHandledAndThrow(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testHandlerDoesNotSetHandledAndThrow(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- DoesNotSetHandledHandler handler = new DoesNotSetHandledHandler(true, dispatch);
+ DoesNotSetHandledHandler handler = new DoesNotSetHandledHandler(true, dispatch, inWait);
server.setHandler(handler);
server.start();
- HttpTester.Response response = executeRequest(httpVersion);
+ HttpTester.Response response;
+ if (inWait)
+ {
+ // exception thrown and handled before any async processing
+ response = executeRequest(httpVersion);
+ }
+ else
+ {
+ // exception thrown after async processing, so cannot be handled
+ try (StacklessLogging log = new StacklessLogging(HttpChannelState.class))
+ {
+ response = executeRequest(httpVersion);
+ }
+ }
- assertThat("response code", response.getStatus(), is(500));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ assertThat(response.getStatus(), is(500));
+ assertThat(handler.failure(), is(nullValue()));
}
private class DoesNotSetHandledHandler extends ThrowExceptionOnDemandHandler
{
private final boolean dispatch;
+ private final boolean inWait;
- private DoesNotSetHandledHandler(boolean throwException, boolean dispatch)
+ private DoesNotSetHandledHandler(boolean throwException, boolean dispatch, boolean inWait)
{
super(throwException);
this.dispatch = dispatch;
+ this.inWait = inWait;
}
@Override
@@ -105,17 +132,13 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
{
final AsyncContext asyncContext = baseRequest.startAsync();
request.setAttribute(contextAttribute, asyncContext);
- new Thread(new Runnable()
+ runAsync(baseRequest, inWait, () ->
{
- @Override
- public void run()
- {
- if (dispatch)
- asyncContext.dispatch();
- else
- asyncContext.complete();
- }
- }).run();
+ if (dispatch)
+ asyncContext.dispatch();
+ else
+ asyncContext.complete();
+ });
}
super.doNonErrorHandle(target, baseRequest, request, response);
}
@@ -123,42 +146,57 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
@ParameterizedTest
@MethodSource("httpVersion")
- public void testHandlerSetsHandledTrueOnly(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testHandlerSetsHandledTrueOnly(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- OnlySetHandledHandler handler = new OnlySetHandledHandler(false, dispatch);
+ OnlySetHandledHandler handler = new OnlySetHandledHandler(false, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- assertThat("response code", response.getStatus(), is(200));
+ assertThat(response.getStatus(), is(200));
if (httpVersion.is("HTTP/1.1"))
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_LENGTH, "0"));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ assertThat(handler.failure(), is(nullValue()));
}
@ParameterizedTest
@MethodSource("httpVersion")
- public void testHandlerSetsHandledTrueOnlyAndThrow(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testHandlerSetsHandledTrueOnlyAndThrow(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- OnlySetHandledHandler handler = new OnlySetHandledHandler(true, dispatch);
+ OnlySetHandledHandler handler = new OnlySetHandledHandler(true, dispatch, inWait);
server.setHandler(handler);
server.start();
- HttpTester.Response response = executeRequest(httpVersion);
+ HttpTester.Response response;
+ if (inWait)
+ {
+ // exception thrown and handled before any async processing
+ response = executeRequest(httpVersion);
+ }
+ else
+ {
+ // exception thrown after async processing, so cannot be handled
+ try (StacklessLogging log = new StacklessLogging(HttpChannelState.class))
+ {
+ response = executeRequest(httpVersion);
+ }
+ }
- assertThat("response code", response.getStatus(), is(500));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ assertThat(response.getStatus(), is(500));
+ assertThat(handler.failure(), is(nullValue()));
}
private class OnlySetHandledHandler extends ThrowExceptionOnDemandHandler
{
private final boolean dispatch;
+ private final boolean inWait;
- private OnlySetHandledHandler(boolean throwException, boolean dispatch)
+ private OnlySetHandledHandler(boolean throwException, boolean dispatch, boolean inWait)
{
super(throwException);
this.dispatch = dispatch;
+ this.inWait = inWait;
}
@Override
@@ -168,17 +206,13 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
{
final AsyncContext asyncContext = baseRequest.startAsync();
request.setAttribute(contextAttribute, asyncContext);
- new Thread(new Runnable()
+ runAsync(baseRequest, inWait, () ->
{
- @Override
- public void run()
- {
- if (dispatch)
- asyncContext.dispatch();
- else
- asyncContext.complete();
- }
- }).run();
+ if (dispatch)
+ asyncContext.dispatch();
+ else
+ asyncContext.complete();
+ });
}
baseRequest.setHandled(true);
super.doNonErrorHandle(target, baseRequest, request, response);
@@ -187,41 +221,55 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
@ParameterizedTest
@MethodSource("httpVersion")
- public void testHandlerSetsHandledAndWritesSomeContent(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testHandlerSetsHandledAndWritesSomeContent(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- SetHandledWriteSomeDataHandler handler = new SetHandledWriteSomeDataHandler(false, dispatch);
+ SetHandledWriteSomeDataHandler handler = new SetHandledWriteSomeDataHandler(false, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- assertThat("response code", response.getStatus(), is(200));
+ assertThat(response.getStatus(), is(200));
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_LENGTH, "6"));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ assertThat(handler.failure(), is(nullValue()));
}
@ParameterizedTest
@MethodSource("httpVersion")
- public void testHandlerSetsHandledAndWritesSomeContentAndThrow(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testHandlerSetsHandledAndWritesSomeContentAndThrow(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- SetHandledWriteSomeDataHandler handler = new SetHandledWriteSomeDataHandler(true, dispatch);
+ SetHandledWriteSomeDataHandler handler = new SetHandledWriteSomeDataHandler(true, dispatch, inWait);
server.setHandler(handler);
server.start();
+ HttpTester.Response response;
+ if (inWait)
+ {
+ // exception thrown and handled before any async processing
+ response = executeRequest(httpVersion);
+ }
+ else
+ {
+ // exception thrown after async processing, so cannot be handled
+ try (StacklessLogging log = new StacklessLogging(HttpChannelState.class))
+ {
+ response = executeRequest(httpVersion);
+ }
+ }
- HttpTester.Response response = executeRequest(httpVersion);
-
- assertThat("response code", response.getStatus(), is(500));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ assertThat(response.getStatus(), is(500));
+ assertThat(handler.failure(), is(nullValue()));
}
private class SetHandledWriteSomeDataHandler extends ThrowExceptionOnDemandHandler
{
private final boolean dispatch;
+ private final boolean inWait;
- private SetHandledWriteSomeDataHandler(boolean throwException, boolean dispatch)
+ private SetHandledWriteSomeDataHandler(boolean throwException, boolean dispatch, boolean inWait)
{
super(throwException);
this.dispatch = dispatch;
+ this.inWait = inWait;
}
@Override
@@ -231,25 +279,21 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
{
final AsyncContext asyncContext = baseRequest.startAsync();
request.setAttribute(contextAttribute, asyncContext);
- new Thread(new Runnable()
+ runAsync(baseRequest, inWait, () ->
{
- @Override
- public void run()
+ try
{
- try
- {
- asyncContext.getResponse().getWriter().write("foobar");
- if (dispatch)
- asyncContext.dispatch();
- else
- asyncContext.complete();
- }
- catch (IOException e)
- {
- markFailed(e);
- }
+ asyncContext.getResponse().getWriter().write("foobar");
+ if (dispatch)
+ asyncContext.dispatch();
+ else
+ asyncContext.complete();
}
- }).run();
+ catch (IOException e)
+ {
+ markFailed(e);
+ }
+ });
}
baseRequest.setHandled(true);
super.doNonErrorHandle(target, baseRequest, request, response);
@@ -258,44 +302,55 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
@ParameterizedTest
@MethodSource("httpVersion")
- public void testHandlerExplicitFlush(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testHandlerExplicitFlush(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- ExplicitFlushHandler handler = new ExplicitFlushHandler(false, dispatch);
+ ExplicitFlushHandler handler = new ExplicitFlushHandler(false, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- assertThat("response code", response.getStatus(), is(200));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ assertThat(response.getStatus(), is(200));
+ assertThat(handler.failure(), is(nullValue()));
if (httpVersion.is("HTTP/1.1"))
assertThat(response, containsHeaderValue(HttpHeader.TRANSFER_ENCODING, "chunked"));
}
@ParameterizedTest
@MethodSource("httpVersion")
- public void testHandlerExplicitFlushAndThrow(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testHandlerExplicitFlushAndThrow(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- ExplicitFlushHandler handler = new ExplicitFlushHandler(true, dispatch);
+ ExplicitFlushHandler handler = new ExplicitFlushHandler(true, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- assertThat("response code", response.getStatus(), is(200));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
- if (httpVersion.is("HTTP/1.1"))
- assertThat(response, containsHeaderValue(HttpHeader.TRANSFER_ENCODING, "chunked"));
+ if (inWait)
+ {
+ // throw happens before flush
+ assertThat(response.getStatus(), is(500));
+ }
+ else
+ {
+ // flush happens before throw
+ assertThat(response.getStatus(), is(200));
+ if (httpVersion.is("HTTP/1.1"))
+ assertThat(response, containsHeaderValue(HttpHeader.TRANSFER_ENCODING, "chunked"));
+ }
+ assertThat(handler.failure(), is(nullValue()));
}
private class ExplicitFlushHandler extends ThrowExceptionOnDemandHandler
{
private final boolean dispatch;
+ private final boolean inWait;
- private ExplicitFlushHandler(boolean throwException, boolean dispatch)
+ private ExplicitFlushHandler(boolean throwException, boolean dispatch, boolean inWait)
{
super(throwException);
this.dispatch = dispatch;
+ this.inWait = inWait;
}
@Override
@@ -305,27 +360,23 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
{
final AsyncContext asyncContext = baseRequest.startAsync();
request.setAttribute(contextAttribute, asyncContext);
- new Thread(new Runnable()
+ runAsync(baseRequest, inWait, () ->
{
- @Override
- public void run()
+ try
{
- try
- {
- ServletResponse asyncContextResponse = asyncContext.getResponse();
- asyncContextResponse.getWriter().write("foobar");
- asyncContextResponse.flushBuffer();
- if (dispatch)
- asyncContext.dispatch();
- else
- asyncContext.complete();
- }
- catch (IOException e)
- {
- markFailed(e);
- }
+ ServletResponse asyncContextResponse = asyncContext.getResponse();
+ asyncContextResponse.getWriter().write("foobar");
+ asyncContextResponse.flushBuffer();
+ if (dispatch)
+ asyncContext.dispatch();
+ else
+ asyncContext.complete();
}
- }).run();
+ catch (IOException e)
+ {
+ markFailed(e);
+ }
+ });
}
baseRequest.setHandled(true);
super.doNonErrorHandle(target, baseRequest, request, response);
@@ -334,44 +385,55 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
@ParameterizedTest
@MethodSource("httpVersion")
- public void testHandledAndFlushWithoutContent(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testHandledAndFlushWithoutContent(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- SetHandledAndFlushWithoutContentHandler handler = new SetHandledAndFlushWithoutContentHandler(false, dispatch);
+ SetHandledAndFlushWithoutContentHandler handler = new SetHandledAndFlushWithoutContentHandler(false, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- assertThat("response code", response.getStatus(), is(200));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ assertThat(response.getStatus(), is(200));
+ assertThat(handler.failure(), is(nullValue()));
if (httpVersion.is("HTTP/1.1"))
assertThat(response, containsHeaderValue(HttpHeader.TRANSFER_ENCODING, "chunked"));
}
@ParameterizedTest
@MethodSource("httpVersion")
- public void testHandledAndFlushWithoutContentAndThrow(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testHandledAndFlushWithoutContentAndThrow(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- SetHandledAndFlushWithoutContentHandler handler = new SetHandledAndFlushWithoutContentHandler(true, dispatch);
+ SetHandledAndFlushWithoutContentHandler handler = new SetHandledAndFlushWithoutContentHandler(true, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- assertThat("response code", response.getStatus(), is(200));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
- if (httpVersion.is("HTTP/1.1"))
- assertThat(response, containsHeaderValue(HttpHeader.TRANSFER_ENCODING, "chunked"));
+ if (inWait)
+ {
+ // throw happens before async behaviour, so is handled
+ assertThat(response.getStatus(), is(500));
+ }
+ else
+ {
+ assertThat(response.getStatus(), is(200));
+ if (httpVersion.is("HTTP/1.1"))
+ assertThat(response, containsHeaderValue(HttpHeader.TRANSFER_ENCODING, "chunked"));
+ }
+
+ assertThat(handler.failure(), is(nullValue()));
}
private class SetHandledAndFlushWithoutContentHandler extends ThrowExceptionOnDemandHandler
{
private final boolean dispatch;
+ private final boolean inWait;
- private SetHandledAndFlushWithoutContentHandler(boolean throwException, boolean dispatch)
+ private SetHandledAndFlushWithoutContentHandler(boolean throwException, boolean dispatch, boolean inWait)
{
super(throwException);
this.dispatch = dispatch;
+ this.inWait = inWait;
}
@Override
@@ -381,25 +443,21 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
{
final AsyncContext asyncContext = baseRequest.startAsync();
request.setAttribute(contextAttribute, asyncContext);
- new Thread(new Runnable()
+ runAsync(baseRequest, inWait, () ->
{
- @Override
- public void run()
+ try
{
- try
- {
- asyncContext.getResponse().flushBuffer();
- if (dispatch)
- asyncContext.dispatch();
- else
- asyncContext.complete();
- }
- catch (IOException e)
- {
- markFailed(e);
- }
+ asyncContext.getResponse().flushBuffer();
+ if (dispatch)
+ asyncContext.dispatch();
+ else
+ asyncContext.complete();
}
- }).run();
+ catch (IOException e)
+ {
+ markFailed(e);
+ }
+ });
}
baseRequest.setHandled(true);
super.doNonErrorHandle(target, baseRequest, request, response);
@@ -408,16 +466,16 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
@ParameterizedTest
@MethodSource("httpVersion")
- public void testWriteFlushWriteMore(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testWriteFlushWriteMore(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- WriteFlushWriteMoreHandler handler = new WriteFlushWriteMoreHandler(false, dispatch);
+ WriteFlushWriteMoreHandler handler = new WriteFlushWriteMoreHandler(false, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- assertThat("response code", response.getStatus(), is(200));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ assertThat(response.getStatus(), is(200));
+ assertThat(handler.failure(), is(nullValue()));
// HTTP/1.0 does not do chunked. it will just send content and close
if (httpVersion.is("HTTP/1.1"))
@@ -426,28 +484,39 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
@ParameterizedTest
@MethodSource("httpVersion")
- public void testWriteFlushWriteMoreAndThrow(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testWriteFlushWriteMoreAndThrow(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- WriteFlushWriteMoreHandler handler = new WriteFlushWriteMoreHandler(true, dispatch);
+ WriteFlushWriteMoreHandler handler = new WriteFlushWriteMoreHandler(true, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- assertThat("response code", response.getStatus(), is(200));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
- if (httpVersion.is("HTTP/1.1"))
- assertThat(response, containsHeaderValue(HttpHeader.TRANSFER_ENCODING, "chunked"));
+ if (inWait)
+ {
+ // The exception is thrown before we do any writing or async operations, so it delivered as onError and then
+ // dispatched.
+ assertThat(response.getStatus(), is(500));
+ }
+ else
+ {
+ assertThat(response.getStatus(), is(200));
+ if (httpVersion.is("HTTP/1.1"))
+ assertThat(response, containsHeaderValue(HttpHeader.TRANSFER_ENCODING, "chunked"));
+ }
+ assertThat(handler.failure(), is(nullValue()));
}
private class WriteFlushWriteMoreHandler extends ThrowExceptionOnDemandHandler
{
private final boolean dispatch;
+ private final boolean inWait;
- private WriteFlushWriteMoreHandler(boolean throwException, boolean dispatch)
+ private WriteFlushWriteMoreHandler(boolean throwException, boolean dispatch, boolean inWait)
{
super(throwException);
this.dispatch = dispatch;
+ this.inWait = inWait;
}
@Override
@@ -457,28 +526,24 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
{
final AsyncContext asyncContext = baseRequest.startAsync();
request.setAttribute(contextAttribute, asyncContext);
- new Thread(new Runnable()
+ runAsync(baseRequest, inWait, () ->
{
- @Override
- public void run()
+ try
{
- try
- {
- ServletResponse asyncContextResponse = asyncContext.getResponse();
- asyncContextResponse.getWriter().write("foo");
- asyncContextResponse.flushBuffer();
- asyncContextResponse.getWriter().write("bar");
- if (dispatch)
- asyncContext.dispatch();
- else
- asyncContext.complete();
- }
- catch (IOException e)
- {
- markFailed(e);
- }
+ ServletResponse asyncContextResponse = asyncContext.getResponse();
+ asyncContextResponse.getWriter().write("foo");
+ asyncContextResponse.flushBuffer();
+ asyncContextResponse.getWriter().write("bar");
+ if (dispatch)
+ asyncContext.dispatch();
+ else
+ asyncContext.complete();
}
- }).run();
+ catch (IOException e)
+ {
+ markFailed(e);
+ }
+ });
}
baseRequest.setHandled(true);
super.doNonErrorHandle(target, baseRequest, request, response);
@@ -487,47 +552,58 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
@ParameterizedTest
@MethodSource("httpVersion")
- public void testBufferOverflow(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testBufferOverflow(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- OverflowHandler handler = new OverflowHandler(false, dispatch);
+ OverflowHandler handler = new OverflowHandler(false, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- assertThat("response code", response.getStatus(), is(200));
+ assertThat(response.getStatus(), is(200));
assertThat(response.getContent(), is("foobar"));
if (httpVersion.is("HTTP/1.1"))
assertThat(response, containsHeaderValue(HttpHeader.TRANSFER_ENCODING, "chunked"));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ assertThat(handler.failure(), is(nullValue()));
}
@ParameterizedTest
@MethodSource("httpVersion")
- public void testBufferOverflowAndThrow(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testBufferOverflowAndThrow(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- OverflowHandler handler = new OverflowHandler(true, dispatch);
+ OverflowHandler handler = new OverflowHandler(true, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- // Buffer size is too small, so the content is written directly producing a 200 response
- assertThat("response code", response.getStatus(), is(200));
- assertThat(response.getContent(), is("foobar"));
- if (httpVersion.is("HTTP/1.1"))
- assertThat(response, containsHeaderValue(HttpHeader.TRANSFER_ENCODING, "chunked"));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ // Buffer size smaller than content, so writing will commit response.
+ // If this happens before the exception is thrown we get a 200, else a 500 is produced
+ if (inWait)
+ {
+ assertThat(response.getStatus(), is(500));
+ assertThat(response.getContent(), containsString("TestCommitException: Thrown by test"));
+ }
+ else
+ {
+ assertThat(response.getStatus(), is(200));
+ assertThat(response.getContent(), is("foobar"));
+ if (httpVersion.is("HTTP/1.1"))
+ assertThat(response, containsHeaderValue(HttpHeader.TRANSFER_ENCODING, "chunked"));
+ assertThat(handler.failure(), is(nullValue()));
+ }
}
private class OverflowHandler extends ThrowExceptionOnDemandHandler
{
private final boolean dispatch;
+ private final boolean inWait;
- private OverflowHandler(boolean throwException, boolean dispatch)
+ private OverflowHandler(boolean throwException, boolean dispatch, boolean inWait)
{
super(throwException);
this.dispatch = dispatch;
+ this.inWait = inWait;
}
@Override
@@ -537,27 +613,23 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
{
final AsyncContext asyncContext = baseRequest.startAsync();
request.setAttribute(contextAttribute, asyncContext);
- new Thread(new Runnable()
+ runAsync(baseRequest, inWait, () ->
{
- @Override
- public void run()
+ try
{
- try
- {
- ServletResponse asyncContextResponse = asyncContext.getResponse();
- asyncContextResponse.setBufferSize(3);
- asyncContextResponse.getWriter().write("foobar");
- if (dispatch)
- asyncContext.dispatch();
- else
- asyncContext.complete();
- }
- catch (IOException e)
- {
- markFailed(e);
- }
+ ServletResponse asyncContextResponse = asyncContext.getResponse();
+ asyncContextResponse.setBufferSize(3);
+ asyncContextResponse.getWriter().write("foobar");
+ if (dispatch)
+ asyncContext.dispatch();
+ else
+ asyncContext.complete();
}
- }).run();
+ catch (IOException e)
+ {
+ markFailed(e);
+ }
+ });
}
baseRequest.setHandled(true);
super.doNonErrorHandle(target, baseRequest, request, response);
@@ -566,45 +638,54 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
@ParameterizedTest
@MethodSource("httpVersion")
- public void testSetContentLengthAndWriteExactlyThatAmountOfBytes(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testSetContentLengthAndWriteExactlyThatAmountOfBytes(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- SetContentLengthAndWriteThatAmountOfBytesHandler handler = new SetContentLengthAndWriteThatAmountOfBytesHandler(false, dispatch);
+ SetContentLengthAndWriteThatAmountOfBytesHandler handler = new SetContentLengthAndWriteThatAmountOfBytesHandler(false, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- assertThat("response code", response.getStatus(), is(200));
- assertThat("response body", response.getContent(), is("foo"));
+ assertThat(response.getStatus(), is(200));
+ assertThat(response.getContent(), is("foo"));
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_LENGTH, "3"));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ assertThat(handler.failure(), is(nullValue()));
}
@ParameterizedTest
@MethodSource("httpVersion")
- public void testSetContentLengthAndWriteExactlyThatAmountOfBytesAndThrow(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testSetContentLengthAndWriteExactlyThatAmountOfBytesAndThrow(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- SetContentLengthAndWriteThatAmountOfBytesHandler handler = new SetContentLengthAndWriteThatAmountOfBytesHandler(true, dispatch);
+ SetContentLengthAndWriteThatAmountOfBytesHandler handler = new SetContentLengthAndWriteThatAmountOfBytesHandler(true, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- //TODO: should we expect 500 here?
- assertThat("response code", response.getStatus(), is(200));
- assertThat("response body", response.getContent(), is("foo"));
- assertThat(response, containsHeaderValue(HttpHeader.CONTENT_LENGTH, "3"));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ if (inWait)
+ {
+ // too late!
+ assertThat(response.getStatus(), is(500));
+ }
+ else
+ {
+ assertThat(response.getStatus(), is(200));
+ assertThat(response.getContent(), is("foo"));
+ assertThat(response, containsHeaderValue(HttpHeader.CONTENT_LENGTH, "3"));
+ }
+ assertThat(handler.failure(), is(nullValue()));
}
private class SetContentLengthAndWriteThatAmountOfBytesHandler extends ThrowExceptionOnDemandHandler
{
private final boolean dispatch;
+ private final boolean inWait;
- private SetContentLengthAndWriteThatAmountOfBytesHandler(boolean throwException, boolean dispatch)
+ private SetContentLengthAndWriteThatAmountOfBytesHandler(boolean throwException, boolean dispatch, boolean inWait)
{
super(throwException);
this.dispatch = dispatch;
+ this.inWait = inWait;
}
@Override
@@ -614,27 +695,23 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
{
final AsyncContext asyncContext = baseRequest.startAsync();
request.setAttribute(contextAttribute, asyncContext);
- new Thread(new Runnable()
+ runAsync(baseRequest, inWait, () ->
{
- @Override
- public void run()
+ try
{
- try
- {
- ServletResponse asyncContextResponse = asyncContext.getResponse();
- asyncContextResponse.setContentLength(3);
- asyncContextResponse.getWriter().write("foo");
- if (dispatch)
- asyncContext.dispatch();
- else
- asyncContext.complete();
- }
- catch (IOException e)
- {
- markFailed(e);
- }
+ ServletResponse asyncContextResponse = asyncContext.getResponse();
+ asyncContextResponse.setContentLength(3);
+ asyncContextResponse.getWriter().write("foo");
+ if (dispatch)
+ asyncContext.dispatch();
+ else
+ asyncContext.complete();
}
- }).run();
+ catch (IOException e)
+ {
+ markFailed(e);
+ }
+ });
}
baseRequest.setHandled(true);
super.doNonErrorHandle(target, baseRequest, request, response);
@@ -643,46 +720,55 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
@ParameterizedTest
@MethodSource("httpVersion")
- public void testSetContentLengthAndWriteMoreBytes(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testSetContentLengthAndWriteMoreBytes(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- SetContentLengthAndWriteMoreBytesHandler handler = new SetContentLengthAndWriteMoreBytesHandler(false, dispatch);
+ SetContentLengthAndWriteMoreBytesHandler handler = new SetContentLengthAndWriteMoreBytesHandler(false, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- assertThat("response code", response.getStatus(), is(200));
+ assertThat(response.getStatus(), is(200));
// jetty truncates the body when content-length is reached.! This is correct and desired behaviour?
- assertThat("response body", response.getContent(), is("foo"));
+ assertThat(response.getContent(), is("foo"));
assertThat(response, containsHeaderValue(HttpHeader.CONTENT_LENGTH, "3"));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ assertThat(handler.failure(), is(nullValue()));
}
@ParameterizedTest
@MethodSource("httpVersion")
- public void testSetContentLengthAndWriteMoreAndThrow(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testSetContentLengthAndWriteMoreAndThrow(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- SetContentLengthAndWriteMoreBytesHandler handler = new SetContentLengthAndWriteMoreBytesHandler(true, dispatch);
+ SetContentLengthAndWriteMoreBytesHandler handler = new SetContentLengthAndWriteMoreBytesHandler(true, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- // TODO: we throw before response is committed. should we expect 500?
- assertThat("response code", response.getStatus(), is(200));
- assertThat("response body", response.getContent(), is("foo"));
- assertThat(response, containsHeaderValue(HttpHeader.CONTENT_LENGTH, "3"));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ if (inWait)
+ {
+ // too late!
+ assertThat(response.getStatus(), is(500));
+ }
+ else
+ {
+ assertThat(response.getStatus(), is(200));
+ assertThat(response.getContent(), is("foo"));
+ assertThat(response, containsHeaderValue(HttpHeader.CONTENT_LENGTH, "3"));
+ }
+ assertThat(handler.failure(), is(nullValue()));
}
private class SetContentLengthAndWriteMoreBytesHandler extends ThrowExceptionOnDemandHandler
{
private final boolean dispatch;
+ private final boolean inWait;
- private SetContentLengthAndWriteMoreBytesHandler(boolean throwException, boolean dispatch)
+ private SetContentLengthAndWriteMoreBytesHandler(boolean throwException, boolean dispatch, boolean inWait)
{
super(throwException);
this.dispatch = dispatch;
+ this.inWait = inWait;
}
@Override
@@ -692,27 +778,23 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
{
final AsyncContext asyncContext = baseRequest.startAsync();
request.setAttribute(contextAttribute, asyncContext);
- new Thread(new Runnable()
+ runAsync(baseRequest, inWait, () ->
{
- @Override
- public void run()
+ try
{
- try
- {
- ServletResponse asyncContextResponse = asyncContext.getResponse();
- asyncContextResponse.setContentLength(3);
- asyncContextResponse.getWriter().write("foobar");
- if (dispatch)
- asyncContext.dispatch();
- else
- asyncContext.complete();
- }
- catch (IOException e)
- {
- markFailed(e);
- }
+ ServletResponse asyncContextResponse = asyncContext.getResponse();
+ asyncContextResponse.setContentLength(3);
+ asyncContextResponse.getWriter().write("foobar");
+ if (dispatch)
+ asyncContext.dispatch();
+ else
+ asyncContext.complete();
}
- }).run();
+ catch (IOException e)
+ {
+ markFailed(e);
+ }
+ });
}
baseRequest.setHandled(true);
super.doNonErrorHandle(target, baseRequest, request, response);
@@ -721,41 +803,50 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
@ParameterizedTest
@MethodSource("httpVersion")
- public void testWriteAndSetContentLength(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testWriteAndSetContentLength(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- WriteAndSetContentLengthHandler handler = new WriteAndSetContentLengthHandler(false, dispatch);
+ WriteAndSetContentLengthHandler handler = new WriteAndSetContentLengthHandler(false, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- assertThat("response code", response.getStatus(), is(200));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ assertThat(response.getStatus(), is(200));
+ assertThat(handler.failure(), is(nullValue()));
//TODO: jetty ignores setContentLength and sends transfer-encoding header. Correct?
}
@ParameterizedTest
@MethodSource("httpVersion")
- public void testWriteAndSetContentLengthAndThrow(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testWriteAndSetContentLengthAndThrow(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- WriteAndSetContentLengthHandler handler = new WriteAndSetContentLengthHandler(true, dispatch);
+ WriteAndSetContentLengthHandler handler = new WriteAndSetContentLengthHandler(true, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
-
- assertThat("response code", response.getStatus(), is(200));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ if (inWait)
+ {
+ // too late
+ assertThat(response.getStatus(), is(500));
+ }
+ else
+ {
+ assertThat(response.getStatus(), is(200));
+ }
+ assertThat(handler.failure(), is(nullValue()));
}
private class WriteAndSetContentLengthHandler extends ThrowExceptionOnDemandHandler
{
private final boolean dispatch;
+ private final boolean inWait;
- private WriteAndSetContentLengthHandler(boolean throwException, boolean dispatch)
+ private WriteAndSetContentLengthHandler(boolean throwException, boolean dispatch, boolean inWait)
{
super(throwException);
this.dispatch = dispatch;
+ this.inWait = inWait;
}
@Override
@@ -765,27 +856,23 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
{
final AsyncContext asyncContext = baseRequest.startAsync();
request.setAttribute(contextAttribute, asyncContext);
- new Thread(new Runnable()
+ runAsync(baseRequest, inWait, () ->
{
- @Override
- public void run()
+ try
{
- try
- {
- ServletResponse asyncContextResponse = asyncContext.getResponse();
- asyncContextResponse.getWriter().write("foo");
- asyncContextResponse.setContentLength(3); // This should commit the response
- if (dispatch)
- asyncContext.dispatch();
- else
- asyncContext.complete();
- }
- catch (IOException e)
- {
- markFailed(e);
- }
+ ServletResponse asyncContextResponse = asyncContext.getResponse();
+ asyncContextResponse.getWriter().write("foo");
+ asyncContextResponse.setContentLength(3); // This should commit the response
+ if (dispatch)
+ asyncContext.dispatch();
+ else
+ asyncContext.complete();
}
- }).run();
+ catch (IOException e)
+ {
+ markFailed(e);
+ }
+ });
}
baseRequest.setHandled(true);
super.doNonErrorHandle(target, baseRequest, request, response);
@@ -794,42 +881,52 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
@ParameterizedTest
@MethodSource("httpVersion")
- public void testWriteAndSetContentLengthTooSmall(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testWriteAndSetContentLengthTooSmall(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- WriteAndSetContentLengthTooSmallHandler handler = new WriteAndSetContentLengthTooSmallHandler(false, dispatch);
+ WriteAndSetContentLengthTooSmallHandler handler = new WriteAndSetContentLengthTooSmallHandler(false, dispatch, inWait);
server.setHandler(handler);
server.start();
HttpTester.Response response = executeRequest(httpVersion);
- // Setting a content-length too small throws an IllegalStateException
- assertThat("response code", response.getStatus(), is(500));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ // Setting a content-length too small throws an IllegalStateException,
+ // but only in the async handler, which completes or dispatches anyway
+ assertThat(response.getStatus(), is(200));
+ assertThat(handler.failure(), not(is(nullValue())));
}
@ParameterizedTest
@MethodSource("httpVersion")
- public void testWriteAndSetContentLengthTooSmallAndThrow(HttpVersion httpVersion, boolean dispatch) throws Exception
+ public void testWriteAndSetContentLengthTooSmallAndThrow(HttpVersion httpVersion, boolean dispatch, boolean inWait) throws Exception
{
- WriteAndSetContentLengthTooSmallHandler handler = new WriteAndSetContentLengthTooSmallHandler(true, dispatch);
+ WriteAndSetContentLengthTooSmallHandler handler = new WriteAndSetContentLengthTooSmallHandler(true, dispatch, inWait);
server.setHandler(handler);
server.start();
- HttpTester.Response response = executeRequest(httpVersion);
+ HttpTester.Response response;
+ try (StacklessLogging stackless = new StacklessLogging(HttpChannelState.class))
+ {
+ response = executeRequest(httpVersion);
+ }
- // Setting a content-length too small throws an IllegalStateException
- assertThat("response code", response.getStatus(), is(500));
- assertThat("no exceptions", handler.failure(), is(nullValue()));
+ assertThat(response.getStatus(), is(500));
+
+ if (!inWait)
+ assertThat(handler.failure(), not(is(nullValue())));
+ else
+ assertThat(handler.failure(), is(nullValue()));
}
private class WriteAndSetContentLengthTooSmallHandler extends ThrowExceptionOnDemandHandler
{
private final boolean dispatch;
+ private final boolean inWait;
- private WriteAndSetContentLengthTooSmallHandler(boolean throwException, boolean dispatch)
+ private WriteAndSetContentLengthTooSmallHandler(boolean throwException, boolean dispatch, boolean inWait)
{
super(throwException);
this.dispatch = dispatch;
+ this.inWait = inWait;
}
@Override
@@ -839,30 +936,93 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
{
final AsyncContext asyncContext = baseRequest.startAsync();
request.setAttribute(contextAttribute, asyncContext);
- new Thread(new Runnable()
+ runAsync(baseRequest, inWait, () ->
{
- @Override
- public void run()
+ try
{
- try
- {
- ServletResponse asyncContextResponse = asyncContext.getResponse();
- asyncContextResponse.getWriter().write("foobar");
- asyncContextResponse.setContentLength(3);
- if (dispatch)
- asyncContext.dispatch();
- else
- asyncContext.complete();
- }
- catch (IOException e)
- {
- markFailed(e);
- }
+ ServletResponse asyncContextResponse = asyncContext.getResponse();
+ asyncContextResponse.getWriter().write("foobar");
+ asyncContextResponse.setContentLength(3);
}
- }).run();
+ catch (Throwable e)
+ {
+ markFailed(e);
+ if (dispatch)
+ asyncContext.dispatch();
+ else
+ asyncContext.complete();
+ }
+ });
}
baseRequest.setHandled(true);
super.doNonErrorHandle(target, baseRequest, request, response);
}
}
+
+ private void runAsyncInAsyncWait(Request request, Runnable task)
+ {
+ server.getThreadPool().execute(() ->
+ {
+ long end = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
+ try
+ {
+ while (System.nanoTime() < end)
+ {
+ switch (request.getHttpChannelState().getState())
+ {
+ case WAITING:
+ task.run();
+ return;
+
+ case HANDLING:
+ Thread.sleep(100);
+ continue;
+
+ default:
+ request.getHttpChannel().abort(new IllegalStateException());
+ return;
+ }
+ }
+ request.getHttpChannel().abort(new TimeoutException());
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ });
+ }
+
+ private void runAsyncWhileDispatched(Runnable task)
+ {
+ CountDownLatch ran = new CountDownLatch(1);
+
+ server.getThreadPool().execute(() ->
+ {
+ try
+ {
+ task.run();
+ }
+ finally
+ {
+ ran.countDown();
+ }
+ });
+
+ try
+ {
+ ran.await(10, TimeUnit.SECONDS);
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void runAsync(Request request, boolean inWait, Runnable task)
+ {
+ if (inWait)
+ runAsyncInAsyncWait(request, task);
+ else
+ runAsyncWhileDispatched(task);
+ }
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
index 3102db8873d..fd687f3b7a5 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
@@ -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 = "\n" +
- "\n" +
- " ";
+ "\n" +
+ " ";
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
" 73 \n" +
" \n" +
" \n" +
- "\n";
+ "\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,27 +448,22 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
{
OutputStream os = client.getOutputStream();
- //@checkstyle-disable-check : IllegalTokenText
- os.write(("GET /R2 HTTP/1.1\015\012" +
- "Host: localhost\015\012" +
- "Transfer-Encoding: chunked\015\012" +
- "Content-Type: text/plain\015\012" +
- "Connection: close\015\012" +
- "\015\012").getBytes());
- //@checkstyle-enable-check : IllegalTokenText
+
+ os.write(("GET /R2 HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Transfer-Encoding: chunked\r\n" +
+ "Content-Type: text/plain\r\n" +
+ "Connection: close\r\n" +
+ "\r\n").getBytes());
os.flush();
Thread.sleep(1000);
os.write(("5").getBytes());
Thread.sleep(1000);
- //@checkstyle-disable-check : IllegalTokenText
- os.write(("\015\012").getBytes());
- //@checkstyle-enable-check : IllegalTokenText
+ os.write(("\r\n").getBytes());
os.flush();
Thread.sleep(1000);
- //@checkstyle-disable-check : IllegalTokenText
- os.write(("ABCDE\015\012" +
- "0;\015\012\015\012").getBytes());
- //@checkstyle-enable-check : IllegalTokenText
+ os.write(("ABCDE\r\n" +
+ "0;\r\n\r\n").getBytes());
os.flush();
// Read the response.
@@ -472,14 +481,14 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
{
OutputStream os = client.getOutputStream();
//@checkstyle-disable-check : IllegalTokenText
- os.write(("GET /R2 HTTP/1.1\015\012" +
- "Host: localhost\015\012" +
- "Content-Length: 5\015\012" +
- "Content-Type: text/plain\015\012" +
- "Connection: close\015\012" +
- "\015\012" +
- "ABCDE\015\012" +
- "\015\012"
+ os.write(("GET /R2 HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Content-Length: 5\r\n" +
+ "Content-Type: text/plain\r\n" +
+ "Connection: close\r\n" +
+ "\r\n" +
+ "ABCDE\r\n" +
+ "\r\n"
//@checkstyle-enable-check : IllegalTokenText
).getBytes());
os.flush();
@@ -1136,26 +1145,26 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
//@checkstyle-disable-check : IllegalTokenText
os.write((
- "POST /R1 HTTP/1.1\015\012" +
+ "POST /R1 HTTP/1.1\r\n" +
"Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
"content-type: text/plain; charset=utf-8\r\n" +
"content-length: 10\r\n" +
- "\015\012" +
+ "\r\n" +
"123456789\n" +
- "HEAD /R2 HTTP/1.1\015\012" +
- "Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\015\012" +
+ "HEAD /R2 HTTP/1.1\r\n" +
+ "Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
"content-type: text/plain; charset=utf-8\r\n" +
"content-length: 10\r\n" +
- "\015\012" +
+ "\r\n" +
"ABCDEFGHI\n" +
- "POST /R3 HTTP/1.1\015\012" +
- "Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\015\012" +
+ "POST /R3 HTTP/1.1\r\n" +
+ "Host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
"content-type: text/plain; charset=utf-8\r\n" +
"content-length: 10\r\n" +
- "Connection: close\015\012" +
- "\015\012" +
+ "Connection: close\r\n" +
+ "\r\n" +
"abcdefghi\n"
//@checkstyle-enable-check : IllegalTokenText
).getBytes(StandardCharsets.ISO_8859_1));
@@ -1553,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++)
{
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java
index 96a17208dd5..bd009b30d24 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java
@@ -40,7 +40,8 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
public class HttpServerTestFixture
-{ // Useful constants
+{
+ // Useful constants
protected static final long PAUSE = 10L;
protected static final int LOOPS = 50;
@@ -186,6 +187,31 @@ public class HttpServerTestFixture
}
}
+
+ protected static class SendErrorHandler extends AbstractHandler
+ {
+ private final int code;
+ private final String message;
+
+ public SendErrorHandler()
+ {
+ this(500, null);
+ }
+
+ public SendErrorHandler(int code, String message)
+ {
+ this.code = code;
+ this.message = message;
+ }
+
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ response.sendError(code, message);
+ }
+ }
+
protected static class ReadExactHandler extends AbstractHandler.ErrorDispatchHandler
{
private int expected;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/LocalAsyncContextTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/LocalAsyncContextTest.java
index 81c89ae2331..001b7438437 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/LocalAsyncContextTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/LocalAsyncContextTest.java
@@ -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)
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
index a872ebdc824..4cb4f63a48a 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
@@ -35,6 +35,8 @@ import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import java.util.stream.Stream;
+import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
@@ -70,6 +72,8 @@ import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -664,65 +668,41 @@ public class ResponseTest
assertEquals("foo/bar; other=pq charset=utf-8 other=xyz;charset=utf-16", response.getContentType());
}
- @Test
- public void testStatusCodes() throws Exception
+ public static Stream sendErrorTestCodes()
{
- Response response = getResponse();
-
- response.sendError(404);
- assertEquals(404, response.getStatus());
- assertEquals("Not Found", response.getReason());
-
- response = getResponse();
-
- response.sendError(500, "Database Error");
- assertEquals(500, response.getStatus());
- assertEquals("Database Error", response.getReason());
- assertEquals("must-revalidate,no-cache,no-store", response.getHeader(HttpHeader.CACHE_CONTROL.asString()));
-
- response = getResponse();
-
- response.setStatus(200);
- assertEquals(200, response.getStatus());
- assertEquals(null, response.getReason());
-
- response = getResponse();
-
- response.sendError(406, "Super Nanny");
- assertEquals(406, response.getStatus());
- assertEquals("Super Nanny", response.getReason());
- assertEquals("must-revalidate,no-cache,no-store", response.getHeader(HttpHeader.CACHE_CONTROL.asString()));
+ List data = new ArrayList<>();
+ data.add(new Object[]{404, null, "Not Found"});
+ data.add(new Object[]{500, "Database Error", "Database Error"});
+ data.add(new Object[]{406, "Super Nanny", "Super Nanny"});
+ return data.stream();
}
- @Test
- public void testStatusCodesNoErrorHandler() throws Exception
+ @ParameterizedTest
+ @MethodSource(value = "sendErrorTestCodes")
+ public void testStatusCodes(int code, String message, String expectedMessage) throws Exception
+ {
+ Response response = getResponse();
+ assertThat(response.getHttpChannel().getState().handling(), is(HttpChannelState.Action.DISPATCH));
+
+ if (message == null)
+ response.sendError(code);
+ else
+ response.sendError(code, message);
+
+ assertTrue(response.getHttpOutput().isClosed());
+ assertEquals(code, response.getStatus());
+ assertEquals(null, response.getReason());
+ assertEquals(expectedMessage, response.getHttpChannel().getRequest().getAttribute(RequestDispatcher.ERROR_MESSAGE));
+ assertThat(response.getHttpChannel().getState().unhandle(), is(HttpChannelState.Action.SEND_ERROR));
+ assertThat(response.getHttpChannel().getState().unhandle(), is(HttpChannelState.Action.COMPLETE));
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = "sendErrorTestCodes")
+ public void testStatusCodesNoErrorHandler(int code, String message, String expectedMessage) throws Exception
{
_server.removeBean(_server.getBean(ErrorHandler.class));
- Response response = getResponse();
-
- response.sendError(404);
- assertEquals(404, response.getStatus());
- assertEquals("Not Found", response.getReason());
-
- response = getResponse();
-
- response.sendError(500, "Database Error");
- assertEquals(500, response.getStatus());
- assertEquals("Database Error", response.getReason());
- assertThat(response.getHeader(HttpHeader.CACHE_CONTROL.asString()), Matchers.nullValue());
-
- response = getResponse();
-
- response.setStatus(200);
- assertEquals(200, response.getStatus());
- assertEquals(null, response.getReason());
-
- response = getResponse();
-
- response.sendError(406, "Super Nanny");
- assertEquals(406, response.getStatus());
- assertEquals("Super Nanny", response.getReason());
- assertThat(response.getHeader(HttpHeader.CACHE_CONTROL.asString()), Matchers.nullValue());
+ testStatusCodes(code, message, expectedMessage);
}
@Test
@@ -898,7 +878,7 @@ public class ResponseTest
assertTrue(!response.isCommitted());
assertTrue(!writer.checkError());
writer.print("");
- assertTrue(!writer.checkError());
+ // assertTrue(!writer.checkError()); // TODO check if this is correct? checkout does an open check and the print above closes
assertTrue(response.isCommitted());
}
@@ -1032,7 +1012,7 @@ public class ResponseTest
}
@Test
- public void testCookiesWithReset() throws Exception
+ public void testResetContent() throws Exception
{
Response response = getResponse();
@@ -1048,9 +1028,27 @@ public class ResponseTest
cookie2.setPath("/path");
response.addCookie(cookie2);
- //keep the cookies
- response.reset(true);
+ response.setContentType("some/type");
+ response.setContentLength(3);
+ response.setHeader(HttpHeader.EXPIRES,"never");
+ response.setHeader("SomeHeader", "SomeValue");
+
+ response.getOutputStream();
+
+ // reset the content
+ response.resetContent();
+
+ // check content is nulled
+ assertThat(response.getContentType(), nullValue());
+ assertThat(response.getContentLength(), is(-1L));
+ assertThat(response.getHeader(HttpHeader.EXPIRES.asString()), nullValue());
+ response.getWriter();
+
+ // check arbitrary header still set
+ assertThat(response.getHeader("SomeHeader"), is("SomeValue"));
+
+ // check cookies are still there
Enumeration set = response.getHttpFields().getValues("Set-Cookie");
assertNotNull(set);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java
index 6964b7c9ef9..e854f3f44f1 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java
@@ -37,6 +37,7 @@ import org.eclipse.jetty.server.AbstractNCSARequestLog;
import org.eclipse.jetty.server.CustomRequestLog;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpChannelState;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.RequestLog;
@@ -44,6 +45,7 @@ import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
@@ -390,7 +392,7 @@ public class NcsaRequestLogTest
data.add(new Object[]{logType, new IOExceptionPartialHandler(), "/ioex", "\"GET /ioex HTTP/1.0\" 200"});
data.add(new Object[]{logType, new RuntimeExceptionHandler(), "/rtex", "\"GET /rtex HTTP/1.0\" 500"});
data.add(new Object[]{logType, new BadMessageHandler(), "/bad", "\"GET /bad HTTP/1.0\" 499"});
- data.add(new Object[]{logType, new AbortHandler(), "/bad", "\"GET /bad HTTP/1.0\" 488"});
+ data.add(new Object[]{logType, new AbortHandler(), "/bad", "\"GET /bad HTTP/1.0\" 500"});
data.add(new Object[]{logType, new AbortPartialHandler(), "/bad", "\"GET /bad HTTP/1.0\" 200"});
}
@@ -517,7 +519,9 @@ public class NcsaRequestLogTest
startServer();
makeRequest(requestPath);
- expectedLogEntry = "\"GET " + requestPath + " HTTP/1.0\" 200";
+ // If we abort, we can't write a 200 error page
+ if (!(testHandler instanceof AbortHandler))
+ expectedLogEntry = expectedLogEntry.replaceFirst(" [1-9][0-9][0-9]", " 200");
assertRequestLog(expectedLogEntry, _log);
}
@@ -577,6 +581,10 @@ public class NcsaRequestLogTest
{
try
{
+ while (baseRequest.getHttpChannel().getState().getState() != HttpChannelState.State.WAITING)
+ {
+ Thread.sleep(10);
+ }
baseRequest.setHandled(false);
testHandler.handle(target, baseRequest, request, response);
if (!baseRequest.isHandled())
@@ -584,18 +592,21 @@ public class NcsaRequestLogTest
}
catch (BadMessageException bad)
{
- response.sendError(bad.getCode());
+ response.sendError(bad.getCode(), bad.getReason());
}
catch (Exception e)
{
- response.sendError(500);
+ response.sendError(500, e.toString());
}
}
- catch (Throwable th)
+ catch (IOException | IllegalStateException th)
{
- throw new RuntimeException(th);
+ Log.getLog().ignore(th);
+ }
+ finally
+ {
+ ac.complete();
}
- ac.complete();
});
}
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java
index 4dd9dfbfa1a..73d5193f262 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java
@@ -266,6 +266,7 @@ public class SecuredRedirectHandlerTest
{
if (!"/".equals(target))
{
+ baseRequest.setHandled(true);
response.sendError(404);
return;
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java
index 5c68b9ef802..27c5d89251e 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java
@@ -20,7 +20,6 @@ package org.eclipse.jetty.server.ssl;
import java.io.File;
import java.io.FileNotFoundException;
-import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
@@ -42,6 +41,7 @@ import javax.net.ssl.SSLSocketFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
@@ -55,8 +55,8 @@ import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SocketCustomizationListener;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
@@ -65,9 +65,8 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.startsWith;
+import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
public class SniSslConnectionFactoryTest
{
@@ -81,23 +80,23 @@ public class SniSslConnectionFactoryTest
{
_server = new Server();
- HttpConfiguration http_config = new HttpConfiguration();
- http_config.setSecureScheme("https");
- http_config.setSecurePort(8443);
- http_config.setOutputBufferSize(32768);
- _httpsConfiguration = new HttpConfiguration(http_config);
+ HttpConfiguration httpConfig = new HttpConfiguration();
+ httpConfig.setSecureScheme("https");
+ httpConfig.setSecurePort(8443);
+ httpConfig.setOutputBufferSize(32768);
+ _httpsConfiguration = new HttpConfiguration(httpConfig);
SecureRequestCustomizer src = new SecureRequestCustomizer();
src.setSniHostCheck(true);
_httpsConfiguration.addCustomizer(src);
- _httpsConfiguration.addCustomizer((connector, httpConfig, request) ->
+ _httpsConfiguration.addCustomizer((connector, hc, request) ->
{
EndPoint endp = request.getHttpChannel().getEndPoint();
if (endp instanceof SslConnection.DecryptedEndPoint)
{
try
{
- SslConnection.DecryptedEndPoint ssl_endp = (SslConnection.DecryptedEndPoint)endp;
- SslConnection sslConnection = ssl_endp.getSslConnection();
+ SslConnection.DecryptedEndPoint sslEndp = (SslConnection.DecryptedEndPoint)endp;
+ SslConnection sslConnection = sslEndp.getSslConnection();
SSLEngine sslEngine = sslConnection.getSSLEngine();
SSLSession session = sslEngine.getSession();
for (Certificate c : session.getLocalCertificates())
@@ -224,6 +223,7 @@ public class SniSslConnectionFactoryTest
public void testSameConnectionRequestsForManyDomains() throws Exception
{
start("src/test/resources/keystore_sni.p12");
+ _server.setErrorHandler(new ErrorHandler());
SslContextFactory clientContextFactory = new SslContextFactory.Client(true);
clientContextFactory.start();
@@ -246,8 +246,8 @@ public class SniSslConnectionFactoryTest
output.flush();
InputStream input = sslSocket.getInputStream();
- String response = response(input);
- assertTrue(response.startsWith("HTTP/1.1 200 "));
+ HttpTester.Response response = HttpTester.parseResponse(input);
+ assertThat(response.getStatus(), is(200));
// Same socket, send a request for a different domain but same alias.
request =
@@ -256,9 +256,8 @@ public class SniSslConnectionFactoryTest
"\r\n";
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
-
- response = response(input);
- assertTrue(response.startsWith("HTTP/1.1 200 "));
+ response = HttpTester.parseResponse(input);
+ assertThat(response.getStatus(), is(200));
// Same socket, send a request for a different domain but different alias.
request =
@@ -268,9 +267,9 @@ public class SniSslConnectionFactoryTest
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
- response = response(input);
- assertThat(response, startsWith("HTTP/1.1 400 "));
- assertThat(response, containsString("Host does not match SNI"));
+ response = HttpTester.parseResponse(input);
+ assertThat(response.getStatus(), is(400));
+ assertThat(response.getContent(), containsString("Host does not match SNI"));
}
finally
{
@@ -303,8 +302,8 @@ public class SniSslConnectionFactoryTest
output.flush();
InputStream input = sslSocket.getInputStream();
- String response = response(input);
- assertTrue(response.startsWith("HTTP/1.1 200 "));
+ HttpTester.Response response = HttpTester.parseResponse(input);
+ assertThat(response.getStatus(), is(200));
// Now, on the same socket, send a request for a different valid domain.
request =
@@ -314,8 +313,8 @@ public class SniSslConnectionFactoryTest
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
- response = response(input);
- assertTrue(response.startsWith("HTTP/1.1 200 "));
+ response = HttpTester.parseResponse(input);
+ assertThat(response.getStatus(), is(200));
// Now make a request for an invalid domain for this connection.
request =
@@ -325,9 +324,9 @@ public class SniSslConnectionFactoryTest
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
- response = response(input);
- assertTrue(response.startsWith("HTTP/1.1 400 "));
- assertThat(response, Matchers.containsString("Host does not match SNI"));
+ response = HttpTester.parseResponse(input);
+ assertThat(response.getStatus(), is(400));
+ assertThat(response.getContent(), containsString("Host does not match SNI"));
}
finally
{
@@ -335,22 +334,6 @@ public class SniSslConnectionFactoryTest
}
}
- private String response(InputStream input) throws IOException
- {
- Utf8StringBuilder buffer = new Utf8StringBuilder();
- int crlfs = 0;
- while (true)
- {
- int read = input.read();
- assertTrue(read >= 0);
- buffer.append((byte)read);
- crlfs = (read == '\r' || read == '\n') ? crlfs + 1 : 0;
- if (crlfs == 4)
- break;
- }
- return buffer.toString();
- }
-
private String getResponse(String host, String cn) throws Exception
{
String response = getResponse(host, host, cn);
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
index 1ae360927b5..6cc49bd60da 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
@@ -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
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncListenerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncListenerTest.java
index 50fc3a3f2b1..6d6bb5824ed 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncListenerTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncListenerTest.java
@@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
+import org.eclipse.jetty.io.QuietException;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.QuietServletException;
import org.eclipse.jetty.server.Server;
@@ -42,6 +43,7 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class AsyncListenerTest
@@ -140,7 +142,7 @@ public class AsyncListenerTest
test_StartAsync_Throw_OnError(event ->
{
HttpServletResponse response = (HttpServletResponse)event.getAsyncContext().getResponse();
- response.sendError(HttpStatus.BAD_GATEWAY_502);
+ response.sendError(HttpStatus.BAD_GATEWAY_502, "Message!!!");
});
String httpResponse = connector.getResponse(
"GET /ctx/path HTTP/1.1\r\n" +
@@ -148,7 +150,8 @@ public class AsyncListenerTest
"Connection: close\r\n" +
"\r\n");
assertThat(httpResponse, containsString("HTTP/1.1 502 "));
- assertThat(httpResponse, containsString(TestRuntimeException.class.getName()));
+ assertThat(httpResponse, containsString("Message!!!"));
+ assertThat(httpResponse, not(containsString(TestRuntimeException.class.getName())));
}
@Test
@@ -191,7 +194,7 @@ public class AsyncListenerTest
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
AsyncContext asyncContext = request.startAsync();
- asyncContext.setTimeout(0);
+ asyncContext.setTimeout(10000);
asyncContext.addListener(new AsyncListenerAdapter()
{
@Override
@@ -268,7 +271,8 @@ public class AsyncListenerTest
"Connection: close\r\n" +
"\r\n");
assertThat(httpResponse, containsString("HTTP/1.1 500 "));
- assertThat(httpResponse, containsString(TestRuntimeException.class.getName()));
+ assertThat(httpResponse, containsString("AsyncContext timeout"));
+ assertThat(httpResponse, not(containsString(TestRuntimeException.class.getName())));
}
@Test
@@ -292,6 +296,7 @@ public class AsyncListenerTest
{
HttpServletResponse response = (HttpServletResponse)event.getAsyncContext().getResponse();
response.sendError(HttpStatus.BAD_GATEWAY_502);
+ event.getAsyncContext().complete();
});
String httpResponse = connector.getResponse(
"GET / HTTP/1.1\r\n" +
@@ -384,7 +389,7 @@ public class AsyncListenerTest
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
AsyncContext asyncContext = request.startAsync();
- asyncContext.setTimeout(0);
+ asyncContext.setTimeout(10000);
asyncContext.addListener(new AsyncListenerAdapter()
{
@Override
@@ -447,7 +452,7 @@ public class AsyncListenerTest
}
// Unique named RuntimeException to help during debugging / assertions.
- public static class TestRuntimeException extends RuntimeException
+ public static class TestRuntimeException extends RuntimeException implements QuietException
{
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java
index 3ea4c804437..b80394f61f6 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java
@@ -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 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());
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
index d197aa3a57b..571d8d76a7a 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
@@ -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
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/CustomRequestLogTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/CustomRequestLogTest.java
index a422cf0480f..3c7ba9a7a55 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/CustomRequestLogTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/CustomRequestLogTest.java
@@ -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
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
index 899cc3d0169..0d8db7f6e55 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
@@ -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("Error 598"));
+ assertThat(response, Matchers.containsString("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("Error 598"));
+ assertThat(response, Matchers.containsString("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("Error 598")));
+ assertThat(response, not(Matchers.containsString("HTTP ERROR 598")));
+ assertThat(response, not(Matchers.containsString("/fail/code")));
+ }
+
+ @Test
+ void testNestedSendErrorDoesNotLoop() throws Exception
+ {
+ String response = _connector.getResponse("GET /fail/code?code=597 HTTP/1.0\r\n\r\n");
+ assertThat(response, Matchers.containsString("HTTP/1.1 597 597"));
+ assertThat(response, not(Matchers.containsString("time this error page is being accessed")));
+ }
+
@Test
public void testSendErrorClosedResponse() throws Exception
{
@@ -167,7 +308,7 @@ public class ErrorPageTest
try (StacklessLogging ignore = new StacklessLogging(Dispatcher.class))
{
String response = _connector.getResponse("GET /app?baa=%88%A4 HTTP/1.0\r\n\r\n");
- assertThat(response, Matchers.containsString("HTTP/1.1 400 Bad query encoding"));
+ assertThat(response, Matchers.containsString("HTTP/1.1 400 Bad Request"));
assertThat(response, Matchers.containsString("ERROR_PAGE: /BadMessageException"));
assertThat(response, Matchers.containsString("ERROR_MESSAGE: Bad query encoding"));
assertThat(response, Matchers.containsString("ERROR_CODE: 400"));
@@ -179,6 +320,94 @@ public class ErrorPageTest
}
}
+ @Test
+ public void testAsyncErrorPageDSC() throws Exception
+ {
+ try (StacklessLogging ignore = new StacklessLogging(Dispatcher.class))
+ {
+ String response = _connector.getResponse("GET /async/info?mode=DSC HTTP/1.0\r\n\r\n");
+ assertThat(response, Matchers.containsString("HTTP/1.1 599 599"));
+ assertThat(response, Matchers.containsString("ERROR_PAGE: /599"));
+ assertThat(response, Matchers.containsString("ERROR_CODE: 599"));
+ assertThat(response, Matchers.containsString("ERROR_EXCEPTION: null"));
+ assertThat(response, Matchers.containsString("ERROR_EXCEPTION_TYPE: null"));
+ assertThat(response, Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$AsyncSendErrorServlet-"));
+ assertThat(response, Matchers.containsString("ERROR_REQUEST_URI: /async/info"));
+ assertTrue(__asyncSendErrorCompleted.await(10, TimeUnit.SECONDS));
+ }
+ }
+
+ @Test
+ public void testAsyncErrorPageSDC() throws Exception
+ {
+ try (StacklessLogging ignore = new StacklessLogging(Dispatcher.class))
+ {
+ String response = _connector.getResponse("GET /async/info?mode=SDC HTTP/1.0\r\n\r\n");
+ assertThat(response, Matchers.containsString("HTTP/1.1 599 599"));
+ assertThat(response, Matchers.containsString("ERROR_PAGE: /599"));
+ assertThat(response, Matchers.containsString("ERROR_CODE: 599"));
+ assertThat(response, Matchers.containsString("ERROR_EXCEPTION: null"));
+ assertThat(response, Matchers.containsString("ERROR_EXCEPTION_TYPE: null"));
+ assertThat(response, Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$AsyncSendErrorServlet-"));
+ assertThat(response, Matchers.containsString("ERROR_REQUEST_URI: /async/info"));
+ assertTrue(__asyncSendErrorCompleted.await(10, TimeUnit.SECONDS));
+ }
+ }
+
+ @Test
+ public void testAsyncErrorPageSCD() throws Exception
+ {
+ try (StacklessLogging ignore = new StacklessLogging(Dispatcher.class))
+ {
+ String response = _connector.getResponse("GET /async/info?mode=SCD HTTP/1.0\r\n\r\n");
+ assertThat(response, Matchers.containsString("HTTP/1.1 599 599"));
+ assertThat(response, Matchers.containsString("ERROR_PAGE: /599"));
+ assertThat(response, Matchers.containsString("ERROR_CODE: 599"));
+ assertThat(response, Matchers.containsString("ERROR_EXCEPTION: null"));
+ assertThat(response, Matchers.containsString("ERROR_EXCEPTION_TYPE: null"));
+ assertThat(response, Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$AsyncSendErrorServlet-"));
+ assertThat(response, Matchers.containsString("ERROR_REQUEST_URI: /async/info"));
+ assertTrue(__asyncSendErrorCompleted.await(10, TimeUnit.SECONDS));
+ }
+ }
+
+ @Test
+ public void testNoop() throws Exception
+ {
+ String response = _connector.getResponse("GET /noop/info HTTP/1.0\r\n\r\n");
+ assertThat(response, Matchers.containsString("HTTP/1.1 404 Not Found"));
+ assertThat(response, Matchers.containsString("DISPATCH: ERROR"));
+ assertThat(response, Matchers.containsString("ERROR_PAGE: /GlobalErrorPage"));
+ assertThat(response, Matchers.containsString("ERROR_CODE: 404"));
+ assertThat(response, Matchers.containsString("ERROR_EXCEPTION: null"));
+ assertThat(response, Matchers.containsString("ERROR_EXCEPTION_TYPE: null"));
+ assertThat(response, Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.DefaultServlet-"));
+ assertThat(response, Matchers.containsString("ERROR_REQUEST_URI: /noop/info"));
+ }
+
+ @Test
+ public void testNotEnough() throws Exception
+ {
+ String response = _connector.getResponse("GET /notenough/info HTTP/1.0\r\n\r\n");
+ assertThat(response, Matchers.containsString("HTTP/1.1 500 Server Error"));
+ assertThat(response, Matchers.containsString("DISPATCH: ERROR"));
+ assertThat(response, Matchers.containsString("ERROR_PAGE: /GlobalErrorPage"));
+ assertThat(response, Matchers.containsString("ERROR_CODE: 500"));
+ assertThat(response, Matchers.containsString("ERROR_EXCEPTION: null"));
+ assertThat(response, Matchers.containsString("ERROR_EXCEPTION_TYPE: null"));
+ assertThat(response, Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$NotEnoughServlet-"));
+ assertThat(response, Matchers.containsString("ERROR_REQUEST_URI: /notenough/info"));
+ }
+
+ @Test
+ public void testNotEnoughCommitted() throws Exception
+ {
+ String response = _connector.getResponse("GET /notenough/info?commit=true HTTP/1.0\r\n\r\n");
+ assertThat(response, Matchers.containsString("HTTP/1.1 200 OK"));
+ assertThat(response, Matchers.containsString("Content-Length: 1000"));
+ assertThat(response, Matchers.endsWith("SomeBytes"));
+ }
+
public static class AppServlet extends HttpServlet implements Servlet
{
@Override
@@ -198,6 +427,112 @@ public class ErrorPageTest
}
}
+ public static class SyncSendErrorServlet extends HttpServlet implements Servlet
+ {
+ public static final AtomicInteger COUNTER = new AtomicInteger();
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ int count = COUNTER.incrementAndGet();
+
+ PrintWriter writer = response.getWriter();
+ writer.println("this is the " + count + " time this error page is being accessed");
+ response.sendError(597, "loop #" + count);
+ }
+ }
+
+ public static class AsyncSendErrorServlet extends HttpServlet implements Servlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ try
+ {
+ final CountDownLatch hold = new CountDownLatch(1);
+ final String mode = request.getParameter("mode");
+ switch(mode)
+ {
+ case "DSC":
+ case "SDC":
+ case "SCD":
+ break;
+ default:
+ throw new IllegalStateException(mode);
+ }
+
+ final boolean lateComplete = "true".equals(request.getParameter("latecomplete"));
+ AsyncContext async = request.startAsync();
+ async.start(() ->
+ {
+ try
+ {
+ switch(mode)
+ {
+ case "SDC":
+ response.sendError(599);
+ break;
+ case "SCD":
+ response.sendError(599);
+ async.complete();
+ break;
+ default:
+ break;
+ }
+
+ // Complete after original servlet
+ hold.countDown();
+
+ // Wait until request async waiting
+ while (Request.getBaseRequest(request).getHttpChannelState().getState() == HttpChannelState.State.HANDLING)
+ {
+ try
+ {
+ Thread.sleep(10);
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ try
+ {
+ switch (mode)
+ {
+ case "DSC":
+ response.sendError(599);
+ async.complete();
+ break;
+ case "SDC":
+ async.complete();
+ break;
+ default:
+ break;
+ }
+ }
+ catch(IllegalStateException e)
+ {
+ Log.getLog().ignore(e);
+ }
+ finally
+ {
+ __asyncSendErrorCompleted.countDown();
+ }
+ }
+ catch (IOException e)
+ {
+ Log.getLog().warn(e);
+ }
+ });
+ hold.await();
+ }
+ catch (InterruptedException e)
+ {
+ throw new ServletException(e);
+ }
+ }
+ }
+
public static class FailServlet extends HttpServlet implements Servlet
{
@Override
@@ -225,16 +560,51 @@ public class ErrorPageTest
}
catch (Throwable ignore)
{
- // no opEchoSocket
+ Log.getLog().ignore(ignore);
}
}
}
+ public static class ErrorAndStatusServlet extends HttpServlet implements Servlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ response.sendError(594, "custom get error");
+ response.setStatus(200);
+ }
+ }
+
+ public static class DeleteServlet extends HttpServlet implements Servlet
+ {
+ @Override
+ protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ response.getWriter().append("This shouldn't be seen");
+ response.sendError(595, "custom delete");
+ }
+ }
+
+ public static class NotEnoughServlet extends HttpServlet implements Servlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ response.setContentLength(1000);
+ response.getOutputStream().write("SomeBytes".getBytes(StandardCharsets.UTF_8));
+ if (Boolean.parseBoolean(request.getParameter("commit")))
+ response.flushBuffer();
+ }
+ }
+
public static class ErrorServlet extends HttpServlet implements Servlet
{
@Override
- protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
+ if (request.getDispatcherType() != DispatcherType.ERROR && request.getDispatcherType() != DispatcherType.ASYNC)
+ throw new IllegalStateException("Bad Dispatcher Type " + request.getDispatcherType());
+
PrintWriter writer = response.getWriter();
writer.println("DISPATCH: " + request.getDispatcherType().name());
writer.println("ERROR_PAGE: " + request.getPathInfo());
@@ -247,4 +617,55 @@ public class ErrorPageTest
writer.println("getParameterMap()= " + request.getParameterMap());
}
}
+
+ public static class SingleDispatchFilter implements Filter
+ {
+ ConcurrentMap 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()
+ {
+
+ }
+ }
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java
index cf8e7d59261..1362ac6f4d4 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java
@@ -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();
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
index fcdff1bdec8..d15f57dc3cc 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
@@ -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)
{
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
index a0eceef6307..452c1be848c 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
@@ -440,6 +440,7 @@ public class BufferUtil
*
* @param to Buffer is flush mode
* @param b byte to append
+ * @throws BufferOverflowException if unable to append buffer due to space limits
*/
public static void append(ByteBuffer to, byte b)
{
@@ -1103,20 +1104,20 @@ public class BufferUtil
for (int i = 0; i < buffer.position(); i++)
{
appendContentChar(buf, buffer.get(i));
- if (i == 16 && buffer.position() > 32)
+ if (i == 8 && buffer.position() > 16)
{
buf.append("...");
- i = buffer.position() - 16;
+ i = buffer.position() - 8;
}
}
buf.append("<<<");
for (int i = buffer.position(); i < buffer.limit(); i++)
{
appendContentChar(buf, buffer.get(i));
- if (i == buffer.position() + 16 && buffer.limit() > buffer.position() + 32)
+ if (i == buffer.position() + 24 && buffer.limit() > buffer.position() + 48)
{
buf.append("...");
- i = buffer.limit() - 16;
+ i = buffer.limit() - 24;
}
}
buf.append(">>>");
@@ -1125,10 +1126,10 @@ public class BufferUtil
for (int i = limit; i < buffer.capacity(); i++)
{
appendContentChar(buf, buffer.get(i));
- if (i == limit + 16 && buffer.capacity() > limit + 32)
+ if (i == limit + 8 && buffer.capacity() > limit + 16)
{
buf.append("...");
- i = buffer.capacity() - 16;
+ i = buffer.capacity() - 8;
}
}
buffer.limit(limit);
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
index c2d77760c08..41f02b80e8b 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
@@ -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);
diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketConnectionStatsTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketConnectionStatsTest.java
index 41e79c01b86..148720950c4 100644
--- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketConnectionStatsTest.java
+++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketConnectionStatsTest.java
@@ -176,4 +176,4 @@ public class WebSocketConnectionStatsTest
assertThat("stats.sendBytes", statistics.getSentBytes(), is(expectedSent));
assertThat("stats.receivedBytes", statistics.getReceivedBytes(), is(expectedReceived));
}
-}
\ No newline at end of file
+}
diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketNegotiationTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketNegotiationTest.java
index 9c23e097ccb..27ec3c2c45a 100644
--- a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketNegotiationTest.java
+++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketNegotiationTest.java
@@ -115,7 +115,7 @@ public class WebSocketNegotiationTest
client.getOutputStream().write(upgradeRequest.getBytes(ISO_8859_1));
String response = getUpgradeResponse(client.getInputStream());
- assertThat(response, containsString("400 Missing request header 'Sec-WebSocket-Key'"));
+ assertThat(response, containsString("400 "));
}
protected static HttpFields newUpgradeRequest(String extensions)
diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java
index 105de580dc2..36989de4607 100644
--- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java
+++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketInvalidVersionTest.java
@@ -89,6 +89,6 @@ public class WebSocketInvalidVersionTest
connFut.get(Timeouts.CONNECT, Timeouts.CONNECT_UNIT);
});
assertThat(x.getCause(), instanceOf(UpgradeException.class));
- assertThat(x.getMessage(), containsString("400 Unsupported websocket version specification"));
+ assertThat(x.getMessage(), containsString("400 "));
}
}
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/BadAppTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/BadAppTests.java
index 190bc381743..9248a2dc5e4 100644
--- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/BadAppTests.java
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/BadAppTests.java
@@ -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("HTTP ERROR 503 Service Unavailable "));
+ assertThat(response.getContentAsString(), containsString("URI: /badapp/ "));
}
}
}
@@ -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("HTTP ERROR 503 Service Unavailable "));
+ assertThat(response.getContentAsString(), containsString("URI: /badapp/ "));
}
}
}