jetty / jetty/jetty.project

ee11 servlet: ServletRequest.getServletContext() returns the source context, not the target, after a cross-context RequestDispatcher.include()

Open
#15,733 0 comments 0 reactions 1 assignee Claimed by @olamy View on GitHub
Dominant language
Java
Stars
4.1k
Forks
2k
Avg merge
3d 56m
Merged PRs (30d)
48

Description

[CrossContextGetServletContextBug.java](https://github.com/user-attachments/files/31871281/CrossContextGetServletContextBug.java)

**Jetty version:** 12.1.12
**Java version:** 21
**OS:** macOS (not OS-specific)

## Description

When a servlet in context A performs a cross-context `RequestDispatcher.include()` into a servlet in context B, the included servlet's own `getServletContext()` (bound at `init()`) correctly reports context B. But `ServletRequest.getServletContext()` on the *request* object passed to that servlet still reports context A — the original, dispatching context — not context B.

Per the Servlet spec, `ServletRequest.getServletContext()` should return "the servlet context to which this ServletRequest was last dispatched", which for code running inside the include() target ought to be context B.

This matters for any code that looks up its `WebApplicationContext`/config/state via `request.getServletContext()` rather than its own inherited `getServletContext()` — it silently gets the wrong context's data instead of an error, which made this tricky to notice.

## Minimal reproduction

Self-contained, only needs `jetty-server`, `jetty-ee11-servlet`, `jetty-security`, `jetty-session`, `jetty-http`, `jetty-io`, `jetty-util`, `jakarta.servlet-api:6.1.0`, `slf4j-api` on the classpath:

```java
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee11.servlet.ServletContextHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

/**
* EntryServlet (context "/a") performs a cross-context RequestDispatcher.include()
* into TargetServlet (context "/b"), using a directly-held ServletContext reference
* obtained from ServletContextHandler.getServletContext() (the usual workaround for
* ServletContext.getContext(String) being restricted).
*
* Expected: request.getServletContext() inside TargetServlet reports "/b".
* Actual: it reports "/a" -- the ORIGINAL context.
*/
public class CrossContextGetServletContextBug {

static ServletContext contextB;

public static class EntryServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
contextB.getRequestDispatcher("/target").include(req, resp);
}
}

public static class TargetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String viaOwnContext = getServletContext().getContextPath(); // correct: set at init() time
String viaRequest = req.getServletContext().getContextPath(); // spec: should be "/b"
resp.setContentType("text/plain");
resp.getWriter().println("getServletContext().getContextPath() = " + viaOwnContext);
resp.getWriter().println("request.getServletContext().getContextPath() = " + viaRequest);
}
}

public static void main(String[] args) throws Exception {
Server server = new Server(0);

ServletContextHandler ctxA = new ServletContextHandler("/a");
ctxA.addServlet(EntryServlet.class, "/entry");

ServletContextHandler ctxB = new ServletContextHandler("/b");
ctxB.addServlet(TargetServlet.class, "/target");

contextB = ctxB.getServletContext();

ContextHandlerCollection contexts = new ContextHandlerCollection();
contexts.addHandler(ctxA);
contexts.addHandler(ctxB);
server.setHandler(contexts);
server.start();

int port = ((ServerConnector) server.getConnectors()[0]).getLocalPort();
String body = HttpClient.newHttpClient()
.send(HttpRequest.newBuilder(URI.create("http://localhost:" + port + "/a/entry")).GET().build(),
HttpResponse.BodyHandlers.ofString())
.body();

System.out.println(body);
System.out.println(body.contains("request.getServletContext().getContextPath() = /b")
? "PASS: request.getServletContext() correctly reflects the include target"
: "FAIL (bug reproduced): request.getServletContext() did not return the include target's context");

server.stop();
}
}
```

**Expected output:**
```
getServletContext().getContextPath() = /b
request.getServletContext().getContextPath() = /b
```

**Actual output (as run against 12.1.12):**
```
getServletContext().getContextPath() = /b
request.getServletContext().getContextPath() = /a

FAIL (bug reproduced): request.getServletContext() did not return the include target's context
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.