JSR-356:如何在握手期间终止websocket连接?

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

4

你是对的,使用´modifyHandShake()´更新响应头,你需要准确地删除或设置头文件Sec-WebSocket-Accept的值,可以从规范文档中查看。

|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过滤器并在那里检查请求。 - Pavel Bucek
2
嗨@Leo,我之前已经尝试过这种方法,但它不起作用(至少在Tomcat上)。在调用modifyHandshake()之后,似乎服务器代码设置了Sec-WebSocket-Accept头。像这样在modifyHandshake()内部打印响应头:logger.info("response.getHeaders() => " + response.getHeaders());我们得到这个:response.getHeaders() => {} - Célio
好的,我已经使用Wildfly进行了测试,它使用了Undertow,我认为这可能是一些实现细节。 - Leo
当modifyHandshake修改标头时,它看起来像一个错误,但是Tomcat的实现忽略了它们。TJWS显示了同样的问题,所以我联系了维护者,他告诉我该问题已在版本118中修复。 - user2305886
不幸的是,这对我没有用(Jetty 9.4.51)。我已经使用了modifyHandshake方法,并像建议的那样设置了Sec-WebSocket-Accept的空值,但是Jetty在此之后完成WebSocket设置,并以成功的价值填充Sec-WebSocket-Accept。 - David Ellis

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);,然后将会关闭session。

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


1
问题在于,你现在有点晚了:WebSocket连接已经建立,你需要再次关闭它。应该可以在HTTP握手时就说“不好意思,拒绝访问”。 - stolsvik
但是如果在Tomcat上将“Sec-WebSocket-Accept”值设置为空字符串不起作用,我该怎么办?必须在Tomcat上解决。 - HS1
config.getUserProperties() 在端点之间共享,并且不是线程安全的。 - Ashwin Prabhu

2
最简单的方法是在web.xml中设置一个简单的Web过滤器,该过滤器将在握手之前执行。
类似于以下内容:
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上它对我不起作用。 - Vladimir Petrakovich
过滤器实际上不应在HTTP升级时运行: https://stackoverflow.com/a/25996042/39334实际上,您应该按照升级发生在Servlet处理之前(包括过滤器)的预期进行工作,因为这是规范的编写方式。有一些关于如何处理与过滤器等交互的规范的未解决错误,但这些错误目前尚未得到回答,并松散地计划在javax.websocket规范的未来版本中解决。 - stolsvik
我没有找到任何可靠的来源表明过滤器会拦截WebSocket连接。因此,除非你能证明相反,否则我不认为你的解决方案是可移植的。 - user2305886

2

另一种技术:

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

这在Tomcat 8中有效。从Jetty示例中得到了这个想法,因此我想它在Jetty中也适用。


是的,这在Jetty 9.4中可以工作,但它会记录一个带有完整异常的严重警告行 - 这一点都不理想。此外,在客户端上,它会触发onerror然后onclose,关闭代码为1006 CLOSED_ABNORMALLY - 至少在Firefox中是这样。但是,该关闭代码也可能出现在其他几种情况下。HTTP握手应该真正存在一个明确的“ACCESS DENIED”关闭代码。 - stolsvik
我在使用TJWS时采用了相同的方法,它完美地运行。 - user2305886

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接