为什么 Jetty JSR356 在 checkOrigin 和 modifyHandshake 方面表现不同
Why does Jetty JSR356 behave differently with regards to checkOrigin and modifyHandshake
我在玩 Jetty (9.2.3v20140905) 时连接了一个网络套接字端点,当我遇到 Jetty 的代码时,我尝试使用自己的 ServerEndpointConfig
看看它是如何使用的。
我注意到在创建 Web 套接字对象时,它在 JsrCreator
中使用:
Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp){
...
// modify handshake
configurator.modifyHandshake(config,hsreq,hsresp);
...
}
我阅读了 ServerEndpointConfig
的 modifyHandshake
的 javadoc (javax.websocket-api 1.0) 指出:
Called by the container after it has formulated a handshake response resulting from
a well-formed handshake request. The container has already
checked that this configuration has a matching URI, determined the
validity of the origin using the checkOrigin method, and filled
out the negotiated subprotocols and extensions based on this configuration.
Custom configurations may override this method in order to inspect
the request parameters and modify the handshake response that the server has formulated.
and the URI checking also.
If the developer does not override this method, no further
modification of the request and response are made by the implementation.
Jetty 的作用如下:
Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp){
...
// modify handshake
configurator.modifyHandshake(config,hsreq,hsresp);
// check origin
if (!configurator.checkOrigin(req.getOrigin())){...}
...
resp.setAcceptedSubProtocol(subprotocol);
...
resp.setExtensions(configs);
}
如您所见,在调用配置器 之后检查原点。在调用配置器 之后 修改响应。
WebSocketServerFactory
的方法 acceptWebSocket
调用 WebSocketCreator:
Object websocketPojo = creator.createWebSocket(sockreq, sockresp);
然后调用:
private boolean upgrade(HttpConnection http, ServletUpgradeRequest request, ServletUpgradeResponse response, EventDriver driver)
这也通过 HandshakeRFC6455
修改响应:
// build response
response.setHeader("Upgrade","WebSocket");
response.addHeader("Connection","Upgrade");
response.addHeader("Sec-WebSocket-Accept",AcceptHash.hashKey(key));
所以我无法仅使用我的配置器修改响应,因为 Jetty 无论如何都会更改它。
在我看来 Jetty 不符合 JSR 356,WebSocket 的 Java API,是吗?
啊,JSR-356 规范中许多模棱两可和定义不当的部分之一。
您可能想阅读 open bugs against the spec。
如果完全遵循原始 1.x 规范,有许多现实世界的场景示例是不可能的。
现在,解决您问题的具体细节:
为什么在Jetty实现中modifyHandshake之后会调用checkOrigin?
这是因为在某些有效场景(尤其是 CDI 和 Spring)中,最终用户 checkOrigin
实施所需的信息无效或不存在,直到 modifyHandshake
呼叫被呼叫。
基本上,端点 Configurator
被创建,modifyHandshake
被调用,此时,所有库集成(CDI、Spring 等)开始,那是端点进入 WebSocket (RFC6455) CONNECTING 状态的时候。 (一旦端点的 onOpen 被调用,WebSocket RFC6455 状态就进入 OPEN 状态)
您可能已经注意到,当涉及 CDI(或 Spring)时,规范中没有定义对象的范围和生命周期。
1.x JSR356 规范实际上与 servlet 容器的特定行为保持距离,这样做是为了使规范尽可能通用,同时也具有非 servlet websocket 服务器容器的能力。不幸的是,这也意味着在 servlet 容器中有许多用例不符合 1.x JSR356 规范。
一旦更新了 JSR356 规范以在 WebSocket 上正确定义 CDI 范围,那么 modifyHandshake
之后 checkOrigin
的这个怪癖就可以得到修复。
为什么实现在modifyHandshake之后修改响应?
实现必须修改响应,否则响应对HTTP/1.1升级无效,实现需要配合端点及其配置,用于子协议和扩展一个现实。 (请注意 JSR356 规范对扩展的支持?)
这也是承诺在下一次JSR356修订中修正的地方。
WebSocket 目前在 HTTP/2 规范上的工作使这变得更加有趣,因为它(当前)没有使用 HTTP/1.1 升级语义。它只是通过握手而存在(没有升级,没有起源等)。
我在玩 Jetty (9.2.3v20140905) 时连接了一个网络套接字端点,当我遇到 Jetty 的代码时,我尝试使用自己的 ServerEndpointConfig
看看它是如何使用的。
我注意到在创建 Web 套接字对象时,它在 JsrCreator
中使用:
Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp){
...
// modify handshake
configurator.modifyHandshake(config,hsreq,hsresp);
...
}
我阅读了 ServerEndpointConfig
的 modifyHandshake
的 javadoc (javax.websocket-api 1.0) 指出:
Called by the container after it has formulated a handshake response resulting from a well-formed handshake request. The container has already checked that this configuration has a matching URI, determined the validity of the origin using the checkOrigin method, and filled out the negotiated subprotocols and extensions based on this configuration. Custom configurations may override this method in order to inspect the request parameters and modify the handshake response that the server has formulated. and the URI checking also. If the developer does not override this method, no further modification of the request and response are made by the implementation.
Jetty 的作用如下:
Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp){
...
// modify handshake
configurator.modifyHandshake(config,hsreq,hsresp);
// check origin
if (!configurator.checkOrigin(req.getOrigin())){...}
...
resp.setAcceptedSubProtocol(subprotocol);
...
resp.setExtensions(configs);
}
如您所见,在调用配置器 之后检查原点。在调用配置器 之后 修改响应。
WebSocketServerFactory
的方法 acceptWebSocket
调用 WebSocketCreator:
Object websocketPojo = creator.createWebSocket(sockreq, sockresp);
然后调用:
private boolean upgrade(HttpConnection http, ServletUpgradeRequest request, ServletUpgradeResponse response, EventDriver driver)
这也通过 HandshakeRFC6455
修改响应:
// build response
response.setHeader("Upgrade","WebSocket");
response.addHeader("Connection","Upgrade");
response.addHeader("Sec-WebSocket-Accept",AcceptHash.hashKey(key));
所以我无法仅使用我的配置器修改响应,因为 Jetty 无论如何都会更改它。
在我看来 Jetty 不符合 JSR 356,WebSocket 的 Java API,是吗?
啊,JSR-356 规范中许多模棱两可和定义不当的部分之一。
您可能想阅读 open bugs against the spec。
如果完全遵循原始 1.x 规范,有许多现实世界的场景示例是不可能的。
现在,解决您问题的具体细节:
为什么在Jetty实现中modifyHandshake之后会调用checkOrigin?
这是因为在某些有效场景(尤其是 CDI 和 Spring)中,最终用户 checkOrigin
实施所需的信息无效或不存在,直到 modifyHandshake
呼叫被呼叫。
基本上,端点 Configurator
被创建,modifyHandshake
被调用,此时,所有库集成(CDI、Spring 等)开始,那是端点进入 WebSocket (RFC6455) CONNECTING 状态的时候。 (一旦端点的 onOpen 被调用,WebSocket RFC6455 状态就进入 OPEN 状态)
您可能已经注意到,当涉及 CDI(或 Spring)时,规范中没有定义对象的范围和生命周期。
1.x JSR356 规范实际上与 servlet 容器的特定行为保持距离,这样做是为了使规范尽可能通用,同时也具有非 servlet websocket 服务器容器的能力。不幸的是,这也意味着在 servlet 容器中有许多用例不符合 1.x JSR356 规范。
一旦更新了 JSR356 规范以在 WebSocket 上正确定义 CDI 范围,那么 modifyHandshake
之后 checkOrigin
的这个怪癖就可以得到修复。
为什么实现在modifyHandshake之后修改响应?
实现必须修改响应,否则响应对HTTP/1.1升级无效,实现需要配合端点及其配置,用于子协议和扩展一个现实。 (请注意 JSR356 规范对扩展的支持?)
这也是承诺在下一次JSR356修订中修正的地方。
WebSocket 目前在 HTTP/2 规范上的工作使这变得更加有趣,因为它(当前)没有使用 HTTP/1.1 升级语义。它只是通过握手而存在(没有升级,没有起源等)。