From f60534a8ac0fda8933f70789bea54cc0f5a5bd6c Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 13 Mar 2019 16:42:00 +1100 Subject: [PATCH] Issue #3458 - jetty websocket upgrades with only websocket-server websocket-servlet exposes core classes as it is used by the jetty and javax sides, so this introduces a way to add websocket mappings with jetty-server which does not depend on websocket-servlet to set websocket mappings through the JettyWebSocketServerContainer you now need to use the JettyWebSocketCreator which abstracts away the core classes from use with the jetty websocket api Signed-off-by: Lachlan Roberts --- .../server/JettyServerUpgradeRequest.java | 310 ++++++++++++++++++ .../server/JettyServerUpgradeResponse.java | 103 ++++++ .../server/JettyWebSocketCreator.java | 21 ++ .../server/JettyWebSocketServerContainer.java | 8 +- 4 files changed, 438 insertions(+), 4 deletions(-) create mode 100644 jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeRequest.java create mode 100644 jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeResponse.java create mode 100644 jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketCreator.java diff --git a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeRequest.java b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeRequest.java new file mode 100644 index 00000000000..9fe199ada75 --- /dev/null +++ b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeRequest.java @@ -0,0 +1,310 @@ +package org.eclipse.jetty.websocket.server; + +import java.net.HttpCookie; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URI; +import java.security.Principal; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; +import org.eclipse.jetty.websocket.common.JettyExtensionConfig; +import org.eclipse.jetty.websocket.core.server.Negotiation; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; + +public class JettyServerUpgradeRequest +{ + private ServletUpgradeRequest upgradeRequest; + + public JettyServerUpgradeRequest(ServletUpgradeRequest request) + { + upgradeRequest = request; + } + + /** + * @return The {@link X509Certificate} instance at request attribute "javax.servlet.request.X509Certificate" or null. + */ + public X509Certificate[] getCertificates() + { + return upgradeRequest.getCertificates(); + } + + /** + * @see HttpServletRequest#getCookies() + * @return Request cookies + */ + public List getCookies() + { + return upgradeRequest.getCookies(); + } + + /** + * @return The extensions offered + * @see Negotiation#getOfferedExtensions() + */ + public List getExtensions() + { + return upgradeRequest.getExtensions().stream().map(JettyExtensionConfig::new).collect(Collectors.toList()); + } + + /** + * @param name Header name + * @return Header value or null + * @see HttpServletRequest#getHeader(String) + */ + public String getHeader(String name) + { + return upgradeRequest.getHeader(name); + } + + /** + * @param name Header name + * @return Header value as integer or -1 + * @see HttpServletRequest#getHeader(String) + */ + public int getHeaderInt(String name) + { + return upgradeRequest.getHeaderInt(name); + } + + /** + * @return Map of headers + */ + public Map> getHeadersMap() + { + return upgradeRequest.getHeadersMap(); + } + + /** + * @param name Header name + * @return List of header values or null + */ + public List getHeaders(String name) + { + return upgradeRequest.getHeaders(name); + } + + /** + * @return The requested host + * @see HttpServletRequest#getRequestURL() + */ + public String getHost() + { + return upgradeRequest.getHost(); + } + + /** + * @return Immutable version of {@link HttpServletRequest} + */ + public HttpServletRequest getHttpServletRequest() + { + return upgradeRequest.getHttpServletRequest(); + } + + /** + * @return The HTTP protocol version + * @see HttpServletRequest#getProtocol() + */ + public String getHttpVersion() + { + return upgradeRequest.getHttpVersion(); + } + + /** + * @return The requested Locale + * @see HttpServletRequest#getLocale() + */ + public Locale getLocale() + { + return upgradeRequest.getLocale(); + } + + /** + * @return The requested Locales + * @see HttpServletRequest#getLocales() + */ + public Enumeration getLocales() + { + return upgradeRequest.getLocales(); + } + + /** + * @return The local requested address, which is typically an {@link InetSocketAddress}, but may be another derivation of {@link SocketAddress} + * @see ServletRequest#getLocalAddr() + * @see ServletRequest#getLocalPort() + */ + public SocketAddress getLocalSocketAddress() + { + return upgradeRequest.getLocalSocketAddress(); + } + + /** + * @return The requested method + * @see HttpServletRequest#getMethod() + */ + public String getMethod() + { + return upgradeRequest.getMethod(); + } + + /** + * @return The origin header value + */ + public String getOrigin() + { + return upgradeRequest.getOrigin(); + } + + /** + * @return The request parameter map + * @see ServletRequest#getParameterMap() + */ + public Map> getParameterMap() + { + return upgradeRequest.getParameterMap(); + } + + /** + * @return WebSocket protocol version from "Sec-WebSocket-Version" header + */ + public String getProtocolVersion() + { + return upgradeRequest.getProtocolVersion(); + } + + /** + * @return The request query string + * @see HttpServletRequest#getQueryString() + */ + public String getQueryString() + { + return upgradeRequest.getQueryString(); + } + + /** + * @return The remote request address, which is typically an {@link InetSocketAddress}, but may be another derivation of {@link SocketAddress} + * @see ServletRequest#getRemoteAddr() + * @see ServletRequest#getRemotePort() + */ + public SocketAddress getRemoteSocketAddress() + { + return upgradeRequest.getRemoteSocketAddress(); + } + + /** + * @return The request URI path within the context + */ + public String getRequestPath() + { + return upgradeRequest.getRequestPath(); + } + + /** + * @return The request URI + * @see HttpServletRequest#getRequestURL() + */ + public URI getRequestURI() + { + return upgradeRequest.getRequestURI(); + } + + /** + * @param name Attribute name + * @return Attribute value or null + * @see ServletRequest#getAttribute(String) + */ + public Object getServletAttribute(String name) + { + return upgradeRequest.getServletAttribute(name); + } + + /** + * @return Request attribute map + */ + public Map getServletAttributes() + { + return upgradeRequest.getServletAttributes(); + } + + /** + * @return Request parameters + * @see ServletRequest#getParameterMap() + */ + public Map> getServletParameters() + { + return upgradeRequest.getServletParameters(); + } + + /** + * @return The HttpSession, which may be null or invalidated + * @see HttpServletRequest#getSession(boolean) + */ + public HttpSession getSession() + { + return upgradeRequest.getSession(); + } + + /** + * @return Get WebSocket negotiation offered sub protocols + */ + public List getSubProtocols() + { + return upgradeRequest.getSubProtocols(); + } + + /** + * @return The User's {@link Principal} or null + * @see HttpServletRequest#getUserPrincipal() + */ + public Principal getUserPrincipal() + { + return upgradeRequest.getUserPrincipal(); + } + + /** + * @param subprotocol A sub protocol name + * @return True if the sub protocol was offered + */ + public boolean hasSubProtocol(String subprotocol) + { + return upgradeRequest.hasSubProtocol(subprotocol); + } + + /** + * @return True if the request is secure + * @see ServletRequest#isSecure() + */ + public boolean isSecure() + { + return upgradeRequest.isSecure(); + } + + /** + * @param role The user role + * @return True if the requests user has the role + * @see HttpServletRequest#isUserInRole(String) + */ + public boolean isUserInRole(String role) + { + return upgradeRequest.isUserInRole(role); + } + + /** + * @param name Attribute name + * @param value Attribute value to set + * @see ServletRequest#setAttribute(String, Object) + */ + public void setServletAttribute(String name, Object value) + { + upgradeRequest.setServletAttribute(name, value); + } +} diff --git a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeResponse.java b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeResponse.java new file mode 100644 index 00000000000..b98d54efed4 --- /dev/null +++ b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyServerUpgradeResponse.java @@ -0,0 +1,103 @@ +package org.eclipse.jetty.websocket.server; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; +import org.eclipse.jetty.websocket.common.JettyExtensionConfig; +import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; + +public class JettyServerUpgradeResponse +{ + private ServletUpgradeResponse upgradeResponse; + + public JettyServerUpgradeResponse(ServletUpgradeResponse response) + { + upgradeResponse = response; + } + + public void addHeader(String name, String value) + { + upgradeResponse.addHeader(name, value); + } + + public void setHeader(String name, String value) + { + upgradeResponse.setHeader(name, value); + } + + public void setHeader(String name, List values) + { + upgradeResponse.setHeader(name, values); + } + + public String getAcceptedSubProtocol() + { + return upgradeResponse.getAcceptedSubProtocol(); + } + + public List getExtensions() + { + return upgradeResponse.getExtensions().stream().map(JettyExtensionConfig::new).collect(Collectors.toList()); + } + + public String getHeader(String name) + { + return upgradeResponse.getHeader(name); + } + + public Set getHeaderNames() + { + return upgradeResponse.getHeaderNames(); + } + + public Map> getHeadersMap() + { + return upgradeResponse.getHeadersMap(); + } + + public List getHeaders(String name) + { + return upgradeResponse.getHeaders(name); + } + + public int getStatusCode() + { + return upgradeResponse.getStatusCode(); + } + + public boolean isCommitted() + { + return upgradeResponse.isCommitted(); + } + + public void sendError(int statusCode, String message) throws IOException + { + upgradeResponse.sendError(statusCode, message); + } + + public void sendForbidden(String message) throws IOException + { + upgradeResponse.sendForbidden(message); + } + + public void setAcceptedSubProtocol(String protocol) + { + upgradeResponse.setAcceptedSubProtocol(protocol); + } + + public void setExtensions(List configs) + { + upgradeResponse.setExtensions(configs.stream() + .map(c->new org.eclipse.jetty.websocket.core.ExtensionConfig(c.getName(), c.getParameters())) + .collect(Collectors.toList())); + } + + public void setStatusCode(int statusCode) + { + upgradeResponse.setStatusCode(statusCode); + } +} diff --git a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketCreator.java b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketCreator.java new file mode 100644 index 00000000000..26d4c706149 --- /dev/null +++ b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketCreator.java @@ -0,0 +1,21 @@ +package org.eclipse.jetty.websocket.server; + + +/** + * Abstract WebSocket creator interface. + *

+ * Should you desire filtering of the WebSocket object creation due to criteria such as origin or sub-protocol, then you will be required to implement a custom + * WebSocketCreator implementation. + *

+ */ +public interface JettyWebSocketCreator +{ + /** + * Create a websocket from the incoming request. + * + * @param req the request details + * @param resp the response details + * @return a websocket object to use, or null if no websocket should be created from this request. + */ + Object createWebSocket(JettyServerUpgradeRequest req, JettyServerUpgradeResponse resp); +} diff --git a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java index 073cc070c66..9ce8f6f45e3 100644 --- a/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java +++ b/jetty-websocket/jetty-websocket-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java @@ -28,7 +28,6 @@ import java.util.function.Consumer; import javax.servlet.ServletContext; import org.eclipse.jetty.http.pathmap.PathSpec; -import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.LifeCycle; @@ -44,7 +43,6 @@ import org.eclipse.jetty.websocket.core.FrameHandler; import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.WebSocketException; import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory; -import org.eclipse.jetty.websocket.servlet.WebSocketCreator; import org.eclipse.jetty.websocket.servlet.WebSocketMapping; public class JettyWebSocketServerContainer extends ContainerLifeCycle implements WebSocketContainer, WebSocketPolicy, LifeCycle.Listener @@ -114,13 +112,15 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements addSessionListener(sessionTracker); } - public void addMapping(String pathSpec, WebSocketCreator creator) + public void addMapping(String pathSpec, JettyWebSocketCreator creator) { PathSpec ps = WebSocketMapping.parsePathSpec(pathSpec); if (webSocketMapping.getMapping(ps) != null) throw new WebSocketException("Duplicate WebSocket Mapping for PathSpec"); - webSocketMapping.addMapping(ps, creator, frameHandlerFactory, customizer); + webSocketMapping.addMapping(ps, + (req, resp)-> creator.createWebSocket(new JettyServerUpgradeRequest(req), new JettyServerUpgradeResponse(resp)), + frameHandlerFactory, customizer); } @Override