diff --git a/microprofile/grpc/server/pom.xml b/microprofile/grpc/server/pom.xml index ebf165bcf89..2d9a63013e4 100644 --- a/microprofile/grpc/server/pom.xml +++ b/microprofile/grpc/server/pom.xml @@ -38,10 +38,18 @@ io.helidon.webserver helidon-webserver-grpc + + io.helidon.webserver + helidon-webserver-http2 + io.helidon.common helidon-common + + io.helidon.config + helidon-config-yaml-mp + io.helidon.config helidon-config-object-mapping @@ -63,6 +71,14 @@ io.helidon.microprofile.server helidon-microprofile-server + + io.helidon.logging + helidon-logging-common + + + io.helidon.logging + helidon-logging-jul + io.grpc grpc-inprocess @@ -76,11 +92,6 @@ org.slf4j slf4j-jdk14 - - io.helidon.microprofile.testing - helidon-microprofile-testing-junit5 - test - org.junit.jupiter junit-jupiter-api @@ -112,6 +123,16 @@ provided true + + io.helidon.microprofile.testing + helidon-microprofile-testing-junit5 + test + + + io.helidon.webserver.testing.junit5 + helidon-webserver-testing-junit5-grpc + test + diff --git a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/ConstantHealthCheck.java b/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/ConstantHealthCheck.java deleted file mode 100644 index fd7a7ea79fd..00000000000 --- a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/ConstantHealthCheck.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2019, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.microprofile.grpc.server; - -import org.eclipse.microprofile.health.HealthCheck; -import org.eclipse.microprofile.health.HealthCheckResponse; - -/** - * A simple {@link HealthCheck} implementation - * that always returns the same response. - */ -public class ConstantHealthCheck implements HealthCheck { - - private final HealthCheckResponse response; - - private ConstantHealthCheck(HealthCheckResponse response) { - this.response = response; - } - - @Override - public HealthCheckResponse call() { - return response; - } - - /** - * Obtain a {@link HealthCheck} that always returns a status of up. - * - * @param name the service name that the health check is for - * @return a {@link HealthCheck} that always returns a status of up - */ - public static HealthCheck up(String name) { - return new ConstantHealthCheck(HealthCheckResponse.named(name).up().build()); - } - - /** - * Obtain a {@link HealthCheck} that always returns a status of down. - * - * @param name the service name that the health check is for - * @return a {@link HealthCheck} that always returns a status of down - */ - public static HealthCheck down(String name) { - return new ConstantHealthCheck(HealthCheckResponse.named(name).down().build()); - } -} diff --git a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcMpCdiExtension.java b/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcMpCdiExtension.java new file mode 100644 index 00000000000..fdb8a0ff927 --- /dev/null +++ b/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcMpCdiExtension.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2019, 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.microprofile.grpc.server; + +import java.lang.annotation.Annotation; +import java.util.logging.Level; +import java.util.logging.Logger; + +import io.grpc.BindableService; +import io.helidon.microprofile.grpc.core.Grpc; +import io.helidon.microprofile.server.ServerCdiExtension; +import io.helidon.webserver.grpc.GrpcRouting; +import io.helidon.webserver.grpc.GrpcService; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Any; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.Extension; + +/** + * A CDI extension that will discover and register gRPC routes. + */ +public class GrpcMpCdiExtension implements Extension { + + private static final Logger LOGGER = Logger.getLogger(GrpcMpCdiExtension.class.getName()); + private static final Logger STARTUP_LOGGER = Logger.getLogger("io.helidon.microprofile.startup.server"); + + private void discoverRoutes(@Observes @Initialized(ApplicationScoped.class) Object event, BeanManager beanManager) { + GrpcRouting.Builder routingBuilder = discoverGrpcRouting(beanManager); + ServerCdiExtension extension = beanManager.getExtension(ServerCdiExtension.class); + extension.addRouting(routingBuilder); + } + + /** + * Discover the services and interceptors to use to configure the {@link GrpcRouting}. + * + * @param beanManager the CDI bean manager + * @return the {@link GrpcRouting} to use or {@code null} if no services + * or routing were discovered + */ + private GrpcRouting.Builder discoverGrpcRouting(BeanManager beanManager) { + Instance instance = beanManager.createInstance(); + GrpcRouting.Builder builder = GrpcRouting.builder(); + + // discover @Grpc annotated beans + // we use the bean manager to do this as we need the actual bean class + beanManager.getBeans(Object.class, Any.Literal.INSTANCE) + .stream() + .filter(this::hasGrpcQualifier) + .forEach(bean -> { + Class beanClass = bean.getBeanClass(); + Annotation[] qualifiers = bean.getQualifiers().toArray(new Annotation[0]); + Object service = instance.select(beanClass, qualifiers).get(); + register(service, builder, beanClass, beanManager); + }); + + // discover beans of type GrpcService + beanManager.getBeans(GrpcService.class) + .forEach(bean -> { + Class beanClass = bean.getBeanClass(); + Annotation[] qualifiers = bean.getQualifiers().toArray(new Annotation[0]); + Object service = instance.select(beanClass, qualifiers).get(); + builder.service((GrpcService) service); + }); + + // discover beans of type BindableService + beanManager.getBeans(BindableService.class) + .forEach(bean -> { + Class beanClass = bean.getBeanClass(); + Annotation[] qualifiers = bean.getQualifiers().toArray(new Annotation[0]); + Object service = instance.select(beanClass, qualifiers).get(); + builder.service((BindableService) service); + }); + + return builder; + } + + private boolean hasGrpcQualifier(Bean bean) { + return bean.getQualifiers() + .stream() + .anyMatch(q -> Grpc.class.isAssignableFrom(q.annotationType())); + } + + /** + * Register the service with the routing. + *

+ * The service is actually a CDI proxy so the real service. + * + * @param service the service to register + * @param builder the gRPC routing + * @param beanManager the {@link BeanManager} to use to locate beans required by the service + */ + private void register(Object service, GrpcRouting.Builder builder, Class cls, BeanManager beanManager) { + GrpcServiceBuilder serviceBuilder = GrpcServiceBuilder.create(cls, () -> service, beanManager); + if (serviceBuilder.isAnnotatedService()) { + builder.service(serviceBuilder.build()); + } else { + LOGGER.log(Level.WARNING, + () -> "Discovered type is not a properly annotated gRPC service " + service.getClass()); + } + } +} diff --git a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServer.java b/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServer.java deleted file mode 100644 index d3188d71342..00000000000 --- a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServer.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright (c) 2019, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.microprofile.grpc.server; - -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; - -import io.helidon.common.context.Context; -import io.helidon.grpc.core.WeightedBag; -import io.helidon.webserver.grpc.GrpcRouting; -import io.helidon.webserver.grpc.ServiceDescriptor; - -import io.grpc.ServerInterceptor; -import org.eclipse.microprofile.health.HealthCheck; - -/** - * Represents a immutably configured gRPC server. - *

- * Provides a basic lifecycle and monitoring API. - *

- * Instance can be created from {@link GrpcRouting} and optionally from {@link - * GrpcServerConfiguration} using {@link #create(GrpcRouting)}, {@link - * #create(GrpcServerConfiguration, GrpcRouting)} or {@link #builder(GrpcRouting)} methods - * and their builder enabled overloads. - */ -public interface GrpcServer { - /** - * Gets effective server configuration. - * - * @return Server configuration - */ - GrpcServerConfiguration configuration(); - - /** - * Gets a {@link GrpcServer} context. - * - * @return a server context - */ - Context context(); - - /** - * Starts the server. Has no effect if server is running. - * - * @return a completion stage of starting process - */ - CompletionStage start(); - - /** - * Completion stage is completed when server is shut down. - * - * @return a completion stage of the server - */ - CompletionStage whenShutdown(); - - /** - * Attempt to gracefully shutdown server. It is possible to use returned - * {@link CompletionStage} to react. - *

- * RequestMethod can be called periodically. - * - * @return to react on finished shutdown process - * @see #start() - */ - CompletionStage shutdown(); - - /** - * Return an array of health checks for this server. - * - * @return an array of {@link HealthCheck} instances for this server - */ - HealthCheck[] healthChecks(); - - /** - * Obtain the deployed services. - * - * @return an immutable {@link Map} of deployed {@link ServiceDescriptor}s - * keyed by service name - */ - Map services(); - - /** - * Returns {@code true} if the server is currently running. A running server - * in the stopping phase returns {@code true} until it is fully stopped. - * - * @return {@code true} if server is running - */ - boolean isRunning(); - - /** - * Returns a port number the default server socket is bound to and is - * listening on; or {@code -1} if unknown or not active. - *

- * Only supported only when server is running. - * - * @return a listen port; or {@code -1} if unknown or the default server - * socket is not active - */ - int port(); - - /** - * Creates a new instance from a provided configuration and a GrpcRouting. - * - * @param configurationBuilder a server configuration builder that will be - * built as a first step of this method - * execution; may be {@code null} - * @param routing a GrpcRouting instance - * @return a new gRPC server instance - * @throws IllegalStateException if none SPI implementation found - * @throws NullPointerException if 'GrpcRouting' parameter is {@code null} - */ - static GrpcServer create(Supplier configurationBuilder, GrpcRouting routing) { - return create(configurationBuilder != null - ? configurationBuilder.get() - : null, routing); - } - - /** - * Creates new instance form provided configuration and GrpcRouting. - * - * @param configurationBuilder a server configuration builder that will be - * built as a first step of this method - * execution; may be {@code null} - * @param routingBuilder a GrpcRouting builder that will be built as a - * second step of this method execution - * @return a new gRPC server instance - * @throws IllegalStateException if none SPI implementation found - * @throws NullPointerException if 'routingBuilder' parameter is {@code - * null} - */ - static GrpcServer create(Supplier configurationBuilder, - Supplier routingBuilder) { - Objects.requireNonNull(routingBuilder, "Parameter 'routingBuilder' must not be null!"); - return create(configurationBuilder != null - ? configurationBuilder.get() - : null, routingBuilder.get()); - } - - /** - * Creates new instance form provided configuration and GrpcRouting. - * - * @param configuration a server configuration instance - * @param routingBuilder a GrpcRouting builder that will be built as a second - * step of this method execution - * @return a new gRPC server instance - * @throws IllegalStateException if none SPI implementation found - * @throws NullPointerException if 'routingBuilder' parameter is {@code - * null} - */ - static GrpcServer create( - GrpcServerConfiguration configuration, - Supplier routingBuilder) { - Objects.requireNonNull(routingBuilder, "Parameter 'routingBuilder' must not be null!"); - return create(configuration, routingBuilder.get()); - } - - /** - * Creates new instance form provided GrpcRouting and default configuration. - * - * @param routing a GrpcRouting instance - * @return a new gRPC server instance - * @throws IllegalStateException if none SPI implementation found - * @throws NullPointerException if 'routing' parameter is {@code null} - */ - static GrpcServer create(GrpcRouting routing) { - return create((GrpcServerConfiguration) null, routing); - } - - /** - * Creates new instance form provided configuration and GrpcRouting. - * - * @param configuration a server configuration instance - * @param routing a GrpcRouting instance - * @return a new gRPC server instance - * @throws IllegalStateException if none SPI implementation found - * @throws NullPointerException if 'GrpcRouting' parameter is {@code null} - */ - static GrpcServer create(GrpcServerConfiguration configuration, GrpcRouting routing) { - Objects.requireNonNull(routing, "Parameter 'routing' is null!"); - - return builder(routing) - .config(configuration) - .build(); - } - - /** - * Creates new instance form provided GrpcRouting and default configuration. - * - * @param routingBuilder a GrpcRouting builder instance that will be built as a - * first step of this method execution - * @return a new gRPC server instance - * @throws IllegalStateException if none SPI implementation found - * @throws NullPointerException if 'GrpcRouting' parameter is {@code null} - */ - static GrpcServer create(Supplier routingBuilder) { - Objects.requireNonNull(routingBuilder, "Parameter 'routingBuilder' must not be null!"); - return create(routingBuilder.get()); - } - - /** - * Creates a builder of the {@link GrpcServer}. - * - * @param routingBuilder the GrpcRouting builder; must not be {@code null} - * @return the builder - */ - static Builder builder(Supplier routingBuilder) { - Objects.requireNonNull(routingBuilder, "Parameter 'routingBuilder' must not be null!"); - return builder(routingBuilder.get()); - } - - /** - * Creates a builder of the {@link GrpcServer}. - * - * @param routing the GrpcRouting; must not be {@code null} - * @return the builder - */ - static Builder builder(GrpcRouting routing) { - return new Builder(GrpcServerConfiguration.create(), routing); - } - - /** - * GrpcServer builder class provides a convenient way to timed a - * GrpcServer instance. - */ - final class Builder implements io.helidon.common.Builder { - - private final GrpcRouting routing; - - private GrpcServerConfiguration configuration; - - private Builder(GrpcServerConfiguration configuration, GrpcRouting routing) { - Objects.requireNonNull(configuration, "Parameter 'configuration' must not be null!"); - Objects.requireNonNull(routing, "Parameter 'routing' must not be null!"); - - this.configuration = configuration; - this.routing = routing; - } - - /** - * Set a configuration of the {@link GrpcServer}. - * - * @param configuration the configuration - * @return an updated builder - */ - public Builder config(GrpcServerConfiguration configuration) { - this.configuration = configuration != null ? configuration : GrpcServerConfiguration.create(); - return this; - } - - /** - * Set a configuration of the {@link GrpcServer}. - * - * @param configurationBuilder the configuration builder - * @return an updated builder - */ - public Builder config(Supplier configurationBuilder) { - this.configuration = configurationBuilder != null - ? configurationBuilder.get() - : GrpcServerConfiguration.create(); - return this; - } - - /** - * Builds the {@link GrpcServer} instance as configured by this builder - * and its parameters. - * - * @return a ready to use {@link GrpcServer} - */ - @Override - public GrpcServer build() { - WeightedBag interceptors = WeightedBag.create(); - GrpcServerImpl server = GrpcServerImpl.create(configuration); - - interceptors.add(ContextSettingServerInterceptor.create()); - - // add the global interceptors from the routing AFTER the tracing interceptor - // so that all of those interceptors are included in the trace timings - interceptors.merge(routing.interceptors()); - - for (ServiceDescriptor service : routing.services()) { - server.deploy(service, interceptors); - } - - return server; - } - } -} diff --git a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServerBasicConfig.java b/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServerBasicConfig.java deleted file mode 100644 index 798ef4679a9..00000000000 --- a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServerBasicConfig.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2019, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.microprofile.grpc.server; - -import java.time.Duration; - -import io.helidon.common.context.Context; - -/** - * Configuration class for the {@link GrpcServer} implementations. - */ -public class GrpcServerBasicConfig - implements GrpcServerConfiguration { - - private final String name; - - private final int port; - - private final boolean nativeTransport; - - private final int workers; - - private final GrpcTlsDescriptor tlsConfig; - - private final Context context; - - private final int maxRapidResets; - - private final Duration rapidResetCheckPeriod; - - /** - * Construct {@link GrpcServerBasicConfig} instance. - * - * @param builder the {@link GrpcServerConfiguration.Builder} to use to configure - * this {@link GrpcServerBasicConfig}. - */ - private GrpcServerBasicConfig(GrpcServerConfiguration.Builder builder) { - this.name = builder.name(); - this.port = builder.port(); - this.context = builder.context(); - this.nativeTransport = builder.useNativeTransport(); - this.workers = builder.workers(); - this.tlsConfig = builder.tlsConfig(); - this.maxRapidResets = builder.maxRapidResets(); - this.rapidResetCheckPeriod = builder.rapidResetCheckPeriod(); - } - - /** - * Create a {@link GrpcServerBasicConfig} instance using the specified builder. - * - * @param builder the {@link GrpcServerConfiguration.Builder} to use to configure - * this {@link GrpcServerBasicConfig} - * @return a {@link GrpcServerBasicConfig} instance - */ - static GrpcServerBasicConfig create(GrpcServerConfiguration.Builder builder) { - return new GrpcServerBasicConfig(builder); - } - - // ---- accessors --------------------------------------------------- - - /** - * Get the server name. - * - * @return the server name - */ - @Override - public String name() { - return name; - } - - /** - * Get the server port. - * - * @return the server port - */ - @Override - public int port() { - return port; - } - - @Override - public Context context() { - return context; - } - - /** - * Determine whether use native transport if possible. - *

- * If native transport support is enabled, gRPC server will use epoll on - * Linux, or kqueue on OS X. Otherwise, the standard NIO transport will - * be used. - * - * @return {@code true} if native transport should be used - */ - @Override - public boolean useNativeTransport() { - return nativeTransport; - } - - @Override - public int workers() { - return workers; - } - - @Override - public GrpcTlsDescriptor tlsConfig() { - return tlsConfig; - } - - @Override - public Duration rapidResetCheckPeriod() { - return rapidResetCheckPeriod; - } - - @Override - public int maxRapidResets() { - return maxRapidResets; - } -} diff --git a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServerCdiExtension.java b/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServerCdiExtension.java deleted file mode 100644 index 56b0713bda4..00000000000 --- a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServerCdiExtension.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (c) 2019, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.microprofile.grpc.server; - -import java.lang.annotation.Annotation; -import java.util.ServiceLoader; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.logging.Logger; - -import io.helidon.common.HelidonServiceLoader; -import io.helidon.config.Config; -import io.helidon.config.mp.MpConfig; -import io.helidon.microprofile.grpc.core.Grpc; -import io.helidon.microprofile.grpc.core.InProcessGrpcChannel; -import io.helidon.microprofile.grpc.server.spi.GrpcMpContext; -import io.helidon.microprofile.grpc.server.spi.GrpcMpExtension; -import io.helidon.webserver.grpc.GrpcRouting; -import io.helidon.webserver.grpc.GrpcService; - -import io.grpc.BindableService; -import io.grpc.Channel; -import io.grpc.inprocess.InProcessChannelBuilder; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.Initialized; -import jakarta.enterprise.event.Observes; -import jakarta.enterprise.inject.Any; -import jakarta.enterprise.inject.Instance; -import jakarta.enterprise.inject.Produces; -import jakarta.enterprise.inject.spi.AfterDeploymentValidation; -import jakarta.enterprise.inject.spi.Bean; -import jakarta.enterprise.inject.spi.BeanManager; -import jakarta.enterprise.inject.spi.BeforeShutdown; -import jakarta.enterprise.inject.spi.Extension; -import org.eclipse.microprofile.config.ConfigProvider; - -/** - * A CDI extension that will start the {@link GrpcServer gRPC server}. - *

- * The server is started when the {@link AfterDeploymentValidation} event - * is received and will be stopped when the {@link BeforeShutdown} event - * is received. - *

- * If no gRPC services are discovered the gRPC server will not be started. - */ -public class GrpcServerCdiExtension implements Extension { - - private static final Logger LOGGER = Logger.getLogger(GrpcServerCdiExtension.class.getName()); - private static final Logger STARTUP_LOGGER = Logger.getLogger("io.helidon.microprofile.startup.server"); - - private GrpcServer server; - - - private void startServer(@Observes @Initialized(ApplicationScoped.class) Object event, BeanManager beanManager) { - GrpcRouting.Builder routingBuilder = discoverGrpcRouting(beanManager); - - Config config = MpConfig.toHelidonConfig(ConfigProvider.getConfig()); - GrpcServerConfiguration.Builder serverConfiguration = GrpcServerConfiguration.builder(config.get("grpc")); - CompletableFuture startedFuture = new CompletableFuture<>(); - CompletableFuture shutdownFuture = new CompletableFuture<>(); - - loadExtensions(beanManager, config, routingBuilder, serverConfiguration, startedFuture, shutdownFuture); - server = GrpcServer.create(serverConfiguration.build(), routingBuilder.build()); - long beforeT = System.nanoTime(); - - server.start() - .whenComplete((grpcServer, throwable) -> { - if (null != throwable) { - STARTUP_LOGGER.log(Level.SEVERE, throwable, () -> "gRPC server startup failed"); - startedFuture.completeExceptionally(throwable); - } else { - long t = TimeUnit.MILLISECONDS.convert(System.nanoTime() - beforeT, TimeUnit.NANOSECONDS); - - int port = grpcServer.port(); - STARTUP_LOGGER.finest("gRPC server started up"); - LOGGER.info(() -> "gRPC server started on localhost:" + port + " (and all other host addresses) " - + "in " + t + " milliseconds."); - - grpcServer.whenShutdown() - .whenComplete((server, error) -> { - if (error == null) { - shutdownFuture.complete(server); - } else { - shutdownFuture.completeExceptionally(error); - } - }); - - startedFuture.complete(grpcServer); - } - }); - - // inject the server into the producer so that it can be discovered later - ServerProducer serverProducer = beanManager.createInstance().select(ServerProducer.class).get(); - serverProducer.server(server); - } - - private void stopServer(@Observes BeforeShutdown event) { - if (server != null) { - LOGGER.info("Stopping gRPC server"); - long beforeT = System.nanoTime(); - server.shutdown() - .whenComplete((webServer, throwable) -> { - if (null != throwable) { - LOGGER.log(Level.SEVERE, throwable, () -> "An error occurred stopping the gRPC server"); - } else { - long t = TimeUnit.MILLISECONDS.convert(System.nanoTime() - beforeT, TimeUnit.NANOSECONDS); - LOGGER.info(() -> "gRPC Server stopped in " + t + " milliseconds."); - } - }); - } - } - - /** - * Discover the services and interceptors to use to configure the - * {@link GrpcRouting}. - * - * @param beanManager the CDI bean manager - * @return the {@link GrpcRouting} to use or {@code null} if no services - * or routing were discovered - */ - private GrpcRouting.Builder discoverGrpcRouting(BeanManager beanManager) { - Instance instance = beanManager.createInstance(); - GrpcRouting.Builder builder = GrpcRouting.builder(); - - // discover @Grpc annotated beans - // we use the bean manager to do this as we need the actual bean class - beanManager.getBeans(Object.class, Any.Literal.INSTANCE) - .stream() - .filter(this::hasGrpcQualifier) - .forEach(bean -> { - Class beanClass = bean.getBeanClass(); - Annotation[] qualifiers = bean.getQualifiers().toArray(new Annotation[0]); - Object service = instance.select(beanClass, qualifiers).get(); - register(service, builder, beanClass, beanManager); - }); - - // discover beans of type GrpcService - beanManager.getBeans(GrpcService.class) - .forEach(bean -> { - Class beanClass = bean.getBeanClass(); - Annotation[] qualifiers = bean.getQualifiers().toArray(new Annotation[0]); - Object service = instance.select(beanClass, qualifiers).get(); - builder.service((GrpcService) service); - }); - - // discover beans of type BindableService - beanManager.getBeans(BindableService.class) - .forEach(bean -> { - Class beanClass = bean.getBeanClass(); - Annotation[] qualifiers = bean.getQualifiers().toArray(new Annotation[0]); - Object service = instance.select(beanClass, qualifiers).get(); - builder.service((BindableService) service); - }); - - return builder; - } - - private boolean hasGrpcQualifier(Bean bean) { - return bean.getQualifiers() - .stream() - .anyMatch(q -> Grpc.class.isAssignableFrom(q.annotationType())); - } - - /** - * Load any instances of {@link GrpcMpExtension} discovered by the - * {@link ServiceLoader} and allow them to further configure the - * gRPC server. - * - * @param beanManager the {@link BeanManager} - * @param config the Helidon configuration - * @param routingBuilder the {@link GrpcRouting.Builder} - * @param serverConfiguration the {@link GrpcServerConfiguration} - */ - private void loadExtensions(BeanManager beanManager, - Config config, - GrpcRouting.Builder routingBuilder, - GrpcServerConfiguration.Builder serverConfiguration, - CompletionStage whenStarted, - CompletionStage whenShutdown) { - - GrpcMpContext context = new GrpcMpContext() { - @Override - public Config config() { - return config; - } - - @Override - public GrpcServerConfiguration.Builder grpcServerConfiguration() { - return serverConfiguration; - } - - @Override - public GrpcRouting.Builder routing() { - return routingBuilder; - } - - @Override - public BeanManager beanManager() { - return beanManager; - } - - @Override - public CompletionStage whenStarted() { - return whenStarted; - } - - @Override - public CompletionStage whenShutdown() { - return whenShutdown; - } - }; - - HelidonServiceLoader.create(ServiceLoader.load(GrpcMpExtension.class)) - .forEach(ext -> ext.configure(context)); - - beanManager.createInstance() - .select(GrpcMpExtension.class) - .stream() - .forEach(ext -> ext.configure(context)); - } - - /** - * Register the service with the routing. - *

- * The service is actually a CDI proxy so the real service. - * - * @param service the service to register - * @param builder the gRPC routing - * @param beanManager the {@link BeanManager} to use to locate beans required by the service - */ - private void register(Object service, GrpcRouting.Builder builder, Class cls, BeanManager beanManager) { - GrpcServiceBuilder serviceBuilder = GrpcServiceBuilder.create(cls, () -> service, beanManager); - if (serviceBuilder.isAnnotatedService()) { - builder.service(serviceBuilder.build()); - } else { - LOGGER.log(Level.WARNING, - () -> "Discovered type is not a properly annotated gRPC service " + service.getClass()); - } - } - - /** - * A CDI producer that can supply the running {@link GrpcServer} - * an in-process {@link Channel}. - */ - @ApplicationScoped - public static class ServerProducer { - - private GrpcServer server; - - /** - * Produce the {@link GrpcServer}. - * - * @return the {@link GrpcServer} - */ - @Produces - public GrpcServer server() { - return server; - } - - /** - * Produce a {@link Supplier} that can supply the {@link GrpcServer}. - *

- * This could be useful where an injection point has the server injected - * before the {@link #startServer} method has actually started it. In that - * case a {@link Supplier Supplier<GrpcServer>} can be injected instead - * that will be able to lazily supply the server. - * - * @return a {@link Supplier} that can supply the {@link GrpcServer} - */ - @Produces - public Supplier supply() { - return this::server; - } - - /** - * Produces an in-process {@link Channel} to connect to the - * running gRPC server. - * - * @return an in-process {@link Channel} to connect to the - * running gRPC server - */ - @Produces - @InProcessGrpcChannel - public Channel channel() { - String name = server.configuration().name(); - return InProcessChannelBuilder.forName(name) - .usePlaintext() - .build(); - } - - /** - * Produces an in-process {@link InProcessChannelBuilder} to - * connect to the running gRPC server. - * - * @return an in-process {@link InProcessChannelBuilder} to - * connect to the running gRPC server - */ - @Produces - @InProcessGrpcChannel - public InProcessChannelBuilder channelBuilder() { - String name = server.configuration().name(); - return InProcessChannelBuilder.forName(name); - } - - void server(GrpcServer server) { - this.server = server; - } - } -} diff --git a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServerConfiguration.java b/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServerConfiguration.java deleted file mode 100644 index 6672e62797c..00000000000 --- a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServerConfiguration.java +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright (c) 2019, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.microprofile.grpc.server; - -import java.time.Duration; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; - -import io.helidon.common.context.Context; -import io.helidon.config.Config; -import io.helidon.config.metadata.Configured; -import io.helidon.config.metadata.ConfiguredOption; - -/** - * The configuration for a gRPC server. - */ -public interface GrpcServerConfiguration { - /** - * The default server name. - */ - String DEFAULT_NAME = "grpc.server"; - - /** - * The default grpc port. - */ - int DEFAULT_PORT = 1408; - - /** - * The default number of worker threads that will be used if not explicitly set. - */ - int DEFAULT_WORKER_COUNT = Runtime.getRuntime().availableProcessors(); - - /** - * Get the server name. - * - * @return the server name - */ - String name(); - - /** - * Get the server port. - * - * @return the server port - */ - int port(); - - /** - * The top level {@link Context} to be used by the server. - * - * @return a context instance with registered application scoped instances - */ - Context context(); - - /** - * Determine whether use native transport if possible. - *

- * If native transport support is enabled, gRPC server will use epoll on - * Linux, or kqueue on OS X. Otherwise, the standard NIO transport will - * be used. - * - * @return {@code true} if native transport should be used - */ - boolean useNativeTransport(); - - /** - * Returns a count of threads in s pool used to process gRPC requests. - *

- * Default value is {@code CPU_COUNT * 2}. - * - * @return a workers count - */ - int workers(); - - /** - * Returns a SslConfiguration to use with the server socket. If not {@code null} then - * the server enforces an SSL communication. - * - * @return a TLS configuration to use - */ - GrpcTlsDescriptor tlsConfig(); - - /** - * Returns the period for counting rapid resets (stream RST sent by client before any data have been sent by server). - * - * @return the period for counting rapid resets - */ - Duration rapidResetCheckPeriod(); - - /** - * Returns the maximum allowed number of rapid resets (stream RST sent by client before any data have been sent by server). - * When reached within {@link #rapidResetCheckPeriod()}, GOAWAY is sent to client and connection is closed. - * - * @return the maximum allowed number of rapid resets - */ - int maxRapidResets(); - - /** - * Creates new instance with default values for all configuration properties. - * - * @return a new instance - */ - static GrpcServerConfiguration create() { - return builder().build(); - } - - /** - * Creates new instance with values from external configuration. - * - * @param config the externalized configuration - * @return a new instance - */ - static GrpcServerConfiguration create(Config config) { - return builder(config).build(); - } - - /** - * Creates new instance of a {@link Builder server configuration builder}. - * - * @return a new builder instance - */ - static Builder builder() { - return new Builder(); - } - - /** - * Creates new instance of a {@link Builder server configuration builder} with defaults from external configuration source. - * - * @param config the externalized configuration - * @return a new builder instance - */ - static Builder builder(Config config) { - return new Builder().config(config); - } - - /** - * A {@link GrpcServerConfiguration} builder. - */ - @Configured - final class Builder implements io.helidon.common.Builder { - private static final AtomicInteger GRPC_SERVER_COUNTER = new AtomicInteger(1); - - private String name = DEFAULT_NAME; - - private int port = DEFAULT_PORT; - - private boolean useNativeTransport; - - private int workers; - - private GrpcTlsDescriptor tlsConfig = null; - - private Context context; - - private int maxRapidResets = 200; - - private Duration rapidResetCheckPeriod = Duration.ofSeconds(30); - - private Builder() { - } - - /** - * Update the builder from configuration. - * - * @param config configuration instance - * @return updated builder - */ - @ConfiguredOption(key = "native", - type = Boolean.class, - value = "false", - description = "Specify if native transport should be used.") - public Builder config(Config config) { - if (config == null) { - return this; - } - - name = config.get("name").asString().orElse(DEFAULT_NAME); - port = config.get("port").asInt().orElse(DEFAULT_PORT); - maxRapidResets = config.get("max-rapid-resets").asInt().orElse(200); - rapidResetCheckPeriod = config.get("rapid-reset-check-period").as(Duration.class).orElse(Duration.ofSeconds(30)); - useNativeTransport = config.get("native").asBoolean().orElse(false); - config.get("workers").asInt().ifPresent(this::workersCount); - - return this; - } - - /** - * Set the name of the gRPC server. - *

- * Configuration key: {@code name} - * - * @param name the name of the gRPC server - * @return an updated builder - */ - @ConfiguredOption(key = "name", value = DEFAULT_NAME) - public Builder name(String name) { - this.name = name == null ? null : name.trim(); - return this; - } - - /** - * Sets server port. If port is {@code 0} or less then any available ephemeral port will be used. - *

- * Configuration key: {@code port} - * - * @param port the server port - * @return an updated builder - */ - @ConfiguredOption(value = "" + DEFAULT_PORT) - public Builder port(int port) { - this.port = port < 0 ? 0 : port; - return this; - } - - /** - * Period for counting rapid resets(stream RST sent by client before any data have been sent by server). - * Default value is {@code PT30S}. - * - * @param rapidResetCheckPeriod duration - * @return updated builder - * @see ISO_8601 Durations - * @see #maxRapidResets() - */ - @ConfiguredOption("PT30S") - public Builder rapidResetCheckPeriod(Duration rapidResetCheckPeriod) { - Objects.requireNonNull(rapidResetCheckPeriod); - this.rapidResetCheckPeriod = rapidResetCheckPeriod; - return this; - } - - /** - * Maximum number of rapid resets(stream RST sent by client before any data have been sent by server). - * When reached within {@link #rapidResetCheckPeriod()}, GOAWAY is sent to client and connection is closed. - * Default value is {@code 200}. - * - * @param maxRapidResets maximum number of rapid resets - * @return updated builder - * @see #rapidResetCheckPeriod() - */ - @ConfiguredOption("200") - public Builder maxRapidResets(int maxRapidResets) { - this.maxRapidResets = maxRapidResets; - return this; - } - - /** - * Configure the application scoped context to be used as a parent for webserver request contexts. - * - * @param context top level context - * @return an updated builder - */ - public Builder context(Context context) { - this.context = context; - return this; - } - - /** - * Sets a count of threads in pool used to process HTTP requests. - * Default value is {@code CPU_COUNT * 2}. - *

- * Configuration key: {@code workers} - * - * @param workers a workers count - * @return an updated builder - */ - @ConfiguredOption(key = "workers", value = "Number of processors available to the JVM") - public Builder workersCount(int workers) { - this.workers = workers; - return this; - } - - /** - * Configures TLS configuration to use with the server socket. If not {@code null} then - * the server enforces an TLS communication. - * - * @param tlsConfig a TLS configuration to use - * @return this builder - */ - public Builder tlsConfig(GrpcTlsDescriptor tlsConfig) { - this.tlsConfig = tlsConfig; - return this; - } - - String name() { - return name; - } - - int port() { - return port; - } - - /** - * Current Helidon {@link Context}. - * - * @return current context - */ - public Context context() { - return context; - } - - GrpcTlsDescriptor tlsConfig() { - return tlsConfig; - } - - boolean useNativeTransport() { - return useNativeTransport; - } - - int workers() { - return workers; - } - - int maxRapidResets() { - return maxRapidResets; - } - - Duration rapidResetCheckPeriod() { - return rapidResetCheckPeriod; - } - - @Override - public GrpcServerConfiguration build() { - if (name == null || name.isEmpty()) { - name = DEFAULT_NAME; - } - - if (port < 0) { - port = 0; - } - - if (context == null) { - context = Context.builder() - .id("grpc-" + GRPC_SERVER_COUNTER.getAndIncrement()) - .build(); - } - - if (workers <= 0) { - workers = DEFAULT_WORKER_COUNT; - } - - return GrpcServerBasicConfig.create(this); - } - } -} diff --git a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServerImpl.java b/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServerImpl.java deleted file mode 100644 index 3aa7a151e7b..00000000000 --- a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcServerImpl.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.microprofile.grpc.server; - -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; - -import io.helidon.common.context.Context; -import io.helidon.grpc.core.WeightedBag; -import io.helidon.webserver.grpc.ServiceDescriptor; - -import io.grpc.ServerInterceptor; -import org.eclipse.microprofile.health.HealthCheck; - -class GrpcServerImpl implements GrpcServer { - - static GrpcServerImpl create() { - return new GrpcServerImpl(); - } - - static GrpcServerImpl create(GrpcServerConfiguration config) { - return new GrpcServerImpl(); // TODO - } - - @Override - public GrpcServerConfiguration configuration() { - return null; - } - - @Override - public Context context() { - return null; - } - - @Override - public CompletionStage start() { - return CompletableFuture.completedFuture(null); - } - - @Override - public CompletionStage whenShutdown() { - return CompletableFuture.completedFuture(null); - } - - @Override - public CompletionStage shutdown() { - return CompletableFuture.completedFuture(null); - } - - @Override - public HealthCheck[] healthChecks() { - return new HealthCheck[0]; - } - - @Override - public Map services() { - return null; - } - - @Override - public boolean isRunning() { - return false; - } - - @Override - public int port() { - return 0; - } - - public void deploy(ServiceDescriptor service, WeightedBag interceptors) { - } -} diff --git a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcTlsDescriptor.java b/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcTlsDescriptor.java deleted file mode 100644 index d75c0ce05e8..00000000000 --- a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/GrpcTlsDescriptor.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (c) 2019, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.microprofile.grpc.server; - -import io.helidon.common.configurable.Resource; -import io.helidon.config.Config; -import io.helidon.config.metadata.Configured; -import io.helidon.config.metadata.ConfiguredOption; -import io.helidon.config.objectmapping.Value; - -/** - * GrpcTlsDescriptor contains details about configuring TLS of a {@link io.grpc.Channel}. - */ -public class GrpcTlsDescriptor { - private final boolean enabled; - private final boolean jdkSSL; - private final Resource tlsCert; - private final Resource tlsKey; - private final Resource tlsCaCert; - - private GrpcTlsDescriptor(boolean enabled, boolean jdkSSL, Resource tlsCert, Resource tlsKey, Resource tlsCaCert) { - this.enabled = enabled; - this.jdkSSL = jdkSSL; - this.tlsCert = tlsCert; - this.tlsKey = tlsKey; - this.tlsCaCert = tlsCaCert; - } - - /** - * Return a new instance of {@link Builder}. - * - * @return a new instance of {@link Builder} - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Return an instance of builder based on the specified external config. - * - * @param config external config - * @return an instance of builder - */ - public static Builder builder(Config config) { - return new Builder(config); - } - - /** - * Create an instance of a TLS configuration from external configuration source. - * - * @param config external config - * @return an instance of a TLS configuration - */ - public static GrpcTlsDescriptor create(Config config) { - return builder(config).build(); - } - - /** - * Check if TLS is enabled. If this is false, then none of the other configuration values are used. - * - * @return true if TLS is enabled; false otherwise - */ - public boolean isEnabled() { - return enabled; - } - - /** - * Check if JDK SSL has be used. Only used for TLS enabled server channels.A Ignored by client channel. - * - * @return true if JDK ssl has to be used; false otherwise - */ - public boolean isJdkSSL() { - return jdkSSL; - } - - /** - * Get the tlsCert path. Can be either client or server cert. - * - * @return the path to tls certificate - */ - public Resource tlsCert() { - return tlsCert; - } - - /** - * Get the client private key path. Can be either client or server private key. - * - * @return the path to tls private key - */ - public Resource tlsKey() { - return tlsKey; - } - - /** - * Get the CA (certificate authority) certificate path. - * - * @return the path to CA certificate - */ - public Resource tlsCaCert() { - return tlsCaCert; - } - - /** - * Builder to build a new instance of {@link GrpcTlsDescriptor}. - */ - @Configured - public static class Builder implements io.helidon.common.Builder { - - private boolean enabled = true; - private boolean jdkSSL; - private Resource tlsCert; - private Resource tlsKey; - private Resource tlsCaCert; - - private Builder() { - - } - - private Builder(Config config) { - if (config == null) { - return; - } - - config.get("tls-cert.resource").as(Resource::create).ifPresent(this::tlsCert); - config.get("tls-key.resource").as(Resource::create).ifPresent(this::tlsKey); - config.get("tls-ca-cert.resource").as(Resource::create).ifPresent(this::tlsCaCert); - - this.jdkSSL = config.get("jdk-ssl").asBoolean().orElse(false); - this.enabled = config.get("enabled").asBoolean().orElse(true); - } - - /** - * Enable or disable TLS. If enabled is false, then the rest of the TLS configuration properties are ignored. - * - * @param enabled true to enable, false otherwise - * @return this instance for fluent API - */ - @ConfiguredOption(value = "true") - @Value(withDefault = "true") - public Builder enabled(boolean enabled) { - this.enabled = enabled; - return this; - } - - /** - * Sets the type of SSL implementation to be used. - * - * @param jdkSSL true to use JDK based SSL, false otherwise - * @return this instance for fluent API - */ - @ConfiguredOption(key = "jdk-ssl", value = "false") - public Builder jdkSSL(boolean jdkSSL) { - this.jdkSSL = jdkSSL; - return this; - } - - /** - * Set the client tlsCert path. Required only if mutual auth is desired. - * - * @param tlsCert the path to client's certificate - * @return this instance for fluent API - */ - @ConfiguredOption - @Value(key = "tls-cert") - public Builder tlsCert(Resource tlsCert) { - this.tlsCert = tlsCert; - return this; - } - - /** - * Set the client private key path. Required only if mutual auth is desired. - * - * @param tlsKey the 's TLS private key - * @return this instance for fluent API - */ - @ConfiguredOption - @Value(key = "tls-key") - public Builder tlsKey(Resource tlsKey) { - this.tlsKey = tlsKey; - return this; - } - - /** - * Set the CA (certificate authority) certificate path. - * - * @param caCert the path to CA certificate - * @return this instance for fluent API - */ - @ConfiguredOption(key = "tls-ca-cert") - @Value(key = "tls-ca-cert") - public Builder tlsCaCert(Resource caCert) { - this.tlsCaCert = caCert; - return this; - } - - /** - * Create and return a new instance of {@link GrpcTlsDescriptor}. - * - * @return a new instance of {@link GrpcTlsDescriptor} - */ - public GrpcTlsDescriptor build() { - return new GrpcTlsDescriptor(enabled, jdkSSL, tlsCert, tlsKey, tlsCaCert); - } - } -} diff --git a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/spi/GrpcMpContext.java b/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/spi/GrpcMpContext.java deleted file mode 100644 index 13b48d70123..00000000000 --- a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/spi/GrpcMpContext.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2019, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.microprofile.grpc.server.spi; - -import java.util.concurrent.CompletionStage; - -import io.helidon.config.Config; -import io.helidon.microprofile.grpc.server.GrpcServer; -import io.helidon.microprofile.grpc.server.GrpcServerConfiguration; -import io.helidon.webserver.grpc.GrpcRouting; - -import jakarta.enterprise.inject.spi.BeanManager; - -/** - * A context to allow a microprofile gRPC server extensions to configure additional - * services or components for the gRPC server or use the CDI bean manager. - */ -public interface GrpcMpContext { - - /** - * Obtain the Helidon configuration. - * - * @return the Helidon configuration - */ - Config config(); - - /** - * Obtain the {@link GrpcServerConfiguration}. - * - * @return the {@link GrpcServerConfiguration} - */ - GrpcServerConfiguration.Builder grpcServerConfiguration(); - - /** - * Obtain the {@link GrpcRouting.Builder} to allow modifications - * to be made to the routing before the server is configured. - * - * @return the {@link GrpcRouting.Builder} - */ - GrpcRouting.Builder routing(); - - /** - * Obtain the {@link jakarta.enterprise.inject.spi.BeanManager}. - * - * @return the {@link jakarta.enterprise.inject.spi.BeanManager} - */ - BeanManager beanManager(); - - /** - * Return a completion stage is completed when the gRPC server is started. - * - * @return a completion stage is completed when the gRPC server is started - */ - CompletionStage whenStarted(); - - /** - * Return a completion stage is completed when the gRPC server is shut down. - * - * @return a completion stage is completed when the gRPC server is shut down - */ - CompletionStage whenShutdown(); -} diff --git a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/spi/GrpcMpExtension.java b/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/spi/GrpcMpExtension.java deleted file mode 100644 index cf44c3252ed..00000000000 --- a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/spi/GrpcMpExtension.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2019, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.microprofile.grpc.server.spi; - -/** - * Microprofile service to extend features of the gRPC server. - */ -public interface GrpcMpExtension { - /** - * Allow the service to add configuration through the context. - * - * @param context context to obtain configuration objects - */ - void configure(GrpcMpContext context); -} diff --git a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/spi/package-info.java b/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/spi/package-info.java deleted file mode 100644 index 5cf2f30af3d..00000000000 --- a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/spi/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2019, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Microprofile gRPC server implementation. - */ -package io.helidon.microprofile.grpc.server.spi; diff --git a/microprofile/grpc/server/src/main/java/module-info.java b/microprofile/grpc/server/src/main/java/module-info.java index 298e1ad1c3b..c4b85ea5643 100644 --- a/microprofile/grpc/server/src/main/java/module-info.java +++ b/microprofile/grpc/server/src/main/java/module-info.java @@ -14,12 +14,13 @@ * limitations under the License. */ +import io.helidon.microprofile.grpc.server.GrpcMpCdiExtension; + /** * gRPC microprofile server module */ module io.helidon.microprofile.grpc.server { exports io.helidon.microprofile.grpc.server; - exports io.helidon.microprofile.grpc.server.spi; requires transitive io.helidon.webserver.grpc; requires transitive io.helidon.microprofile.grpc.core; @@ -43,12 +44,11 @@ requires io.helidon.config.metadata; requires io.helidon.common.context; - uses io.helidon.microprofile.grpc.server.spi.GrpcMpExtension; - uses io.helidon.microprofile.grpc.server.GrpcServerCdiExtension; + uses GrpcMpCdiExtension; uses io.helidon.microprofile.grpc.server.AnnotatedServiceConfigurer; provides jakarta.enterprise.inject.spi.Extension - with io.helidon.microprofile.grpc.server.GrpcServerCdiExtension; + with GrpcMpCdiExtension; // needed when running with modules - to make private methods accessible opens io.helidon.microprofile.grpc.server to weld.core.impl, io.helidon.microprofile.cdi; diff --git a/microprofile/grpc/server/src/main/resources/META-INF/beans.xml b/microprofile/grpc/server/src/main/resources/META-INF/beans.xml deleted file mode 100644 index a0938bff7d4..00000000000 --- a/microprofile/grpc/server/src/main/resources/META-INF/beans.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - diff --git a/microprofile/grpc/server/src/test/java/io/helidon/microprofile/grpc/server/EchoService.java b/microprofile/grpc/server/src/test/java/io/helidon/microprofile/grpc/server/EchoService.java deleted file mode 100644 index 0180267b819..00000000000 --- a/microprofile/grpc/server/src/test/java/io/helidon/microprofile/grpc/server/EchoService.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.microprofile.grpc.server; - -import io.grpc.stub.StreamObserver; -import io.helidon.grpc.server.test.Echo.EchoRequest; -import io.helidon.grpc.server.test.Echo.EchoResponse; -import io.helidon.microprofile.grpc.core.Grpc; -import io.helidon.microprofile.grpc.core.Unary; -import jakarta.enterprise.context.ApplicationScoped; - -import static io.helidon.grpc.core.ResponseHelper.complete; - -/** - * A simple test gRPC echo service. - */ -@Grpc -@ApplicationScoped -public class EchoService { - - /** - * Echo the message back to the caller. - * - * @param request the echo request containing the message to echo - * @param observer the call response - */ - @Unary - public void echo(EchoRequest request, StreamObserver observer) { - String message = request.getMessage(); - EchoResponse response = EchoResponse.newBuilder().setMessage(message).build(); - complete(observer, response); - } -} diff --git a/microprofile/grpc/server/src/test/java/io/helidon/microprofile/grpc/server/EchoServiceTest.java b/microprofile/grpc/server/src/test/java/io/helidon/microprofile/grpc/server/EchoServiceTest.java index 9c88a54fe88..eeec93d2155 100644 --- a/microprofile/grpc/server/src/test/java/io/helidon/microprofile/grpc/server/EchoServiceTest.java +++ b/microprofile/grpc/server/src/test/java/io/helidon/microprofile/grpc/server/EchoServiceTest.java @@ -15,21 +15,86 @@ */ package io.helidon.microprofile.grpc.server; +import java.time.Duration; + +import io.grpc.stub.StreamObserver; +import io.helidon.common.configurable.Resource; +import io.helidon.common.tls.Tls; +import io.helidon.microprofile.grpc.core.Grpc; +import io.helidon.microprofile.grpc.core.Unary; +import io.helidon.microprofile.grpc.server.test.Echo; import io.helidon.microprofile.testing.junit5.AddExtension; import io.helidon.microprofile.testing.junit5.HelidonTest; -import jakarta.enterprise.inject.spi.CDI; +import io.helidon.webclient.grpc.GrpcClient; +import io.helidon.webclient.grpc.GrpcClientMethodDescriptor; +import io.helidon.webclient.grpc.GrpcServiceDescriptor; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.client.WebTarget; import org.junit.jupiter.api.Test; +import static io.helidon.grpc.core.ResponseHelper.complete; import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; @HelidonTest -@AddExtension(GrpcServerCdiExtension.class) +@AddExtension(GrpcMpCdiExtension.class) class EchoServiceTest { + private final GrpcClient grpcClient; + private final GrpcServiceDescriptor serviceDescriptor; + + @Inject + public EchoServiceTest(WebTarget webTarget) { + Tls clientTls = Tls.builder() + .trust(trust -> trust + .keystore(store -> store + .passphrase("password") + .trustStore(true) + .keystore(Resource.create("client.p12")))) + .build(); + this.grpcClient = GrpcClient.builder() + .tls(clientTls) + .readTimeout(Duration.ofSeconds(300)) // debugging + .baseUri("https://localhost:" + webTarget.getUri().getPort()) + .build(); + + this.serviceDescriptor = GrpcServiceDescriptor.builder() + .serviceName("EchoService") + .putMethod("echo", + GrpcClientMethodDescriptor.unary("EchoService", "echo") + .requestType(Echo.EchoRequest.class) + .responseType(Echo.EchoResponse.class) + .build()) + .build(); + } + @Test void test() { - assertThat(CDI.current(), is(notNullValue())); + Echo.EchoResponse res = grpcClient.serviceClient(serviceDescriptor) + .unary("echo", fromString("Howdy")); + assertThat(res.getMessage(), is("Howdy")); + } + + private Echo.EchoRequest fromString(String value) { + return Echo.EchoRequest.newBuilder().setMessage(value).build(); + } + + @Grpc + @ApplicationScoped + public static class EchoService { + + /** + * Echo the message back to the caller. + * + * @param request the echo request containing the message to echo + * @param observer the call response + */ + @Unary + public void echo(Echo.EchoRequest request, StreamObserver observer) { + String message = request.getMessage(); + Echo.EchoResponse response = Echo.EchoResponse.newBuilder().setMessage(message).build(); + complete(observer, response); + } } } diff --git a/microprofile/grpc/server/src/test/proto/echo.proto b/microprofile/grpc/server/src/test/proto/echo.proto index 739b442d005..af0d4c50d0d 100644 --- a/microprofile/grpc/server/src/test/proto/echo.proto +++ b/microprofile/grpc/server/src/test/proto/echo.proto @@ -15,7 +15,7 @@ */ syntax = "proto3"; -option java_package = "io.helidon.grpc.server.test"; +option java_package = "io.helidon.microprofile.grpc.server.test"; service EchoService { rpc Echo (EchoRequest) returns (EchoResponse) {} diff --git a/microprofile/grpc/server/src/test/resources/application.yaml b/microprofile/grpc/server/src/test/resources/application.yaml new file mode 100644 index 00000000000..00b3aef2e03 --- /dev/null +++ b/microprofile/grpc/server/src/test/resources/application.yaml @@ -0,0 +1,30 @@ +# +# Copyright (c) 2024 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +server: + port: 0 + tls: + trust: + keystore: + passphrase: "password" + trust-store: true + resource: + resource-path: "server.p12" + private-key: + keystore: + passphrase: "password" + resource: + resource-path: "server.p12" diff --git a/microprofile/grpc/server/src/test/resources/client.p12 b/microprofile/grpc/server/src/test/resources/client.p12 new file mode 100644 index 00000000000..4eb3b8325cd Binary files /dev/null and b/microprofile/grpc/server/src/test/resources/client.p12 differ diff --git a/microprofile/grpc/server/src/test/resources/logging.properties b/microprofile/grpc/server/src/test/resources/logging.properties index ab333c926fe..0931614a96f 100644 --- a/microprofile/grpc/server/src/test/resources/logging.properties +++ b/microprofile/grpc/server/src/test/resources/logging.properties @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n @@ -26,9 +26,6 @@ java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$ # Global logging level. Can be overridden by specific loggers .level=INFO -# Component specific log levels -#io.helidon.webserver.level=INFO -#io.helidon.config.level=INFO -#io.helidon.security.level=INFO -#io.helidon.common.level=INFO -#io.netty.level=INFO +#io.helidon.webserver.http2.level=DEBUG +#io.helidon.webclient.grpc.level=DEBUG + diff --git a/microprofile/grpc/server/src/test/resources/server.p12 b/microprofile/grpc/server/src/test/resources/server.p12 new file mode 100644 index 00000000000..ff8e4ddfc7f Binary files /dev/null and b/microprofile/grpc/server/src/test/resources/server.p12 differ diff --git a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/BindableServiceImpl.java b/webserver/grpc/src/main/java/io/helidon/webserver/grpc/BindableServiceImpl.java similarity index 96% rename from microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/BindableServiceImpl.java rename to webserver/grpc/src/main/java/io/helidon/webserver/grpc/BindableServiceImpl.java index b7965ae9bc4..d71dcf860c4 100644 --- a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/BindableServiceImpl.java +++ b/webserver/grpc/src/main/java/io/helidon/webserver/grpc/BindableServiceImpl.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.helidon.microprofile.grpc.server; +package io.helidon.webserver.grpc; import java.util.LinkedHashSet; import java.util.List; @@ -22,12 +22,9 @@ import java.util.concurrent.CompletionException; import java.util.function.BiConsumer; import java.util.function.Supplier; -import java.util.stream.Collectors; import io.helidon.grpc.core.InterceptorWeights; import io.helidon.grpc.core.WeightedBag; -import io.helidon.webserver.grpc.MethodDescriptor; -import io.helidon.webserver.grpc.ServiceDescriptor; import io.grpc.BindableService; import io.grpc.Metadata; @@ -99,7 +96,7 @@ private ServerCallHandler wrapCallHandler(MethodDescr priorityServerInterceptors.addAll(globalInterceptors); priorityServerInterceptors.addAll(descriptor.interceptors()); priorityServerInterceptors.addAll(method.interceptors()); - List interceptors = priorityServerInterceptors.stream().collect(Collectors.toList()); + List interceptors = priorityServerInterceptors.stream().toList(); if (!interceptors.isEmpty()) { LinkedHashSet uniqueInterceptors = new LinkedHashSet<>(interceptors.size()); @@ -125,7 +122,7 @@ static Supplier createSupplier(Callable callable) { } static class CallableSupplier implements Supplier { - private Callable callable; + private final Callable callable; CallableSupplier(Callable callable) { this.callable = callable; diff --git a/webserver/grpc/src/main/java/io/helidon/webserver/grpc/Grpc.java b/webserver/grpc/src/main/java/io/helidon/webserver/grpc/Grpc.java index 7ed1caedd9f..30be63b6ec7 100644 --- a/webserver/grpc/src/main/java/io/helidon/webserver/grpc/Grpc.java +++ b/webserver/grpc/src/main/java/io/helidon/webserver/grpc/Grpc.java @@ -50,7 +50,6 @@ static Grpc unary(Descriptors.FileDescriptor proto, String serviceName, String methodName, ServerCalls.UnaryMethod method) { - return grpc(proto, serviceName, methodName, ServerCalls.asyncUnaryCall(method)); } @@ -58,7 +57,6 @@ static Grpc bidi(Descriptors.FileDescriptor proto, String serviceName, String methodName, ServerCalls.BidiStreamingMethod method) { - return grpc(proto, serviceName, methodName, ServerCalls.asyncBidiStreamingCall(method)); } @@ -66,7 +64,6 @@ static Grpc serverStream(Descriptors.FileDescriptor pro String serviceName, String methodName, ServerCalls.ServerStreamingMethod method) { - return grpc(proto, serviceName, methodName, ServerCalls.asyncServerStreamingCall(method)); } @@ -74,7 +71,6 @@ static Grpc clientStream(Descriptors.FileDescriptor pro String serviceName, String methodName, ServerCalls.ClientStreamingMethod method) { - return grpc(proto, serviceName, methodName, ServerCalls.asyncClientStreamingCall(method)); } @@ -82,19 +78,27 @@ static Grpc clientStream(Descriptors.FileDescriptor pro * Create a {@link io.helidon.webserver.grpc.Grpc gRPC route} from a {@link io.grpc.ServerMethodDefinition}. * * @param definition the {@link io.grpc.ServerMethodDefinition} representing the method to execute - * @param proto an optional protocol buffer {@link com.google.protobuf.Descriptors.FileDescriptor} - * containing the service definition - * @param the request type - * @param the response type - * + * @param proto an optional protocol buffer {@link com.google.protobuf.Descriptors.FileDescriptor} + * containing the service definition + * @param the request type + * @param the response type * @return a {@link io.helidon.webserver.grpc.Grpc gRPC route} created - * from the {@link io.grpc.ServerMethodDefinition} + * from the {@link io.grpc.ServerMethodDefinition} */ static Grpc methodDefinition(ServerMethodDefinition definition, Descriptors.FileDescriptor proto) { return grpc(definition.getMethodDescriptor(), definition.getServerCallHandler(), proto); } + public static Grpc unary(ServiceDescriptor service, io.helidon.webserver.grpc.MethodDescriptor method) { + String path = service.fullName() + "/" + method.name(); + return new Grpc<>((MethodDescriptor) method.descriptor(), + PathMatchers.exact(path), + (Class) method.requestType(), + (Class) method.responseType(), + method.callHandler()); + } + @Override Grpc toGrpc(HttpPrologue grpcPrologue) { return this; @@ -153,15 +157,14 @@ private static Grpc grpc(Descriptors.FileDescriptor pro /** * Create a {@link io.helidon.webserver.grpc.Grpc gRPC route} from a {@link io.grpc.MethodDescriptor}. * - * @param grpcDesc the {@link io.grpc.MethodDescriptor} describing the method to execute + * @param grpcDesc the {@link io.grpc.MethodDescriptor} describing the method to execute * @param callHandler the {@link io.grpc.ServerCallHandler} that will execute the method - * @param proto an optional protocol buffer {@link com.google.protobuf.Descriptors.FileDescriptor} containing - * the service definition - * @param the request type - * @param the response type - * + * @param proto an optional protocol buffer {@link com.google.protobuf.Descriptors.FileDescriptor} containing + * the service definition + * @param the request type + * @param the response type * @return a {@link io.helidon.webserver.grpc.Grpc gRPC route} created - * from the {@link io.grpc.ServerMethodDefinition} + * from the {@link io.grpc.ServerMethodDefinition} */ private static Grpc grpc(MethodDescriptor grpcDesc, ServerCallHandler callHandler, diff --git a/webserver/grpc/src/main/java/io/helidon/webserver/grpc/GrpcRouting.java b/webserver/grpc/src/main/java/io/helidon/webserver/grpc/GrpcRouting.java index e39d914015d..7b7ff4a0b19 100644 --- a/webserver/grpc/src/main/java/io/helidon/webserver/grpc/GrpcRouting.java +++ b/webserver/grpc/src/main/java/io/helidon/webserver/grpc/GrpcRouting.java @@ -23,17 +23,18 @@ import java.util.List; import java.util.Map; +import io.helidon.grpc.core.InterceptorWeights; +import io.helidon.grpc.core.WeightedBag; +import io.helidon.http.HttpPrologue; +import io.helidon.http.PathMatchers; +import io.helidon.webserver.Routing; + import com.google.protobuf.Descriptors; import io.grpc.BindableService; import io.grpc.ServerInterceptor; import io.grpc.ServerMethodDefinition; import io.grpc.ServerServiceDefinition; import io.grpc.stub.ServerCalls; -import io.helidon.grpc.core.InterceptorWeights; -import io.helidon.grpc.core.WeightedBag; -import io.helidon.http.HttpPrologue; -import io.helidon.http.PathMatchers; -import io.helidon.webserver.Routing; /** * GRPC specific routing. @@ -102,7 +103,7 @@ public WeightedBag interceptors() { * contained in this {@link GrpcRouting}. * * @return a {@link List} of the {@link ServiceDescriptor} instances - * contained in this {@link GrpcRouting} + * contained in this {@link GrpcRouting} */ public List services() { return services; @@ -152,7 +153,7 @@ public Builder service(GrpcService service) { * @return updated builder */ public Builder service(BindableService service) { - throw new UnsupportedOperationException("Not implemented"); // TODO + return route(GrpcServiceRoute.create(service)); } /** @@ -167,7 +168,7 @@ public Builder service(ServiceDescriptor service) { throw new IllegalArgumentException("Attempted to register service name " + name + " multiple times"); } services.put(name, service); - return this; + return route(GrpcServiceRoute.create(service)); } /** @@ -205,12 +206,12 @@ public Builder intercept(int weight, ServerInterceptor... interceptors) { /** * Unary route. * - * @param proto proto descriptor + * @param proto proto descriptor * @param serviceName service name - * @param methodName method name - * @param method method to handle this route - * @param request type - * @param response type + * @param methodName method name + * @param method method to handle this route + * @param request type + * @param response type * @return updated builder */ public Builder unary(Descriptors.FileDescriptor proto, @@ -223,12 +224,12 @@ public Builder unary(Descriptors.FileDescriptor proto, /** * Bidirectional route. * - * @param proto proto descriptor + * @param proto proto descriptor * @param serviceName service name - * @param methodName method name - * @param method method to handle this route - * @param request type - * @param response type + * @param methodName method name + * @param method method to handle this route + * @param request type + * @param response type * @return updated builder */ public Builder bidi(Descriptors.FileDescriptor proto, @@ -241,12 +242,12 @@ public Builder bidi(Descriptors.FileDescriptor proto, /** * Server streaming route. * - * @param proto proto descriptor + * @param proto proto descriptor * @param serviceName service name - * @param methodName method name - * @param method method to handle this route - * @param request type - * @param response type + * @param methodName method name + * @param method method to handle this route + * @param request type + * @param response type * @return updated builder */ public Builder serverStream(Descriptors.FileDescriptor proto, @@ -259,12 +260,12 @@ public Builder serverStream(Descriptors.FileDescriptor proto, /** * Client streaming route. * - * @param proto proto descriptor + * @param proto proto descriptor * @param serviceName service name - * @param methodName method name - * @param method method to handle this route - * @param request type - * @param response type + * @param methodName method name + * @param method method to handle this route + * @param request type + * @param response type * @return updated builder */ public Builder clientStream(Descriptors.FileDescriptor proto, @@ -277,9 +278,8 @@ public Builder clientStream(Descriptors.FileDescriptor proto, /** * Add all the routes for a {@link BindableService} service. * - * @param proto the proto descriptor - * @param service the {@link BindableService} to add routes for - * + * @param proto the proto descriptor + * @param service the {@link BindableService} to add routes for * @return updated builder */ public Builder service(Descriptors.FileDescriptor proto, BindableService service) { @@ -292,8 +292,7 @@ public Builder service(Descriptors.FileDescriptor proto, BindableService service /** * Add all the routes for the {@link ServerServiceDefinition} service. * - * @param service the {@link ServerServiceDefinition} to add routes for - * + * @param service the {@link ServerServiceDefinition} to add routes for * @return updated builder */ public Builder service(ServerServiceDefinition service) { diff --git a/webserver/grpc/src/main/java/io/helidon/webserver/grpc/GrpcServiceRoute.java b/webserver/grpc/src/main/java/io/helidon/webserver/grpc/GrpcServiceRoute.java index fdd0d91e76b..d4a8fd7aa3c 100644 --- a/webserver/grpc/src/main/java/io/helidon/webserver/grpc/GrpcServiceRoute.java +++ b/webserver/grpc/src/main/java/io/helidon/webserver/grpc/GrpcServiceRoute.java @@ -23,6 +23,7 @@ import io.helidon.http.PathMatchers; import com.google.protobuf.Descriptors; +import io.grpc.BindableService; import io.grpc.stub.ServerCalls; class GrpcServiceRoute extends GrpcRoute { @@ -40,6 +41,35 @@ static GrpcRoute create(GrpcService service) { return svcRouter.build(); } + static GrpcRoute create(BindableService service) { + throw new UnsupportedOperationException("Not implemented"); + } + + static GrpcRoute create(ServiceDescriptor service) { + String serviceName = service.name(); + List> routes = new LinkedList<>(); + + service.methods().forEach(method -> { + io.grpc.MethodDescriptor descriptor = method.descriptor(); + switch (descriptor.getType()) { + case UNARY -> routes.add(Grpc.unary(service, method)); + /* + case CLIENT_STREAMING -> + routes.add(Grpc.clientStream(service, method)); + case SERVER_STREAMING -> + routes.add(Grpc.serverStream(service, method)); + case BIDI_STREAMING -> + routes.add(Grpc.bidi(service, method)); + */ + case UNKNOWN -> throw new IllegalArgumentException("gRPC method of type " + + descriptor.getType() + " not supported"); + default -> throw new IllegalStateException("Invalid gRPC method type"); + } + }); + + return new GrpcServiceRoute(serviceName, routes); + } + @Override Grpc toGrpc(HttpPrologue prologue) { for (Grpc route : routes) { diff --git a/webserver/grpc/src/main/java/io/helidon/webserver/grpc/MethodDescriptor.java b/webserver/grpc/src/main/java/io/helidon/webserver/grpc/MethodDescriptor.java index 8660e65d1ab..4903ecf2d32 100644 --- a/webserver/grpc/src/main/java/io/helidon/webserver/grpc/MethodDescriptor.java +++ b/webserver/grpc/src/main/java/io/helidon/webserver/grpc/MethodDescriptor.java @@ -42,14 +42,20 @@ public class MethodDescriptor { private final ServerCallHandler callHandler; private final Map, Object> context; private final WeightedBag interceptors; + private final Class requestType; + private final Class responseType; private MethodDescriptor(String name, io.grpc.MethodDescriptor descriptor, + Class requestType, + Class responseType, ServerCallHandler callHandler, Map, Object> context, WeightedBag interceptors) { this.name = name; this.descriptor = descriptor; + this.requestType = requestType; + this.responseType = responseType; this.callHandler = callHandler; this.context = context; this.interceptors = interceptors.copyMe(); @@ -82,6 +88,24 @@ public ServerCallHandler callHandler() { return callHandler; } + /** + * Return the method's request type. + * + * @return request type + */ + public Class requestType() { + return requestType; + } + + /** + * Return the method's response type. + * + * @return response type + */ + public Class responseType() { + return responseType; + } + /** * Obtain the {@link Map} of {@link Context.Key}s and values to add to the * call context when this method is invoked. @@ -328,6 +352,8 @@ public MethodDescriptor build() { return new MethodDescriptor<>(name, descriptor.build(), + (Class) requestType, + (Class) requestType, callHandler, context, interceptors);