grpcvert.x

How to run gRPC and HTTP server on the same port in Vert.x without port conflict?


I'm trying to run both a gRPC server and an HTTP server on the same port using Vert.x, but I'm encountering a port conflict error. Is there a way to resolve this issue and have both servers operate on the same port? Any guidance would be appreciated.

I am building a gRPC server using Vert.x. In my architecture, I plan to place a load balancer (LB) in front of the Vert.x server. The LB needs to perform health checks on the backend server using the HTTP protocol.

Therefore, I attempted to modify the code to have the gRPC server launched with VertxServerBuilder and have an HTTP server listen on the same port.

val vertxVersion = "4.5.11"
val grpcVersion = "1.58.0"
val protobufVersion = "3.21.6"
val protocVersion = protobufVersion

dependencies {
    implementation("io.vertx:vertx-grpc:$vertxVersion")
    implementation("io.vertx:vertx-grpc-server:$vertxVersion")
    implementation("io.vertx:vertx-grpc-client:$vertxVersion")
    implementation("io.grpc:grpc-protobuf:$grpcVersion")
    implementation("io.grpc:grpc-stub:$grpcVersion")
    implementation("io.grpc:grpc-services:$grpcVersion")
    implementation("com.google.protobuf:protobuf-java-util:$protobufVersion")
private static final String ANY_ADDRESS = "0.0.0.0";
private static final int GRPC_SERVER_PORT = 50051;
private static final int HTTP_SERVER_PORT = 50051;

private VertxServer grpcServer;

@Autowired
private HBaseStoreManager hbaseStoreManager;

@Autowired
private HealthStatusManager healthStatusManager;

@Override
public void start(Promise<Void> startPromise) throws Exception {
    log.info("Starting gRPC Server...");

    // Interceptor
    TraceIdContextInterceptor traceIdContextInterceptor = new TraceIdContextInterceptor();

    // gRPC Service
    HbaseServiceHandler hbaseService = HbaseServiceHandler.of(this.hbaseStoreManager);

    grpcServer = VertxServerBuilder
        .forAddress(vertx, new InetSocketAddress(ANY_ADDRESS, GRPC_SERVER_PORT))
        .intercept(traceIdContextInterceptor)
        .addService(hbaseService)
        .addService(healthStatusManager.getHealthService())
        .handshakeTimeout(Defaults.handshakeTimeout, TimeUnit.SECONDS)
        .maxInboundMessageSize(Defaults.maxMessageSize)
        .maxInboundMetadataSize(Defaults.maxMetadataSize)
        .build();

    grpcServer.start(asyncResult -> {
      if (asyncResult.succeeded()) {
        log.info("Listening Native gRPC on {}:{}", ANY_ADDRESS, GRPC_SERVER_PORT);
        startPromise.complete();
      } else {
        log.info("Failed to start gRPC server: " + asyncResult.cause().getMessage());
        startPromise.fail(asyncResult.cause());
      }
    });

    // HTTP server options
    HttpServerOptions serverOptions = new HttpServerOptions()
        .setUseAlpn(true)
        .setSsl(false);

    // HTTP Server (for ingress health check)
    vertx.createHttpServer(serverOptions)
        .requestHandler(
            req -> {
              if (req.uri().equals("/monitor/healthz")) {
                req.response()
                    .putHeader("content-type", "text/plain")
                    .end("OK");
              } else {
                req.response().setStatusCode(404).end();
              }
            })
        .listen(HTTP_SERVER_PORT, ar -> {
          if (ar.succeeded()) {
            log.info("HTTP server started on port {}", HTTP_SERVER_PORT);
          } else {
            log.error("Failed to start HTTP server: " + ar.cause().getMessage());
          }
        });
}

When I run the modified code, I encounter a port conflict error:

Caused by: java.net.BindException: Address already in use

Is it not possible to serve both HTTP and gRPC on the same port in Vert.x? If there is a solution, I would appreciate your help.


Solution

    // Interceptor
    TraceIdContextInterceptor traceIdContextInterceptor = new TraceIdContextInterceptor();

    // gRPC Service
    ServerServiceDefinition serviceDefinition = HbaseServiceGrpc
        .bindService(HbaseServiceHandler.of(this.hbaseStoreManager));

    ServerServiceDefinition defWithInterceptors = ServerInterceptors.intercept(serviceDefinition, traceIdContextInterceptor);
    GrpcServiceBridge serverStub = GrpcServiceBridge.bridge(defWithInterceptors);
    GrpcServer grpcServer = GrpcServer.server(vertx, new GrpcServerOptions());
    serverStub.bind(grpcServer);

    Router router = Router.router(vertx);
    router.route()
        .consumes("application/grpc")
        .handler(routingContext -> {
          grpcServer.handle(routingContext.request());
        });

    router.get("/monitor/healthz").handler(routingContext -> {
      routingContext.response()
          .putHeader("content-type", "text/plain")
          .end("OK");
    });

    vertx.createHttpServer()
        .requestHandler(router)
        .listen(GRPC_SERVER_PORT, ar -> {
          if (ar.succeeded()) {
            log.info("HTTP server started on port {}", GRPC_SERVER_PORT);
          } else {
            log.error("Failed to start HTTP server: " + ar.cause().getMessage());
          }
        });

Solution

  • I think the problem is that you start the grpcServer instead of using it to delegate gRPC requests.

    With the help of Vert.x Web router, you could setup an HTTP server like this:

    Router router = Router.router(vertx);
    router.route()
      .consumes("application/grpc") 
      // Hand over all gRPC requests to gRPC server
      .handler(rc -> grpcServer.handle(rc.request()));
    
    // Set any other Web route as needed
    router.get("/monitor/healthz").handler(rc-> {
      rc.response()
        .putHeader("content-type", "text/plain")
        .end("OK"); 
    });
    
    return vertx.createHttpServer()
      .requestHandler(router)
      .listen(8080);