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

Tyrus is hard to start programmatically #915

Open
zenbones opened this issue Oct 28, 2024 · 5 comments
Open

Tyrus is hard to start programmatically #915

zenbones opened this issue Oct 28, 2024 · 5 comments

Comments

@zenbones
Copy link

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.

@jansupol
Copy link
Contributor

jansupol commented Nov 6, 2024

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?

@zenbones
Copy link
Author

zenbones commented Nov 7, 2024

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.

@jansupol
Copy link
Contributor

jansupol commented Nov 7, 2024

@zenbones What Tyrus version are you on?

@zenbones
Copy link
Author

zenbones commented Nov 8, 2024

2.1.5, but I can move to 2.2.0.

@zenbones
Copy link
Author

zenbones commented Nov 8, 2024

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.

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

2 participants