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

grpcstarter.server.DefaultGrpcServer Maven / Gradle / Ivy

package grpcstarter.server;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.grpc.BindableService;
import io.grpc.Grpc;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.ServerInterceptor;
import io.grpc.TlsServerCredentials;
import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.internal.GrpcUtil;
import jakarta.annotation.Nullable;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.unit.DataSize;

/**
 * gRPC server.
 *
 * @author Freeman
 */
public class DefaultGrpcServer implements GrpcServer, ApplicationEventPublisherAware {

    private static final Logger log = LoggerFactory.getLogger(DefaultGrpcServer.class);

    private final Server server;
    private final AtomicBoolean isRunning = new AtomicBoolean(false);
    private final CountDownLatch latch = new CountDownLatch(1);
    private final GrpcServerProperties properties;

    private ApplicationEventPublisher publisher;

    @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
    public DefaultGrpcServer(
            GrpcServerProperties properties,
            ObjectProvider> serverBuilder,
            ObjectProvider serviceProvider,
            ObjectProvider interceptorProvider,
            ObjectProvider customizers) {
        this.properties = properties;
        this.server = buildGrpcServer(properties, serverBuilder, serviceProvider, interceptorProvider, customizers);
    }

    private static Server buildGrpcServer(
            GrpcServerProperties properties,
            ObjectProvider> serverBuilder,
            ObjectProvider serviceProvider,
            ObjectProvider interceptorProvider,
            ObjectProvider customizers) {
        ServerBuilder builder = serverBuilder.getIfUnique(() -> getDefaultServerBuilder(properties));

        // add services
        serviceProvider.forEach(builder::addService);

        // add interceptors, gRPC applies interceptors in reversed order
        interceptorProvider.stream()
                .sorted(AnnotationAwareOrderComparator.INSTANCE.reversed())
                .forEach(builder::intercept);

        Optional.ofNullable(properties.getMaxInboundMessageSize())
                .map(DataSize::toBytes)
                .map(Long::intValue)
                .ifPresent(builder::maxInboundMessageSize);
        Optional.ofNullable(properties.getMaxInboundMetadataSize())
                .map(DataSize::toBytes)
                .map(Long::intValue)
                .ifPresent(builder::maxInboundMetadataSize);

        // apply customizers
        customizers.orderedStream().forEach(customizer -> customizer.customize(builder));

        return builder.build();
    }

    @SneakyThrows
    private static ServerBuilder> getDefaultServerBuilder(GrpcServerProperties properties) {
        if (properties.getInProcess() != null) {
            Assert.hasText(properties.getInProcess().getName(), "In-process server name must not be empty");
            return InProcessServerBuilder.forName(properties.getInProcess().getName())
                    .directExecutor();
        }
        int port = Math.max(properties.getPort(), 0);
        GrpcServerProperties.Tls tls = properties.getTls();
        if (tls == null) {
            return ServerBuilder.forPort(port);
        }
        TlsServerCredentials.Builder tlsBuilder = TlsServerCredentials.newBuilder();
        GrpcServerProperties.Tls.KeyManager keyManager = tls.getKeyManager();
        if (keyManager != null) {
            if (StringUtils.hasText(keyManager.getPrivateKeyPassword())) {
                tlsBuilder.keyManager(
                        keyManager.getCertChain().getInputStream(),
                        keyManager.getPrivateKey().getInputStream(),
                        keyManager.getPrivateKeyPassword());
            } else {
                tlsBuilder.keyManager(
                        keyManager.getCertChain().getInputStream(),
                        keyManager.getPrivateKey().getInputStream());
            }
        }
        GrpcServerProperties.Tls.TrustManager trustManager = tls.getTrustManager();
        if (trustManager != null) {
            tlsBuilder.trustManager(trustManager.getRootCerts().getInputStream());
        }
        return Grpc.newServerBuilderForPort(port, tlsBuilder.build());
    }

    @Override
    public void start() {
        if (isRunning()) {
            return;
        }
        try {
            server.start();
            isRunning.set(true);
            if (log.isInfoEnabled()) {
                if (properties.getInProcess() != null
                        && StringUtils.hasText(properties.getInProcess().getName())) {
                    log.info(
                            "gRPC in-process server started: {}",
                            properties.getInProcess().getName());
                } else {
                    log.info("gRPC server started on port: {} ({})", server.getPort(), GrpcUtil.getGrpcBuildVersion());
                }
            }

            publisher.publishEvent(new GrpcServerStartedEvent(server));

            waitUntilShutdown();
        } catch (IOException e) {
            gracefulShutdown();
            throw new IllegalStateException(e);
        }
    }

    @Override
    public int getPort() {
        return server.getPort();
    }

    @Nullable
    @Override
    public Object getServer() {
        return server;
    }

    @Override
    public void stop() {
        if (isRunning.get()) {
            gracefulShutdown();
            isRunning.set(false);
            latch.countDown();
        }
    }

    @Override
    public boolean isRunning() {
        return isRunning.get();
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    private void waitUntilShutdown() {
        new Thread(
                        () -> {
                            try {
                                // wait here until terminating
                                latch.await();
                            } catch (InterruptedException e) {
                                log.warn("gRPC server await termination interrupted", e);
                                Thread.currentThread().interrupt();
                            }
                        },
                        "grpc-termination-awaiter")
                .start();
    }

    private void gracefulShutdown() {
        long start = System.currentTimeMillis();

        // stop accepting new calls
        server.shutdown();

        // publish shutdown event, user can listen to the event to complete the StreamObserver manually
        publisher.publishEvent(new GrpcServerShutdownEvent(server));

        try {
            long time = properties.getShutdownTimeout();
            if (time > 0L) {
                server.awaitTermination(time, TimeUnit.MILLISECONDS);
            } else {
                server.awaitTermination();
            }
        } catch (InterruptedException e) {
            log.warn("gRPC server graceful shutdown interrupted", e);
            Thread.currentThread().interrupt();
        }
        if (!server.isTerminated()) {
            server.shutdownNow();
        }

        publisher.publishEvent(new GrpcServerTerminatedEvent(server));

        if (log.isInfoEnabled()) {
            log.info("gRPC server graceful shutdown in {} ms", System.currentTimeMillis() - start);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy