I haven't used Java for over a decade and know I'm trying to use the embedded tomcat 8.5.95 (stuck with this version because of guacamole-common) together with websocket support.
Tried various ways to get it work, but I'm stuck and I have no idea what it is causing the issues.
The problem is that (ServerContainer)ctx.getAttribute("javax.websocket.server.ServerContainer")
is always null
.
I know there are a lot of questions here on SO with the same issue, but I tried a lot of them and none seems to work.
It doesn't matter if a call the code above in my main
or in a ServletContainerInitializer
main
public static void main(String[] args) throws Exception {
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
// tried tomcat.addWebapp and tomcat.addContext
var ctx = tomcat.addWebapp("", new File(".").getAbsolutePath());
ctx.addServletContainerInitializer(new Init(), null);
// normal http implementation for guacamole
Tomcat.addServlet(ctx, "tunnel", new DummyGuacamoleTunnelServlet());
ctx.addServletMappingDecoded("/tunnel", "tunnel");
// tried this first, but according the internet ;) this should go in a
// String serverContainerClass = ServerContainer.class.getName();
// ServerEndpointConfig config =
// ServerEndpointConfig.Builder.create(DummyGuacamoleWebsocketEndpoint.class, "/websocket-tunnel")
// .subprotocols(Arrays.asList(new String[]{"guacamole"}))
// .build();
// String serverContainerClass = ServerContainer.class.getName();
// ServerEndpointConfig config =
// ServerEndpointConfig.Builder.create(DummyGuacamoleWebsocketEndpoint.class, "/websocket-tunnel")
// .subprotocols(Arrays.asList(new String[]{"guacamole"}))
// .build();
//
// ServerContainer container = (ServerContainer)ctx.getServletContext().getAttribute(serverContainerClass);
// if (container == null) {
// return;
// }
//
// try {
//
// // Add configuration to container
// container.addEndpoint(config);
//
// }
// catch (DeploymentException e) {
// System.out.println("Unable to deploy WebSocket tunnel. " + e);
// }
tomcat.start();
tomcat.getServer().await();
}
Init (ServletContainerInitializer)
public class Init implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
String serverContainerClass = ServerContainer.class.getName();
ServerContainer container = (ServerContainer)ctx.getAttribute(serverContainerClass);
if (container == null) {
return;
}
ServerEndpointConfig config =
ServerEndpointConfig.Builder.create(DummyGuacamoleWebsocketEndpoint.class, "/websocket-tunnel")
.subprotocols(Arrays.asList(new String[]{"guacamole"}))
.build();
try {
// Add configuration to container
container.addEndpoint(config);
}
catch (DeploymentException e) {
System.out.println("Unable to deploy WebSocket tunnel. " + e);
}
}
}
Endpoint
// GuacamoleWebSocketTunnelEndpoint is from guacamole-common, which I can't change
public class DummyGuacamoleWebsocketEndpoint extends GuacamoleWebSocketTunnelEndpoint {
@Override
protected GuacamoleTunnel createTunnel(javax.websocket.Session session, javax.websocket.EndpointConfig config) throws GuacamoleException {
return null;
}
}
gradle dependencies
dependencies {
implementation("org.apache.guacamole:guacamole-common:1.5.3")
implementation("org.apache.tomcat.embed:tomcat-embed-core:8.5.95")
implementation("org.apache.tomcat.embed:tomcat-embed-websocket:8.5.95")
compileOnly("javax.websocket:javax.websocket-api:1.1")
compileOnly("javax.websocket:javax.websocket-all:1.1")
}
EDIT
I also tried to implement a ServletContextListener
in which I tried to get the ServerContainer
, to no avail.
How do I get the correct ServerContainer
?
Ok, after some further searching and investigating I found the solution :)
I don't need to the two javax.websocket...
dependencies
There are basically two possibilites to add the endpoint
With annotations
Create the context like that (in main
)
var ctx = tomcat.addWebapp("", new File(".").getAbsolutePath());
This will also add support for jsp (but needs additional dependencies, e.g. jasper)
add the ServletContainerInitializer provided by org.apache.tomcat.embed:tomcat-embed-websocket
(in main
)
ctx.addServletContainerInitializer(new WsSci(), null);
annotate the endpoint with @ServerEndpoint("/path")
or you can explicitly add the endpoint to the initializer
ctx.addServletContainerInitializer(new WsSci(), new HashSet<>(List.of(GuacamoleWebSocketTunnelEndpoint.class)));
then you dont need the annotation
without annotations
Create the context like that (in main)
var ctx = tomcat.addContext("", new File(".").getAbsolutePath());
Wrapper defaultServlet = ctx.createWrapper();
defaultServlet.setName("default");
defaultServlet.setServletClass("org.apache.catalina.servlets.DefaultServlet");
defaultServlet.setLoadOnStartup(1);
ctx.addChild(defaultServlet);
ctx.addServletMappingDecoded("/", "default");
you have to add the default servlet as described here
you won't get JSP support.
add the ServletContainerInitializer provided by org.apache.tomcat.embed:tomcat-embed-websocket
(in main
)
ctx.addServletContainerInitializer(new WsSci(), null);
add a listener
public class Listener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent event) {
String serverContainerClass = ServerContainer.class.getName();
ServerContainer container = (ServerContainer)event.getServletContext().getAttribute(serverContainerClass);
ServerEndpointConfig config =
ServerEndpointConfig.Builder.create(DummyGuacamoleWebsocketEndpoint.class, "/websocket-tunnel")
.build();
try {
container.addEndpoint(config);
} catch (DeploymentException e) {
throw new RuntimeException(e);
}
}
@Override
public void contextDestroyed(ServletContextEvent event) {
// Do stuff during server shutdown.
}
}
register the listener in main
ctx.addApplicationListener("full.name.to.ListenerClass");