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