我需要能够在握手期间中止 Websocket 连接,以防 HTTP 请求不满足某些条件。据我了解,正确的位置是在我自己的 Configurator
实现的
ServerEndpointConfig.Configurator.modifyHandshake()方法中。我只是不知道该怎么做才能中止连接。有一个 HandshakeResponse 参数允许向响应添加标头,但我找不到任何可以完成这项工作的标头。
那么如何在握手期间中止 Websocket 连接呢?这可能吗?
你是对的,使用“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 握手时出错
我知道 - 旧线程,但我没有看到任何改进。所以也许有人可以讨论我对这个问题的解决方案。
我尝试了 leos 版本,运行 Wildfly 8.0 、Undertow 1.0
final ArrayList<String> finalEmpty = new ArrayList<String>();
response.getHeaders().put(HandshakeResponse.SEC_WEBSOCKET_ACCEPT,finalEmpty);
这会导致一些有趣的行为:
即使您的浏览器应该关闭连接,它们也会经历
onOpen()
过程。
onOpen()
,然后触发 onError()
。在我的
案例中,我对断开连接的客户端做了一些记录,所以我总是打电话
onClose()
出现错误时。不要弄乱你的标题,也不要让客户做这些工作。
相反,您应该将身份验证数据添加到配置中。
/** some verification foo code...**/
config.getUserProperties().put("isValid",true);
然后在
onOpen()
中检查 isValid 的值。如果不是,请致电 onClose(session,null);
。并且会议将关闭。
这不是最好的解决方案,但那是因为 websocket 身份验证很糟糕,而且每个浏览器有时表现不同。请参阅:Websocket:关闭浏览器在 chrome 中会触发 onError(),但在 Firefox 中会触发 onClose() 事件
最简单的方法是在 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 {
...
}
另一种技巧:
当您从
ServerEndpointConfig.Configurator#modifyHandshake
抛出 RuntimeException 时,则未建立连接。
这适用于 Tomcat 8。从 Jetty 示例得到了这个想法,所以我想它也适用于 Jetty。
我在仅为授权用户实现 websocket 时遇到了同样的问题。不幸的是,Tomcat 不会修改标头,而是通过modifyHandshake 添加它们。但在此之前,它会检查 checkOrigin,如果 checkOrigin 返回 false,则抛出正确的 403 代码。 header不合适,但是我没有看到Websocket规范中描述了它的使用。最后,我是这样做的,它应该适用于Tomcat,我没有检查其他服务器。
public class WebsocketAuthConfigurator extends ServerEndpointConfig.Configurator {
@Override
public boolean checkOrigin(String originHeaderValue) {
return "testAuthKey".equals(originHeaderValue);
}
}
对于客户
public class WebsocketTestConfigurator extends ClientEndpointConfig.Configurator {
@Override
public void beforeRequest(Map<String, List<String>> headers) {
headers.put("Origin", List.of("testAuthKey"));
}
}