All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.scalecube.services.gateway.websocket.WebsocketGateway Maven / Gradle / Ivy

The newest version!
package io.scalecube.services.gateway.websocket;

import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.scalecube.services.Address;
import io.scalecube.services.ServiceCall;
import io.scalecube.services.ServiceInfo;
import io.scalecube.services.exceptions.DefaultErrorMapper;
import io.scalecube.services.exceptions.ServiceProviderErrorMapper;
import io.scalecube.services.gateway.Gateway;
import io.scalecube.services.gateway.GatewaySessionHandler;
import io.scalecube.services.registry.api.ServiceRegistry;
import io.scalecube.services.transport.api.ServiceMessageDataDecoder;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.function.Function;
import reactor.netty.Connection;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import reactor.netty.resources.LoopResources;

public class WebsocketGateway implements Gateway {

  private final String id;
  private final int port;
  private final Function callFactory;
  private final GatewaySessionHandler gatewayHandler;
  private final Duration keepAliveInterval;
  private final boolean heartbeatEnabled;
  private final ServiceProviderErrorMapper errorMapper;

  private DisposableServer server;
  private LoopResources loopResources;

  private WebsocketGateway(Builder builder) {
    this.id = builder.id;
    this.port = builder.port;
    this.callFactory = builder.callFactory;
    this.gatewayHandler = builder.gatewayHandler;
    this.keepAliveInterval = builder.keepAliveInterval;
    this.heartbeatEnabled = builder.heartbeatEnabled;
    this.errorMapper = builder.errorMapper;
  }

  public static Builder builder() {
    return new Builder();
  }

  @Override
  public String id() {
    return id;
  }

  @Override
  public Gateway start(ServiceCall call, ServiceRegistry serviceRegistry) {
    loopResources =
        LoopResources.create(id + ":" + port, LoopResources.DEFAULT_IO_WORKER_COUNT, true);

    if (heartbeatEnabled) {
      serviceRegistry.registerService(
          ServiceInfo.fromServiceInstance(new HeartbeatServiceImpl())
              .errorMapper(DefaultErrorMapper.INSTANCE)
              .dataDecoder(ServiceMessageDataDecoder.INSTANCE)
              .build());
    }

    try {
      server =
          HttpServer.create()
              .runOn(loopResources)
              .bindAddress(() -> new InetSocketAddress(port))
              .doOnConnection(this::setupKeepAlive)
              .handle(
                  new WebsocketGatewayAcceptor(
                      callFactory.apply(call), gatewayHandler, errorMapper))
              .bind()
              .toFuture()
              .get();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }

    return this;
  }

  @Override
  public Address address() {
    InetSocketAddress address = (InetSocketAddress) server.address();
    return Address.create(address.getHostString(), address.getPort());
  }

  @Override
  public void stop() {
    shutdownServer(server);
    shutdownLoopResources(loopResources);
  }

  private void shutdownServer(DisposableServer server) {
    if (server != null) {
      server.dispose();
    }
  }

  private void shutdownLoopResources(LoopResources loopResources) {
    if (loopResources != null) {
      loopResources.dispose();
    }
  }

  private void setupKeepAlive(Connection connection) {
    if (keepAliveInterval != Duration.ZERO) {
      connection
          .onReadIdle(keepAliveInterval.toMillis(), () -> onReadIdle(connection))
          .onWriteIdle(keepAliveInterval.toMillis(), () -> onWriteIdle(connection));
    }
  }

  private void onWriteIdle(Connection connection) {
    connection
        .outbound()
        .sendObject(new PingWebSocketFrame())
        .then()
        .subscribe(
            null,
            ex -> {
              // no-op
            });
  }

  private void onReadIdle(Connection connection) {
    connection
        .outbound()
        .sendObject(new PingWebSocketFrame())
        .then()
        .subscribe(
            null,
            ex -> {
              // no-op
            });
  }

  public static class Builder {

    private String id = "websocket@" + Integer.toHexString(hashCode());
    private int port;
    private Function callFactory = call -> call;
    private GatewaySessionHandler gatewayHandler = GatewaySessionHandler.DEFAULT_INSTANCE;
    private Duration keepAliveInterval = Duration.ZERO;
    private boolean heartbeatEnabled = false;
    private ServiceProviderErrorMapper errorMapper = DefaultErrorMapper.INSTANCE;

    private Builder() {}

    public String id() {
      return id;
    }

    public Builder id(String id) {
      this.id = id;
      return this;
    }

    public int port() {
      return port;
    }

    public Builder port(int port) {
      this.port = port;
      return this;
    }

    public Builder serviceCall(Function operator) {
      callFactory = callFactory.andThen(operator);
      return this;
    }

    public GatewaySessionHandler gatewayHandler() {
      return gatewayHandler;
    }

    public Builder gatewayHandler(GatewaySessionHandler gatewayHandler) {
      this.gatewayHandler = gatewayHandler;
      return this;
    }

    public Duration keepAliveInterval() {
      return keepAliveInterval;
    }

    public Builder keepAliveInterval(Duration keepAliveInterval) {
      this.keepAliveInterval = keepAliveInterval;
      return this;
    }

    public boolean heartbeatEnabled() {
      return heartbeatEnabled;
    }

    public Builder heartbeatEnabled(boolean heartbeatEnabled) {
      this.heartbeatEnabled = heartbeatEnabled;
      return this;
    }

    public ServiceProviderErrorMapper errorMapper() {
      return errorMapper;
    }

    public Builder errorMapper(ServiceProviderErrorMapper errorMapper) {
      this.errorMapper = errorMapper;
      return this;
    }

    public WebsocketGateway build() {
      return new WebsocketGateway(this);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy