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