You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2013/08/15 21:20:00 UTC
svn commit: r1514447 - in
/tomcat/trunk/java/org/apache/tomcat/websocket/server: UpgradeUtil.java
WsFilter.java WsServerContainer.java
Author: markt
Date: Thu Aug 15 19:19:59 2013
New Revision: 1514447
URL: http://svn.apache.org/r1514447
Log:
Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=55314
Provide a container specific extension to the WsServerContainer to allow frameworks to more easily diaptch requests to WebSocket endpoints.
Added:
tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java (with props)
Modified:
tomcat/trunk/java/org/apache/tomcat/websocket/server/WsFilter.java
tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java
Added: tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java?rev=1514447&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java Thu Aug 15 19:19:59 2013
@@ -0,0 +1,236 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.websocket.server;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.websocket.Endpoint;
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.apache.tomcat.util.codec.binary.Base64;
+import org.apache.tomcat.websocket.Constants;
+import org.apache.tomcat.websocket.WsHandshakeResponse;
+import org.apache.tomcat.websocket.pojo.PojoEndpointServer;
+
+public class UpgradeUtil {
+
+ private static final byte[] WS_ACCEPT =
+ "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(
+ StandardCharsets.ISO_8859_1);
+ private static final Queue<MessageDigest> sha1Helpers =
+ new ConcurrentLinkedQueue<>();
+
+ private UpgradeUtil() {
+ // Utility class. Hide default constructor.
+ }
+
+ /**
+ * Checks to see if this is an HTTP request that includes a valid upgrade
+ * request to web socket.
+ * <p>
+ * Note: RFC 2616 does not limit HTTP upgrade to GET requests but the Java
+ * WebSocket spec 1.0, section 8.2 implies such a limitation and RFC
+ * 6455 section 4.1 requires that a WebSocket Upgrade uses GET.
+ */
+ public static boolean isWebSocketUpgrageRequest(ServletRequest request,
+ ServletResponse response) {
+
+ return ((request instanceof HttpServletRequest) &&
+ (response instanceof HttpServletResponse) &&
+ headerContainsToken((HttpServletRequest) request,
+ Constants.UPGRADE_HEADER_NAME,
+ Constants.UPGRADE_HEADER_VALUE) &&
+ "GET".equals(((HttpServletRequest) request).getMethod()));
+ }
+
+
+ public static void doUpgrade(WsServerContainer sc, HttpServletRequest req,
+ HttpServletResponse resp, ServerEndpointConfig sec,
+ Map<String,String> pathParams)
+ throws ServletException, IOException {
+
+ // Validate the rest of the headers and reject the request if that
+ // validation fails
+ String key;
+ String subProtocol = null;
+ List<Extension> extensions = Collections.emptyList();
+ if (!headerContainsToken(req, Constants.CONNECTION_HEADER_NAME,
+ Constants.CONNECTION_HEADER_VALUE)) {
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+ if (!headerContainsToken(req, Constants.WS_VERSION_HEADER_NAME,
+ Constants.WS_VERSION_HEADER_VALUE)) {
+ resp.setStatus(426);
+ resp.setHeader(Constants.WS_VERSION_HEADER_NAME,
+ Constants.WS_VERSION_HEADER_VALUE);
+ return;
+ }
+ key = req.getHeader(Constants.WS_KEY_HEADER_NAME);
+ if (key == null) {
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+
+ // Origin check
+ String origin = req.getHeader("Origin");
+ if (!sec.getConfigurator().checkOrigin(origin)) {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ // Sub-protocols
+ List<String> subProtocols = getTokensFromHeader(req,
+ "Sec-WebSocket-Protocol");
+ if (!subProtocols.isEmpty()) {
+ subProtocol = sec.getConfigurator().
+ getNegotiatedSubprotocol(
+ sec.getSubprotocols(), subProtocols);
+ }
+
+ // Extensions
+ // Currently no extensions are supported by this implementation
+
+ // If we got this far, all is good. Accept the connection.
+ resp.setHeader(Constants.UPGRADE_HEADER_NAME,
+ Constants.UPGRADE_HEADER_VALUE);
+ resp.setHeader(Constants.CONNECTION_HEADER_NAME,
+ Constants.CONNECTION_HEADER_VALUE);
+ resp.setHeader(HandshakeResponse.SEC_WEBSOCKET_ACCEPT,
+ getWebSocketAccept(key));
+ if (subProtocol != null) {
+ resp.setHeader("Sec-WebSocket-Protocol", subProtocol);
+ }
+ if (!extensions.isEmpty()) {
+ StringBuilder sb = new StringBuilder();
+ Iterator<Extension> iter = extensions.iterator();
+ // There must be at least one
+ sb.append(iter.next());
+ while (iter.hasNext()) {
+ sb.append(',');
+ sb.append(iter.next().getName());
+ }
+ resp.setHeader("Sec-WebSocket-Extensions", sb.toString());
+ }
+ Endpoint ep;
+ try {
+ Class<?> clazz = sec.getEndpointClass();
+ if (Endpoint.class.isAssignableFrom(clazz)) {
+ ep = (Endpoint) sec.getConfigurator().getEndpointInstance(
+ clazz);
+ } else {
+ ep = new PojoEndpointServer();
+ }
+ } catch (InstantiationException e) {
+ throw new ServletException(e);
+ }
+
+ WsHandshakeRequest wsRequest = new WsHandshakeRequest(req);
+ WsHandshakeResponse wsResponse = new WsHandshakeResponse();
+ sec.getConfigurator().modifyHandshake(sec, wsRequest, wsResponse);
+ wsRequest.finished();
+
+ // Add any additional headers
+ for (Entry<String,List<String>> entry :
+ wsResponse.getHeaders().entrySet()) {
+ for (String headerValue: entry.getValue()) {
+ resp.addHeader(entry.getKey(), headerValue);
+ }
+ }
+
+ WsHttpUpgradeHandler wsHandler =
+ req.upgrade(WsHttpUpgradeHandler.class);
+ wsHandler.preInit(ep, sec, sc, wsRequest, subProtocol,
+ pathParams, req.isSecure());
+
+ }
+
+
+ /*
+ * This only works for tokens. Quoted strings need more sophisticated
+ * parsing.
+ */
+ private static boolean headerContainsToken(HttpServletRequest req,
+ String headerName, String target) {
+ Enumeration<String> headers = req.getHeaders(headerName);
+ while (headers.hasMoreElements()) {
+ String header = headers.nextElement();
+ String[] tokens = header.split(",");
+ for (String token : tokens) {
+ if (target.equalsIgnoreCase(token.trim())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ /*
+ * This only works for tokens. Quoted strings need more sophisticated
+ * parsing.
+ */
+ private static List<String> getTokensFromHeader(HttpServletRequest req,
+ String headerName) {
+ List<String> result = new ArrayList<>();
+ Enumeration<String> headers = req.getHeaders(headerName);
+ while (headers.hasMoreElements()) {
+ String header = headers.nextElement();
+ String[] tokens = header.split(",");
+ for (String token : tokens) {
+ result.add(token.trim());
+ }
+ }
+ return result;
+ }
+
+
+ private static String getWebSocketAccept(String key) throws ServletException {
+ MessageDigest sha1Helper = sha1Helpers.poll();
+ if (sha1Helper == null) {
+ try {
+ sha1Helper = MessageDigest.getInstance("SHA1");
+ } catch (NoSuchAlgorithmException e) {
+ throw new ServletException(e);
+ }
+ }
+ sha1Helper.reset();
+ sha1Helper.update(key.getBytes(StandardCharsets.ISO_8859_1));
+ String result = Base64.encodeBase64String(sha1Helper.digest(WS_ACCEPT));
+ sha1Helpers.add(sha1Helper);
+ return result;
+ }
+}
Propchange: tomcat/trunk/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: tomcat/trunk/java/org/apache/tomcat/websocket/server/WsFilter.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/server/WsFilter.java?rev=1514447&r1=1514446&r2=1514447&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/server/WsFilter.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/server/WsFilter.java Thu Aug 15 19:19:59 2013
@@ -17,17 +17,6 @@
package org.apache.tomcat.websocket.server;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@@ -37,37 +26,19 @@ import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import javax.websocket.Endpoint;
-import javax.websocket.Extension;
-import javax.websocket.HandshakeResponse;
-import javax.websocket.server.ServerEndpointConfig;
-
-import org.apache.tomcat.util.codec.binary.Base64;
-import org.apache.tomcat.websocket.Constants;
-import org.apache.tomcat.websocket.WsHandshakeResponse;
-import org.apache.tomcat.websocket.pojo.PojoEndpointServer;
/**
* Handles the initial HTTP connection for WebSocket connections.
*/
public class WsFilter implements Filter {
- private static final byte[] WS_ACCEPT =
- "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(
- StandardCharsets.ISO_8859_1);
- private final Queue<MessageDigest> sha1Helpers =
- new ConcurrentLinkedQueue<>();
- private final WsServerContainer sc;
-
-
- WsFilter(WsServerContainer sc) {
- this.sc = sc;
- }
+ private WsServerContainer sc;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
- // NO-OP
+ sc = (WsServerContainer) filterConfig.getServletContext().getAttribute(
+ Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
}
@@ -76,18 +47,7 @@ public class WsFilter implements Filter
FilterChain chain) throws IOException, ServletException {
// This filter only needs to handle WebSocket upgrade requests
- if (!(request instanceof HttpServletRequest) ||
- !(response instanceof HttpServletResponse) ||
- !headerContainsToken((HttpServletRequest) request,
- Constants.UPGRADE_HEADER_NAME,
- Constants.UPGRADE_HEADER_VALUE) ||
- !"GET".equals(((HttpServletRequest) request).getMethod())) {
- // Not an HTTP request that includes a valid upgrade request to
- // web socket
- // Note: RFC 2616 does not limit HTTP upgrade to GET requests but
- // the the Java WebSocket spec 1.0, section 8.2 implies such a
- // limitation and RFC 6455 section 4.1 requires that a
- // WebSocket Upgrade uses GET.
+ if (UpgradeUtil.isWebSocketUpgrageRequest(request, response)) {
chain.doFilter(request, response);
return;
}
@@ -113,100 +73,8 @@ public class WsFilter implements Filter
return;
}
- // Validate the rest of the headers and reject the request if that
- // validation fails
- String key;
- String subProtocol = null;
- List<Extension> extensions = Collections.emptyList();
- if (!headerContainsToken(req, Constants.CONNECTION_HEADER_NAME,
- Constants.CONNECTION_HEADER_VALUE)) {
- resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
- return;
- }
- if (!headerContainsToken(req, Constants.WS_VERSION_HEADER_NAME,
- Constants.WS_VERSION_HEADER_VALUE)) {
- resp.setStatus(426);
- resp.setHeader(Constants.WS_VERSION_HEADER_NAME,
- Constants.WS_VERSION_HEADER_VALUE);
- return;
- }
- key = req.getHeader(Constants.WS_KEY_HEADER_NAME);
- if (key == null) {
- resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
- return;
- }
-
- ServerEndpointConfig sec = mappingResult.getConfig();
-
- // Origin check
- String origin = req.getHeader("Origin");
- if (!sec.getConfigurator().checkOrigin(origin)) {
- resp.sendError(HttpServletResponse.SC_FORBIDDEN);
- return;
- }
- // Sub-protocols
- List<String> subProtocols = getTokensFromHeader(req,
- "Sec-WebSocket-Protocol");
- if (!subProtocols.isEmpty()) {
- subProtocol = sec.getConfigurator().
- getNegotiatedSubprotocol(
- sec.getSubprotocols(), subProtocols);
- }
-
- // Extensions
- // Currently no extensions are supported by this implementation
-
- // If we got this far, all is good. Accept the connection.
- resp.setHeader(Constants.UPGRADE_HEADER_NAME,
- Constants.UPGRADE_HEADER_VALUE);
- resp.setHeader(Constants.CONNECTION_HEADER_NAME,
- Constants.CONNECTION_HEADER_VALUE);
- resp.setHeader(HandshakeResponse.SEC_WEBSOCKET_ACCEPT,
- getWebSocketAccept(key));
- if (subProtocol != null) {
- resp.setHeader("Sec-WebSocket-Protocol", subProtocol);
- }
- if (!extensions.isEmpty()) {
- StringBuilder sb = new StringBuilder();
- Iterator<Extension> iter = extensions.iterator();
- // There must be at least one
- sb.append(iter.next());
- while (iter.hasNext()) {
- sb.append(',');
- sb.append(iter.next().getName());
- }
- resp.setHeader("Sec-WebSocket-Extensions", sb.toString());
- }
- Endpoint ep;
- try {
- Class<?> clazz = sec.getEndpointClass();
- if (Endpoint.class.isAssignableFrom(clazz)) {
- ep = (Endpoint) sec.getConfigurator().getEndpointInstance(
- clazz);
- } else {
- ep = new PojoEndpointServer();
- }
- } catch (InstantiationException e) {
- throw new ServletException(e);
- }
-
- WsHandshakeRequest wsRequest = new WsHandshakeRequest(req);
- WsHandshakeResponse wsResponse = new WsHandshakeResponse();
- sec.getConfigurator().modifyHandshake(sec, wsRequest, wsResponse);
- wsRequest.finished();
-
- // Add any additional headers
- for (Entry<String,List<String>> entry :
- wsResponse.getHeaders().entrySet()) {
- for (String headerValue: entry.getValue()) {
- resp.addHeader(entry.getKey(), headerValue);
- }
- }
-
- WsHttpUpgradeHandler wsHandler =
- req.upgrade(WsHttpUpgradeHandler.class);
- wsHandler.preInit(ep, sec, sc, wsRequest, subProtocol,
- mappingResult.getPathParams(), req.isSecure());
+ UpgradeUtil.doUpgrade(sc, req, resp, mappingResult.getConfig(),
+ mappingResult.getPathParams());
}
@@ -216,58 +84,4 @@ public class WsFilter implements Filter
}
- /*
- * This only works for tokens. Quoted strings need more sophisticated
- * parsing.
- */
- private boolean headerContainsToken(HttpServletRequest req,
- String headerName, String target) {
- Enumeration<String> headers = req.getHeaders(headerName);
- while (headers.hasMoreElements()) {
- String header = headers.nextElement();
- String[] tokens = header.split(",");
- for (String token : tokens) {
- if (target.equalsIgnoreCase(token.trim())) {
- return true;
- }
- }
- }
- return false;
- }
-
-
- /*
- * This only works for tokens. Quoted strings need more sophisticated
- * parsing.
- */
- private List<String> getTokensFromHeader(HttpServletRequest req,
- String headerName) {
- List<String> result = new ArrayList<>();
- Enumeration<String> headers = req.getHeaders(headerName);
- while (headers.hasMoreElements()) {
- String header = headers.nextElement();
- String[] tokens = header.split(",");
- for (String token : tokens) {
- result.add(token.trim());
- }
- }
- return result;
- }
-
-
- private String getWebSocketAccept(String key) throws ServletException {
- MessageDigest sha1Helper = sha1Helpers.poll();
- if (sha1Helper == null) {
- try {
- sha1Helper = MessageDigest.getInstance("SHA1");
- } catch (NoSuchAlgorithmException e) {
- throw new ServletException(e);
- }
- }
- sha1Helper.reset();
- sha1Helper.update(key.getBytes(StandardCharsets.ISO_8859_1));
- String result = Base64.encodeBase64String(sha1Helper.digest(WS_ACCEPT));
- sha1Helpers.add(sha1Helper);
- return result;
- }
}
Modified: tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java?rev=1514447&r1=1514446&r2=1514447&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java Thu Aug 15 19:19:59 2013
@@ -30,6 +30,9 @@ import java.util.concurrent.ConcurrentHa
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
import javax.websocket.CloseReason;
import javax.websocket.CloseReason.CloseCodes;
import javax.websocket.DeploymentException;
@@ -103,7 +106,7 @@ public class WsServerContainer extends W
}
FilterRegistration.Dynamic fr = servletContext.addFilter(
- WsFilter.class.getName(), new WsFilter(this));
+ WsFilter.class.getName(), new WsFilter());
fr.setAsyncSupported(true);
EnumSet<DispatcherType> types = EnumSet.of(DispatcherType.REQUEST,
@@ -220,6 +223,14 @@ public class WsServerContainer extends W
}
+ public void doUpgrade(HttpServletRequest request,
+ HttpServletResponse response, ServerEndpointConfig sec,
+ Map<String,String> pathParams)
+ throws ServletException, IOException {
+ UpgradeUtil.doUpgrade(this, request, response, sec, pathParams);
+ }
+
+
public WsMappingResult findMapping(String path) {
// Prevent registering additional endpoints once the first attempt has
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org