How do I properly close a websocket and and provide a clean, informative response to the client when an internal error occurs on my server? In my current case, the client must provide a parameter when it connects, and I am trying to handle incorrect or missing parameters received by OnOpen.
This example suggests I can just throw an exception in OnOpen, which will ultimately call OnError where I can close with a reason and message. It kinda works, but the client only receives an EOF, 1006, CLOSE_ABNORMAL.
Also, because I have found no other discussion, I can't tell what might be best practice.
I'm using the JSR-356 spec, as follows:
@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);
}
}
}
Edit: The exception throwing per linked example seems weird, so now I am catching exception within OnOpen and immediately doing
session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "some text"));
Edit: This turned out to be correct, though a separate bug disguised it for a while.
Edit2: Clarification: HTTP
is my own static utility class. HTTP.getRequiredLongParameter()
gets query parameters from the client's initial request by using
session.getRequestParameterMap().get(name)
and does further processing.
I believe I should have placed...
session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "some text"));
...right where the error occurs, within @OnOpen(). (For generic errors, use CloseCodes.UNEXPECTED_CONDITION
.)
The client receives:
onClose(1003, some text)
This is, of course, the obvious answer. I think I was misled, by the example cited, into throwing an exception from @OnOpen(). As Remy Lebeau suggested, the socket was probably closed by this, blocking any further handling by me in @OnError(). (Some other bug may have obscured the evidence that was discussed.)