-
Notifications
You must be signed in to change notification settings - Fork 112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
websocket/ee/jakarta/websocket/server/serverendpointconfig/WSClient.java#getUserPropertiesTest TCK challenge #873
Comments
The TCK test has been added in January. |
The change was made to add clarify to an area of the specification that was undefined and the approach taken was chosen by the WebSocket project as it best matched the expectations of users. I am aware of at least two implementations that have implemented the behaviour described in the updated WebSocket 2.1 Javadoc for at least 8 years without reports of issues from users. At least one of those implementations uses wrapping and again there have been no reports from users of issues trying to cast to the original implementation class in that time. That said, I accept the backwards compatibility concern. While wrapping has been used successfully for an extended period of time, I believe there is at least one alternative approach that would provide the same behaviour while still exposing the original implementation class. I need to validate that such an approach is viable. I should be able to do that in the next day or two. |
Some guidance from the TCK Process 1.2 under "Valid Challenges":
I think both of you already know ^ but I think the point of #4 helps reinforce what Mark just said about the test clarifying an area of spec that was undefined, which I think maps to #4 because it means the test is not portable to all implementations that follow the spec exactly. |
#4 doesn't apply because both the specification and the TCK were updated. The TCK test is testing exactly what the spec says should happen. You could argue that none of those 4 points apply but I am happy leaving this challenge open while we figure out the best way to resolve it. I'm still investigating alternative approaches. My current thinking is that we'll end up selecting one of the following:
My current thinking is that option 1 is going to involve some sort of dynamic proxy. The result is always that Mainly a question for @jansupol : assuming that all other methods present on the original |
I've implemented an alternative solution for Tomcat using Javassist that demonstrates that the backwards compatibility issues can be avoided. |
Decisions, decisions...
|
Either of the two can bite the user. I understand the reason behind this, and the reason is good, the solution is difficult:
Another idea is to update the modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response). HandshakeRequest is not implemented by the user, and it is per-connection, the copy of the properties would better suit there... |
I'd prefer to avoid re-instating the grey area.
The spec would be updated to state that the container wraps the original The intention is that it is a NO-OP for existing code unless it needs to get hold of the original user properties map. |
That sounds like a great idea! I think that solves all the issues and concerns. |
@joakime WDYT? |
Don't follow your point here.
The user provided In the old jakarta websocket spec 1.0, if you used the
This is the change discussed at jakartaee/websocket#235 (comment)
Again, this is just what your application ServerEndpointConfig.Configurator is specifying as it's preferred properties, that Map is not used directly, nor is the ServerEndpointConfig passed as-is into the ServerEndpointConfig.Configurator. This is how it's been since WebSocket 1.0
This is still true, as long as the TCK doesn't assert that the UserProperties is the SAME one as returned by EndpointConfig.getUserProperties().
The wrapping is exactly how most implementations have done this currently. This kind of casting is invalid per websocket spec ... @OnOpen
public void open(EndpointConfig config, Session session)
{
MyCustomEndpointConfig myconfig = (MyCustomEndpointConfig) config;
myconfig.doThing();
} The Using a param that is anything but @OnOpen
public void open(MyCustomEndpointConfig config, Session session) // <-- this is a DeploymentException (invalid params)
{
config.doThing();
} Even using @OnOpen
public void open(ServerEndpointConfig config, Session session) // <-- this is a DeploymentException (invalid params)
{
MyCustomEndpointConfig myconfig = (MyCustomEndpointConfig) config;
}
This is not a backward incompatible change. |
Now this would be a backward incompatible change. I'm -1 on this proposal as it stands. This ability to unwrap cannot possible represent the truth of the The The methods that are accessed once, at initialization, not every time the endpoint upgrades.
The methods that SHOULD be called per upgrade. (this keeps behavior with manual
The methods that MUST be called per upgrade. (this method/config does not exist if using the
Note that the websocket spec does not define how often the ServerEndpointConfig methods are called, or when in the lifecycle, etc. |
@joakime Thank you for the elaborate about how Jetty works. However, I think not every statement you made is supported by the spec, though.
I assume you mean this would be an incompatible change for Jetty if the
It never has been said the user-provided
This statement about dynamic/decorated classes does not justify the reason to prohibit the user implementation, decorating the class, etc. is done at the registration point, not at the Is there really some good reason to prohibit the user-defined |
I'd be happy with the spec as is but I can see the point that forcing the container to wrap the
Apps that want the original I think that could be argued as all clarification and therefore we'd stick with 2.1 as a version. |
The Lets talk about the call order for The call order is (copied here from a previous talk about this, all the way back in 2013) ...
To note, the This is intentional, and desired, to allow implementors several features.
If the implementations created a new This behavior and call order is how Jetty, Tomcat, Wildfly, and Glassfish/Tyrus work today. (tested this myself). If the Take this for example ...
If you want common behavior, and singleton style logic, the place for that is in the In the In fact, in the millions of installations, and years of experience with javax.websocket / jakarta.websocket, you are the only one to ever have asked about casting an
(drop the Casting of the The only API that is guaranteed to work for I spent the last hour going through github search results for https://github.com/search?p=1&q=ServerEndpointConfig+modifyHandshake&type=Code (stopped at page 60) looking at how people use The one, and only one place, that I found with casting in onOpen isn't even code, but a discussion in an issue ... Simply put, being able to cast to the application (or server provided) EndpointConfig instance is new functionality and that represents a backward incompatible change. |
This doesn't solve anything in my opinion. |
This proposed change also allows for the registered values on Lets look at the most common import jakarta.servlet.http.HttpSession;
import jakarta.websocket.HandshakeResponse;
import jakarta.websocket.server.HandshakeRequest;
import jakarta.websocket.server.ServerEndpointConfig;
public class HttpSessionConfigurator extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
HttpSession httpSession = (HttpSession) request.getHttpSession();
if (httpSession != null) {
sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
}
}
} If the |
It helps to view The API should have been ... Configurator.modifyHandshake(ServerEndpointConfig config, Map<String,Object> instanceUserProperties, HandshakeRequest request, HandshakeResponse response)
ServerContainer.addEndpoint(ServerEndpointConfig config, Map<String,Object> baseUserProperties) Where the But that ship sailed a long time ago, so we are stuck with wrapping of the |
Now you are right. Without wrapping, |
I am reopening the issue. The TCK tests have and always had a test
And the endpoint:
|
The test isn't doing what you think it is doing. The test is confirming that the The associated assertion makes no reference to the original |
Right, the wrapper needs to be sent to the onOpen... |
That (the apparent casting in onOpen) has never been supported by Jetty. The flaws in the 2 pieces of code you pasted ...
That TCK testcase is not a real world use case. NOT wrapping the ServerEndpointConfig is THE backward incompatible change and will surprise countless existing use cases (the UserProperties unique to each server endpoint upgrade). Changing the rules now will cause massive upheaval in the many existing projects and libraries that use jakarta websockets today. (I'll be happy to show you a few thousand examples across github if you want to see them). If you want that (casting in onopen), then the API has to undergo some serious changes to support UserProperties per upgrade. (too late to do that now) Now for point 2, the expectation that the application declared ServerEndpointConfig class or instance will be used and presented as-is also doesn't take into account other real world use cases (eg: proxy classes, mutated classed, decorated classes, class file transformers, various forms of instrumentation, etc) the only API guarantee is what is spec'd by the jakarta websocket spec, which is the Your implementation can choose to use casting and completely be incompatible with the past 8 years of real world use cases. Now, that being said, the new rule on this new version of the websocket spec is that all UserProperties are per endpoint instance. Lets take these 2 proposed use cases. Use Case 1: Getting the HttpSession of the upgrade into the Endpoint via UserPropertiesNote: There are thousands of examples of this use case on github. import jakarta.servlet.http.HttpSession;
import jakarta.websocket.HandshakeResponse;
import jakarta.websocket.server.HandshakeRequest;
import jakarta.websocket.server.ServerEndpointConfig;
public class HttpSessionConfigurator extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
HttpSession httpSession = (HttpSession) request.getHttpSession();
if (httpSession != null) {
sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
}
}
}
@ServerEndpoint(value = "/wants-session", configurator = HttpSessionConfigurator.class)
public class MyEndpoint {
private HttpSession httpSession;
@OnOpen
public void onOpen(EndpointConfig config) {
this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
}
} Use Case 2: Getting the HttpSession of the upgrade into the Endpoint via custom EndpointConfigimport jakarta.servlet.http.HttpSession;
import jakarta.websocket.HandshakeResponse;
import jakarta.websocket.server.HandshakeRequest;
import jakarta.websocket.server.ServerEndpointConfig;
public class HttpSessionServerEndpointConfig extends ServerEndpointConfig {
private HttpSession httpSession;
public void setHttpSession(HttpSession httpSession) {
this.httpSession = httpSession;
}
public HttpSession getHttpSession() {
return this.httpSession;
}
}
public class HttpSessionConfigurator extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
HttpSessionServerEndpointConfig config = (HttpSessionServerEndpointConfig) sec;
HttpSession httpSession = (HttpSession) request.getHttpSession();
config.setHttpSession(httpSession);
}
}
public class MyEndpoint extends Endpoint {
private HttpSessionServerEndpointConfig config;
private HttpSession httpSession;
public void onOpen(Session session, EndpointConfig config) {
this.config = (HttpSessionServerEndpointConfig) config;
this.httpSession = this.config.getHttpSession();
}
} Use Case 2 cannot work when the application provides a
Use case 1 is how the real world has implemented this carryover of information OK, I've presented the 2 use cases being disucussed here. Now, present a way to carry over information from the upgrade to the endpoint. We can safely assume that if a The jakarta websocket spec defined class for handling common upgrade behavior, and The jakarata websocket spec has always had language that the ServerEndpointConfig is about See: |
Please close this challenge as invalid. |
As per Websocket Specification team decision as determined by Spec lead @markt-asf this challenge is now being closed as invalid. |
@markt-asf Thank you for pointing out wrapping the class on
WebSocket 1.1 was the last TCK that added new tests (before jakarta). That was before Java EE 8. This test is there for WebSocket 1.1 TCK, Java EE 8 TCK, Jakarta EE 8 TCK, and Jakarta EE 9 TCK. Sure, it is possible the test is not correct.
This is likely for a broader discussion. WebSocket Spec never said it is not supported. There is no reason for that not being supported. The wrapper passed in is
I agree with that. I am not 100% happy about how it is done, but let's agree on wrapping. For Use Case 2:
I believe these are called within a single thread defined by the HttpRequest/HttpSession. |
I agree that the test challenge is invalid, thank you again @markt-asf @joakime @scottmarlow |
Websocket 2.1 TCK challenge (Jakarta EE 10) for test websocket/ee/jakarta/websocket/server/serverendpointconfig/WSClient.java#getUserPropertiesTest
Description:
ServerEndpointConfig
class which hasgetUserProperties
method.ServerEndpointConfig
is an interface, whose implementation instance is registered in ServerApplicationConfig#getEndpointConfigs.ServerEndpointConfig.Configurator
is returned by ServerEndpointConfig.getConfigurator. BothServerEndpointConfig
andServerEndpointConfig.Configurator
are instances provided by the WebSocket user then.In WebSocket 2.0, the ServerEndpointConfig#Configurator class, which has modifyHandshake method is called and the user provided instance of
ServerEndpointConfig
is passed in as an argument.In WebSocket 2.1, the
modifyHandshake
method javadoc was altered by:userProperties
method that returns a mutable static map.userProperties
method from a user implementation ofmodifyHandshake
method will somehow return a copy of the user properties.The text was updated successfully, but these errors were encountered: