javawebsocketjettyjsr356

Why does Jetty JSR356 behave differently with regards to checkOrigin and modifyHandshake


I was playing around with Jetty (9.2.3v20140905) by connecting a web socket endpoint where I tried to use my own ServerEndpointConfig when I came across Jetty's code to see how it was used. I notice that it is used in JsrCreator when a web socket object is created:

Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp){
 ...
 // modify handshake
 configurator.modifyHandshake(config,hsreq,hsresp);
 ...
}

I read the javadoc of modifyHandshake of ServerEndpointConfig (javax.websocket-api 1.0) that states:

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.

Here's what Jetty does:

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);
}

As you can see, the origin is checked after the configurator as been called. The response is modified after the configurator as been called. The method acceptWebSocket of WebSocketServerFactory makes a call to the WebSocketCreator:

Object websocketPojo = creator.createWebSocket(sockreq, sockresp);

And after that calls:

private boolean upgrade(HttpConnection http, ServletUpgradeRequest request, ServletUpgradeResponse response, EventDriver driver)

which also modifies the response via HandshakeRFC6455:

 // build response
 response.setHeader("Upgrade","WebSocket");
 response.addHeader("Connection","Upgrade");
 response.addHeader("Sec-WebSocket-Accept",AcceptHash.hashKey(key));

So I have no way modifying the response only with my configurator because Jetty will change it anyway.

It seems to me Jetty does not comply with JSR 356, the Java API for WebSocket, does it?


Solution

  • Ah, one of the many ambiguous and ill defined parts of the JSR-356 spec.

    You might want to read the open bugs against the spec.

    There are many real world examples of scenarios that are rendered impossible if the original 1.x spec is follow exactly.

    Now, to tackle the specific details of your question:

    Why is checkOrigin called after modifyHandshake in the Jetty implementation?

    This is because there are valid scenarios (esp with CDI and Spring) where the information needed by a checkOrigin implementation by the end user is not valid, or exists, until the modifyHandshake call is called.

    Basically, the endpoint Configurator is created, the modifyHandshake is called, and at that point, all of the library integration (CDI, Spring, etc.) starts, that's when the endpoint enters the WebSocket (RFC6455) CONNECTING state. (once the endpoint's onOpen is called, then the WebSocket RFC6455 state goes to the OPEN state)

    As you have probably noticed, there's no definitions in the spec of the scopes and lifetimes of objects when CDI (or Spring) is involved.

    The 1.x JSR356 spec actually distances itself from servlet container specific behavior, it was done to make the spec as generic as possible, with the ability to have non-servlet websocket server containers too. Unfortunately, that also means that there are many use cases in a servlet container that doesn't mesh with the 1.x JSR356 spec.

    Once the JSR356 spec is updated to properly define the CDI scopes on WebSocket, then this quirk of checkOrigin after modifyHandshake can be fixed.

    Why is the implementation modifying the response after modifyHandshake?

    The implementation has to modify the response, otherwise the response is invalid for HTTP/1.1 upgrade, the need of the implementation to cooperate with the endpoint and its configuration, for sub protocols, and extensions makes this a reality. (Notice that the the JSR356 spec punts on Extensions?)

    This is also an area that is promised to be corrected in the next JSR356 revision.

    The current work on the WebSocket over HTTP/2 spec makes this even more interesting, as it isn't (currently) using the HTTP/1.1 upgrade semantic. It just comes into existence with a handshake only (no Upgrade, no Origin, etc).