From 9b49da3a323ae20bfb1b610426f43f2f9517cc5a Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 10 May 2023 07:23:14 -0500 Subject: [PATCH] Issue #8885 - Restore HttpChannel.Listener --- .../jetty/server/AbstractConnector.java | 30 +++ .../org/eclipse/jetty/server/HttpChannel.java | 200 ++++++++++++++++++ .../jetty/server/HttpChannelListeners.java | 136 ++++++------ .../server/internal/HttpChannelState.java | 51 ++++- .../jetty/server/HttpChannelEventTest.java | 175 +++++---------- 5 files changed, 401 insertions(+), 191 deletions(-) diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java index 13bd50fec21d..d7f8d865e3bf 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java @@ -41,6 +41,7 @@ import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.component.Container; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -137,6 +138,7 @@ @ManagedObject("Abstract implementation of the Connector Interface") public abstract class AbstractConnector extends ContainerLifeCycle implements Connector, Dumpable { + public static final HttpChannel.Listener NOOP_LISTENER = new HttpChannel.Listener() {}; protected static final Logger LOG = LoggerFactory.getLogger(AbstractConnector.class); private final AutoLock _lock = new AutoLock(); @@ -150,6 +152,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co private final Set _endpoints = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set _immutableEndPoints = Collections.unmodifiableSet(_endpoints); private Shutdown _shutdown; + private HttpChannel.Listener _httpChannelListeners = NOOP_LISTENER; private long _idleTimeout = 30000; private long _shutdownIdleTimeout = 1000L; private String _defaultProtocol; @@ -188,6 +191,23 @@ public AbstractConnector( _bufferPool = bufferPool != null ? bufferPool : server.getByteBufferPool(); addBean(_bufferPool, bufferPool != null); + addEventListener(new Container.Listener() + { + @Override + public void beanAdded(Container parent, Object bean) + { + if (bean instanceof HttpChannel.Listener) + _httpChannelListeners = new HttpChannelListeners(getBeans(HttpChannel.Listener.class)); + } + + @Override + public void beanRemoved(Container parent, Object bean) + { + if (bean instanceof HttpChannel.Listener) + _httpChannelListeners = new HttpChannelListeners(getBeans(HttpChannel.Listener.class)); + } + }); + for (ConnectionFactory factory : factories) { addConnectionFactory(factory); @@ -267,6 +287,16 @@ public int getAcceptors() return _acceptors.length; } + /** + * Get the {@link HttpChannel.Listener} for this connector. + * + * @return the {@link HttpChannel.Listener} for this connector. + */ + public HttpChannel.Listener getHttpChannelListeners() + { + return _httpChannelListeners; + } + @Override protected void doStart() throws Exception { diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 31a9b67af218..f4e7dc84613e 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -13,6 +13,9 @@ package org.eclipse.jetty.server; +import java.nio.ByteBuffer; +import java.util.EventListener; + import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.server.internal.HttpChannelState; import org.eclipse.jetty.util.thread.Invocable; @@ -101,6 +104,203 @@ interface Factory HttpChannel newHttpChannel(ConnectionMetaData connectionMetaData); } + /** + *

Listener for {@link HttpChannel} events.

+ *

HttpChannel will emit events for the various phases it goes through while + * processing an HTTP request and response.

+ *

Implementations of this interface may listen to those events to track + * timing and/or other values such as request URI, etc.

+ *

The events parameters, especially the {@link Request} object, may be + * in a transient state depending on the event, and not all properties/features + * of the parameters may be available inside a listener method.

+ *

It is recommended that the event parameters are not acted upon + * in the listener methods, or undefined behavior may result. For example, it + * would be a bad idea to try to read some content from the + * {@link Request#read()} in listener methods. On the other + * hand, it is legit to store request attributes in one listener method that + * may be possibly retrieved in another listener method in a later event.

+ *

Listener methods are invoked synchronously from the thread that is + * performing the request processing, and they should not call blocking code + * (otherwise the request processing will be blocked as well).

+ *

Listener instances that are set as a bean on the {@link Connector} are + * efficiently added to {@link HttpChannel}. + */ + public interface Listener extends EventListener + { + /** + * Invoked just after the HTTP request line and headers have been parsed. + * + * @param request the request object + * @param response the response object + */ + default void onRequestBegin(Request request, Response response) + { + // done + } + + /** + * Invoked just before calling the application. + * + * @param request the request object + * @param response the response object + */ + default void onBeforeDispatch(Request request, Response response) + { + // done + } + + /** + * Invoked when the application threw an exception. + * + * @param request the request object + * @param response the response object + * @param failure the exception thrown by the application + */ + default void onDispatchFailure(Request request, Response response, Throwable failure) + { + // done + } + + /** + * Invoked just after the application returns from the first invocation. + * + * @param request the request object + * @param response the response object + */ + default void onAfterDispatch(Request request, Response response) + { + // done + } + + /** + * Invoked every time a request content chunk has been parsed, just before + * making it available to the application. + * + * @param request the request object + * @param response the response object + * @param content a {@link ByteBuffer#slice() slice} of the request content chunk + */ + default void onRequestContent(Request request, Response response, ByteBuffer content) + { + // done + } + + /** + * Invoked when the end of the request content is detected. + * + * @param request the request object + * @param response the response object + */ + default void onRequestContentEnd(Request request, Response response) + { + // done + } + + /** + * Invoked when the request trailers have been parsed. + * + * @param request the request object + * @param response the response object + */ + default void onRequestTrailers(Request request, Response response) + { + // done + } + + /** + * Invoked when the request has been fully parsed. + * + * @param request the request object + * @param response the response object + */ + default void onRequestEnd(Request request, Response response) + { + // done + } + + /** + * Invoked when the request processing failed. + * + * @param request the request object + * @param response the response object + * @param failure the request failure + */ + default void onRequestFailure(Request request, Response response, Throwable failure) + { + // done + } + + /** + * Invoked just before the response line is written to the network. + * + * @param request the request object + * @param response the response object + */ + default void onResponseBegin(Request request, Response response) + { + // done + } + + /** + * Invoked just after the response is committed (that is, the response + * line, headers and possibly some content have been written to the + * network). + * + * @param request the request object + * @param response the response object + */ + default void onResponseCommit(Request request, Response response) + { + // done + } + + /** + * Invoked after a response content chunk has been written to the network. + * + * @param request the request object + * @param response the response object + * @param content a {@link ByteBuffer#slice() slice} of the response content chunk + */ + default void onResponseContent(Request request, Response response, ByteBuffer content) + { + // done + } + + /** + * Invoked when the response has been fully written. + * + * @param request the request object + * @param response the response object + */ + default void onResponseEnd(Request request, Response response) + { + // done + } + + /** + * Invoked when the response processing failed. + * + * @param request the request object + * @param response the response object + * @param failure the response failure + */ + default void onResponseFailure(Request request, Response response, Throwable failure) + { + // done + } + + /** + * Invoked when the request and response processing are complete. + * + * @param request the request object + * @param response the response object + */ + default void onComplete(Request request, Response response) + { + // done + } + } + /** *

The factory that creates default implementations of {@link HttpChannel}.

*/ diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelListeners.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelListeners.java index e2760becd99b..4176040dbf90 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelListeners.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelListeners.java @@ -13,16 +13,20 @@ package org.eclipse.jetty.server; +import java.nio.ByteBuffer; +import java.util.Collection; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * A HttpChannel.Listener that holds a collection of * other HttpChannel.Listener instances that are efficiently * invoked without iteration. * @see AbstractConnector */ -@Deprecated // TODO update or remove -public class HttpChannelListeners // TODO ??? implements HttpChannel.Listener +public class HttpChannelListeners implements HttpChannel.Listener { - /* TODO static final Logger LOG = LoggerFactory.getLogger(HttpChannelListeners.class); public static HttpChannel.Listener NOOP = new HttpChannel.Listener() {}; @@ -64,35 +68,35 @@ public HttpChannelListeners(Collection listeners) for (HttpChannel.Listener listener : listeners) { - if (!listener.getClass().getMethod("onRequestBegin", Request.class).isDefault()) + if (!listener.getClass().getMethod("onRequestBegin", Request.class, Response.class).isDefault()) onRequestBegin = combine(onRequestBegin, listener::onRequestBegin); - if (!listener.getClass().getMethod("onBeforeDispatch", Request.class).isDefault()) + if (!listener.getClass().getMethod("onBeforeDispatch", Request.class, Response.class).isDefault()) onBeforeDispatch = combine(onBeforeDispatch, listener::onBeforeDispatch); - if (!listener.getClass().getMethod("onDispatchFailure", Request.class, Throwable.class).isDefault()) + if (!listener.getClass().getMethod("onDispatchFailure", Request.class, Response.class, Throwable.class).isDefault()) onDispatchFailure = combine(onDispatchFailure, listener::onDispatchFailure); - if (!listener.getClass().getMethod("onAfterDispatch", Request.class).isDefault()) + if (!listener.getClass().getMethod("onAfterDispatch", Request.class, Response.class).isDefault()) onAfterDispatch = combine(onAfterDispatch, listener::onAfterDispatch); - if (!listener.getClass().getMethod("onRequestContent", Request.class, ByteBuffer.class).isDefault()) + if (!listener.getClass().getMethod("onRequestContent", Request.class, Response.class, ByteBuffer.class).isDefault()) onRequestContent = combine(onRequestContent, listener::onRequestContent); - if (!listener.getClass().getMethod("onRequestContentEnd", Request.class).isDefault()) + if (!listener.getClass().getMethod("onRequestContentEnd", Request.class, Response.class).isDefault()) onRequestContentEnd = combine(onRequestContentEnd, listener::onRequestContentEnd); - if (!listener.getClass().getMethod("onRequestTrailers", Request.class).isDefault()) + if (!listener.getClass().getMethod("onRequestTrailers", Request.class, Response.class).isDefault()) onRequestTrailers = combine(onRequestTrailers, listener::onRequestTrailers); - if (!listener.getClass().getMethod("onRequestEnd", Request.class).isDefault()) + if (!listener.getClass().getMethod("onRequestEnd", Request.class, Response.class).isDefault()) onRequestEnd = combine(onRequestEnd, listener::onRequestEnd); - if (!listener.getClass().getMethod("onRequestFailure", Request.class, Throwable.class).isDefault()) + if (!listener.getClass().getMethod("onRequestFailure", Request.class, Response.class, Throwable.class).isDefault()) onRequestFailure = combine(onRequestFailure, listener::onRequestFailure); - if (!listener.getClass().getMethod("onResponseBegin", Request.class).isDefault()) + if (!listener.getClass().getMethod("onResponseBegin", Request.class, Response.class).isDefault()) onResponseBegin = combine(onResponseBegin, listener::onResponseBegin); - if (!listener.getClass().getMethod("onResponseCommit", Request.class).isDefault()) + if (!listener.getClass().getMethod("onResponseCommit", Request.class, Response.class).isDefault()) onResponseCommit = combine(onResponseCommit, listener::onResponseCommit); - if (!listener.getClass().getMethod("onResponseContent", Request.class, ByteBuffer.class).isDefault()) + if (!listener.getClass().getMethod("onResponseContent", Request.class, Response.class, ByteBuffer.class).isDefault()) onResponseContent = combine(onResponseContent, listener::onResponseContent); - if (!listener.getClass().getMethod("onResponseEnd", Request.class).isDefault()) + if (!listener.getClass().getMethod("onResponseEnd", Request.class, Response.class).isDefault()) onResponseEnd = combine(onResponseEnd, listener::onResponseEnd); - if (!listener.getClass().getMethod("onResponseFailure", Request.class, Throwable.class).isDefault()) + if (!listener.getClass().getMethod("onResponseFailure", Request.class, Response.class, Throwable.class).isDefault()) onResponseFailure = combine(onResponseFailure, listener::onResponseFailure); - if (!listener.getClass().getMethod("onComplete", Request.class).isDefault()) + if (!listener.getClass().getMethod("onComplete", Request.class, Response.class).isDefault()) onComplete = combine(onComplete, listener::onComplete); } @@ -119,118 +123,118 @@ public HttpChannelListeners(Collection listeners) } @Override - public void onRequestBegin(Request request) + public void onRequestBegin(Request request, Response response) { - onRequestBegin.onRequest(request); + onRequestBegin.onRequest(request, response); } @Override - public void onBeforeDispatch(Request request) + public void onBeforeDispatch(Request request, Response response) { - onBeforeDispatch.onRequest(request); + onBeforeDispatch.onRequest(request, response); } @Override - public void onDispatchFailure(Request request, Throwable failure) + public void onDispatchFailure(Request request, Response response, Throwable failure) { - onDispatchFailure.onFailure(request, failure); + onDispatchFailure.onFailure(request, response, failure); } @Override - public void onAfterDispatch(Request request) + public void onAfterDispatch(Request request, Response response) { - onAfterDispatch.onRequest(request); + onAfterDispatch.onRequest(request, response); } @Override - public void onRequestContent(Request request, ByteBuffer content) + public void onRequestContent(Request request, Response response, ByteBuffer content) { - onRequestContent.onContent(request, content); + onRequestContent.onContent(request, response, content); } @Override - public void onRequestContentEnd(Request request) + public void onRequestContentEnd(Request request, Response response) { - onRequestContentEnd.onRequest(request); + onRequestContentEnd.onRequest(request, response); } @Override - public void onRequestTrailers(Request request) + public void onRequestTrailers(Request request, Response response) { - onRequestTrailers.onRequest(request); + onRequestTrailers.onRequest(request, response); } @Override - public void onRequestEnd(Request request) + public void onRequestEnd(Request request, Response response) { - onRequestEnd.onRequest(request); + onRequestEnd.onRequest(request, response); } @Override - public void onRequestFailure(Request request, Throwable failure) + public void onRequestFailure(Request request, Response response, Throwable failure) { - onRequestFailure.onFailure(request, failure); + onRequestFailure.onFailure(request, response, failure); } @Override - public void onResponseBegin(Request request) + public void onResponseBegin(Request request, Response response) { - onResponseBegin.onRequest(request); + onResponseBegin.onRequest(request, response); } @Override - public void onResponseCommit(Request request) + public void onResponseCommit(Request request, Response response) { - onResponseCommit.onRequest(request); + onResponseCommit.onRequest(request, response); } @Override - public void onResponseContent(Request request, ByteBuffer content) + public void onResponseContent(Request request, Response response, ByteBuffer content) { - onResponseContent.onContent(request, content); + onResponseContent.onContent(request, response, content); } @Override - public void onResponseEnd(Request request) + public void onResponseEnd(Request request, Response response) { - onResponseEnd.onRequest(request); + onResponseEnd.onRequest(request, response); } @Override - public void onResponseFailure(Request request, Throwable failure) + public void onResponseFailure(Request request, Response response, Throwable failure) { - onResponseFailure.onFailure(request, failure); + onResponseFailure.onFailure(request, response, failure); } @Override - public void onComplete(Request request) + public void onComplete(Request request, Response response) { - onComplete.onRequest(request); + onComplete.onRequest(request, response); } private interface NotifyRequest { - void onRequest(Request request); + void onRequest(Request request, Response response); - NotifyRequest NOOP = request -> + NotifyRequest NOOP = (request, response) -> { }; } private interface NotifyFailure { - void onFailure(Request request, Throwable failure); + void onFailure(Request request, Response response, Throwable failure); - NotifyFailure NOOP = (request, failure) -> + NotifyFailure NOOP = (request, response, failure) -> { }; } private interface NotifyContent { - void onContent(Request request, ByteBuffer content); + void onContent(Request request, Response response, ByteBuffer content); - NotifyContent NOOP = (request, content) -> + NotifyContent NOOP = (request, response, content) -> { }; } @@ -241,10 +245,10 @@ private static NotifyRequest combine(NotifyRequest first, NotifyRequest second) return second; if (second == NotifyRequest.NOOP) return first; - return request -> + return (request, response) -> { - first.onRequest(request); - second.onRequest(request); + first.onRequest(request, response); + second.onRequest(request, response); }; } @@ -254,26 +258,24 @@ private static NotifyFailure combine(NotifyFailure first, NotifyFailure second) return second; if (second == NotifyFailure.NOOP) return first; - return (request, throwable) -> + return (request, response, throwable) -> { - first.onFailure(request, throwable); - second.onFailure(request, throwable); + first.onFailure(request, response, throwable); + second.onFailure(request, response, throwable); }; } private static NotifyContent combine(NotifyContent first, NotifyContent second) { if (first == NotifyContent.NOOP) - return (request, content) -> second.onContent(request, content.slice()); + return (request, response, content) -> second.onContent(request, response, content.slice()); if (second == NotifyContent.NOOP) - return (request, content) -> first.onContent(request, content.slice()); - return (request, content) -> + return (request, response, content) -> first.onContent(request, response, content.slice()); + return (request, response, content) -> { content = content.slice(); - first.onContent(request, content); - second.onContent(request, content); + first.onContent(request, response, content); + second.onContent(request, response, content); }; } - - */ } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java index 90c2bc7a03e2..41fe179e29c5 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java @@ -42,6 +42,7 @@ import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.Content; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.Components; import org.eclipse.jetty.server.ConnectionMetaData; import org.eclipse.jetty.server.Connector; @@ -104,6 +105,7 @@ enum WriteState private final SerializedInvoker _serializedInvoker; private final Attributes _requestAttributes = new Attributes.Lazy(); private final ResponseHttpFields _responseHeaders = new ResponseHttpFields(); + private final HttpChannel.Listener _combinedListener; private Thread _handling; private boolean _handled; private WriteState _writeState = WriteState.NOT_LAST; @@ -123,6 +125,11 @@ public HttpChannelState(ConnectionMetaData connectionMetaData) _connectionMetaData = connectionMetaData; // The SerializedInvoker is used to prevent infinite recursion of callbacks calling methods calling callbacks etc. _serializedInvoker = new HttpChannelSerializedInvoker(); + + Connector connector = connectionMetaData.getConnector(); + _combinedListener = (connector instanceof AbstractConnector) + ? ((AbstractConnector)connector).getHttpChannelListeners() + : AbstractConnector.NOOP_LISTENER; } @Override @@ -268,6 +275,8 @@ public Runnable onRequest(MetaData.Request request) if (getHttpConfiguration().getSendDateHeader()) responseHeaders.add(getConnectionMetaData().getConnector().getServer().getDateField()); + _combinedListener.onRequestBegin(_request, _request._response); + // This is deliberately not serialized to allow a handler to block. return _handlerInvoker; } @@ -536,13 +545,20 @@ public void run() if (customized != request && server.getRequestLog() != null) request.setLoggedRequest(customized); + _combinedListener.onBeforeDispatch(request, request._response); + if (!server.handle(customized, request._response, request._callback)) Response.writeError(customized, request._response, request._callback, HttpStatus.NOT_FOUND_404); } catch (Throwable t) { + _combinedListener.onDispatchFailure(request, request._response, t); failure = t; } + finally + { + _combinedListener.onAfterDispatch(request, request._response); + } HttpStream stream; boolean completeStream = false; @@ -642,6 +658,7 @@ private void completeStream(HttpStream stream, Throwable failure) } finally { + _combinedListener.onComplete(_request, _request._response); // This is THE ONLY PLACE the stream is succeeded or failed. if (failure == null) stream.succeeded(); @@ -667,6 +684,7 @@ public static class ChannelRequest implements Attributes, Request private final ChannelResponse _response; private final AutoLock _lock; private final LongAdder _contentBytesRead = new LongAdder(); + private final HttpChannel.Listener _listener; private HttpChannelState _httpChannel; private Request _loggedRequest; private HttpFields _trailers; @@ -679,6 +697,7 @@ public static class ChannelRequest implements Attributes, Request _metaData = Objects.requireNonNull(metaData); _response = new ChannelResponse(this); _lock = httpChannel._lock; + _listener = httpChannel._combinedListener; } public void setLoggedRequest(Request request) @@ -857,11 +876,24 @@ public Content.Chunk read() if (LOG.isDebugEnabled()) LOG.debug("read {}", chunk); - if (chunk != null && chunk.hasRemaining()) - _contentBytesRead.add(chunk.getByteBuffer().remaining()); + if (chunk != null) + { + _listener.onRequestContent(this, _response, chunk.getByteBuffer().slice()); + if (chunk.hasRemaining()) + _contentBytesRead.add(chunk.getByteBuffer().remaining()); + } + + if (chunk.isLast()) + _listener.onRequestEnd(this, _response); if (chunk instanceof Trailers trailers) + { _trailers = trailers.getTrailers(); + _listener.onRequestTrailers(this, _response); + } + + if (chunk == Content.Chunk.EOF || chunk instanceof Content.Chunk.Error) + _listener.onRequestEnd(this, _response); return chunk; } @@ -912,7 +944,7 @@ public void demand(Runnable demandCallback) @Override public void fail(Throwable failure) { - // TODO + _listener.onRequestFailure(this, _response, failure); } @Override @@ -980,10 +1012,12 @@ public static class ChannelResponse implements Response, Callback private int _status; private long _contentBytesWritten; private Supplier _trailers; + private HttpChannel.Listener _listener; private ChannelResponse(ChannelRequest request) { _request = request; + _listener = request.getHttpChannel()._combinedListener; } public long getContentBytesWritten() @@ -1037,6 +1071,9 @@ public void setTrailersSupplier(Supplier trailers) @Override public void write(boolean last, ByteBuffer content, Callback callback) { + if (!isCommitted()) + _listener.onResponseBegin(_request, this); + long length = BufferUtil.length(content); long totalWritten; @@ -1084,9 +1121,16 @@ else if (httpChannel._error != null) if (failure == null) { + boolean wasCommitted = isCommitted(); + ByteBuffer contentSlice = content.slice(); if (LOG.isDebugEnabled()) LOG.debug("writing last={} {} {}", last, BufferUtil.toDetailString(content), this); stream.send(_request._metaData, responseMetaData, last, content, this); + if (!wasCommitted) + _listener.onResponseCommit(_request, this); + _listener.onResponseContent(_request, this, contentSlice); + if (last) + _listener.onResponseEnd(_request, this); } else if (failure == DO_NOT_SEND) { @@ -1095,6 +1139,7 @@ else if (failure == DO_NOT_SEND) else { Throwable t = failure; + _listener.onResponseFailure(_request, this, t); httpChannel._serializedInvoker.run(() -> callback.failed(t)); } } diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpChannelEventTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpChannelEventTest.java index 639fb577b927..57f758e2a76d 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpChannelEventTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpChannelEventTest.java @@ -15,24 +15,30 @@ import java.io.IOException; import java.nio.ByteBuffer; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.NanoTime; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -@Disabled // TODO public class HttpChannelEventTest { private Server server; @@ -57,29 +63,25 @@ public void dispose() throws Exception @Test public void testRequestContentSlice() throws Exception { - int data = 'x'; + byte data = 'x'; CountDownLatch applicationLatch = new CountDownLatch(1); - /* TODO - start(new TestHandler() + start(new Handler.Abstract() { @Override - protected void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public boolean handle(Request request, Response response, Callback callback) throws IOException { - ServletInputStream input = request.getInputStream(); - int content = input.read(); - assertEquals(data, content); + Content.Chunk chunk = request.read(); + assertEquals(data, chunk.getByteBuffer().get()); applicationLatch.countDown(); + return true; } }); - */ - CountDownLatch listenerLatch = new CountDownLatch(1); - /* TODO connector.addBean(new HttpChannel.Listener() { @Override - public void onRequestContent(Request request, ByteBuffer content) + public void onRequestContent(Request request, Response response, ByteBuffer content) { // Consume the buffer to verify it's a slice. content.position(content.limit()); @@ -87,8 +89,6 @@ public void onRequestContent(Request request, ByteBuffer content) } }); - */ - HttpTester.Request request = HttpTester.newRequest(); request.setHeader("Host", "localhost"); request.setContent(new byte[]{(byte)data}); @@ -107,32 +107,27 @@ public void onRequestContent(Request request, ByteBuffer content) public void testResponseContentSlice() throws Exception { byte[] data = new byte[]{'y'}; - /* TODO - start(new TestHandler() + start(new Handler.Abstract() { @Override - protected void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public boolean handle(Request request, Response response, Callback callback) throws IOException { - response.write(true, callback, ByteBuffer.wrap(data)); + response.write(true, ByteBuffer.wrap(data), callback); + return true; } }); - */ - CountDownLatch latch = new CountDownLatch(1); - /* TODO connector.addBean(new HttpChannel.Listener() { @Override - public void onResponseContent(Request request, ByteBuffer content) + public void onResponseContent(Request request, Response response, ByteBuffer content) { assertTrue(content.hasRemaining()); latch.countDown(); } }); - */ - HttpTester.Request request = HttpTester.newRequest(); request.setHeader("Host", "localhost"); HttpTester.Response response = HttpTester.parseResponse(connector.getResponse(request.toString(), 5, TimeUnit.SECONDS)); @@ -146,27 +141,31 @@ public void onResponseContent(Request request, ByteBuffer content) @Test public void testRequestFailure() throws Exception { - start(new TestHandler()); + start(new Handler.Abstract() + { + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception + { + return true; + } + }); CountDownLatch latch = new CountDownLatch(2); - /* TODO connector.addBean(new HttpChannel.Listener() { @Override - public void onRequestFailure(Request request, Throwable failure) + public void onRequestFailure(Request request, Response response, Throwable failure) { latch.countDown(); } @Override - public void onComplete(Request request) + public void onComplete(Request request, Response response) { latch.countDown(); } }); - */ - // No Host header, request will fail. String request = HttpTester.newRequest().toString(); HttpTester.Response response = HttpTester.parseResponse(connector.getResponse(request, 5, TimeUnit.SECONDS)); @@ -178,35 +177,31 @@ public void onComplete(Request request) @Test public void testResponseBeginModifyHeaders() throws Exception { - start(new TestHandler() + start(new Handler.Abstract() { - /* TODO @Override - protected void handle(HttpServletRequest request, HttpServletResponse response) + public boolean handle(Request request, Response response, Callback callback) throws IOException { - response.setCharacterEncoding("utf-8"); - response.setContentType("text/plain"); + response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain; charset=utf-8"); // Intentionally add two values for a header - response.addHeader("X-Header", "foo"); - response.addHeader("X-Header", "bar"); + response.getHeaders().put("X-Header", "foo"); + response.getHeaders().put("X-Header", "bar"); + return true; } - */ }); - /* TODO CountDownLatch latch = new CountDownLatch(1); connector.addBean(new HttpChannel.Listener() { @Override - public void onResponseBegin(Request request) + public void onResponseBegin(Request request, Response response) { - Response response = request.getResponse(); // Eliminate all "X-Header" values from Handler, and force it to be the one value "zed" - response.getHttpFields().computeField("X-Header", (n, f) -> new HttpField(n, "zed")); + response.getHeaders().computeField("X-Header", (n, f) -> new HttpField(n, "zed")); } @Override - public void onComplete(Request request) + public void onComplete(Request request, Response response) { latch.countDown(); } @@ -224,44 +219,38 @@ public void onComplete(Request request) List xheaders = response.getFields("X-Header"); assertThat("X-Header count", xheaders.size(), is(1)); assertThat("X-Header[0].value", xheaders.get(0).getValue(), is("zed")); - */ } @Test public void testResponseFailure() throws Exception { - /* TODO - start(new TestHandler() + start(new Handler.Abstract() { @Override - protected void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public boolean handle(Request request, Response response, Callback callback) throws IOException { // Closes all connections, response will fail. connector.getConnectedEndPoints().forEach(EndPoint::close); + return true; } }); - */ - CountDownLatch latch = new CountDownLatch(2); - /* TODO connector.addBean(new HttpChannel.Listener() { @Override - public void onResponseFailure(Request request, Throwable failure) + public void onResponseFailure(Request request, Response response, Throwable failure) { latch.countDown(); } @Override - public void onComplete(Request request) + public void onComplete(Request request, Response response) { latch.countDown(); } }); - */ - HttpTester.Request request = HttpTester.newRequest(); request.setHeader("Host", "localhost"); HttpTester.parseResponse(connector.getResponse(request.toString(), 5, TimeUnit.SECONDS)); @@ -272,23 +261,29 @@ public void onComplete(Request request) @Test public void testExchangeTimeRecording() throws Exception { - start(new TestHandler()); + start(new Handler.Abstract() + { + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception + { + return true; + } + }); CountDownLatch latch = new CountDownLatch(1); AtomicLong elapsed = new AtomicLong(); - /* TODO connector.addBean(new HttpChannel.Listener() { private final String attribute = getClass().getName() + ".begin"; @Override - public void onRequestBegin(Request request) + public void onRequestBegin(Request request, Response response) { request.setAttribute(attribute, NanoTime.now()); } @Override - public void onComplete(Request request) + public void onComplete(Request request, Response response) { long beginTime = (Long)request.getAttribute(attribute); elapsed.set(NanoTime.since(beginTime)); @@ -296,8 +291,6 @@ public void onComplete(Request request) } }); - */ - HttpTester.Request request = HttpTester.newRequest(); request.setHeader("Host", "localhost"); HttpTester.Response response = HttpTester.parseResponse(connector.getResponse(request.toString(), 5, TimeUnit.SECONDS)); @@ -306,64 +299,4 @@ public void onComplete(Request request) assertTrue(latch.await(5, TimeUnit.SECONDS)); assertThat(elapsed.get(), Matchers.greaterThan(0L)); } - - @SuppressWarnings("deprecation") - @Test - public void testTransientListener() throws Exception - { - start(new TestHandler()); - - CountDownLatch latch = new CountDownLatch(1); - /* TODO - connector.addBean(new HttpChannel.TransientListeners()); - connector.addBean(new HttpChannel.Listener() - { - @Override - public void onRequestBegin(Request request) - { - request.getHttpChannel().addListener(new HttpChannel.Listener() - { - @Override - public void onComplete(Request request) - { - latch.countDown(); - } - }); - } - }); - - */ - - HttpTester.Request request = HttpTester.newRequest(); - request.setHeader("Host", "localhost"); - HttpTester.Response response = HttpTester.parseResponse(connector.getResponse(request.toString(), 5, TimeUnit.SECONDS)); - - assertEquals(HttpStatus.OK_200, response.getStatus()); - assertTrue(latch.await(5, TimeUnit.SECONDS)); - } - - private static class TestHandler extends Handler.Abstract - { - @Override - public boolean handle(Request request, Response response, Callback callback) throws Exception - { - try - { - handle(request, response); - } - catch (Throwable t) - { - callback.failed(t); - } - finally - { - callback.succeeded(); - } - return true; - } - - protected void handle(Request request, Response response) throws IOException - { - } - } }