【问题标题】:JSR-356: How to abort a websocket connection during the handshake?JSR-356:如何在握手期间中止 websocket 连接?
【发布时间】:2014-03-12 21:32:16
【问题描述】:

我需要能够在握手期间中止 websocket 连接,以防 HTTP 请求不符合某些标准。据我了解,执行此操作的正确位置是在我自己的 Configurator 实现的 ServerEndpointConfig.Configurator.modifyHandshake() 方法中。我只是不知道该怎么做才能中止连接。有一个 HandshakeResponse 参数允许在响应中添加标头,但我找不到任何可以完成这项工作的标头。

那么如何在握手期间中止 websocket 连接?这可能吗?

【问题讨论】:

    标签: java jakarta-ee websocket java-7 java-ee-7


    【解决方案1】:

    你是对的,使用'modifyHandShake()'来更新响应头,你需要删除或设置头Sec-WebSocket-Accept的值,从the spec检查这个

    |Sec-WebSocket-Accept|头域表示是否 服务器愿意接受连接。如果存在,这 标头字段必须包含客户端发送的随机数的散列 |Sec-WebSocket-Key|以及预定义的 GUID。任何其他值 不得被解释为对连接的接受 服务器。

        HTTP/1.1 101 Switching Protocols
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    

    这些字段由 WebSocket 客户端检查脚本页面。 如果 |Sec-WebSocket-Accept|值与预期不符 值,如果缺少标头字段,或者 HTTP 状态代码是 不是101,连接不会建立,WebSocket 帧 不会发送。

    您的代码将如下所示:

     @Override
    public void modifyHandshake(ServerEndpointConfig sec,
        HandshakeRequest request, HandshakeResponse response) {
        super.modifyHandshake(sec, request, response);
        response.getHeaders().put(HandshakeResponse.SEC_WEBSOCKET_ACCEPT, new ArrayList<String>());
    }
    

    浏览器会将此解释为服务器未接受连接。例如在 chrome 中我收到消息

    Websocket 握手期间出错

    【讨论】:

    • 这应该可以,但只是为了更清晰的实现,我建议实现 servlet 过滤器并在那里检查请求..
    • 嗨@Leo,我之前已经尝试过这种方法,但它不起作用(至少在Tomcat上)。在我看来,服务器代码在调用modifyHandshake() 之后设置了Sec-WebSocket-Accept 标头。当在modifyHandshake() 中打印响应头时,如下所示:logger.info("response.getHeaders() =&gt; " + response.getHeaders()); 我们得到:response.getHeaders() =&gt; {}
    • 好吧,我用 wildfly 测试过,它使用 undertow,我想这可能是一个实现细节。
    【解决方案2】:

    最简单的方法是在您的 web.xml 中设置简单的网络过滤器,该过滤器将在握手之前执行。

    像这样的:

    public class MyHandshakeFilter implements Filter {
    
    @Override
    public void init(FilterConfig config) throws ServletException {
        // do nothing
    }
    
    /**
     * Passes request to chain when request port is equal to specified in the allowedPort property
     * and returns HttpServletResponse.SC_NOT_FOUND in other case.
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {
    
        if (<your validation here based on http request>) {
            // request conditions are met. continue handshake...
            chain.doFilter(request, response);
        } else {
            // request conditions are NOT met. reject handshake...
            ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_FORBIDDEN);
        }
    }
    }
    

    在 web.xml 中:

    <filter>
        <filter-name>yourHandshakeFilter</filter-name>
        <filter-class>com....MyHandshakeFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>yourHandshakeFilter</filter-name>
        <url-pattern>/yourEndpointUrl</url-pattern>
    </filter-mapping>
    

    你的 ServerEndpoint 应该是这样的:

    @ServerEndpoint("/yourEndpointUrl")
    public class MyServerEndpoint {
    ...
    }
    

    【讨论】:

    • 不幸的是,它对我不起作用(在 Jetty 9.4 上)
    • 过滤器实际上不应在 HTTP 升级上运行:stackoverflow.com/a/25996042/39334“实际上,您应该期望在 Servlet 处理之前发生升级(这包括过滤器),因为这是规范的方式已编写。关于如何处理与过滤器的交互以及应如何处理之类的规范存在一些未解决的错误,但这些目前尚未得到答复,并且为 javax.websocket 规范的未来版本制定了松散的计划。”
    【解决方案3】:

    我知道 - 旧线程,但我没有看到任何改进。所以也许有人可以讨论我对这个问题的解决方案。

    我尝试了 leos 版本,运行 Wildfly 8.0 ,Undertow 1.0

    final  ArrayList<String> finalEmpty = new ArrayList<String>();
    response.getHeaders().put(HandshakeResponse.SEC_WEBSOCKET_ACCEPT,finalEmpty);
    

    这会导致一些有趣的行为:

    即使您的浏览器应该关闭连接,它们都将通过onOpen() 过程。

    • Chrome:将触发onOpen(),然后触发onError()。在我的 情况下,我对断开连接的客户端进行了一些记录,所以我总是打电话 onClose() 出现错误时。
    • IE:会像 Chrome 一样运行
    • Firefox:Firefox 将只运行 onOpen() 过程。并且不会触发 onError()。因此,您的服务器甚至不知道客户端已断开连接。

    不要弄乱你的标题,也不要让客户端做这些工作。

    相反,您应该将身份验证数据添加到配置中。

    /** some verification foo code...**/
    
    config.getUserProperties().put("isValid",true);
    

    然后在onOpen() 中检查isValid 的值。如果不是,请致电onClose(session,null);。会话将被关闭。

    这不是最好的解决方案,但那是因为 websocket 身份验证很糟糕,而且每个浏览器有时表现不同。请看:Websocket: Closing browser triggers onError() in chrome but onClose() event in Firefox

    【讨论】:

    • 这个问题是你有点晚了:WebSocket连接已经建立,你需要再次关闭它。应该可以通过 HTTP 握手说“不,抱歉,访问被拒绝”。
    • 但是如果将“Sec-WebSocket-Accept”值设置为空字符串在 Tomcat 上不起作用。我能做些什么?它必须在 Tomcat 上。
    【解决方案4】:

    另一种技巧:

    当您从 ServerEndpointConfig.Configurator#modifyHandshake 抛出 RuntimeException 时,连接未建立。

    这在 Tomcat 8 中有效。从 Jetty example 得到这个想法,所以我猜它也适用于 Jetty。

    【讨论】:

    • 是的,这在 Jetty 9.4 中有效,但它会记录一个带有完整异常的繁重的 WARN 行 - 一点也不理想。此外,在客户端它触发 onerror 然后 onclose 与 CloseCode 1006 CLOSED_ABNORMALLY - 至少在 Firefox 中。但是,该关闭代码也可能出现在其他几种情况下。确实应该有一个明确的“拒绝访问”关闭代码,可以从 HTTP 握手触发。
    猜你喜欢
    • 2017-05-09
    • 2020-07-09
    • 2013-10-13
    • 2019-05-16
    • 1970-01-01
    • 1970-01-01
    • 2017-02-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多