Re: Using custom Configurator with WebSockets
Your idea of wrapping the interface returned by the registry was a stroke of brilliance! And I'm pleased to say it was successful. I'm now working to resolve another small hiccup. The IoC registry can be configured to provide the service as either a singleton or a new instance for each new thread. I chose the latter. Socket connections are established without problem, but if Tomcat subsequently calls @OnError and/or @OnClose on a connection, the IoC registry provides a new instance (not good). This suggests that the subsequent method call on the socket comes via a new thread. So bit of a mismatch there between sessions and threads. I may have to move to a singleton service, meaning no longer being able to persist connection endpoints to a static map with a simple: connections.put(this). Thanks again, Chris. - To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org For additional commands, e-mail: users-h...@tomcat.apache.org
Re: Using custom Configurator with WebSockets
Hmm. Looking at this some more I'm not sure this is going to work. HarbourServerEndpoint needs to extend javax.websocket.Endpoint which it can't do as an interface. I think you are going to have to wrap the instance returned by the registry. In which case you can probably go back to using POJO. So you'd have something like (I'm typing directly in email so the chances of this compiling first time are slim): @ServerEndpoint(...) HarbourServerEndpointWrapper private HarbourServerEndpoint inner; public HarbourServerEndpointWrapper() { inner = RegistryProxy.getService(HarbourServerEndpoint.class); } @OnOpen void onOpen(Session session, EndpointConfig config) { inner.onOpen(Session session, EndpointConfig config); } @OnMessage void onMessageMessage(Session session, Message message) { inner.onMessageMessage(Session session, Message message); } @OnClose void onClose(Session session, CloseReason reason) { inner.onClose(Session session, CloseReason reason); } @OnError void onError(Session session, Throwable throwable) { inner.onError(Session session, Throwable throwable); } } Mark On 26/04/2019 11:07, Christopher Dodunski wrote: > So I've converted the server endpoint from annotation-based to > programmatic, to get around the constraint of the @ServerEndpoint > annotation having to decorate a concrete class. The elements of this > annotation now occupy an implementation of ServerApplicationConfig: > > > public class HarbourServerApplicationConfig implements > ServerApplicationConfig { > > ... > > @Override > public Set getEndpointConfigs(Set extends Endpoint>> endpointClasses) { > Set result = new HashSet<>(); > ServerEndpointConfig configuration = > ServerEndpointConfig.Builder.create(HarbourServerEndpoint.class, > "/websocket/{port-name}/{username}") > .configurator(configurator) > .encoders(encoders) > .decoders(decoders) > .build(); > result.add(configuration); > > return result; > } > } > > > All works fine when the Builder.create() parameter is a concrete endpoint > class, but not if an interface (the goal is having Tomcat accept as > endpoint instances IoC proxy objects cast back to their interface). > > The error message suggests that Tomcat is treating this class as concrete. > > > 26-Apr-2019 20:00:40.605 SEVERE [ajp-nio-127.0.0.1-8009-exec-197] > org.apache.catalina.core.ContainerBase.addChildInternal > ContainerBase.addChild: start: > org.apache.catalina.LifecycleException: Failed to start component > [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/harbour]] > at > org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:167) > at > org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:754) > at > org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:730) > at > org.apache.catalina.core.StandardHost.addChild(StandardHost.java:734) > at > org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:985) > ... > Caused by: java.lang.NullPointerException > at > org.apache.tomcat.websocket.pojo.PojoMethodMapping.(PojoMethodMapping.java:85) > at > org.apache.tomcat.websocket.server.WsServerContainer.addEndpoint(WsServerContainer.java:147) > at org.apache.tomcat.websocket.server.WsSci.onStartup(WsSci.java:116) > at > org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5245) > at > org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) > ... 46 more > > > Lastly, below is the interface itself (my programmatic endpoint implements > this). > > > public interface HarbourServerEndpoint { > > void onOpen(Session session, EndpointConfig config); > > void onMessageMessage(Session session, Message message); > > void onClose(Session session, CloseReason reason); > > void onError(Session session, Throwable throwable); > } > > > Unfortunately online examples of programmatic endpoints are sparse, and > the few to be found very basic. So am quite reliant on those familiar > with the WebSocket library, and Tomcat's application of it. > > Kind regards, > > Chris. > > > - > To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org > For additional commands, e-mail: users-h...@tomcat.apache.org > - To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org For additional commands, e-mail: users-h...@tomcat.apache.org
Re: Using custom Configurator with WebSockets
So I've converted the server endpoint from annotation-based to programmatic, to get around the constraint of the @ServerEndpoint annotation having to decorate a concrete class. The elements of this annotation now occupy an implementation of ServerApplicationConfig: public class HarbourServerApplicationConfig implements ServerApplicationConfig { ... @Override public Set getEndpointConfigs(Set> endpointClasses) { Set result = new HashSet<>(); ServerEndpointConfig configuration = ServerEndpointConfig.Builder.create(HarbourServerEndpoint.class, "/websocket/{port-name}/{username}") .configurator(configurator) .encoders(encoders) .decoders(decoders) .build(); result.add(configuration); return result; } } All works fine when the Builder.create() parameter is a concrete endpoint class, but not if an interface (the goal is having Tomcat accept as endpoint instances IoC proxy objects cast back to their interface). The error message suggests that Tomcat is treating this class as concrete. 26-Apr-2019 20:00:40.605 SEVERE [ajp-nio-127.0.0.1-8009-exec-197] org.apache.catalina.core.ContainerBase.addChildInternal ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/harbour]] at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:167) at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:754) at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:730) at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:734) at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:985) ... Caused by: java.lang.NullPointerException at org.apache.tomcat.websocket.pojo.PojoMethodMapping.(PojoMethodMapping.java:85) at org.apache.tomcat.websocket.server.WsServerContainer.addEndpoint(WsServerContainer.java:147) at org.apache.tomcat.websocket.server.WsSci.onStartup(WsSci.java:116) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5245) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) ... 46 more Lastly, below is the interface itself (my programmatic endpoint implements this). public interface HarbourServerEndpoint { void onOpen(Session session, EndpointConfig config); void onMessageMessage(Session session, Message message); void onClose(Session session, CloseReason reason); void onError(Session session, Throwable throwable); } Unfortunately online examples of programmatic endpoints are sparse, and the few to be found very basic. So am quite reliant on those familiar with the WebSocket library, and Tomcat's application of it. Kind regards, Chris. - To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org For additional commands, e-mail: users-h...@tomcat.apache.org
Re: Using custom Configurator with WebSockets
Thanks Mark, you've made it clear that annotating the interface is not an option. Converting my server endpoint from annotation based to programmatic is not a problem, nor is implementing ServerApplicationConfig to configure what were previously @ServerEndpoint elements: value, encoders, decoders, configurator. What is not clear is how this will solve the problem of Tomcat not accepting an interface as an endpoint. Afterall, the programmatic approach still employs Configurator.getEndpointInstance(), and this is where I came unstuck with annotations. Below is the beginnings of my custom ServerApplicationConfig implementation. public class CustomServerAppConfig implements ServerApplicationConfig { @Override public Set getEndpointConfigs(Set> endpointClasses) { Set result = new HashSet<>(); for (Class epClass : endpointClasses) { if (epClass.equals(MyEndpointInterface.class)) { ServerEndpointConfig sec = ServerEndpointConfig.Builder.create(epClass, "/websocket").build(); result.add(sec); } } return result; } @Override public Set> getAnnotatedEndpointClasses(Set> scanned) { return Collections.emptySet(); } } Regards, Chris. - To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org For additional commands, e-mail: users-h...@tomcat.apache.org
Re: Using custom Configurator with WebSockets
On 24/04/2019 07:16, Christopher Dodunski wrote: > Hi Mark, > > Looking at the Tapestry-IoC Registry code I notice that although it > constructs a (plastic) service proxy object, it does cast it to its > associated interface before making it available from the registry's > getService() method. So if I move the WebSocket annotations to my > interface as previously thought, Tomcat should be getting back from the > registry what it expects: an instance of the interface annotated with > @ServerEndpoint. > > Just wondering how this sits with your understanding of the WebSocket > library. It won't work. From the Java WebSockets specification: 4.1 @ServerEndpoint This class level annotation signifies that the Java class it decorates must be deployed by the implementation as a websocket server endpoint and made available in the URI-space of the websocket implementation. *The class must be public, concrete, and have a public no-args constructor.* (my emphasis) I don't see a way to do this with annotations. You are going to need to do it programmatically. The WebSocket examples that ship with Tomcat include several endpoints configured this way. There are further examples in the unit tests. Mark - To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org For additional commands, e-mail: users-h...@tomcat.apache.org
Re: Using custom Configurator with WebSockets
Hi Mark, Looking at the Tapestry-IoC Registry code I notice that although it constructs a (plastic) service proxy object, it does cast it to its associated interface before making it available from the registry's getService() method. So if I move the WebSocket annotations to my interface as previously thought, Tomcat should be getting back from the registry what it expects: an instance of the interface annotated with @ServerEndpoint. Just wondering how this sits with your understanding of the WebSocket library. Regards, Chris. - To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org For additional commands, e-mail: users-h...@tomcat.apache.org
Re: Using custom Configurator with WebSockets
Based on what you wrote regarding WebSocket annotations not following Java inheritance, I imagine the below wouldn't work either. public class MyServerEndpointConfig extends ServerEndpointConfig { @Override public Class getEndpointClass() { return MyServiceInterface.class; } } - To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org For additional commands, e-mail: users-h...@tomcat.apache.org
Re: Using custom Configurator with WebSockets
> The custom Configurator looks fine. The problem is with trying to do > this with a POJO endpoint. There is an underlying assumption that - for > a POJO endpoint - the endpoints will will instances of the POJO class. > This doesn't seem to hold in your case so hence it breaks. > > The WebSocket spec explicitly states that WebSocket annotations do not > follow Java inheritance so moving the annotation to the interface is not > an option. > > I think you are going to have to build your ServerEndpointConfig > programmatically so you can specify the correct endpoint class. > > Mark Thank you very much Mark for explaining. After examining the Tapestry-IoC a little closer, I discovered that what is being returned from the Registry is in fact a 'service proxy object'. It implements the same service interface as my implementation class, but it is NOT an instance of my implementation class. I was thinking to shift my WebSocket annotations from the service implementation to the service interface, but you write that this won't work either. So I searched the net and found a number of examples of using build() to define an endpoint configuration. Do you happen to have an example where the objective was the same - or very similar to - my own? I guess the goal is in having Tomcat accept a service proxy object that merely implements a given service interface, yes? Quoting from the Tapestry-IoC documentation: "Services consist of two main parts: a service interface and a service implementation. The service interface is how the service will be represented throughout the rest of the registry. Since what gets passed around is normally a proxy, you can't expect to cast a service object down to the implementation class (you'll see a ClassCastException instead). In other words, you should be careful to ensure that your service interface is complete, since Tapestry IoC effectively walls you off from back doors such as casts." Regards, Chris. - To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org For additional commands, e-mail: users-h...@tomcat.apache.org
Re: Using custom Configurator with WebSockets
On 17/04/2019 22:58, Christopher Dodunski wrote: > Hello, > > Just a quick question with regard to extending > ServerEndpointConfig.Configurator to override Tomcat's default action of > instantiating the POJO class annotated with @ServerEndpoint on receiving a > WebSocket request. My reason for doing this is that my endpoint class > depends on IoC dependency injection, and therefore needs to be got from > the registry to have its dependencies in place. > > My Configurator method: > > > @Override > public T getEndpointInstance(Class endpointClass) throws > InstantiationException { > return > endpointClass.cast(RegistryProxy.getService(HarbourServerEndpoint.class)); > } > > > The @ServerEndpoint annotation is placed on on my > HarbourServerEndpointImpl POJO class, not the interface that it > implements. Based on the below runtime catalina.out error message the > problem appears to be that the registry is returning HarbourServerEndpoint > whereas Tomcat is expecting an instance of HarbourServerEndpointImpl? > > I'm hoping someone can please explain what is going wrong with my custom > Configurator. The custom Configurator looks fine. The problem is with trying to do this with a POJO endpoint. There is an underlying assumption that - for a POJO endpoint - the endpoints will will instances of the POJO class. This doesn't seem to hold in your case so hence it breaks. The WebSocket spec explicitly states that WebSocket annotations do not follow Java inheritance so moving the annotation to the interface is not an option. I think you are going to have to build your ServerEndpointConfig programmatically so you can specify the correct endpoint class. Mark > > > 15-Apr-2019 12:45:28.488 SEVERE [http-nio-8080-exec-915] > org.apache.coyote.AbstractProtocol$ConnectionHandler.process Error reading > request, ignored > java.lang.ClassCastException: Cannot cast > $HarbourServerEndpoint_39c9cc24eb8b2a to > com.optomus.harbour.services.HarbourServerEndpointImpl > at java.lang.Class.cast(Class.java:3369) > at > com.optomus.harbour.services.HarbourServerEndpointConfigurator.getEndpointInstance(HarbourServerEndpointConfigurator.java:17) > at > org.apache.tomcat.websocket.pojo.PojoEndpointServer.onOpen(PojoEndpointServer.java:44) > at > org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.init(WsHttpUpgradeHandler.java:133) > at > org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:846) > at > org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1471) > at > org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) > at > java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) > at > java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) > at > org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) > at java.lang.Thread.run(Thread.java:748) > > > Finally, with no casting at all, the compiler gives the error: > > Error:(17, 40) java: incompatible types: inference variable T has > incompatible bounds > equality constraints: > com.optomus.harbour.services.HarbourServerEndpoint > upper bounds: T,java.lang.Object > > > - > To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org > For additional commands, e-mail: users-h...@tomcat.apache.org > - To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org For additional commands, e-mail: users-h...@tomcat.apache.org
Using custom Configurator with WebSockets
Hello, Just a quick question with regard to extending ServerEndpointConfig.Configurator to override Tomcat's default action of instantiating the POJO class annotated with @ServerEndpoint on receiving a WebSocket request. My reason for doing this is that my endpoint class depends on IoC dependency injection, and therefore needs to be got from the registry to have its dependencies in place. My Configurator method: @Override public T getEndpointInstance(Class endpointClass) throws InstantiationException { return endpointClass.cast(RegistryProxy.getService(HarbourServerEndpoint.class)); } The @ServerEndpoint annotation is placed on on my HarbourServerEndpointImpl POJO class, not the interface that it implements. Based on the below runtime catalina.out error message the problem appears to be that the registry is returning HarbourServerEndpoint whereas Tomcat is expecting an instance of HarbourServerEndpointImpl? I'm hoping someone can please explain what is going wrong with my custom Configurator. 15-Apr-2019 12:45:28.488 SEVERE [http-nio-8080-exec-915] org.apache.coyote.AbstractProtocol$ConnectionHandler.process Error reading request, ignored java.lang.ClassCastException: Cannot cast $HarbourServerEndpoint_39c9cc24eb8b2a to com.optomus.harbour.services.HarbourServerEndpointImpl at java.lang.Class.cast(Class.java:3369) at com.optomus.harbour.services.HarbourServerEndpointConfigurator.getEndpointInstance(HarbourServerEndpointConfigurator.java:17) at org.apache.tomcat.websocket.pojo.PojoEndpointServer.onOpen(PojoEndpointServer.java:44) at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.init(WsHttpUpgradeHandler.java:133) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:846) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1471) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748) Finally, with no casting at all, the compiler gives the error: Error:(17, 40) java: incompatible types: inference variable T has incompatible bounds equality constraints: com.optomus.harbour.services.HarbourServerEndpoint upper bounds: T,java.lang.Object - To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org For additional commands, e-mail: users-h...@tomcat.apache.org