Bug 63521

Summary: javax.websocket.server.ServerContainer.addEndpoint(Class<?> endpointClass) throws DeploymentException
Product: Tomcat 8 Reporter: Polina Georgieva <poli.m.georgieva>
Component: WebSocketAssignee: Tomcat Developers Mailing List <dev>
Severity: normal    
Priority: P2    
Version: 8.5.x-trunk   
Target Milestone: ----   
Hardware: PC   
OS: All   
Attachments: Test application and patch.

Description Polina Georgieva 2019-06-20 12:16:03 UTC
Created attachment 36635 [details]
Test application and patch.


Problem description:
The following API method is not working correctly on latest 8.5.x (and 9.0.x) Tomcat versions.
javax.websocket.server.ServerContainer. void addEndpoint(Class<?> endpointClass) [1]
The invocation of this method causes: 
javax.websocket.DeploymentException: Multiple Endpoints may not be deployed to the same path [/websocket/echoAnnotation] : existing endpoint was [class test.websocket.EchoAnnotation] and new endpoint is [class test.websocket.EchoAnnotation]

Steps to reproduce: 
1. Deploy the attached test application ServerContainerTest.war
2. Request to ws://localhost:8080/ServerContainerTest/websocket/echoAnnotation hangs. 

Problem analysis:
The issue is reproducible since v.8.5.38. Since then the DeploymentExceptions started to cause failures of all the application endpoints (even already successfully deployed).
The root issue causing the DeploymentException:  one and the same endpoint class gets deployed twice - once deployed by the auto scan mechanism and then via the programmatic approach. The second fails because the same path is already used. This I think is a valid scenario according to the API description[1] and the WebSocket specification.

What the spec says:
From JSR 356, section 6.4 Programmatic Server Deployment:
“It is recommended that developers use either the programmatic deployment API, or base their application
on the scanning and ServerApplicationConfig mechanism, but not mix both methods. Developers can
suppress a deployment by scan of the endpoints in the WAR file by providing a ServerApplicationConfig
that returns empty sets from its methods. If however, the developer does mix both modes of deployment, it is possible in the case of annotated endpoints, for the same annotated endpoint to be submitted twice for deployment, once as a result of a scan of
the WAR file, and once by means of the developer calling the programmatic deployment API. In this case
of an attempt to deploy the same annotated endpoint class more than once, the websocket implementation
must only deploy the annotated endpoint once, and ignore the duplicate submission.”

Workaround is available:
Suppress the auto scan mechanism by providing a custom ServerApplicationConfig implementation (returning empty sets).

Proposed solution:
A possible fix would be - when we detect duplicate paths to add a check if they are for the same endpoint class and in this case ignore the second deployment and omit the DeploymentException. 
An example patch plus junits adaptations are attached. 

Best Regards,

[1] https://docs.oracle.com/javaee/7/api/javax/websocket/server/ServerContainer.html#addEndpoint-java.lang.Class-
Comment 1 Mark Thomas 2019-06-21 19:25:06 UTC
Checking for the same class is too broad. There are invalid circumstances (e.g. duplicate programmatic deployment) that would then be allowed. I'm working on implementing a narrower check.
Comment 2 Mark Thomas 2019-06-21 20:21:24 UTC
Thanks for the report. I'd missed that in the spec.

Fixed in:
- master for 9.0.22 onwards
- 8.5.x for 8.5.43 onwards
- 7.0.x for 7.0.95 onwards