javajava-websocketjavax

How to properly report an error to client through WebSockets


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.


Solution

  • 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.)