From 9818975ece68fc5981eb44f169fabc0daf932fce Mon Sep 17 00:00:00 2001
From: Lachlan Roberts <lachlan.p.roberts@gmail.com>
Date: Wed, 5 Mar 2025 18:12:38 +1100
Subject: [PATCH 1/5] Issue #11294 - fix access to websocket UpgradeRequest
 after upgrade

Signed-off-by: Lachlan Roberts <lachlan.p.roberts@gmail.com>
---
 .../internal/UpgradeRequestDelegate.java      |  16 +-
 .../internal/UpgradeResponseDelegate.java     |  11 +-
 .../jetty-websocket-jetty-tests/pom.xml       |   5 +
 .../tests/ServerUpgradeRequestTest.java       | 267 ++++++++++++++++++
 .../DelegatedServerUpgradeRequest.java        | 102 +++++--
 .../DelegatedServerUpgradeResponse.java       |  54 +++-
 .../JettyServerFrameHandlerFactory.java       |   4 +-
 .../tests/ServerUpgradeRequestTest.java       | 161 ++++++++---
 8 files changed, 531 insertions(+), 89 deletions(-)
 create mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ServerUpgradeRequestTest.java

diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestDelegate.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestDelegate.java
index 4cc743bf64e7..b3d0fd81cfef 100644
--- a/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestDelegate.java
+++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestDelegate.java
@@ -36,19 +36,26 @@ class UpgradeRequestDelegate implements UpgradeRequest
 {
     private final ServerUpgradeRequest request;
     private final Map<String, List<String>> headers;
+    private final List<HttpCookie> cookies;
+    private final Principal userPrincipal;
 
     UpgradeRequestDelegate(ServerUpgradeRequest request)
     {
         this.request = request;
         this.headers = HttpFields.asMap(request.getHeaders());
+
+        Request.AuthenticationState authenticationState = Request.getAuthenticationState(request);
+        userPrincipal = (authenticationState == null) ? null : authenticationState.getUserPrincipal();
+
+        this.cookies = Request.getCookies(request).stream()
+            .map(org.eclipse.jetty.http.HttpCookie::asJavaNetHttpCookie)
+            .toList();
     }
 
     @Override
     public List<HttpCookie> getCookies()
     {
-        return Request.getCookies(request).stream()
-            .map(org.eclipse.jetty.http.HttpCookie::asJavaNetHttpCookie)
-            .toList();
+        return cookies;
     }
 
     @Override
@@ -149,8 +156,7 @@ public List<String> getSubProtocols()
     @Override
     public Principal getUserPrincipal()
     {
-        // TODO: no Principal concept in Jetty core.
-        return null;
+        return userPrincipal;
     }
 
     @Override
diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseDelegate.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseDelegate.java
index 9340be8a6b3f..dd2827b086c9 100644
--- a/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseDelegate.java
+++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseDelegate.java
@@ -19,6 +19,8 @@
 import java.util.stream.Collectors;
 
 import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
 import org.eclipse.jetty.websocket.api.ExtensionConfig;
 import org.eclipse.jetty.websocket.api.UpgradeResponse;
 import org.eclipse.jetty.websocket.common.JettyExtensionConfig;
@@ -28,11 +30,16 @@ class UpgradeResponseDelegate implements UpgradeResponse
 {
     private final ServerUpgradeResponse response;
     private final Map<String, List<String>> headers;
+    private final int status;
 
     UpgradeResponseDelegate(ServerUpgradeResponse response)
     {
         this.response = response;
-        this.headers = HttpFields.asMap(response.getHeaders());
+        this.headers = HttpFields.asMap(response.getHeaders().asImmutable());
+
+        // Fake status code as it not set at the time this is created.
+        HttpVersion httpVersion = response.getRequest().getConnectionMetaData().getHttpVersion();
+        this.status = (httpVersion == HttpVersion.HTTP_1_1) ? HttpStatus.SWITCHING_PROTOCOLS_101 : HttpStatus.OK_200;
     }
 
     @Override
@@ -76,6 +83,6 @@ public List<String> getHeaders(String name)
     @Override
     public int getStatusCode()
     {
-        return response.getStatus();
+        return status;
     }
 }
diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/pom.xml b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/pom.xml
index 44f9b82406f3..bebfcf53c397 100644
--- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/pom.xml
+++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/pom.xml
@@ -72,6 +72,11 @@
       <artifactId>jetty-websocket-jetty-server</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-security</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ServerUpgradeRequestTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ServerUpgradeRequestTest.java
new file mode 100644
index 000000000000..29749cae295f
--- /dev/null
+++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ServerUpgradeRequestTest.java
@@ -0,0 +1,267 @@
+//
+// ========================================================================
+// 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.websocket.tests;
+
+import java.net.URI;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.security.AbstractLoginService;
+import org.eclipse.jetty.security.AuthenticationState;
+import org.eclipse.jetty.security.Constraint;
+import org.eclipse.jetty.security.DefaultIdentityService;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.RolePrincipal;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.security.UserIdentity;
+import org.eclipse.jetty.security.UserPrincipal;
+import org.eclipse.jetty.security.authentication.LoginAuthenticator;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.security.Credential;
+import org.eclipse.jetty.websocket.api.Callback;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.StatusCode;
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+import org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler;
+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.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ServerUpgradeRequestTest
+{
+    private Server _server;
+    private WebSocketClient _client;
+    private ServerConnector _connector;
+
+    private static class TestLoginService extends AbstractLoginService
+    {
+        public TestLoginService(IdentityService identityService)
+        {
+            setIdentityService(identityService);
+        }
+
+        @Override
+        protected List<RolePrincipal> loadRoleInfo(UserPrincipal user)
+        {
+            return List.of();
+        }
+
+        @Override
+        protected UserPrincipal loadUserInfo(String username)
+        {
+            return new UserPrincipal(username, null)
+            {
+                @Override
+                public boolean authenticate(Object credentials)
+                {
+                    return true;
+                }
+
+                @Override
+                public boolean authenticate(Credential c)
+                {
+                    return true;
+                }
+
+                @Override
+                public boolean authenticate(UserPrincipal u)
+                {
+                    return true;
+                }
+            };
+        }
+    }
+
+    private static class TestAuthenticator extends LoginAuthenticator
+    {
+        @Override
+        public String getAuthenticationType()
+        {
+            return "TEST";
+        }
+
+        @Override
+        public AuthenticationState validateRequest(Request request, Response response, org.eclipse.jetty.util.Callback callback)
+        {
+            UserIdentity user = login("user123", null, request, response);
+            if (user != null)
+                return new UserAuthenticationSucceeded(getAuthenticationType(), user);
+
+            Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403);
+            return AuthenticationState.SEND_FAILURE;
+        }
+    }
+
+    @BeforeEach
+    public void start() throws Exception
+    {
+        _server = new Server();
+        _connector = new ServerConnector(_server);
+        _server.addConnector(_connector);
+
+        WebSocketUpgradeHandler upgradeHandler = WebSocketUpgradeHandler.from(_server, container ->
+        {
+            container.addMapping("/", (req, resp, cb) ->
+            {
+                resp.getHeaders().put("customHeader", "customHeaderValue");
+                resp.setAcceptedSubProtocol(req.getSubProtocols().get(0));
+
+                return new ServerSocket();
+            });
+        });
+
+        SecurityHandler.PathMapped securityHandler = new SecurityHandler.PathMapped();
+        securityHandler.put("/*", Constraint.ANY_USER);
+        DefaultIdentityService identityService = new DefaultIdentityService();
+        securityHandler.setLoginService(new TestLoginService(identityService));
+        securityHandler.setIdentityService(identityService);
+        securityHandler.setAuthenticator(new TestAuthenticator());
+        securityHandler.setHandler(upgradeHandler);
+
+        _server.setHandler(securityHandler);
+        _server.start();
+
+        _client = new WebSocketClient();
+        _client.start();
+    }
+
+    @AfterEach
+    public void stop() throws Exception
+    {
+        _client.stop();
+        _server.stop();
+    }
+
+    @WebSocket
+    public static class ServerSocket extends EventSocket
+    {
+        @Override
+        public void onMessage(String message)
+        {
+            StringBuilder builder = new StringBuilder();
+
+            try
+            {
+                switch (message)
+                {
+                    case "getUpgradeRequest" ->
+                    {
+                        UpgradeRequest upgradeRequest = session.getUpgradeRequest();
+                        builder.append("getRequestURI: ").append(upgradeRequest.getRequestURI()).append("\n");
+                        builder.append("getHeaders: ").append(upgradeRequest.getHeaders()).append("\n");
+                        builder.append("getExtensions: ").append(upgradeRequest.getExtensions()).append("\n");
+                        builder.append("getHost: ").append(upgradeRequest.getHost()).append("\n");
+                        builder.append("getHttpVersion: ").append(upgradeRequest.getHttpVersion()).append("\n");
+                        builder.append("getQueryString: ").append(upgradeRequest.getQueryString()).append("\n");
+                        builder.append("getSubProtocols: ").append(upgradeRequest.getSubProtocols()).append("\n");
+                        builder.append("getProtocolVersion: ").append(upgradeRequest.getProtocolVersion()).append("\n");
+                        builder.append("getCookies: ").append(upgradeRequest.getCookies()).append("\n");
+                        builder.append("getUserPrincipal: ").append(upgradeRequest.getUserPrincipal()).append("\n");
+                        builder.append("getOrigin: ").append(upgradeRequest.getOrigin()).append("\n");
+                        builder.append("isSecure: ").append(upgradeRequest.isSecure()).append("\n");
+                        builder.append("getParameterMap: ").append(upgradeRequest.getParameterMap()).append("\n");
+                    }
+                    case "getUpgradeResponse" ->
+                    {
+                        UpgradeResponse upgradeResponse = session.getUpgradeResponse();
+                        builder.append("getHeaders: ").append(upgradeResponse.getHeaders()).append("\n");
+                        builder.append("getExtensions: ").append(upgradeResponse.getExtensions()).append("\n");
+                        builder.append("getStatusCode: ").append(upgradeResponse.getStatusCode()).append("\n");
+                        builder.append("getAcceptedSubProtocol: ").append(upgradeResponse.getAcceptedSubProtocol()).append("\n");
+                    }
+                    default -> throw new IllegalStateException("Unknown message: " + message);
+                }
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace(System.err);
+                throw e;
+            }
+
+            session.sendText(builder.toString(), Callback.NOOP);
+        }
+    }
+
+    @Test
+    public void testUpgradeRequest() throws Exception
+    {
+        URI uri = new URI("ws://localhost:" + _connector.getLocalPort() + "/?queryParam1=queryParamValue1");
+        ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
+        upgradeRequest.setSubProtocols("subProtocol1", "subProtocol2");
+        upgradeRequest.addExtensions("permessage-deflate");
+        upgradeRequest.setHeader("Cookie", "cookieHeader1=cookieValue1");
+        upgradeRequest.setHeader("Origin", "jetty-test");
+        upgradeRequest.setHeader("CustomRequestHeader", "request-header-value");
+
+        EventSocket clientEndpoint = new EventSocket();
+        Session session = _client.connect(clientEndpoint, uri, upgradeRequest).get(5, TimeUnit.SECONDS);
+
+        session.sendText("getUpgradeRequest", Callback.NOOP);
+        String received = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS);
+        assertThat(received, containsString("getRequestURI: " + uri));
+        assertThat(received, containsString("CustomRequestHeader=[request-header-value]"));
+        assertThat(received, containsString("getExtensions: [permessage-deflate]"));
+        assertThat(received, containsString("getHost: localhost"));
+        assertThat(received, containsString("getHttpVersion: HTTP/1.1"));
+        assertThat(received, containsString("getQueryString: queryParam1=queryParamValue1"));
+        assertThat(received, containsString("getSubProtocols: [subProtocol1, subProtocol2]"));
+        assertThat(received, containsString("getProtocolVersion: 13"));
+        assertThat(received, containsString("getCookies: [cookieHeader1=cookieValue1]"));
+        assertThat(received, containsString("getUserPrincipal: user123"));
+        assertThat(received, containsString("getOrigin: jetty-test"));
+        assertThat(received, containsString("isSecure: false"));
+        assertThat(received, containsString("getParameterMap: {queryParam1=[queryParamValue1]}"));
+
+        session.close();
+        assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
+        assertThat(clientEndpoint.closeCode, equalTo(StatusCode.NORMAL));
+    }
+
+    @Test
+    public void testUpgradeResponse() throws Exception
+    {
+        URI uri = new URI("ws://localhost:" + _connector.getLocalPort());
+
+        ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
+        upgradeRequest.setSubProtocols("subProtocol1", "subProtocol2");
+        upgradeRequest.addExtensions("permessage-deflate");
+
+        EventSocket clientEndpoint = new EventSocket();
+        Session session = _client.connect(clientEndpoint, uri, upgradeRequest).get(5, TimeUnit.SECONDS);
+
+        session.sendText("getUpgradeResponse", Callback.NOOP);
+        String received = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS);
+        assertThat(received, containsString("customHeader=[customHeaderValue]"));
+        assertThat(received, containsString("getExtensions: [permessage-deflate]"));
+        assertThat(received, containsString("getStatusCode: 101"));
+        assertThat(received, containsString("getAcceptedSubProtocol: subProtocol1"));
+
+        session.close();
+        assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
+        assertThat(clientEndpoint.closeCode, equalTo(StatusCode.NORMAL));
+    }
+}
diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeRequest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeRequest.java
index 77d7e71e1a16..e17753c71f95 100644
--- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeRequest.java
+++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeRequest.java
@@ -42,23 +42,56 @@
 
 public class DelegatedServerUpgradeRequest implements JettyServerUpgradeRequest
 {
+    private final boolean upgraded;
     private final URI requestURI;
     private final String queryString;
     private final ServerUpgradeRequest upgradeRequest;
     private final HttpServletRequest httpServletRequest;
     private final Principal userPrincipal;
+    private final String origin;
+    private final boolean isSecure;
     private final Map<String, List<String>> headers;
     private List<HttpCookie> cookies;
     private Map<String, List<String>> parameterMap;
 
     public DelegatedServerUpgradeRequest(ServerUpgradeRequest request)
     {
+        this(request, false);
+    }
+
+    public DelegatedServerUpgradeRequest(ServerUpgradeRequest request, boolean upgraded)
+    {
+        this.upgraded = upgraded;
         this.httpServletRequest = (HttpServletRequest)request
             .getAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE);
         this.upgradeRequest = request;
+        this.headers = HttpFields.asMap(upgradeRequest.getHeaders());
         this.queryString = httpServletRequest.getQueryString();
         this.userPrincipal = httpServletRequest.getUserPrincipal();
-        this.headers = HttpFields.asMap(upgradeRequest.getHeaders());
+        this.origin = httpServletRequest.getHeader(HttpHeader.ORIGIN.asString());
+        this.isSecure = httpServletRequest.isSecure();
+
+        Map<String, String[]> requestParams = httpServletRequest.getParameterMap();
+        if (requestParams != null)
+        {
+            parameterMap = new HashMap<>(requestParams.size());
+            for (Map.Entry<String, String[]> entry : requestParams.entrySet())
+            {
+                parameterMap.put(entry.getKey(), Arrays.asList(entry.getValue()));
+            }
+        }
+
+        Cookie[] reqCookies = httpServletRequest.getCookies();
+        if (reqCookies != null)
+        {
+            cookies = Arrays.stream(reqCookies)
+                .map(c -> new HttpCookie(c.getName(), c.getValue()))
+                .collect(Collectors.toList());
+        }
+        else
+        {
+            cookies = Collections.emptyList();
+        }
 
         try
         {
@@ -82,21 +115,6 @@ public ServerUpgradeRequest getServerUpgradeRequest()
     @Override
     public List<HttpCookie> getCookies()
     {
-        if (cookies == null)
-        {
-            Cookie[] reqCookies = httpServletRequest.getCookies();
-            if (reqCookies != null)
-            {
-                cookies = Arrays.stream(reqCookies)
-                    .map(c -> new HttpCookie(c.getName(), c.getValue()))
-                    .collect(Collectors.toList());
-            }
-            else
-            {
-                cookies = Collections.emptyList();
-            }
-        }
-
         return cookies;
     }
 
@@ -153,24 +171,12 @@ public String getMethod()
     @Override
     public String getOrigin()
     {
-        return httpServletRequest.getHeader(HttpHeader.ORIGIN.asString());
+        return origin;
     }
 
     @Override
     public Map<String, List<String>> getParameterMap()
     {
-        if (parameterMap == null)
-        {
-            Map<String, String[]> requestParams = httpServletRequest.getParameterMap();
-            if (requestParams != null)
-            {
-                parameterMap = new HashMap<>(requestParams.size());
-                for (Map.Entry<String, String[]> entry : requestParams.entrySet())
-                {
-                    parameterMap.put(entry.getKey(), Arrays.asList(entry.getValue()));
-                }
-            }
-        }
         return parameterMap;
     }
 
@@ -195,6 +201,9 @@ public URI getRequestURI()
     @Override
     public HttpSession getSession()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return httpServletRequest.getSession();
     }
 
@@ -219,42 +228,60 @@ public boolean hasSubProtocol(String subprotocol)
     @Override
     public boolean isSecure()
     {
-        return httpServletRequest.isSecure();
+        return isSecure;
     }
 
     @Override
     public X509Certificate[] getCertificates()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return (X509Certificate[])httpServletRequest.getAttribute("jakarta.servlet.request.X509Certificate");
     }
 
     @Override
     public HttpServletRequest getHttpServletRequest()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return httpServletRequest;
     }
 
     @Override
     public Locale getLocale()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return httpServletRequest.getLocale();
     }
 
     @Override
     public Enumeration<Locale> getLocales()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return httpServletRequest.getLocales();
     }
 
     @Override
     public SocketAddress getLocalSocketAddress()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return upgradeRequest.getConnectionMetaData().getLocalSocketAddress();
     }
 
     @Override
     public SocketAddress getRemoteSocketAddress()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return upgradeRequest.getConnectionMetaData().getRemoteSocketAddress();
     }
 
@@ -267,12 +294,18 @@ public String getRequestPath()
     @Override
     public Object getServletAttribute(String name)
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return upgradeRequest.getAttribute(name);
     }
 
     @Override
     public Map<String, Object> getServletAttributes()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         Map<String, Object> attributes = new HashMap<>(2);
         Enumeration<String> attributeNames = httpServletRequest.getAttributeNames();
         while (attributeNames.hasMoreElements())
@@ -286,18 +319,27 @@ public Map<String, Object> getServletAttributes()
     @Override
     public Map<String, List<String>> getServletParameters()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return getParameterMap();
     }
 
     @Override
     public boolean isUserInRole(String role)
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return httpServletRequest.isUserInRole(role);
     }
 
     @Override
     public void setServletAttribute(String name, Object value)
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         upgradeRequest.setAttribute(name, value);
     }
 }
diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java
index 9ec85c0177f1..516a1e225c21 100644
--- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java
+++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java
@@ -24,6 +24,7 @@
 import org.eclipse.jetty.ee10.websocket.server.JettyServerUpgradeResponse;
 import org.eclipse.jetty.http.HttpFields;
 import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
 import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.websocket.api.ExtensionConfig;
 import org.eclipse.jetty.websocket.common.JettyExtensionConfig;
@@ -32,36 +33,54 @@
 
 public class DelegatedServerUpgradeResponse implements JettyServerUpgradeResponse
 {
+    private final boolean upgraded;
     private final ServerUpgradeResponse upgradeResponse;
     private final HttpServletResponse httpServletResponse;
     private final Map<String, List<String>> headers;
+    private final int status;
 
     public DelegatedServerUpgradeResponse(ServerUpgradeResponse response)
     {
-        upgradeResponse = response;
-        ServletContextResponse servletContextResponse = Response.as(response, ServletContextResponse.class);
-        this.httpServletResponse = (HttpServletResponse)servletContextResponse.getRequest()
+        this(response, false);
+    }
+
+    public DelegatedServerUpgradeResponse(ServerUpgradeResponse response, boolean upgraded)
+    {
+        this.upgraded = upgraded;
+        this.upgradeResponse = response;
+        this.httpServletResponse = (HttpServletResponse)Response.as(response, ServletContextResponse.class).getRequest()
             .getAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
-        this.headers = HttpFields.asMap(upgradeResponse.getHeaders());
+        this.headers = HttpFields.asMap(upgraded ? upgradeResponse.getHeaders().asImmutable() : upgradeResponse.getHeaders());
+
+        // Fake status code if already upgraded, as it not set at the time this is created.
+        HttpVersion httpVersion = response.getRequest().getConnectionMetaData().getHttpVersion();
+        this.status = (httpVersion == HttpVersion.HTTP_1_1) ? HttpStatus.SWITCHING_PROTOCOLS_101 : HttpStatus.OK_200;
     }
 
     @Override
     public void addHeader(String name, String value)
     {
-        // TODO: This should go to the httpServletResponse for headers but then it won't do interception of the websocket headers
-        //  which are done through the jetty-core Response wrapping ServerUpgradeResponse done by websocket-core.
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         upgradeResponse.getHeaders().add(name, value);
     }
 
     @Override
     public void setHeader(String name, String value)
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         headers.put(name, List.of(value));
     }
 
     @Override
     public void setHeader(String name, List<String> values)
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         headers.put(name, values);
     }
 
@@ -104,24 +123,36 @@ public List<String> getHeaders(String name)
     @Override
     public int getStatusCode()
     {
-        return httpServletResponse.getStatus();
+        if (upgraded)
+            return status;
+        else
+            return httpServletResponse.getStatus();
     }
 
     @Override
     public void sendForbidden(String message) throws IOException
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         httpServletResponse.sendError(HttpStatus.FORBIDDEN_403, message);
     }
 
     @Override
     public void setAcceptedSubProtocol(String protocol)
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         upgradeResponse.setAcceptedSubProtocol(protocol);
     }
 
     @Override
     public void setExtensions(List<ExtensionConfig> configs)
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         upgradeResponse.setExtensions(configs.stream()
             .map(c -> new org.eclipse.jetty.websocket.core.ExtensionConfig(c.getName(), c.getParameters()))
             .collect(Collectors.toList()));
@@ -130,18 +161,27 @@ public void setExtensions(List<ExtensionConfig> configs)
     @Override
     public void setStatusCode(int statusCode)
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         httpServletResponse.setStatus(statusCode);
     }
 
     @Override
     public boolean isCommitted()
     {
+        if (upgraded)
+            return true;
+
         return httpServletResponse.isCommitted();
     }
 
     @Override
     public void sendError(int statusCode, String message) throws IOException
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         httpServletResponse.sendError(statusCode, message);
     }
 }
diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/JettyServerFrameHandlerFactory.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/JettyServerFrameHandlerFactory.java
index 17f50e82a461..b3657a058637 100644
--- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/JettyServerFrameHandlerFactory.java
+++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/JettyServerFrameHandlerFactory.java
@@ -40,8 +40,8 @@ public JettyServerFrameHandlerFactory(JettyWebSocketServerContainer container, W
     public FrameHandler newFrameHandler(Object websocketPojo, ServerUpgradeRequest upgradeRequest, ServerUpgradeResponse upgradeResponse)
     {
         JettyWebSocketFrameHandler frameHandler = super.newJettyFrameHandler(websocketPojo);
-        frameHandler.setUpgradeRequest(new DelegatedServerUpgradeRequest(upgradeRequest));
-        frameHandler.setUpgradeResponse(new DelegatedServerUpgradeResponse(upgradeResponse));
+        frameHandler.setUpgradeRequest(new DelegatedServerUpgradeRequest(upgradeRequest, true));
+        frameHandler.setUpgradeResponse(new DelegatedServerUpgradeResponse(upgradeResponse, true));
         return frameHandler;
     }
 }
diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/ServerUpgradeRequestTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/ServerUpgradeRequestTest.java
index 8c97bce7cbc2..e6261f9f4d50 100644
--- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/ServerUpgradeRequestTest.java
+++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/ServerUpgradeRequestTest.java
@@ -36,49 +36,29 @@
 import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.util.Callback;
 import org.eclipse.jetty.util.security.Credential;
+import org.eclipse.jetty.websocket.api.Callback;
 import org.eclipse.jetty.websocket.api.Session;
 import org.eclipse.jetty.websocket.api.StatusCode;
 import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
 import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
 import org.eclipse.jetty.websocket.client.WebSocketClient;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import static org.eclipse.jetty.websocket.api.Callback.NOOP;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.equalTo;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class ServerUpgradeRequestTest
 {
     private Server _server;
-    private ServerConnector _connector;
     private WebSocketClient _client;
-
-    @WebSocket
-    public static class MyEndpoint
-    {
-        @OnWebSocketOpen
-        public void onOpen(Session session) throws Exception
-        {
-            UpgradeRequest upgradeRequest = session.getUpgradeRequest();
-            session.sendText("userPrincipal=" + upgradeRequest.getUserPrincipal(), NOOP);
-            session.sendText("requestURI=" + upgradeRequest.getRequestURI(), NOOP);
-            session.close();
-        }
-
-        @OnWebSocketError
-        public void onError(Throwable t)
-        {
-            t.printStackTrace();
-        }
-    }
+    private ServerConnector _connector;
 
     private static class TestLoginService extends AbstractLoginService
     {
@@ -128,7 +108,7 @@ public String getAuthenticationType()
         }
 
         @Override
-        public AuthenticationState validateRequest(Request request, Response response, Callback callback)
+        public AuthenticationState validateRequest(Request request, Response response, org.eclipse.jetty.util.Callback callback)
         {
             UserIdentity user = login("user123", null, request, response);
             if (user != null)
@@ -140,19 +120,20 @@ public AuthenticationState validateRequest(Request request, Response response, C
     }
 
     @BeforeEach
-    public void before() throws Exception
+    public void start() throws Exception
     {
         _server = new Server();
         _connector = new ServerConnector(_server);
         _server.addConnector(_connector);
 
-        ServletContextHandler contextHandler = new ServletContextHandler();
-        contextHandler.setContextPath("/context1");
-        JettyWebSocketServletContainerInitializer.configure(contextHandler, ((servletContext, serverContainer) ->
-        {
-            serverContainer.addMapping("/ws", MyEndpoint.class);
-        }));
-        _server.setHandler(contextHandler);
+        ServletContextHandler servletContextHandler = new ServletContextHandler();
+        JettyWebSocketServletContainerInitializer.configure(servletContextHandler, (servletContext, container) ->
+            container.addMapping("/", (req, resp) ->
+            {
+                resp.setHeader("customHeader", "customHeaderValue");
+                resp.setAcceptedSubProtocol(req.getSubProtocols().get(0));
+                return new ServerSocket();
+            }));
 
         DefaultIdentityService identityService = new DefaultIdentityService();
         LoginService loginService = new TestLoginService(identityService);
@@ -165,34 +146,128 @@ public void before() throws Exception
         securityHandler.setConstraintMappings(List.of(constraintMapping));
         securityHandler.setLoginService(loginService);
         securityHandler.setIdentityService(identityService);
-        contextHandler.setSecurityHandler(securityHandler);
+        servletContextHandler.setSecurityHandler(securityHandler);
         securityHandler.setAuthenticator(new TestAuthenticator());
 
+        _server.setHandler(servletContextHandler);
         _server.start();
+
         _client = new WebSocketClient();
         _client.start();
     }
 
     @AfterEach
-    public void after() throws Exception
+    public void stop() throws Exception
     {
         _client.stop();
         _server.stop();
     }
 
+    @WebSocket
+    public static class ServerSocket extends EventSocket
+    {
+        @Override
+        public void onMessage(String message)
+        {
+            StringBuilder builder = new StringBuilder();
+
+            try
+            {
+                switch (message)
+                {
+                    case "getUpgradeRequest" ->
+                    {
+                        UpgradeRequest upgradeRequest = session.getUpgradeRequest();
+                        builder.append("getRequestURI: ").append(upgradeRequest.getRequestURI()).append("\n");
+                        builder.append("getHeaders: ").append(upgradeRequest.getHeaders()).append("\n");
+                        builder.append("getExtensions: ").append(upgradeRequest.getExtensions()).append("\n");
+                        builder.append("getHost: ").append(upgradeRequest.getHost()).append("\n");
+                        builder.append("getHttpVersion: ").append(upgradeRequest.getHttpVersion()).append("\n");
+                        builder.append("getQueryString: ").append(upgradeRequest.getQueryString()).append("\n");
+                        builder.append("getSubProtocols: ").append(upgradeRequest.getSubProtocols()).append("\n");
+                        builder.append("getProtocolVersion: ").append(upgradeRequest.getProtocolVersion()).append("\n");
+                        builder.append("getCookies: ").append(upgradeRequest.getCookies()).append("\n");
+                        builder.append("getUserPrincipal: ").append(upgradeRequest.getUserPrincipal()).append("\n");
+                        builder.append("getOrigin: ").append(upgradeRequest.getOrigin()).append("\n");
+                        builder.append("isSecure: ").append(upgradeRequest.isSecure()).append("\n");
+                        builder.append("getParameterMap: ").append(upgradeRequest.getParameterMap()).append("\n");
+                    }
+                    case "getUpgradeResponse" ->
+                    {
+                        UpgradeResponse upgradeResponse = session.getUpgradeResponse();
+                        builder.append("getHeaders: ").append(upgradeResponse.getHeaders()).append("\n");
+                        builder.append("getExtensions: ").append(upgradeResponse.getExtensions()).append("\n");
+                        builder.append("getStatusCode: ").append(upgradeResponse.getStatusCode()).append("\n");
+                        builder.append("getAcceptedSubProtocol: ").append(upgradeResponse.getAcceptedSubProtocol()).append("\n");
+                    }
+                    default -> throw new IllegalStateException("Unknown message: " + message);
+                }
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace(System.err);
+                throw e;
+            }
+
+            session.sendText(builder.toString(), Callback.NOOP);
+        }
+    }
+
     @Test
-    public void test() throws Exception
+    public void testUpgradeRequest() throws Exception
     {
-        URI uri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/context1/ws");
+        URI uri = new URI("ws://localhost:" + _connector.getLocalPort() + "/?queryParam1=queryParamValue1");
+        ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
+        upgradeRequest.setSubProtocols("subProtocol1", "subProtocol2");
+        upgradeRequest.addExtensions("permessage-deflate");
+        upgradeRequest.setHeader("Cookie", "cookieHeader1=cookieValue1");
+        upgradeRequest.setHeader("Origin", "jetty-test");
+        upgradeRequest.setHeader("CustomRequestHeader", "request-header-value");
+
         EventSocket clientEndpoint = new EventSocket();
-        assertNotNull(_client.connect(clientEndpoint, uri));
+        Session session = _client.connect(clientEndpoint, uri, upgradeRequest).get(5, TimeUnit.SECONDS);
+
+        session.sendText("getUpgradeRequest", Callback.NOOP);
+        String received = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS);
+        assertThat(received, containsString("getRequestURI: " + uri));
+        assertThat(received, containsString("CustomRequestHeader=[request-header-value]"));
+        assertThat(received, containsString("getExtensions: [permessage-deflate]"));
+        assertThat(received, containsString("getHost: localhost"));
+        assertThat(received, containsString("getHttpVersion: HTTP/1.1"));
+        assertThat(received, containsString("getQueryString: queryParam1=queryParamValue1"));
+        assertThat(received, containsString("getSubProtocols: [subProtocol1, subProtocol2]"));
+        assertThat(received, containsString("getProtocolVersion: 13"));
+        assertThat(received, containsString("getCookies: [cookieHeader1=\"cookieValue1\"]"));
+        assertThat(received, containsString("getUserPrincipal: user123"));
+        assertThat(received, containsString("getOrigin: jetty-test"));
+        assertThat(received, containsString("isSecure: false"));
+        assertThat(received, containsString("getParameterMap: {queryParam1=[queryParamValue1]}"));
 
-        String msg = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS);
-        assertThat(msg, equalTo("userPrincipal=user123"));
+        session.close();
+        assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
+        assertThat(clientEndpoint.closeCode, equalTo(StatusCode.NORMAL));
+    }
+
+    @Test
+    public void testUpgradeResponse() throws Exception
+    {
+        URI uri = new URI("ws://localhost:" + _connector.getLocalPort());
+
+        ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
+        upgradeRequest.setSubProtocols("subProtocol1", "subProtocol2");
+        upgradeRequest.addExtensions("permessage-deflate");
+
+        EventSocket clientEndpoint = new EventSocket();
+        Session session = _client.connect(clientEndpoint, uri, upgradeRequest).get(5, TimeUnit.SECONDS);
 
-        msg = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS);
-        assertThat(msg, equalTo("requestURI=ws://localhost:" + _connector.getLocalPort() + "/context1/ws"));
+        session.sendText("getUpgradeResponse", Callback.NOOP);
+        String received = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS);
+        assertThat(received, containsString("customHeader=[customHeaderValue]"));
+        assertThat(received, containsString("getExtensions: [permessage-deflate]"));
+        assertThat(received, containsString("getStatusCode: 101"));
+        assertThat(received, containsString("getAcceptedSubProtocol: subProtocol1"));
 
+        session.close();
         assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
         assertThat(clientEndpoint.closeCode, equalTo(StatusCode.NORMAL));
     }

From b059d3d5c846caa4db3f6680e7a9f13c6d91a2ce Mon Sep 17 00:00:00 2001
From: Lachlan Roberts <lachlan.p.roberts@gmail.com>
Date: Wed, 5 Mar 2025 20:10:51 +1100
Subject: [PATCH 2/5] Issue #11294 - fix access to websocket UpgradeRequest
 after upgrade

Signed-off-by: Lachlan Roberts <lachlan.p.roberts@gmail.com>
---
 .../internal/UpgradeResponseDelegate.java       | 10 ++++++----
 .../DelegatedServerUpgradeResponse.java         | 17 ++++++++++-------
 2 files changed, 16 insertions(+), 11 deletions(-)

diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseDelegate.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseDelegate.java
index dd2827b086c9..d185a0d622da 100644
--- a/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseDelegate.java
+++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseDelegate.java
@@ -29,13 +29,15 @@
 class UpgradeResponseDelegate implements UpgradeResponse
 {
     private final ServerUpgradeResponse response;
+    private final HttpFields httpFields;
     private final Map<String, List<String>> headers;
     private final int status;
 
     UpgradeResponseDelegate(ServerUpgradeResponse response)
     {
         this.response = response;
-        this.headers = HttpFields.asMap(response.getHeaders().asImmutable());
+        this.httpFields = response.getHeaders().asImmutable();
+        this.headers = HttpFields.asMap(httpFields);
 
         // Fake status code as it not set at the time this is created.
         HttpVersion httpVersion = response.getRequest().getConnectionMetaData().getHttpVersion();
@@ -59,13 +61,13 @@ public List<ExtensionConfig> getExtensions()
     @Override
     public String getHeader(String name)
     {
-        return response.getHeaders().get(name);
+        return httpFields.get(name);
     }
 
     @Override
     public Set<String> getHeaderNames()
     {
-        return response.getHeaders().getFieldNamesCollection();
+        return httpFields.getFieldNamesCollection();
     }
 
     @Override
@@ -77,7 +79,7 @@ public Map<String, List<String>> getHeaders()
     @Override
     public List<String> getHeaders(String name)
     {
-        return response.getHeaders().getValuesList(name);
+        return httpFields.getValuesList(name);
     }
 
     @Override
diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java
index 516a1e225c21..7425be162ade 100644
--- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java
+++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java
@@ -38,6 +38,7 @@ public class DelegatedServerUpgradeResponse implements JettyServerUpgradeRespons
     private final HttpServletResponse httpServletResponse;
     private final Map<String, List<String>> headers;
     private final int status;
+    private final HttpFields.Mutable httpFields;
 
     public DelegatedServerUpgradeResponse(ServerUpgradeResponse response)
     {
@@ -50,7 +51,9 @@ public DelegatedServerUpgradeResponse(ServerUpgradeResponse response, boolean up
         this.upgradeResponse = response;
         this.httpServletResponse = (HttpServletResponse)Response.as(response, ServletContextResponse.class).getRequest()
             .getAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
-        this.headers = HttpFields.asMap(upgraded ? upgradeResponse.getHeaders().asImmutable() : upgradeResponse.getHeaders());
+
+        this.httpFields = upgradeResponse.getHeaders();
+        this.headers = HttpFields.asMap(upgraded ? httpFields.asImmutable() : httpFields);
 
         // Fake status code if already upgraded, as it not set at the time this is created.
         HttpVersion httpVersion = response.getRequest().getConnectionMetaData().getHttpVersion();
@@ -63,7 +66,7 @@ public void addHeader(String name, String value)
         if (upgraded)
             throw new IllegalStateException("Already Upgraded to WebSocket");
 
-        upgradeResponse.getHeaders().add(name, value);
+        httpFields.add(name, value);
     }
 
     @Override
@@ -72,7 +75,7 @@ public void setHeader(String name, String value)
         if (upgraded)
             throw new IllegalStateException("Already Upgraded to WebSocket");
 
-        headers.put(name, List.of(value));
+        httpFields.put(name, List.of(value));
     }
 
     @Override
@@ -81,7 +84,7 @@ public void setHeader(String name, List<String> values)
         if (upgraded)
             throw new IllegalStateException("Already Upgraded to WebSocket");
 
-        headers.put(name, values);
+        httpFields.put(name, values);
     }
 
     @Override
@@ -99,13 +102,13 @@ public List<ExtensionConfig> getExtensions()
     @Override
     public String getHeader(String name)
     {
-        return upgradeResponse.getHeaders().get(name);
+        return httpFields.get(name);
     }
 
     @Override
     public Set<String> getHeaderNames()
     {
-        return upgradeResponse.getHeaders().getFieldNamesCollection();
+        return httpFields.getFieldNamesCollection();
     }
 
     @Override
@@ -117,7 +120,7 @@ public Map<String, List<String>> getHeaders()
     @Override
     public List<String> getHeaders(String name)
     {
-        return upgradeResponse.getHeaders().getValuesList(name);
+        return httpFields.getValuesList(name);
     }
 
     @Override

From 672b84132e7afb2b7e8861106e247c81ffdf571d Mon Sep 17 00:00:00 2001
From: Lachlan Roberts <lachlan.p.roberts@gmail.com>
Date: Wed, 5 Mar 2025 21:59:46 +1100
Subject: [PATCH 3/5] PR #12861 - fixes to pom file

Signed-off-by: Lachlan Roberts <lachlan.p.roberts@gmail.com>
---
 .../jetty-websocket-jetty-tests/pom.xml                | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/pom.xml b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/pom.xml
index bebfcf53c397..5bf0f9e58077 100644
--- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/pom.xml
+++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/pom.xml
@@ -42,6 +42,11 @@
       <artifactId>jetty-jmx</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-security</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-slf4j-impl</artifactId>
@@ -72,11 +77,6 @@
       <artifactId>jetty-websocket-jetty-server</artifactId>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-security</artifactId>
-      <scope>test</scope>
-    </dependency>
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>

From 7eee7a7d35efc16c830226475e0998d821a38448 Mon Sep 17 00:00:00 2001
From: Lachlan Roberts <lachlan.p.roberts@gmail.com>
Date: Thu, 6 Mar 2025 16:52:57 +1100
Subject: [PATCH 4/5] Issue #11294 - fix access to websocket UpgradeRequest
 after upgrade

Signed-off-by: Lachlan Roberts <lachlan.p.roberts@gmail.com>
---
 .../DelegatedServerUpgradeRequest.java        | 102 +++++++----
 .../DelegatedServerUpgradeResponse.java       |  69 ++++++--
 .../JettyServerFrameHandlerFactory.java       |   4 +-
 .../tests/ServerUpgradeRequestTest.java       | 160 +++++++++++++-----
 4 files changed, 247 insertions(+), 88 deletions(-)

diff --git a/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee11/websocket/server/internal/DelegatedServerUpgradeRequest.java b/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee11/websocket/server/internal/DelegatedServerUpgradeRequest.java
index 7b73b6b20d8c..f593a078a39a 100644
--- a/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee11/websocket/server/internal/DelegatedServerUpgradeRequest.java
+++ b/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee11/websocket/server/internal/DelegatedServerUpgradeRequest.java
@@ -42,23 +42,56 @@
 
 public class DelegatedServerUpgradeRequest implements JettyServerUpgradeRequest
 {
+    private final boolean upgraded;
     private final URI requestURI;
     private final String queryString;
     private final ServerUpgradeRequest upgradeRequest;
     private final HttpServletRequest httpServletRequest;
     private final Principal userPrincipal;
+    private final String origin;
+    private final boolean isSecure;
     private final Map<String, List<String>> headers;
     private List<HttpCookie> cookies;
     private Map<String, List<String>> parameterMap;
 
     public DelegatedServerUpgradeRequest(ServerUpgradeRequest request)
     {
+        this(request, false);
+    }
+
+    public DelegatedServerUpgradeRequest(ServerUpgradeRequest request, boolean upgraded)
+    {
+        this.upgraded = upgraded;
         this.httpServletRequest = (HttpServletRequest)request
             .getAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE);
         this.upgradeRequest = request;
+        this.headers = HttpFields.asMap(upgradeRequest.getHeaders());
         this.queryString = httpServletRequest.getQueryString();
         this.userPrincipal = httpServletRequest.getUserPrincipal();
-        this.headers = HttpFields.asMap(upgradeRequest.getHeaders());
+        this.origin = httpServletRequest.getHeader(HttpHeader.ORIGIN.asString());
+        this.isSecure = httpServletRequest.isSecure();
+
+        Map<String, String[]> requestParams = httpServletRequest.getParameterMap();
+        if (requestParams != null)
+        {
+            parameterMap = new HashMap<>(requestParams.size());
+            for (Map.Entry<String, String[]> entry : requestParams.entrySet())
+            {
+                parameterMap.put(entry.getKey(), Arrays.asList(entry.getValue()));
+            }
+        }
+
+        Cookie[] reqCookies = httpServletRequest.getCookies();
+        if (reqCookies != null)
+        {
+            cookies = Arrays.stream(reqCookies)
+                .map(c -> new HttpCookie(c.getName(), c.getValue()))
+                .collect(Collectors.toList());
+        }
+        else
+        {
+            cookies = Collections.emptyList();
+        }
 
         try
         {
@@ -82,21 +115,6 @@ public ServerUpgradeRequest getServerUpgradeRequest()
     @Override
     public List<HttpCookie> getCookies()
     {
-        if (cookies == null)
-        {
-            Cookie[] reqCookies = httpServletRequest.getCookies();
-            if (reqCookies != null)
-            {
-                cookies = Arrays.stream(reqCookies)
-                    .map(c -> new HttpCookie(c.getName(), c.getValue()))
-                    .collect(Collectors.toList());
-            }
-            else
-            {
-                cookies = Collections.emptyList();
-            }
-        }
-
         return cookies;
     }
 
@@ -153,24 +171,12 @@ public String getMethod()
     @Override
     public String getOrigin()
     {
-        return httpServletRequest.getHeader(HttpHeader.ORIGIN.asString());
+        return origin;
     }
 
     @Override
     public Map<String, List<String>> getParameterMap()
     {
-        if (parameterMap == null)
-        {
-            Map<String, String[]> requestParams = httpServletRequest.getParameterMap();
-            if (requestParams != null)
-            {
-                parameterMap = new HashMap<>(requestParams.size());
-                for (Map.Entry<String, String[]> entry : requestParams.entrySet())
-                {
-                    parameterMap.put(entry.getKey(), Arrays.asList(entry.getValue()));
-                }
-            }
-        }
         return parameterMap;
     }
 
@@ -195,6 +201,9 @@ public URI getRequestURI()
     @Override
     public HttpSession getSession()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return httpServletRequest.getSession();
     }
 
@@ -219,42 +228,60 @@ public boolean hasSubProtocol(String subprotocol)
     @Override
     public boolean isSecure()
     {
-        return httpServletRequest.isSecure();
+        return isSecure;
     }
 
     @Override
     public X509Certificate[] getCertificates()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return (X509Certificate[])httpServletRequest.getAttribute("jakarta.servlet.request.X509Certificate");
     }
 
     @Override
     public HttpServletRequest getHttpServletRequest()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return httpServletRequest;
     }
 
     @Override
     public Locale getLocale()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return httpServletRequest.getLocale();
     }
 
     @Override
     public Enumeration<Locale> getLocales()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return httpServletRequest.getLocales();
     }
 
     @Override
     public SocketAddress getLocalSocketAddress()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return upgradeRequest.getConnectionMetaData().getLocalSocketAddress();
     }
 
     @Override
     public SocketAddress getRemoteSocketAddress()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return upgradeRequest.getConnectionMetaData().getRemoteSocketAddress();
     }
 
@@ -267,12 +294,18 @@ public String getRequestPath()
     @Override
     public Object getServletAttribute(String name)
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return upgradeRequest.getAttribute(name);
     }
 
     @Override
     public Map<String, Object> getServletAttributes()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         Map<String, Object> attributes = new HashMap<>(2);
         Enumeration<String> attributeNames = httpServletRequest.getAttributeNames();
         while (attributeNames.hasMoreElements())
@@ -286,18 +319,27 @@ public Map<String, Object> getServletAttributes()
     @Override
     public Map<String, List<String>> getServletParameters()
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return getParameterMap();
     }
 
     @Override
     public boolean isUserInRole(String role)
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         return httpServletRequest.isUserInRole(role);
     }
 
     @Override
     public void setServletAttribute(String name, Object value)
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         upgradeRequest.setAttribute(name, value);
     }
 }
diff --git a/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee11/websocket/server/internal/DelegatedServerUpgradeResponse.java b/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee11/websocket/server/internal/DelegatedServerUpgradeResponse.java
index a42b75ded54f..a7718494001a 100644
--- a/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee11/websocket/server/internal/DelegatedServerUpgradeResponse.java
+++ b/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee11/websocket/server/internal/DelegatedServerUpgradeResponse.java
@@ -24,6 +24,7 @@
 import org.eclipse.jetty.ee11.websocket.server.JettyServerUpgradeResponse;
 import org.eclipse.jetty.http.HttpFields;
 import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
 import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.websocket.api.ExtensionConfig;
 import org.eclipse.jetty.websocket.common.JettyExtensionConfig;
@@ -32,37 +33,58 @@
 
 public class DelegatedServerUpgradeResponse implements JettyServerUpgradeResponse
 {
+    private final boolean upgraded;
     private final ServerUpgradeResponse upgradeResponse;
     private final HttpServletResponse httpServletResponse;
     private final Map<String, List<String>> headers;
+    private final int status;
+    private final HttpFields.Mutable httpFields;
 
     public DelegatedServerUpgradeResponse(ServerUpgradeResponse response)
     {
-        upgradeResponse = response;
-        ServletContextResponse servletContextResponse = Response.as(response, ServletContextResponse.class);
-        this.httpServletResponse = (HttpServletResponse)servletContextResponse.getRequest()
+        this(response, false);
+    }
+
+    public DelegatedServerUpgradeResponse(ServerUpgradeResponse response, boolean upgraded)
+    {
+        this.upgraded = upgraded;
+        this.upgradeResponse = response;
+        this.httpServletResponse = (HttpServletResponse)Response.as(response, ServletContextResponse.class).getRequest()
             .getAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
-        this.headers = HttpFields.asMap(upgradeResponse.getHeaders());
+
+        this.httpFields = upgradeResponse.getHeaders();
+        this.headers = HttpFields.asMap(upgraded ? httpFields.asImmutable() : httpFields);
+
+        // Fake status code if already upgraded, as it not set at the time this is created.
+        HttpVersion httpVersion = response.getRequest().getConnectionMetaData().getHttpVersion();
+        this.status = (httpVersion == HttpVersion.HTTP_1_1) ? HttpStatus.SWITCHING_PROTOCOLS_101 : HttpStatus.OK_200;
     }
 
     @Override
     public void addHeader(String name, String value)
     {
-        // TODO: This should go to the httpServletResponse for headers but then it won't do interception of the websocket headers
-        //  which are done through the jetty-core Response wrapping ServerUpgradeResponse done by websocket-core.
-        upgradeResponse.getHeaders().add(name, value);
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
+        httpFields.add(name, value);
     }
 
     @Override
     public void setHeader(String name, String value)
     {
-        upgradeResponse.getHeaders().put(name, value);
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
+        httpFields.put(name, List.of(value));
     }
 
     @Override
     public void setHeader(String name, List<String> values)
     {
-        upgradeResponse.getHeaders().put(name, values);
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
+        httpFields.put(name, values);
     }
 
     @Override
@@ -80,13 +102,13 @@ public List<ExtensionConfig> getExtensions()
     @Override
     public String getHeader(String name)
     {
-        return upgradeResponse.getHeaders().get(name);
+        return httpFields.get(name);
     }
 
     @Override
     public Set<String> getHeaderNames()
     {
-        return upgradeResponse.getHeaders().getFieldNamesCollection();
+        return httpFields.getFieldNamesCollection();
     }
 
     @Override
@@ -98,30 +120,42 @@ public Map<String, List<String>> getHeaders()
     @Override
     public List<String> getHeaders(String name)
     {
-        return upgradeResponse.getHeaders().getValuesList(name);
+        return httpFields.getValuesList(name);
     }
 
     @Override
     public int getStatusCode()
     {
-        return httpServletResponse.getStatus();
+        if (upgraded)
+            return status;
+        else
+            return httpServletResponse.getStatus();
     }
 
     @Override
     public void sendForbidden(String message) throws IOException
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         httpServletResponse.sendError(HttpStatus.FORBIDDEN_403, message);
     }
 
     @Override
     public void setAcceptedSubProtocol(String protocol)
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         upgradeResponse.setAcceptedSubProtocol(protocol);
     }
 
     @Override
     public void setExtensions(List<ExtensionConfig> configs)
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         upgradeResponse.setExtensions(configs.stream()
             .map(c -> new org.eclipse.jetty.websocket.core.ExtensionConfig(c.getName(), c.getParameters()))
             .collect(Collectors.toList()));
@@ -130,18 +164,27 @@ public void setExtensions(List<ExtensionConfig> configs)
     @Override
     public void setStatusCode(int statusCode)
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         httpServletResponse.setStatus(statusCode);
     }
 
     @Override
     public boolean isCommitted()
     {
+        if (upgraded)
+            return true;
+
         return httpServletResponse.isCommitted();
     }
 
     @Override
     public void sendError(int statusCode, String message) throws IOException
     {
+        if (upgraded)
+            throw new IllegalStateException("Already Upgraded to WebSocket");
+
         httpServletResponse.sendError(statusCode, message);
     }
 }
diff --git a/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee11/websocket/server/internal/JettyServerFrameHandlerFactory.java b/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee11/websocket/server/internal/JettyServerFrameHandlerFactory.java
index dbe89eec2aee..885c03cb5da8 100644
--- a/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee11/websocket/server/internal/JettyServerFrameHandlerFactory.java
+++ b/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee11/websocket/server/internal/JettyServerFrameHandlerFactory.java
@@ -40,8 +40,8 @@ public JettyServerFrameHandlerFactory(JettyWebSocketServerContainer container, W
     public FrameHandler newFrameHandler(Object websocketPojo, ServerUpgradeRequest upgradeRequest, ServerUpgradeResponse upgradeResponse)
     {
         JettyWebSocketFrameHandler frameHandler = super.newJettyFrameHandler(websocketPojo);
-        frameHandler.setUpgradeRequest(new DelegatedServerUpgradeRequest(upgradeRequest));
-        frameHandler.setUpgradeResponse(new DelegatedServerUpgradeResponse(upgradeResponse));
+        frameHandler.setUpgradeRequest(new DelegatedServerUpgradeRequest(upgradeRequest, true));
+        frameHandler.setUpgradeResponse(new DelegatedServerUpgradeResponse(upgradeResponse, true));
         return frameHandler;
     }
 }
diff --git a/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee11/websocket/tests/ServerUpgradeRequestTest.java b/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee11/websocket/tests/ServerUpgradeRequestTest.java
index 4891dd116e23..913ca646ef55 100644
--- a/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee11/websocket/tests/ServerUpgradeRequestTest.java
+++ b/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee11/websocket/tests/ServerUpgradeRequestTest.java
@@ -36,49 +36,28 @@
 import org.eclipse.jetty.server.Response;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.util.Callback;
 import org.eclipse.jetty.util.security.Credential;
 import org.eclipse.jetty.websocket.api.Session;
 import org.eclipse.jetty.websocket.api.StatusCode;
 import org.eclipse.jetty.websocket.api.UpgradeRequest;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
 import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
 import org.eclipse.jetty.websocket.client.WebSocketClient;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import static org.eclipse.jetty.websocket.api.Callback.NOOP;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.equalTo;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class ServerUpgradeRequestTest
 {
     private Server _server;
-    private ServerConnector _connector;
     private WebSocketClient _client;
-
-    @WebSocket
-    public static class MyEndpoint
-    {
-        @OnWebSocketOpen
-        public void onOpen(Session session) throws Exception
-        {
-            UpgradeRequest upgradeRequest = session.getUpgradeRequest();
-            session.sendText("userPrincipal=" + upgradeRequest.getUserPrincipal(), NOOP);
-            session.sendText("requestURI=" + upgradeRequest.getRequestURI(), NOOP);
-            session.close();
-        }
-
-        @OnWebSocketError
-        public void onError(Throwable t)
-        {
-            t.printStackTrace();
-        }
-    }
+    private ServerConnector _connector;
 
     private static class TestLoginService extends AbstractLoginService
     {
@@ -128,7 +107,7 @@ public String getAuthenticationType()
         }
 
         @Override
-        public AuthenticationState validateRequest(Request request, Response response, Callback callback)
+        public AuthenticationState validateRequest(Request request, Response response, org.eclipse.jetty.util.Callback callback)
         {
             UserIdentity user = login("user123", null, request, response);
             if (user != null)
@@ -140,19 +119,20 @@ public AuthenticationState validateRequest(Request request, Response response, C
     }
 
     @BeforeEach
-    public void before() throws Exception
+    public void start() throws Exception
     {
         _server = new Server();
         _connector = new ServerConnector(_server);
         _server.addConnector(_connector);
 
-        ServletContextHandler contextHandler = new ServletContextHandler();
-        contextHandler.setContextPath("/context1");
-        JettyWebSocketServletContainerInitializer.configure(contextHandler, ((servletContext, serverContainer) ->
-        {
-            serverContainer.addMapping("/ws", MyEndpoint.class);
-        }));
-        _server.setHandler(contextHandler);
+        ServletContextHandler servletContextHandler = new ServletContextHandler();
+        JettyWebSocketServletContainerInitializer.configure(servletContextHandler, (servletContext, container) ->
+            container.addMapping("/", (req, resp) ->
+            {
+                resp.setHeader("customHeader", "customHeaderValue");
+                resp.setAcceptedSubProtocol(req.getSubProtocols().get(0));
+                return new ServerSocket();
+            }));
 
         DefaultIdentityService identityService = new DefaultIdentityService();
         LoginService loginService = new TestLoginService(identityService);
@@ -165,34 +145,128 @@ public void before() throws Exception
         securityHandler.setConstraintMappings(List.of(constraintMapping));
         securityHandler.setLoginService(loginService);
         securityHandler.setIdentityService(identityService);
-        contextHandler.setSecurityHandler(securityHandler);
+        servletContextHandler.setSecurityHandler(securityHandler);
         securityHandler.setAuthenticator(new TestAuthenticator());
 
+        _server.setHandler(servletContextHandler);
         _server.start();
+
         _client = new WebSocketClient();
         _client.start();
     }
 
     @AfterEach
-    public void after() throws Exception
+    public void stop() throws Exception
     {
         _client.stop();
         _server.stop();
     }
 
+    @WebSocket
+    public static class ServerSocket extends EventSocket
+    {
+        @Override
+        public void onMessage(String message)
+        {
+            StringBuilder builder = new StringBuilder();
+
+            try
+            {
+                switch (message)
+                {
+                    case "getUpgradeRequest" ->
+                    {
+                        UpgradeRequest upgradeRequest = session.getUpgradeRequest();
+                        builder.append("getRequestURI: ").append(upgradeRequest.getRequestURI()).append("\n");
+                        builder.append("getHeaders: ").append(upgradeRequest.getHeaders()).append("\n");
+                        builder.append("getExtensions: ").append(upgradeRequest.getExtensions()).append("\n");
+                        builder.append("getHost: ").append(upgradeRequest.getHost()).append("\n");
+                        builder.append("getHttpVersion: ").append(upgradeRequest.getHttpVersion()).append("\n");
+                        builder.append("getQueryString: ").append(upgradeRequest.getQueryString()).append("\n");
+                        builder.append("getSubProtocols: ").append(upgradeRequest.getSubProtocols()).append("\n");
+                        builder.append("getProtocolVersion: ").append(upgradeRequest.getProtocolVersion()).append("\n");
+                        builder.append("getCookies: ").append(upgradeRequest.getCookies()).append("\n");
+                        builder.append("getUserPrincipal: ").append(upgradeRequest.getUserPrincipal()).append("\n");
+                        builder.append("getOrigin: ").append(upgradeRequest.getOrigin()).append("\n");
+                        builder.append("isSecure: ").append(upgradeRequest.isSecure()).append("\n");
+                        builder.append("getParameterMap: ").append(upgradeRequest.getParameterMap()).append("\n");
+                    }
+                    case "getUpgradeResponse" ->
+                    {
+                        UpgradeResponse upgradeResponse = session.getUpgradeResponse();
+                        builder.append("getHeaders: ").append(upgradeResponse.getHeaders()).append("\n");
+                        builder.append("getExtensions: ").append(upgradeResponse.getExtensions()).append("\n");
+                        builder.append("getStatusCode: ").append(upgradeResponse.getStatusCode()).append("\n");
+                        builder.append("getAcceptedSubProtocol: ").append(upgradeResponse.getAcceptedSubProtocol()).append("\n");
+                    }
+                    default -> throw new IllegalStateException("Unknown message: " + message);
+                }
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace(System.err);
+                throw e;
+            }
+
+            session.sendText(builder.toString(), org.eclipse.jetty.websocket.api.Callback.NOOP);
+        }
+    }
+
     @Test
-    public void test() throws Exception
+    public void testUpgradeRequest() throws Exception
     {
-        URI uri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/context1/ws");
+        URI uri = new URI("ws://localhost:" + _connector.getLocalPort() + "/?queryParam1=queryParamValue1");
+        ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
+        upgradeRequest.setSubProtocols("subProtocol1", "subProtocol2");
+        upgradeRequest.addExtensions("permessage-deflate");
+        upgradeRequest.setHeader("Cookie", "cookieHeader1=cookieValue1");
+        upgradeRequest.setHeader("Origin", "jetty-test");
+        upgradeRequest.setHeader("CustomRequestHeader", "request-header-value");
+
         EventSocket clientEndpoint = new EventSocket();
-        assertNotNull(_client.connect(clientEndpoint, uri));
+        Session session = _client.connect(clientEndpoint, uri, upgradeRequest).get(5, TimeUnit.SECONDS);
+
+        session.sendText("getUpgradeRequest", org.eclipse.jetty.websocket.api.Callback.NOOP);
+        String received = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS);
+        assertThat(received, containsString("getRequestURI: " + uri));
+        assertThat(received, containsString("CustomRequestHeader=[request-header-value]"));
+        assertThat(received, containsString("getExtensions: [permessage-deflate]"));
+        assertThat(received, containsString("getHost: localhost"));
+        assertThat(received, containsString("getHttpVersion: HTTP/1.1"));
+        assertThat(received, containsString("getQueryString: queryParam1=queryParamValue1"));
+        assertThat(received, containsString("getSubProtocols: [subProtocol1, subProtocol2]"));
+        assertThat(received, containsString("getProtocolVersion: 13"));
+        assertThat(received, containsString("getCookies: [cookieHeader1=\"cookieValue1\"]"));
+        assertThat(received, containsString("getUserPrincipal: user123"));
+        assertThat(received, containsString("getOrigin: jetty-test"));
+        assertThat(received, containsString("isSecure: false"));
+        assertThat(received, containsString("getParameterMap: {queryParam1=[queryParamValue1]}"));
 
-        String msg = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS);
-        assertThat(msg, equalTo("userPrincipal=user123"));
+        session.close();
+        assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
+        assertThat(clientEndpoint.closeCode, equalTo(StatusCode.NORMAL));
+    }
+
+    @Test
+    public void testUpgradeResponse() throws Exception
+    {
+        URI uri = new URI("ws://localhost:" + _connector.getLocalPort());
+
+        ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
+        upgradeRequest.setSubProtocols("subProtocol1", "subProtocol2");
+        upgradeRequest.addExtensions("permessage-deflate");
+
+        EventSocket clientEndpoint = new EventSocket();
+        Session session = _client.connect(clientEndpoint, uri, upgradeRequest).get(5, TimeUnit.SECONDS);
 
-        msg = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS);
-        assertThat(msg, equalTo("requestURI=ws://localhost:" + _connector.getLocalPort() + "/context1/ws"));
+        session.sendText("getUpgradeResponse", org.eclipse.jetty.websocket.api.Callback.NOOP);
+        String received = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS);
+        assertThat(received, containsString("customHeader=[customHeaderValue]"));
+        assertThat(received, containsString("getExtensions: [permessage-deflate]"));
+        assertThat(received, containsString("getStatusCode: 101"));
+        assertThat(received, containsString("getAcceptedSubProtocol: subProtocol1"));
 
+        session.close();
         assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
         assertThat(clientEndpoint.closeCode, equalTo(StatusCode.NORMAL));
     }

From 08df54b580934592d362671c39601af731745572 Mon Sep 17 00:00:00 2001
From: Lachlan Roberts <lachlan.p.roberts@gmail.com>
Date: Thu, 6 Mar 2025 16:57:48 +1100
Subject: [PATCH 5/5] Issue #11294 - fix access to websocket UpgradeRequest
 after upgrade

Signed-off-by: Lachlan Roberts <lachlan.p.roberts@gmail.com>
---
 .../DelegatedServerUpgradeRequest.java        | 38 -------------------
 .../DelegatedServerUpgradeResponse.java       | 24 ------------
 2 files changed, 62 deletions(-)

diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeRequest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeRequest.java
index e17753c71f95..5390409992b7 100644
--- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeRequest.java
+++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeRequest.java
@@ -42,7 +42,6 @@
 
 public class DelegatedServerUpgradeRequest implements JettyServerUpgradeRequest
 {
-    private final boolean upgraded;
     private final URI requestURI;
     private final String queryString;
     private final ServerUpgradeRequest upgradeRequest;
@@ -61,7 +60,6 @@ public DelegatedServerUpgradeRequest(ServerUpgradeRequest request)
 
     public DelegatedServerUpgradeRequest(ServerUpgradeRequest request, boolean upgraded)
     {
-        this.upgraded = upgraded;
         this.httpServletRequest = (HttpServletRequest)request
             .getAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE);
         this.upgradeRequest = request;
@@ -201,9 +199,6 @@ public URI getRequestURI()
     @Override
     public HttpSession getSession()
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         return httpServletRequest.getSession();
     }
 
@@ -234,54 +229,36 @@ public boolean isSecure()
     @Override
     public X509Certificate[] getCertificates()
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         return (X509Certificate[])httpServletRequest.getAttribute("jakarta.servlet.request.X509Certificate");
     }
 
     @Override
     public HttpServletRequest getHttpServletRequest()
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         return httpServletRequest;
     }
 
     @Override
     public Locale getLocale()
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         return httpServletRequest.getLocale();
     }
 
     @Override
     public Enumeration<Locale> getLocales()
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         return httpServletRequest.getLocales();
     }
 
     @Override
     public SocketAddress getLocalSocketAddress()
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         return upgradeRequest.getConnectionMetaData().getLocalSocketAddress();
     }
 
     @Override
     public SocketAddress getRemoteSocketAddress()
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         return upgradeRequest.getConnectionMetaData().getRemoteSocketAddress();
     }
 
@@ -294,18 +271,12 @@ public String getRequestPath()
     @Override
     public Object getServletAttribute(String name)
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         return upgradeRequest.getAttribute(name);
     }
 
     @Override
     public Map<String, Object> getServletAttributes()
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         Map<String, Object> attributes = new HashMap<>(2);
         Enumeration<String> attributeNames = httpServletRequest.getAttributeNames();
         while (attributeNames.hasMoreElements())
@@ -319,27 +290,18 @@ public Map<String, Object> getServletAttributes()
     @Override
     public Map<String, List<String>> getServletParameters()
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         return getParameterMap();
     }
 
     @Override
     public boolean isUserInRole(String role)
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         return httpServletRequest.isUserInRole(role);
     }
 
     @Override
     public void setServletAttribute(String name, Object value)
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         upgradeRequest.setAttribute(name, value);
     }
 }
diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java
index 7425be162ade..3cfcbf0394b0 100644
--- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java
+++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java
@@ -63,27 +63,18 @@ public DelegatedServerUpgradeResponse(ServerUpgradeResponse response, boolean up
     @Override
     public void addHeader(String name, String value)
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         httpFields.add(name, value);
     }
 
     @Override
     public void setHeader(String name, String value)
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         httpFields.put(name, List.of(value));
     }
 
     @Override
     public void setHeader(String name, List<String> values)
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         httpFields.put(name, values);
     }
 
@@ -135,27 +126,18 @@ public int getStatusCode()
     @Override
     public void sendForbidden(String message) throws IOException
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         httpServletResponse.sendError(HttpStatus.FORBIDDEN_403, message);
     }
 
     @Override
     public void setAcceptedSubProtocol(String protocol)
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         upgradeResponse.setAcceptedSubProtocol(protocol);
     }
 
     @Override
     public void setExtensions(List<ExtensionConfig> configs)
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         upgradeResponse.setExtensions(configs.stream()
             .map(c -> new org.eclipse.jetty.websocket.core.ExtensionConfig(c.getName(), c.getParameters()))
             .collect(Collectors.toList()));
@@ -164,9 +146,6 @@ public void setExtensions(List<ExtensionConfig> configs)
     @Override
     public void setStatusCode(int statusCode)
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         httpServletResponse.setStatus(statusCode);
     }
 
@@ -182,9 +161,6 @@ public boolean isCommitted()
     @Override
     public void sendError(int statusCode, String message) throws IOException
     {
-        if (upgraded)
-            throw new IllegalStateException("Already Upgraded to WebSocket");
-
         httpServletResponse.sendError(statusCode, message);
     }
 }