Skip to content
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

Running as deployed WAR two conflicting endpoints are registered #117

Closed
reckart opened this issue Jul 17, 2017 · 20 comments
Closed

Running as deployed WAR two conflicting endpoints are registered #117

reckart opened this issue Jul 17, 2017 · 20 comments
Milestone

Comments

@reckart
Copy link
Contributor

reckart commented Jul 17, 2017

Running as deployed WAR both standard and web-socket endpoints are triggered. I get this exception:

Exception sending context initialized event to listener instance of class [com.giffing.wicket.spring.boot.starter.web.servlet.websocket.WicketServerEndpointConfigRegister]
com.giffing.wicket.spring.boot.context.exceptions.WicketSpringBootException: javax.websocket.DeploymentException: Multiple Endpoints may not be deployed to the same path [/wicket/websocket] : existing endpoint was [class org.apache.wicket.protocol.ws.javax.WicketEndpoint] and new endpoint is [class org.apache.wicket.protocol.ws.javax.WicketEndpoint]
	at com.giffing.wicket.spring.boot.starter.web.servlet.websocket.WicketServerEndpointConfigRegister.contextInitialized(WicketServerEndpointConfigRegister.java:34) ~[wicket-spring-boot-starter-1.0.9.jar:1.0.9]
	at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4743) [catalina.jar:8.5.16]
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5207) [catalina.jar:8.5.16]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [catalina.jar:8.5.16]
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1419) [catalina.jar:8.5.16]
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1409) [catalina.jar:8.5.16]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_131]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [?:1.8.0_131]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [?:1.8.0_131]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_131]
Caused by: javax.websocket.DeploymentException: Multiple Endpoints may not be deployed to the same path [/wicket/websocket] : existing endpoint was [class org.apache.wicket.protocol.ws.javax.WicketEndpoint] and new endpoint is [class org.apache.wicket.protocol.ws.javax.WicketEndpoint]
	at org.apache.tomcat.websocket.server.WsServerContainer.addEndpoint(WsServerContainer.java:180) ~[tomcat-websocket.jar:8.5.16]
	at com.giffing.wicket.spring.boot.starter.web.servlet.websocket.WicketServerEndpointConfigRegister.contextInitialized(WicketServerEndpointConfigRegister.java:32) ~[wicket-spring-boot-starter-1.0.9.jar:1.0.9]
	... 9 more

It doesn't happen when I run via the embedded Tomcat. Any ideas?

@reckart
Copy link
Contributor Author

reckart commented Jul 17, 2017

It seems as if first Tomcat auto-detects the endpoint and later Wicket Spring Boot adds it again:

First time WicketServerEndpointConfig is added (very early):

Daemon Thread [localhost-startStop-1] (Suspended (breakpoint at line 135 in WsServerContainer))	
	owns: StandardContext  (id=42)	
	WsServerContainer.addEndpoint(ServerEndpointConfig) line: 135	
	WsSci.onStartup(Set<Class<?>>, ServletContext) line: 116	
	StandardContext.startInternal() line: 5196	
	StandardContext(LifecycleBase).start() line: 150	
	ContainerBase$StartChild.call() line: 1419	
	ContainerBase$StartChild.call() line: 1409	
	FutureTask<V>.run() line: 266	
	ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) line: 1142	
	ThreadPoolExecutor$Worker.run() line: 617	
	Thread.run() line: 748	

Second time WicketServerEndpointConfig is added (via a future after the Spring application has started up):

Daemon Thread [localhost-startStop-1] (Suspended (breakpoint at line 135 in WsServerContainer))	
	owns: StandardContext  (id=42)	
	WsServerContainer.addEndpoint(ServerEndpointConfig) line: 135	
	WicketServerEndpointConfigRegister.contextInitialized(ServletContextEvent) line: 32	
	StandardContext.listenerStart() line: 4743	
	StandardContext.startInternal() line: 5207	
	StandardContext(LifecycleBase).start() line: 150	
	ContainerBase$StartChild.call() line: 1419	
	ContainerBase$StartChild.call() line: 1409	
	FutureTask<V>.run() line: 266	
	ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) line: 1142	
	ThreadPoolExecutor$Worker.run() line: 617	
	Thread.run() line: 748

@reckart
Copy link
Contributor Author

reckart commented Jul 17, 2017

Running in JAR application mode (which works), I get this trace:

Daemon Thread [localhost-startStop-1] (Suspended (breakpoint at line 135 in WsServerContainer))	
	owns: TomcatEmbeddedContext  (id=104)	
	WsServerContainer.addEndpoint(ServerEndpointConfig) line: 135	
	WicketServerEndpointConfigRegister.contextInitialized(ServletContextEvent) line: 32	
	TomcatEmbeddedContext(StandardContext).listenerStart() line: 4743	
	TomcatEmbeddedContext(StandardContext).startInternal() line: 5207	
	TomcatEmbeddedContext(LifecycleBase).start() line: 150	
	ContainerBase$StartChild.call() line: 1419	
	ContainerBase$StartChild.call() line: 1409	
	FutureTask<V>.run() line: 266	
	ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) line: 1142	
	ThreadPoolExecutor$Worker.run() line: 617	
	Thread.run() line: 748	

@reckart
Copy link
Contributor Author

reckart commented Jul 17, 2017

Adding

<Context containerSciFilter="org.apache.tomcat.websocket.server.WsSci">

in the context.xml also doesn't help. If I do that, I get an NPE instead:

xception sending context initialized event to listener instance of class [com.giffing.wicket.spring.boot.starter.web.servlet.websocket.WicketServerEndpointConfigRegister]
java.lang.NullPointerException: null
	at com.giffing.wicket.spring.boot.starter.web.servlet.websocket.WicketServerEndpointConfigRegister.contextInitialized(WicketServerEndpointConfigRegister.java:32) ~[wicket-spring-boot-starter-1.0.9.jar:1.0.9]
	at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4743) [catalina.jar:8.5.16]
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5207) [catalina.jar:8.5.16]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [catalina.jar:8.5.16]
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1419) [catalina.jar:8.5.16]
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1409) [catalina.jar:8.5.16]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_131]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [?:1.8.0_131]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [?:1.8.0_131]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_131]

@MarcGiffing
Copy link
Owner

I don't know how to prevent this behaviour. Is there a way to stop Tomcat to auto detect the WicketServerEndpointConfig? Can the check if the WicketServerEndpointConfig is already registered in the WicketServerEndpointConfigRegister? Or should we add a property on the WicketServerEndpointConfigRegister bean to disable it?

@martin-g
Copy link
Contributor

Tomcat scans for implementations of some Java EE classes, like javax.websocket.server.ServerApplicationConfig.
The problem is that it is able to detect them only if the application is packaged in .war file.
If the format is custom, like fat-jar, then it should be helped to be able to construct the application classpath.

Jetty does the same. That's why the Wicket Quickstart Archetype manually registers the endpoint in Start.java: https://github.com/apache/wicket/blob/42bf896538953b302933362ce347e820a72ba40d/archetypes/quickstart/src/main/resources/archetype-resources/src/test/java/Start.java#L84

One workaround that I imagine for this issue is to test for class existence before executing the logic in WicketServerEndpointConfigRegister. E.g. for Tomcat you can test for any ot the embed specific classes.
But you will have to do this for Jetty, Undertow, ... as well, I'm afraid.

@reckart
Copy link
Contributor Author

reckart commented Jul 17, 2017

I am thinking of two options

  • catch the exception and ignore it (bad solution)
  • ask the container if there is already an endpoint configured at /wicket/websocket and if yes, then don't try to register it again (better solution I guess)

@reckart
Copy link
Contributor Author

reckart commented Jul 17, 2017

Another option might be to check if the URL of the WicketServerEndpointConfigRegister class (or any other sensible reference class) hints at being inside a fat-jar.

@MarcGiffing
Copy link
Owner

@MarcGiffing
Copy link
Owner

I'll try the following:

Spring Boots EmbeddedServletContainerAutoConfiguration class creates the factories for the embedded servlet containers (tomcat, jetty, undertow). A EmbeddedServletContainerFactory bean is created here for the specific servlet container. I could configure the WicketServerEndpointConfigRegister only as a bean if the EmbeddedServletContainerFactory bean is present....

@MarcGiffing
Copy link
Owner

The WicketServerEndpointConfigRegister is now only configured if the bean EmbeddedServletContainerFactory exits. @reckart are you able to build the starter project and test it?

{
WicketWebInitializerAutoConfig.WebSocketWicketWebInitializerAutoConfiguration#wicketServerEndpointConfigRegister: [
{
condition: "OnBeanCondition",
message: "@ConditionalOnBean (types: org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; SearchStrategy: all) found bean 'tomcatEmbeddedServletContainerFactory'"
}
]
}

@reckart
Copy link
Contributor Author

reckart commented Jul 26, 2017

@MarcGiffing that looked promising but I'm afraid for the case that caused me to report the issue originally, it doesn't work. But I also have good news.

First why does it not work: it is probably that Eclipse doesn't seem to care much about Maven scopes. So WebSocketWicketWebInitializerAutoConfiguration still triggers even with your changes when running the application in Eclipse via WTP and the endpoint is registered twice. -- IF the setting "Serve modules without publishing" is set to its default value false.

IF "Serve modules without publishing" is set to true, then everything works nicely - with 1.0.9 and also with your recent changes.

So the good news is that for the purpose of development, I have a workaround and set "Serve modules without publishing" is set to true (which I prefer anyway).

The question is: what happens if an actual WAR is build and deployed in a Tomcat outside Eclipse. That is something I haven't tried yet.

So we have these situations:

Running in Eclipse Mode Version Works
yes Run as Java application 1.0.9 yes
yes Run as Java application 1.0.10-SNAPSHOT yes
yes Run-on-server; serve with publishing 1.0.9 no
yes Run-on-server; serve with publishing 1.0.10-SNAPSHOT no
yes Run-on-server; serve without publishing 1.0.9 yes
yes Run-on-server; serve without publishing 1.0.10-SNAPSHOT yes
no Standalone JAR 1.0.9 ??? (probably yes)
no Standalone JAR 1.0.10-SNAPSHOT ??? (probably yes)
no WAR deployed in Tomcat 1.0.9 no
no WAR deployed in Tomcat 1.0.10-SNAPSHOT no

@reckart
Copy link
Contributor Author

reckart commented Jul 28, 2017

Ok, I also tested with a proper WAR build and deployed into a Tomcat 8.5.14. Both with 1.0.9 and 1.0.10-SNAPSHOT an attempt is made to register a second WebSocket endpoint.

So we have 4 configurations in total that do not work - neither with 1.0.9 nor with 1.0.10-SNAPSHOT.

I have updated the table above accordingly.

@reckart
Copy link
Contributor Author

reckart commented Jul 28, 2017

Maybe it would be an option to add a static lookup table into WicketEndpoint proper where every all endpoints register themselves. That could be used e.g. by Spring Boot Wicket Starter to check if a WicketEndpoint has already been deployed under a given URL and not try it again.

Another thing that might work is that Spring Boot Wicket Starter could try making a connection to the WicketEndpoint URL and if that works not try registering a second endpoint - although I don't know if the endpoint actually is already able to accept connections at the time Spring Boot Wicket Starter tries to register the second endpoint.

@MarcGiffing
Copy link
Owner

I've tried different solutions to detect the embedded mode but without luck.

Maybe it would be an option to add a static lookup table into WicketEndpoint proper where every all endpoints register themselves. That could be used e.g. by Spring Boot Wicket Starter to check if a WicketEndpoint has already been deployed under a given URL and not try it again.

I don't understand this proposal. There have to be a change in the Wicket libraries? How should the Wicket Spring Boot Starter check the url.

Another thing that might work is that Spring Boot Wicket Starter could try making a connection to the WicketEndpoint URL and if that works not try registering a second endpoint - although I don't know if the endpoint actually is already able to accept connections at the time Spring Boot Wicket Starter tries to register the second endpoint.

I didn't found a solution to get the endpoints to check the existing first. I don't think that a connection is possible while configuration.

catch the exception and ignore it (bad solution)
Sound not that bad anymore ;)

I could also add a property on the bean to disable/enable it but then you have add the configuration in your tomcat.

@reckart
Copy link
Contributor Author

reckart commented Jul 29, 2017

The proposal regarding the WicketEndpoint maintaining an internal registry would indeed require a change in Wicket itself, yes.

A property sounds like a good idea. IMHO the default should be that it is disabled. If the Spring Boot application is deployed as a runnable JAR, then the property should be set to enable the endpoint registration in the main method (which is only called in standalone mode, not in WAR mode). I could also manually set the property when running with the Eclipse option "serve without publishing" enabled.

@MarcGiffing
Copy link
Owner

No, the default should be that it is enabled. For the need of deploying it as a war in a servlet container like tomcat you have to disable it. You can use the SpringBootServletInitializer to disable it. We need to update the documentation with this hint (#115)

@Bean
@ConditionalOnProperty(prefix = "wicket.external.websocket.registerServerEndpoint", value = "enabled", matchIfMissing = true)
public WicketServerEndpointConfigRegister wicketServerEndpointConfigRegister() {
  return new WicketServerEndpointConfigRegister();
}

@SpringBootApplication
public class WicketApplication extends SpringBootServletInitializer  {

	public static void main(String[] args) throws Exception {
		new SpringApplicationBuilder()
			.sources(WicketApplication.class)
			.run(args);
	}


	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
		builder.properties( "wicket.external.websocket.register-server-endpoint.enabled=false" );
		return super.configure( builder );
	}

}

@reckart
Copy link
Contributor Author

reckart commented Jul 30, 2017

It would be nice if there was a constant with the property name that could be used. E.g.

@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
		builder.properties( WicketServerEndpointConfigRegister.ENABLED + "=false" );
		return super.configure( builder );
	}

@eximius313
Copy link

eximius313 commented Jul 30, 2017

I'd suggest to create separate class for that.
It is very common that your WicketApplication class extends WebApplication or WicketBootSecuredWebApplication and then you can not extend SpringBootServletInitializer. This is what I was struggling with #115 - no documentation said that it can be separate class!
And here I'm explaining why

@MarcGiffing
Copy link
Owner

I've added a hint in the Readme down below, Here is also a pull request welcome :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants