diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index c53a65f6f919..727206a87473 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -1219,7 +1219,12 @@ public void run(Runnable runnable, Request request) @Override public void execute(Runnable runnable) { - getServer().getContext().execute(() -> run(runnable)); + execute(runnable, null); + } + + public void execute(Runnable runnable, Request request) + { + getServer().getContext().execute(() -> run(runnable, request)); } protected DecoratedObjectFactory getDecoratedObjectFactory() diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/AsyncContextEvent.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/AsyncContextEvent.java index dd1febba5795..583b51de6259 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/AsyncContextEvent.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/AsyncContextEvent.java @@ -20,13 +20,12 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import org.eclipse.jetty.http.HttpURI; -import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.thread.Scheduler; public class AsyncContextEvent extends AsyncEvent implements Runnable { private final ServletContext _servletContext; - private final ContextHandler.ScopedContext _context; + private final ServletContextHandler.ServletScopedContext _context; private final AsyncContextState _asyncContext; private final HttpURI _baseURI; private final ServletChannelState _state; @@ -35,7 +34,7 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable private volatile Scheduler.Task _timeoutTask; private Throwable _throwable; - public AsyncContextEvent(ContextHandler.ScopedContext context, AsyncContextState asyncContext, ServletChannelState state, ServletRequest request, ServletResponse response) + public AsyncContextEvent(ServletContextHandler.ServletScopedContext context, AsyncContextState asyncContext, ServletChannelState state, ServletRequest request, ServletResponse response) { super(null, request, response, null); _context = context; @@ -76,7 +75,7 @@ public ServletContext getServletContext() return _dispatchContext == null ? _servletContext : _dispatchContext; } - public ContextHandler.ScopedContext getContext() + public ServletContextHandler.ServletScopedContext getContext() { return _context; } diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/AsyncContextState.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/AsyncContextState.java index cc2513a31c77..65df72fc7f4f 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/AsyncContextState.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/AsyncContextState.java @@ -141,7 +141,7 @@ public void start(final Runnable task) { task.run(); } - }); + }, _state.getServletChannel().getRequest()); } public void reset() diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java index 23d5decb6cf4..7eb62eb48957 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java @@ -788,6 +788,11 @@ protected void execute(Runnable task) _context.execute(task); } + protected void execute(Runnable task, Request request) + { + _context.execute(task, request); + } + /** * If a write or similar operation to this channel fails, * then this method should be called. diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java index 9d2bc9b45295..b74dd79ff238 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannelState.java @@ -1174,7 +1174,7 @@ public void upgrade() protected void scheduleDispatch() { - _servletChannel.execute(_servletChannel::handle); + _servletChannel.execute(_servletChannel::handle, _servletChannel.getRequest()); } protected void cancelTimeout() diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ContextScopeListenerTest.java b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ContextScopeListenerTest.java new file mode 100644 index 000000000000..668747071411 --- /dev/null +++ b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ContextScopeListenerTest.java @@ -0,0 +1,138 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.ee10.servlet; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.locks.ReentrantLock; + +import jakarta.servlet.AsyncContext; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.client.ContentResponse; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Context; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +public class ContextScopeListenerTest +{ + private Server _server; + private ServerConnector _connector; + private HttpClient _client; + private final List _history = new CopyOnWriteArrayList<>(); + private ServletContextHandler _contextHandler; + + @BeforeEach + public void before() throws Exception + { + _server = new Server(); + _connector = new ServerConnector(_server); + _server.addConnector(_connector); + + _contextHandler = new ServletContextHandler(); + _server.setHandler(_contextHandler); + _server.start(); + + _client = new HttpClient(); + _client.start(); + } + + @AfterEach + public void after() throws Exception + { + _client.stop(); + _server.stop(); + } + + @Test + public void testAsyncServlet() throws Exception + { + _contextHandler.addServlet(new ServletHolder(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + { + if (req.getDispatcherType() == DispatcherType.ASYNC) + { + _history.add("asyncDispatch"); + return; + } + + _history.add("doGet"); + AsyncContext asyncContext = req.startAsync(); + asyncContext.start(() -> + { + _history.add("asyncRunnable"); + asyncContext.dispatch("/dispatch"); + }); + } + }), "/"); + + _contextHandler.addEventListener(new ContextHandler.ContextScopeListener() + { + // Use a lock to prevent the async thread running the listener concurrently. + private final ReentrantLock _lock = new ReentrantLock(); + + @Override + public void enterScope(Context context, Request request) + { + _lock.lock(); + String pathInContext = (request == null) ? "null" : Request.getPathInContext(request); + _history.add("enterScope " + pathInContext); + } + + @Override + public void exitScope(Context context, Request request) + { + String pathInContext = (request == null) ? "null" : Request.getPathInContext(request); + _history.add("exitScope " + pathInContext); + _lock.unlock(); + } + }); + + URI uri = URI.create("http://localhost:" + _connector.getLocalPort() + "/initialPath"); + ContentResponse response = _client.GET(uri); + assertThat(response.getStatus(), equalTo(HttpStatus.OK_200)); + assertHistory( + "enterScope /initialPath", + "doGet", + "exitScope /initialPath", + "enterScope /initialPath", + "asyncRunnable", + "exitScope /initialPath", + "enterScope /initialPath", + "asyncDispatch", + "exitScope /initialPath" + ); + } + + private void assertHistory(String... values) + { + assertThat(_history, equalTo(Arrays.asList(values))); + } +} diff --git a/jetty-ee9/jetty-ee9-servlet/src/test/java/org/eclipse/jetty/ee9/servlet/ContextScopeListenerTest.java b/jetty-ee9/jetty-ee9-servlet/src/test/java/org/eclipse/jetty/ee9/servlet/ContextScopeListenerTest.java new file mode 100644 index 000000000000..3ae532c74bf3 --- /dev/null +++ b/jetty-ee9/jetty-ee9-servlet/src/test/java/org/eclipse/jetty/ee9/servlet/ContextScopeListenerTest.java @@ -0,0 +1,139 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.ee9.servlet; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.locks.ReentrantLock; + +import jakarta.servlet.AsyncContext; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.client.ContentResponse; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.ee9.nested.ContextHandler; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.URIUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +public class ContextScopeListenerTest +{ + private Server _server; + private ServerConnector _connector; + private HttpClient _client; + private final List _history = new CopyOnWriteArrayList<>(); + private ServletContextHandler _contextHandler; + + @BeforeEach + public void before() throws Exception + { + _server = new Server(); + _connector = new ServerConnector(_server); + _server.addConnector(_connector); + + _contextHandler = new ServletContextHandler(); + _server.setHandler(_contextHandler); + _server.start(); + + _client = new HttpClient(); + _client.start(); + } + + @AfterEach + public void after() throws Exception + { + _client.stop(); + _server.stop(); + } + + @Test + public void testAsyncServlet() throws Exception + { + _contextHandler.addServlet(new ServletHolder(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + { + if (req.getDispatcherType() == DispatcherType.ASYNC) + { + _history.add("asyncDispatch"); + return; + } + + _history.add("doGet"); + AsyncContext asyncContext = req.startAsync(); + asyncContext.start(() -> + { + _history.add("asyncRunnable"); + asyncContext.dispatch("/dispatch"); + }); + } + }), "/"); + + _contextHandler.addEventListener(new ContextHandler.ContextScopeListener() + { + // Use a lock to prevent the async thread running the listener concurrently. + private final ReentrantLock _lock = new ReentrantLock(); + + @Override + public void enterScope(ContextHandler.APIContext context, org.eclipse.jetty.ee9.nested.Request request, Object reason) + { + _lock.lock(); + String pathInContext = (request == null) ? "null" : URIUtil.addPaths(request.getServletPath(), request.getPathInfo()); + _history.add("enterScope " + pathInContext); + } + + @Override + public void exitScope(ContextHandler.APIContext context, org.eclipse.jetty.ee9.nested.Request request) + { + String pathInContext = (request == null) ? "null" : URIUtil.addPaths(request.getServletPath(), request.getPathInfo()); + _history.add("exitScope " + pathInContext); + _lock.unlock(); + } + }); + + URI uri = URI.create("http://localhost:" + _connector.getLocalPort() + "/initialPath"); + ContentResponse response = _client.GET(uri); + assertThat(response.getStatus(), equalTo(HttpStatus.OK_200)); + assertHistory( + "enterScope /initialPath", + "doGet", + "exitScope /initialPath", + "enterScope /initialPath", + "asyncRunnable", + "exitScope /initialPath", + "enterScope /dispatch", + "asyncDispatch", + "exitScope /dispatch", + "enterScope /dispatch", + "exitScope /dispatch" + ); + } + + private void assertHistory(String... values) + { + assertThat(_history, equalTo(Arrays.asList(values))); + } +}