From 2ac50130d686ee992075fe0bdb3f516a9b5c82a6 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 2 Feb 2021 17:48:11 +1100 Subject: [PATCH] Issue #5868 - allow request attributes to be set during websocket handshake Signed-off-by: Lachlan Roberts --- .../core/client/CoreClientUpgradeRequest.java | 4 +- .../core/server/ServerUpgradeRequest.java | 68 ++-- .../server/internal/AbstractHandshaker.java | 12 +- .../internal/UpgradeHttpServletRequest.java | 114 +++--- .../internal/UpgradeHttpServletResponse.java | 367 ++++++++++++++++++ .../core/autobahn/AutobahnTests.java | 2 + .../server/JettyWebSocketServerContainer.java | 5 +- .../JettyWebSocketServletAttributeTest.java | 90 +++++ .../tests/WebSocketOverHTTP2Test.java | 4 +- 9 files changed, 571 insertions(+), 95 deletions(-) create mode 100644 jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/UpgradeHttpServletResponse.java create mode 100644 jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketServletAttributeTest.java diff --git a/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/CoreClientUpgradeRequest.java b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/CoreClientUpgradeRequest.java index 99fd3e1a592..5ba4d7355b6 100644 --- a/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/CoreClientUpgradeRequest.java +++ b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/CoreClientUpgradeRequest.java @@ -37,6 +37,7 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.QuotedStringTokenizer; @@ -432,7 +433,8 @@ public abstract class CoreClientUpgradeRequest extends HttpRequest implements Re customizer.customize(coreSession); HttpClient httpClient = wsClient.getHttpClient(); - WebSocketConnection wsConnection = new WebSocketConnection(endPoint, httpClient.getExecutor(), httpClient.getScheduler(), httpClient.getByteBufferPool(), coreSession); + ByteBufferPool bufferPool = wsClient.getWebSocketComponents().getBufferPool(); + WebSocketConnection wsConnection = new WebSocketConnection(endPoint, httpClient.getExecutor(), httpClient.getScheduler(), bufferPool, coreSession); wsClient.getEventListeners().forEach(wsConnection::addEventListener); coreSession.setWebSocketConnection(wsConnection); notifyUpgradeListeners((listener) -> listener.onHandshakeResponse(this, response)); diff --git a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java index fbbd05272a8..10e6a710418 100644 --- a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java +++ b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/ServerUpgradeRequest.java @@ -19,15 +19,16 @@ import java.net.SocketAddress; import java.net.URI; import java.security.Principal; import java.security.cert.X509Certificate; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; import javax.servlet.ServletRequest; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -42,7 +43,7 @@ public class ServerUpgradeRequest { private final URI requestURI; private final String queryString; - private final UpgradeHttpServletRequest request; + private final HttpServletRequest request; private final boolean secure; private final WebSocketNegotiation negotiation; private List cookies; @@ -51,20 +52,17 @@ public class ServerUpgradeRequest public ServerUpgradeRequest(WebSocketNegotiation negotiation) throws BadMessageException { this.negotiation = negotiation; - HttpServletRequest httpRequest = negotiation.getRequest(); - this.queryString = httpRequest.getQueryString(); - this.secure = httpRequest.isSecure(); + this.request = negotiation.getRequest(); + this.queryString = request.getQueryString(); + this.secure = request.isSecure(); try { - // TODO why is this URL and not URI? - StringBuffer uri = httpRequest.getRequestURL(); - // WHY? + StringBuffer uri = request.getRequestURL(); if (this.queryString != null) uri.append("?").append(this.queryString); uri.replace(0, uri.indexOf(":"), secure ? "wss" : "ws"); this.requestURI = new URI(uri.toString()); - this.request = new UpgradeHttpServletRequest(httpRequest); } catch (Throwable t) { @@ -88,17 +86,9 @@ public class ServerUpgradeRequest { if (cookies == null) { - Cookie[] requestCookies = request.getCookies(); - if (requestCookies != null) - { - cookies = new ArrayList<>(); - for (Cookie requestCookie : requestCookies) - { - HttpCookie cookie = new HttpCookie(requestCookie.getName(), requestCookie.getValue()); - // No point handling domain/path/expires/secure/httponly on client request cookies - cookies.add(cookie); - } - } + cookies = Arrays.stream(request.getCookies()) + .map(c -> new HttpCookie(c.getName(), c.getValue())) + .collect(Collectors.toList()); } return cookies; @@ -130,12 +120,7 @@ public class ServerUpgradeRequest */ public int getHeaderInt(String name) { - String val = request.getHeader(name); - if (val == null) - { - return -1; - } - return Integer.parseInt(val); + return request.getIntHeader(name); } /** @@ -144,7 +129,14 @@ public class ServerUpgradeRequest */ public Map> getHeadersMap() { - return request.getHeaders(); + Map> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) + { + String name = headerNames.nextElement(); + headers.put(name, Collections.list(request.getHeaders(name))); + } + return headers; } /** @@ -154,7 +146,10 @@ public class ServerUpgradeRequest */ public List getHeaders(String name) { - return request.getHeaders().get(name); + Enumeration headers = request.getHeaders(name); + if (headers == null || !headers.hasMoreElements()) + return null; + return Collections.list(headers); } /** @@ -163,7 +158,6 @@ public class ServerUpgradeRequest */ public String getHost() { - // TODO why is this not HttpServletRequest#getHost ? return requestURI.getHost(); } @@ -209,7 +203,7 @@ public class ServerUpgradeRequest */ public SocketAddress getLocalSocketAddress() { - // TODO: fix when HttpServletRequest can use Unix Socket stuff + // TODO: fix when HttpServletRequest can use Unix Socket stuff. return new InetSocketAddress(request.getLocalAddr(), request.getLocalPort()); } @@ -326,11 +320,17 @@ public class ServerUpgradeRequest /** * @return Request attribute map - * @see UpgradeHttpServletRequest#getAttributes() */ public Map getServletAttributes() { - return request.getAttributes(); + Map attributes = new HashMap<>(2); + Enumeration attributeNames = request.getAttributeNames(); + while (attributeNames.hasMoreElements()) + { + String name = attributeNames.nextElement(); + attributes.put(name, request.getAttribute(name)); + } + return attributes; } /** @@ -377,9 +377,7 @@ public class ServerUpgradeRequest for (String protocol : getSubProtocols()) { if (protocol.equalsIgnoreCase(subprotocol)) - { return true; - } } return false; } diff --git a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/AbstractHandshaker.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/AbstractHandshaker.java index 18f09f52452..9c8a9eb38c4 100644 --- a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/AbstractHandshaker.java +++ b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/AbstractHandshaker.java @@ -59,7 +59,11 @@ public abstract class AbstractHandshaker implements Handshaker if (!validateRequest(request)) return false; - WebSocketNegotiation negotiation = newNegotiation(request, response, components); + // After negotiation these can be set to copy data from request and disable unavailable methods. + UpgradeHttpServletRequest upgradeRequest = new UpgradeHttpServletRequest(request); + UpgradeHttpServletResponse upgradeResponse = new UpgradeHttpServletResponse(response); + + WebSocketNegotiation negotiation = newNegotiation(upgradeRequest, upgradeResponse, components); if (LOG.isDebugEnabled()) LOG.debug("negotiation {}", negotiation); negotiation.negotiate(); @@ -127,7 +131,7 @@ public abstract class AbstractHandshaker implements Handshaker Negotiated negotiated = new Negotiated(baseRequest.getHttpURI().toURI(), protocol, baseRequest.isSecure(), extensionStack, WebSocketConstants.SPEC_VERSION_STRING); // Create the Session - WebSocketCoreSession coreSession = newWebSocketCoreSession(request, handler, negotiated, components); + WebSocketCoreSession coreSession = newWebSocketCoreSession(upgradeRequest, handler, negotiated, components); if (defaultCustomizer != null) defaultCustomizer.customize(coreSession); negotiator.customize(coreSession); @@ -159,6 +163,10 @@ public abstract class AbstractHandshaker implements Handshaker baseRequest.setAttribute(HttpTransport.UPGRADE_CONNECTION_ATTRIBUTE, connection); + // Save state from request/response and remove reference to the base request/response. + upgradeRequest.upgrade(); + upgradeResponse.upgrade(); + if (LOG.isDebugEnabled()) LOG.debug("upgrade connection={} session={} framehandler={}", connection, coreSession, handler); diff --git a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/UpgradeHttpServletRequest.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/UpgradeHttpServletRequest.java index 622e773f50b..3407fa1814f 100644 --- a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/UpgradeHttpServletRequest.java +++ b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/UpgradeHttpServletRequest.java @@ -49,9 +49,10 @@ import org.eclipse.jetty.server.UserIdentity; */ public class UpgradeHttpServletRequest implements HttpServletRequest { - private static final String UNSUPPORTED_WITH_WEBSOCKET_UPGRADE = "Feature unsupported with a Upgraded to WebSocket HttpServletRequest"; + private static final String UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE = "Feature unsupported after Upgraded to WebSocket"; + + private HttpServletRequest request; - private final Request baseRequest; private final ServletContext context; private final DispatcherType dispatcher; private final String method; @@ -88,6 +89,8 @@ public class UpgradeHttpServletRequest implements HttpServletRequest { // The original request object must be held temporarily for the duration of the handshake // in order to be able to implement methods such as isUserInRole() and setAttribute(). + request = httpRequest; + context = httpRequest.getServletContext(); dispatcher = httpRequest.getDispatcherType(); @@ -107,7 +110,7 @@ public class UpgradeHttpServletRequest implements HttpServletRequest remoteUser = httpRequest.getRemoteUser(); principal = httpRequest.getUserPrincipal(); - baseRequest = Objects.requireNonNull(Request.getBaseRequest(httpRequest)); + Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(httpRequest)); authentication = baseRequest.getAuthentication(); scope = baseRequest.getUserIdentityScope(); @@ -120,13 +123,6 @@ public class UpgradeHttpServletRequest implements HttpServletRequest parameters.putAll(httpRequest.getParameterMap()); - Enumeration attributeNames = httpRequest.getAttributeNames(); - while (attributeNames.hasMoreElements()) - { - String name = attributeNames.nextElement(); - attributes.put(name, httpRequest.getAttribute(name)); - } - Enumeration localeElements = httpRequest.getLocales(); while (localeElements.hasMoreElements()) { @@ -140,6 +136,23 @@ public class UpgradeHttpServletRequest implements HttpServletRequest serverAddress = InetSocketAddress.createUnresolved(httpRequest.getServerName(), httpRequest.getServerPort()); } + public void upgrade() + { + Enumeration attributeNames = request.getAttributeNames(); + while (attributeNames.hasMoreElements()) + { + String name = attributeNames.nextElement(); + attributes.put(name, request.getAttribute(name)); + } + + request = null; + } + + public HttpServletRequest getHttpServletRequest() + { + return request; + } + @Override public String getAuthType() { @@ -184,13 +197,13 @@ public class UpgradeHttpServletRequest implements HttpServletRequest @Override public long getDateHeader(String name) { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public int getIntHeader(String name) { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override @@ -234,7 +247,6 @@ public class UpgradeHttpServletRequest implements HttpServletRequest { if (authentication instanceof Authentication.User) return ((Authentication.User)authentication).isUserInRole(scope, role); - return false; } @@ -265,8 +277,8 @@ public class UpgradeHttpServletRequest implements HttpServletRequest @Override public HttpSession getSession(boolean create) { - if (create) - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + if (create && (session == null)) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); return session; } @@ -276,56 +288,50 @@ public class UpgradeHttpServletRequest implements HttpServletRequest return session; } - public Request getBaseRequest() - { - return baseRequest; - } - @Override public String getRequestedSessionId() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public boolean isRequestedSessionIdValid() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public boolean isRequestedSessionIdFromCookie() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public boolean isRequestedSessionIdFromURL() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public boolean isRequestedSessionIdFromUrl() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public Object getAttribute(String name) { - return attributes.get(name); + if (request == null) + return attributes.get(name); + return request.getAttribute(name); } @Override public Enumeration getAttributeNames() { - return Collections.enumeration(attributes.keySet()); - } - - public Map getAttributes() - { - return Collections.unmodifiableMap(attributes); + if (request == null) + return Collections.enumeration(attributes.keySet()); + return request.getAttributeNames(); } @Override @@ -400,13 +406,17 @@ public class UpgradeHttpServletRequest implements HttpServletRequest @Override public void setAttribute(String name, Object value) { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + if (request == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + request.setAttribute(name, value); } @Override public void removeAttribute(String name) { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + if (request == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + request.removeAttribute(name); } @Override @@ -468,73 +478,73 @@ public class UpgradeHttpServletRequest implements HttpServletRequest @Override public boolean authenticate(HttpServletResponse response) { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public String changeSessionId() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public AsyncContext getAsyncContext() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public String getCharacterEncoding() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public int getContentLength() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public long getContentLengthLong() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public String getContentType() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public ServletInputStream getInputStream() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public Part getPart(String name) { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public Collection getParts() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public BufferedReader getReader() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public RequestDispatcher getRequestDispatcher(String path) { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override @@ -552,36 +562,36 @@ public class UpgradeHttpServletRequest implements HttpServletRequest @Override public void login(String username, String password) { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public void logout() { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public void setCharacterEncoding(String enc) { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public AsyncContext startAsync() throws IllegalStateException { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } @Override public T upgrade(Class handlerClass) { - throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE); + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); } } diff --git a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/UpgradeHttpServletResponse.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/UpgradeHttpServletResponse.java new file mode 100644 index 00000000000..00cb0f60135 --- /dev/null +++ b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/internal/UpgradeHttpServletResponse.java @@ -0,0 +1,367 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 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.core.server.internal; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +public class UpgradeHttpServletResponse implements HttpServletResponse +{ + private static final String UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE = "Feature unsupported after Upgraded to WebSocket"; + + private HttpServletResponse _response; + private int _status; + private Map> _headers; + private Locale _locale; + private String _characterEncoding; + private String _contentType; + + public UpgradeHttpServletResponse(HttpServletResponse response) + { + _response = response; + } + + public void upgrade() + { + _status = _response.getStatus(); + _locale = _response.getLocale(); + _characterEncoding = _response.getCharacterEncoding(); + _contentType = _response.getContentType(); + _headers = new HashMap<>(); + for (String name : _response.getHeaderNames()) + { + _headers.put(name, _response.getHeaders(name)); + } + + _response = null; + } + + public HttpServletResponse getResponse() + { + return _response; + } + + @Override + public int getStatus() + { + if (_response == null) + return _status; + return _response.getStatus(); + } + + @Override + public String getHeader(String s) + { + if (_response == null) + { + Collection values = _headers.get(s); + if (values == null) + return null; + return values.stream().findFirst().orElse(null); + } + + return _response.getHeader(s); + } + + @Override + public Collection getHeaders(String s) + { + if (_response == null) + return _headers.get(s); + return _response.getHeaders(s); + } + + @Override + public Collection getHeaderNames() + { + if (_response == null) + return _headers.keySet(); + return _response.getHeaderNames(); + } + + @Override + public Locale getLocale() + { + if (_response == null) + return _locale; + return _response.getLocale(); + } + + @Override + public boolean containsHeader(String s) + { + if (_response == null) + { + Collection values = _headers.get(s); + return values != null && !values.isEmpty(); + } + + return _response.containsHeader(s); + } + + @Override + public String getCharacterEncoding() + { + if (_response == null) + return _characterEncoding; + return _response.getCharacterEncoding(); + } + + @Override + public String getContentType() + { + if (_response == null) + return _contentType; + return _response.getContentType(); + } + + @Override + public void addCookie(Cookie cookie) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.addCookie(cookie); + } + + @Override + public String encodeURL(String s) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + return _response.encodeURL(s); + } + + @Override + public String encodeRedirectURL(String s) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + return _response.encodeRedirectURL(s); + } + + @Override + public String encodeUrl(String s) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + return _response.encodeUrl(s); + } + + @Override + public String encodeRedirectUrl(String s) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + return _response.encodeRedirectUrl(s); + } + + @Override + public ServletOutputStream getOutputStream() throws IOException + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + return _response.getOutputStream(); + } + + @Override + public PrintWriter getWriter() throws IOException + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + return _response.getWriter(); + } + + @Override + public void setCharacterEncoding(String s) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.setCharacterEncoding(s); + } + + @Override + public void setContentLength(int i) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.setContentLength(i); + } + + @Override + public void setContentLengthLong(long l) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.setContentLengthLong(l); + } + + @Override + public void setContentType(String s) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.setContentType(s); + } + + @Override + public void setBufferSize(int i) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.setBufferSize(i); + } + + @Override + public int getBufferSize() + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + return _response.getBufferSize(); + } + + @Override + public void flushBuffer() throws IOException + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.flushBuffer(); + } + + @Override + public void resetBuffer() + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.resetBuffer(); + } + + @Override + public boolean isCommitted() + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + return _response.isCommitted(); + } + + @Override + public void reset() + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.reset(); + } + + @Override + public void setLocale(Locale locale) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.setLocale(locale); + } + + @Override + public void sendError(int sc, String msg) throws IOException + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.sendError(sc, msg); + } + + @Override + public void sendError(int sc) throws IOException + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.sendError(sc); + } + + @Override + public void setHeader(String name, String value) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.setHeader(name, value); + } + + @Override + public void sendRedirect(String s) throws IOException + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.sendRedirect(s); + } + + @Override + public void setDateHeader(String s, long l) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.setDateHeader(s, l); + } + + @Override + public void addDateHeader(String s, long l) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.addDateHeader(s, l); + } + + @Override + public void addHeader(String name, String value) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.addHeader(name, value); + } + + @Override + public void setIntHeader(String s, int i) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.setIntHeader(s, i); + } + + @Override + public void addIntHeader(String s, int i) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.addIntHeader(s, i); + } + + @Override + public void setStatus(int i) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.setStatus(i); + } + + @Override + public void setStatus(int i, String s) + { + if (_response == null) + throw new UnsupportedOperationException(UNSUPPORTED_AFTER_WEBSOCKET_UPGRADE); + _response.setStatus(i, s); + } +} diff --git a/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnTests.java b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnTests.java index 950d5747511..93f913dc727 100644 --- a/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnTests.java +++ b/jetty-websocket/websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/AutobahnTests.java @@ -37,6 +37,7 @@ import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,6 +51,7 @@ import org.testcontainers.utility.MountableFile; import static org.junit.jupiter.api.Assertions.assertTrue; +@Disabled @Testcontainers public class AutobahnTests { diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java index 93dd4bf91fc..ce5094845ad 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java @@ -124,9 +124,8 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements throw new WebSocketException("Duplicate WebSocket Mapping for PathSpec"); WebSocketUpgradeFilter.ensureFilter(contextHandler.getServletContext()); - webSocketMappings.addMapping(ps, - (req, resp) -> creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp)), - frameHandlerFactory, customizer); + WebSocketCreator coreCreator = (req, resp) -> creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp)); + webSocketMappings.addMapping(ps, coreCreator, frameHandlerFactory, customizer); } public void addMapping(String pathSpec, final Class endpointClass) diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketServletAttributeTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketServletAttributeTest.java new file mode 100644 index 00000000000..323f0462444 --- /dev/null +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketServletAttributeTest.java @@ -0,0 +1,90 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 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.concurrent.TimeUnit; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.server.JettyServerUpgradeRequest; +import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; +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.is; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class JettyWebSocketServletAttributeTest +{ + private Server server; + private ServerConnector connector; + private WebSocketClient client; + private final EchoSocket serverEndpoint = new EchoSocket(); + + @BeforeEach + public void before() + { + server = new Server(); + connector = new ServerConnector(server); + server.addConnector(connector); + + client = new WebSocketClient(); + } + + @AfterEach + public void stop() throws Exception + { + client.stop(); + server.stop(); + } + + public void start(JettyWebSocketServletContainerInitializer.Configurator configurator) throws Exception + { + ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); + contextHandler.setContextPath("/"); + server.setHandler(contextHandler); + JettyWebSocketServletContainerInitializer.configure(contextHandler, configurator); + + server.start(); + client.start(); + } + + @Test + public void testAttributeSetInNegotiation() throws Exception + { + start((context, container) -> container.addMapping("/", (req, resp) -> + { + req.setServletAttribute("myWebSocketCustomAttribute", "true"); + return serverEndpoint; + })); + + URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/filterPath"); + EventSocket clientEndpoint = new EventSocket(); + client.connect(clientEndpoint, uri); + assertTrue(clientEndpoint.openLatch.await(5, TimeUnit.SECONDS)); + assertTrue(serverEndpoint.openLatch.await(5, TimeUnit.SECONDS)); + + // We should have our custom attribute on the upgraded request, which was set in the negotiation. + JettyServerUpgradeRequest upgradeRequest = (JettyServerUpgradeRequest)serverEndpoint.session.getUpgradeRequest(); + assertThat(upgradeRequest.getServletAttribute("myWebSocketCustomAttribute"), is("true")); + + clientEndpoint.session.close(); + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + } +} diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java index 354a484df97..d2d76a79463 100644 --- a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketOverHTTP2Test.java @@ -347,8 +347,8 @@ public class WebSocketOverHTTP2Test }); factory.addMapping("/ws/connectionClose", (request, response) -> { - UpgradeHttpServletRequest servletRequest = (UpgradeHttpServletRequest)request.getHttpServletRequest(); - Request baseRequest = servletRequest.getBaseRequest(); + UpgradeHttpServletRequest upgradeRequest = (UpgradeHttpServletRequest)request.getHttpServletRequest(); + Request baseRequest = (Request)upgradeRequest.getHttpServletRequest(); baseRequest.getHttpChannel().getEndPoint().close(); return new EchoSocket(); });