如何在我的服务器上发生内部错误时正确关闭websocket并向客户端提供干净,信息丰富的响应?在我目前的情况下,客户端必须在连接时提供参数,我正在尝试处理OnOpen收到的不正确或缺少的参数。
This example建议我可以在OnOpen中抛出一个异常,最终会调用OnError,我可以通过原因和消息关闭它。它有点工作,但客户端只收到一个EOF,1006,CLOSE_ABNORMAL。
另外,因为我没有找到其他讨论,所以我不能说出最佳做法。
我正在使用JSR-356规范,如下所示:
@ClientEndpoint
@ServerEndpoint(value="/ws/events/")
public class WebSocketEvents
{
private javax.websocket.Session session;
private long token;
@OnOpen
public void onWebSocketConnect(javax.websocket.Session session) throws BadRequestException
{
logger.info("WebSocket connection attempt: " + session);
this.session = session;
// this throws BadRequestException if null or invalid long
// with short detail message, e.g., "Missing parameter: token"
token = HTTP.getRequiredLongParameter(session, "token");
}
@OnMessage
public void onWebSocketText(String message)
{
logger.info("Received text message: " + message);
}
@OnClose
public void onWebSocketClose(CloseReason reason)
{
logger.info("WebSocket Closed: " + reason);
}
@OnError
public void onWebSocketError(Throwable t)
{
logger.info("WebSocket Error: ");
logger.debug(t, t);
if (!session.isOpen())
{
logger.info("Throwable in closed websocket:" + t, t);
return;
}
CloseCode reason = t instanceof BadRequestException ? CloseReason.CloseCodes.PROTOCOL_ERROR : CloseReason.CloseCodes.UNEXPECTED_CONDITION;
try
{
session.close(new CloseReason(reason, t.getMessage()));
}
catch (IOException e)
{
logger.warn(e, e);
}
}
}
编辑:每个链接示例的异常抛出似乎很奇怪,所以现在我在OnOpen中捕获异常并立即执行
session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "some text"));
编辑:事实证明这是正确的,尽管一段时间内有一个错误伪装了它。
Edit2 :澄清:HTTP
是我自己的静态实用工具类。 HTTP.getRequiredLongParameter()
使用
session.getRequestParameterMap().get(name)
并做进一步处理。
答案 0 :(得分:1)
我相信我应该放置......
session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "some text"));
...在@OnOpen()内发生错误的地方。 (对于一般错误,请使用CloseCodes.UNEXPECTED_CONDITION
。)
客户收到:
onClose(1003, some text)
这当然是明显的答案。我想我被引用的例子误导了@OnOpen()抛出一个异常。正如 Remy Lebeau 所建议的那样,套接字可能已被关闭,阻止了我在@OnError()中的任何进一步处理。 (其他一些错误可能会掩盖所讨论的证据。)
答案 1 :(得分:1)
为了开发我提到的要点,关于“如何处理所需参数”的问题,我可以看到以下选项。首先,让我们考虑一下端点:
3
客户端和服务器之间的第一次联系是HTTP请求。您可以使用过滤器对其进行过滤,以防止发生websocket握手。过滤器可以阻止请求或通过:
@ServerEndpoint(value = "/websocket/myendpoint",
configuration = MyWebsocketConfiguration.class)
public class MyEndpoint{
// @OnOpen, @OnClose, @OnMessage, @OnError...
}
如果您使用过滤器,则必须将其添加到web.xml中:
import javax.servlet.Filter;
public class MyEndpointFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// nothing for this example
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// if the connection URL is /websocket/myendpoint?parameter=value
// feel free to dig in what you can get from ServletRequest
String myToken = request.getParameter("token");
// if the parameter is mandatory
if (myToken == null){
// you can return an HTTP error code like:
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
// if the parameter must match an expected value
if (!isValid(myToken)){
// process the error like above, you can
// use the 403 HTTP status code for instance
return;
}
// this part is very important: the filter allows
// the request to keep going: all green and good to go!
chain.doFilter(request, response);
}
@Override
public void destroy() {
//nothing for this example
}
private boolean isValid(String token){
// how your token is checked? put it here
}
}
建议<web-app ...>
<!-- you declare the filter here -->
<filter>
<filter-name>myWebsocketFilter</filter-name>
<filter-class>com.mypackage.MyEndpointFilter </filter-class>
<async-supported>true</async-supported>
</filter>
<!-- then you map your filter to an url pattern. In websocket
case, it must match the serverendpoint value -->
<filter-mapping>
<filter-name>myWebsocketFilter</filter-name>
<url-pattern>/websocket/myendpoint</url-pattern>
</filter-mapping>
</web-app>
by BalusC in my question以支持异步消息发送。
<强> TL,DR 强>
如果您需要在连接时操作客户端提供的GET参数,如果您对纯HTTP答案(403状态代码等)感到满意,则Filter可以是一个解决方案
您可能已经注意到,我添加了async-supported
。这样的课程看起来像:
configuration = MyWebsocketConfiguration.class
好的,很好,这与滤镜有什么不同?最大的区别在于,您在握手期间在用户属性中添加了一些信息。这意味着public class MyWebsocketConfigurationextends ServerEndpointConfig.Configurator {
// as the name suggests, we operate here at the handshake level
// so we can start talking in websocket vocabulary
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
// much like ServletRequest, the HandshakeRequest contains
// all the information provided by the client at connection time
// a common usage is:
Map<String, List<String>> parameters = request.getParameterMap();
// this is not a Map<String, String> to handle situation like
// URL = /websocket/myendpoint?token=value1&token=value2
// then the key "token" is bound to the list {"value1", "value2"}
sec.getUserProperties().put("myFetchedToken", parameters.get("token"));
}
}
可以访问此信息:
@OnOpen
<强> TL; DR 强>
您想要操纵一些参数但是以websocket方式处理可能的错误?创建自己的配置。
我还提到了try / catch选项:
@ServerEndpoint(value = "/websocket/myendpoint",
configuration = MyWebsocketConfiguration.class)
public class MyEndpoint{
// you can fetch the information added during the
// handshake via the EndpointConfig
@OnOpen
public void onOpen(Session session, EndpointConfig config){
List<String> token = (List<String>) config.getUserProperties().get("myFetchedToken");
// now you can manipulate the token:
if(token.isEmpty()){
// for example:
session.close(new CloseReasons(CloseReason.CloseCodes.CANNOT_ACCEPT, "the token is mandatory!");
}
}
// @OnClose, @OnMessage, @OnError...
}
希望这个帮助