tyrus icon indicating copy to clipboard operation
tyrus copied to clipboard

Tyrus is hard to start programmatically

Open zenbones opened this issue 1 year ago • 7 comments

I have a an installation of Grizzly which I start programmatically and add Tyrus to. Unfortunately Tyrus is not helpful in this endeavor.

  1. TyrusGrizzlyServerContainer assumes a stand alone start where Tyrus starts Grizzly as opposed to the other way around. I can steal and correct the code for TyrusGrizzlyServerContainer, but that leads to a second issue...
  2. GrizzlyServerFilter is not public, and correcting TyrusGrizzlyServerContainer means I then need to steal and copy GrizzlyServerFilter.

It would be nice if there was a class meant to be used from something manipulating starting up Grizzly already, and/or if GrizzlyServerFilter were publicly accessible to make creating such an entity easier.

zenbones avatar Oct 28 '24 23:10 zenbones

Tyrus works well in the Servlet Container such as Tomcat, or it comes as a part of Glassfish, or Payara.

Grizzly is not a servlet container. It is NIO server that works well as a server for a deployment of Tyrus, although some use-cases might not be available there. Grizzly is a part of Glassfish or Payara, too.

Are you sure you want to use standalone Grizzly?

jansupol avatar Nov 06 '24 13:11 jansupol

Tyrus comes as part of Glassfish? Yes, I want to use Grizzly. We also use Glassfish (for Jersey) and Tyrus. Currently, we add Tyrus to Grizzly like this...

      GrizzlyWebAppState webAppState = webAppStateFor(webApplicationOption.getContextPath());
              webAppState.setTyrusGrizzlyServerContainer(new TyrusGrizzlyServerContainer(httpServer, configuredNetworkListener, webAppState.getWebAppContext(), null, webApplicationOption.getWebSocketOption().isIncludeWsadlSupport(), null, webAppState.getWebSocketExtensionInstallerList().toArray(new WebSocketExtensionInstaller[0])));

...where TyrusGrizzlyServerContainer is mostly a copy of code found in the Tyrus project but not in a useable form...

public class TyrusGrizzlyServerContainer extends TyrusServerContainer {

  private final TyrusWebSocketEngine engine;
  private final WebSocketExtensionInstaller[] webSocketExtensionInstallers;
  private final NetworkListener networkListener;
  private final String contextPath;

  public TyrusGrizzlyServerContainer (HttpServer httpServer, NetworkListener networkListener, WebappContext webappContext, Map<String, Object> properties, boolean includeWsadlSupport, HttpHandler staticHttpHandler, WebSocketExtensionInstaller... webSocketExtensionInstallers) {

    super((Set<Class<?>>)null);

    final Map<String, Object> localProperties;

    this.networkListener = networkListener;
    this.webSocketExtensionInstallers = webSocketExtensionInstallers;

    // defensive copy
    if (properties == null) {
      localProperties = Collections.emptyMap();
    } else {
      localProperties = new HashMap<>(properties);
    }

    final Integer incomingBufferSize = Utils.getProperty(localProperties, TyrusWebSocketEngine.INCOMING_BUFFER_SIZE, Integer.class);
    final ClusterContext clusterContext = Utils.getProperty(localProperties, ClusterContext.CLUSTER_CONTEXT, ClusterContext.class);
    final Integer maxSessionsPerApp = Utils.getProperty(localProperties, TyrusWebSocketEngine.MAX_SESSIONS_PER_APP, Integer.class);
    final Integer maxSessionsPerRemoteAddr = Utils.getProperty(localProperties, TyrusWebSocketEngine.MAX_SESSIONS_PER_REMOTE_ADDR, Integer.class);
    final Boolean parallelBroadcastEnabled = Utils.getProperty(localProperties, TyrusWebSocketEngine.PARALLEL_BROADCAST_ENABLED, Boolean.class);
    final DebugContext.TracingType tracingType = Utils.getProperty(localProperties, TyrusWebSocketEngine.TRACING_TYPE, DebugContext.TracingType.class, DebugContext.TracingType.OFF);
    final DebugContext.TracingThreshold tracingThreshold = Utils.getProperty(localProperties, TyrusWebSocketEngine.TRACING_THRESHOLD, DebugContext.TracingThreshold.class, DebugContext.TracingThreshold.TRACE);

    final ApplicationEventListener applicationEventListener = Utils.getProperty(localProperties, ApplicationEventListener.APPLICATION_EVENT_LISTENER, ApplicationEventListener.class);

    engine = TyrusWebSocketEngine.builder(this)
               .incomingBufferSize(incomingBufferSize)
               .clusterContext(clusterContext)
               .applicationEventListener(applicationEventListener)
               .maxSessionsPerApp(maxSessionsPerApp)
               .maxSessionsPerRemoteAddr(maxSessionsPerRemoteAddr)
               .parallelBroadcastEnabled(parallelBroadcastEnabled)
               .tracingType(tracingType)
               .tracingThreshold(tracingThreshold)
               .build();

    // idle timeout set to indefinite.
    networkListener.getKeepAlive().setIdleTimeoutInSeconds(-1);
    networkListener.registerAddOn(new TyrusWebSocketAddOn(this, webappContext.getContextPath()));

    if (includeWsadlSupport) {
      httpServer.getServerConfiguration().addHttpHandler(new WsadlHttpHandler(engine, staticHttpHandler));
    }

    contextPath = webappContext.getContextPath();
    webappContext.setAttribute("jakarta.websocket.server.ServerContainer", this);
  }

  public void start ()
    throws IOException, DeploymentException {

    super.start(contextPath, getPort());
  }

  @Override
  public int getPort () {

    return ((networkListener != null) && (networkListener.getPort() > 0)) ? networkListener.getPort() : -1;
  }

  @Override
  public WebSocketEngine getWebSocketEngine () {

    return engine;
  }

  @Override
  public void register (Class<?> endpointClass)
    throws DeploymentException {

    engine.register(endpointClass, contextPath);
  }

  @Override
  public void register (ServerEndpointConfig serverEndpointConfig)
    throws DeploymentException {

    engine.register(mergeExtensions(serverEndpointConfig), contextPath);
  }

  private ServerEndpointConfig mergeExtensions (ServerEndpointConfig serverEndpointConfig) {

    if (webSocketExtensionInstallers != null) {
      for (WebSocketExtensionInstaller webSocketExtensionInstaller : webSocketExtensionInstallers) {
        if (webSocketExtensionInstaller.getEndpointClass().equals(serverEndpointConfig.getEndpointClass()) && webSocketExtensionInstaller.getPath().equals(serverEndpointConfig.getPath())) {

          LinkedList<Extension> addedExtensionList = new LinkedList<>(Arrays.asList(webSocketExtensionInstaller.getExtensions()));

          for (Extension extension : serverEndpointConfig.getExtensions()) {
            addedExtensionList.removeIf((addedExtension) -> addedExtension.getClass().equals(extension.getClass()) || ((addedExtension.getName() != null) && addedExtension.getName().equals(extension.getName())));
            if (addedExtensionList.isEmpty()) {
              break;
            }
          }

          if (!addedExtensionList.isEmpty()) {

            addedExtensionList.addAll(serverEndpointConfig.getExtensions());

            return ServerEndpointConfig.Builder.create(serverEndpointConfig.getEndpointClass(), serverEndpointConfig.getPath())
                     .configurator(serverEndpointConfig.getConfigurator())
                     .decoders(serverEndpointConfig.getDecoders())
                     .encoders(serverEndpointConfig.getEncoders())
                     .extensions(addedExtensionList)
                     .subprotocols(serverEndpointConfig.getSubprotocols()).build();
          }
        }
      }
    }

    return serverEndpointConfig;
  }
}

This code makes use of TyrusWebsocketAddon...

public class TyrusWebSocketAddOn implements AddOn {

  private final ServerContainer serverContainer;
  private final String contextPath;

  public TyrusWebSocketAddOn (ServerContainer serverContainer, String contextPath) {

    this.serverContainer = serverContainer;
    this.contextPath = contextPath;
  }

  @Override
  public void setup (NetworkListener networkListener, FilterChainBuilder filterChainBuilder) {

    int httpServerFilterIndex;

    if ((httpServerFilterIndex = filterChainBuilder.indexOfType(HttpServerFilter.class)) < 0) {
      throw new GrizzlyInitializationException("Missing http servlet filter in the available filter chain");
    } else {
      // Insert the WebSocketFilter right before HttpServerFilter
      filterChainBuilder.add(httpServerFilterIndex, new TyrusGrizzlyServerFilter(serverContainer, contextPath));
    }
  }
}

..and that uses TyrusGrizzlyServerFilter which we copy from the Tyrus project because it's not publicly available. Grizzly itself is started by code which can be configured via Spring, and the Spring context is initialized by Tanukisoft wrapper. So our system is completely code (Spring) configured, requires only maven dependencies, with no prior installations other than Java itself.

The characteristic we're looking for is a code-only configuration, preferable Spring amenable, and easily code extensible. Is there a better way to accomplish what we're doing? If so, I'm certainly open. If what we're doing is reasonable, it would be nice to have access to create instances of TyrusGrizzlyServerFilter at a minimum.

zenbones avatar Nov 07 '24 06:11 zenbones

@zenbones What Tyrus version are you on?

jansupol avatar Nov 07 '24 09:11 jansupol

2.1.5, but I can move to 2.2.0.

zenbones avatar Nov 08 '24 22:11 zenbones

Not also that were trying to carefully merge extensions that are in our configuration to be added, which may have been part of the original impetus to extend the TyrusServerContainer, as well as gaining control over the start so that we're starting Tyrus as opposed to Tyrus starting Grizzly, which does not work as we have other things going on with our Grizzly setup.

zenbones avatar Nov 08 '24 22:11 zenbones

Nothing?

zenbones avatar Jan 27 '25 00:01 zenbones

Ok, I'll put together a PR I guess, if I'm serious, and close this.

zenbones avatar Apr 10 '25 17:04 zenbones