Git Product home page Git Product logo

micronaut-grpc's Introduction

Micronaut GRPC

Maven Central Build Status Revved up by Develocity

This project includes integration between Micronaut and GRPC.

Documentation

See the Documentation for more information.

See the Snapshot Documentation for the current development docs.

Examples

Examples for Java, Kotlin and Groovy can be found in the examples directory.

Snapshots and Releases

Snaphots are automatically published to JFrog OSS using Github Actions.

See the documentation in the Micronaut Docs for how to configure your build to use snapshots.

Releases are published to JCenter and Maven Central via Github Actions.

A release is performed with the following steps:

micronaut-grpc's People

Contributors

altro3 avatar alvarosanchez avatar dependabot-preview[bot] avatar dependabot[bot] avatar graemerocher avatar ilopmar avatar jameskleeh avatar jeremyg484 avatar jntakpe avatar kow3ns avatar luistrigueiros avatar marcoscouto avatar marcosflobo avatar mickoallen avatar micronaut-build avatar milanspre avatar moehaydar avatar msupic avatar n0tl3ss avatar newink avatar racevedoo avatar rafaelpontezup avatar renovate[bot] avatar schneidersteve avatar sdelamo avatar t0rr3sp3dr0 avatar timyates avatar victorbonnet avatar victorgs18 avatar wetted avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

micronaut-grpc's Issues

Micronaut fails to create GrpcNamedManagedChannelConfiguration.class due to multiple executor services

Task List

  • Steps to reproduce provided
  • Stacktrace (if present) provided
  • Example that reproduces the problem uploaded to Github
  • Full description of the issue provided (see below)

Steps to Reproduce

  1. Using micronaut launch simply create a gRPC application
  2. In application.yml file configure a gRPC channel as follow:
grpc:
  channels:
    foo:
      address: http://localhost:8081
      plaintext: true
  1. Try to inject the configured channel:
@Factory
class GrpcConfig {
    @Singleton
    fun userStub(@GrpcChannel("foo") channel: ManagedChannel): ExecutorServiceGrpc.ExecutorServiceBlockingStub {
        return ExecutorServiceGrpc.newBlockingStub(channel)
    }
}
  1. Try to inject the stub:
@Singleton
class BarService(private val fooStub: ExecutorServiceGrpc.ExecutorServiceBlockingStub): ExecutorServiceGrpc.ExecutorServiceImplBase() {
    override fun send(request: ExecutorRequest, responseObserver: StreamObserver<ExecutorReply>) {
        fooStub.toString()
        responseObserver.onCompleted()
    }
}

Expected Behaviour

The channel (@GrpcChannel("foo") channel: ManagedChannel) should be configured and injected.

Actual Behaviour

The application fails to start with the following message:

Message: Multiple possible bean candidates found: [java.util.concurrent.ExecutorService, java.util.concurrent.ExecutorService]
Path Taken: new GrpcNamedManagedChannelConfiguration(String name,Environment env,[ExecutorService executorService])
io.micronaut.context.exceptions.DependencyInjectionException: Failed to inject value for parameter [executorService] of class: io.micronaut.grpc.channels.GrpcNamedManagedChannelConfiguration

Message: Multiple possible bean candidates found: [java.util.concurrent.ExecutorService, java.util.concurrent.ExecutorService]
Path Taken: new GrpcNamedManagedChannelConfiguration(String name,Environment env,[ExecutorService executorService])

Environment Information

  • Operating System: OS X Mojave : 10.14.6
  • Micronaut Version: 2.0.0
  • JDK Version: 11.0.7 hotspot

Example Application

simple reproducer

Service discovery registration with Postfix

Hi. I'm newbie to micronaut world and trying to apply micronaut to our legacy system (spring based).

When I tried to use grpc with service discovery, there was a problem.

If we have multiple server instances (e.g. http netty server, grpc server), we can get only one service entry in consul, but expecting two service entries. Moereover, entries registered randomly at server startup time (I think it is affected by the sequence of registration event).

See below.
you can see that registration events fired two times

2019-08-13T08:27:17,990Z [main] INFO  c.n.a.grpcdemo.event.EventListener - server startup event received.
2019-08-13T08:27:18,157Z [main] INFO  c.n.a.grpcdemo.event.EventListener - server startup event received.
2019-08-13T08:27:18,535Z [main] INFO  i.m.g.s.GrpcEmbeddedServerListener - GRPC started on port 14000
2019-08-13T08:27:18,548Z [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 3107ms. Server Running: http://0.0.0.0:28081
2019-08-13T08:27:18,777Z [nioEventLoopGroup-1-2] INFO  i.m.d.registration.AutoRegistration - Registered service [greeter-service] with Consul
2019-08-13T08:27:18,777Z [nioEventLoopGroup-1-3] INFO  i.m.d.registration.AutoRegistration - Registered service [greeter-service] with Consul

And service entry is like this.

greeter-service-org

This is because micronaut register service entries only by service name, set from [micronaut.application.name].

When it comes to grpc, I suggest putting some postfix like '-grpc', and find grpc service by 'SERVICE_ID-grpc'.

So probably result will be like this.

greeter-service-after

If this idea is acceptable, I can submit PR as this strategy.

Plase consider this improvement.

GRPC Server certificates and keys are not loaded from filesystem

I'm trying to run GRPC sever using ssl certificates. According to the docs, I can specify path to certificates like this:

grpc:
    server:
        ssl:
            cert-chain: '/path/to/my.cert'
            private-key: '/path/to/my.key'

However, my certificates are mounted in file system, and paths above can only be loaded from resources.
I tried different paths patterns like /path/to/my.cert, file:/path/to/my.cert, file://path/to/my.cert, none of them worked.
Debugging through the code I found a line final Optional<InputStream> certChain = sslConfiguration.getCertChain() .flatMap(environment::getResourceAsStream); which suggests that framework use some kind of classpath resource loader to load certs.

Task List

  • Steps to reproduce provided
  • Stacktrace (if present) provided
  • Example that reproduces the problem uploaded to Github
  • Full description of the issue provided (see below)

Steps to Reproduce

  1. Setup app with grpc service
  2. set grpc server configuration
grpc:
  server:
    port: 8443
    ssl:
      cert-chain: 'file://Users/szymonmysiak/some/path/tls.crt'
      private-key: 'file://Users/szymonmysiak/some/path/tls.key'

Expected Behaviour

There is possibility to load certificates from both filesystem and classpath

Actual Behaviour

Only classpath files are considered

Environment Information

  • Operating System: Macos Catalina 10.15.5
  • Micronaut Version: core: 2.0.0, grpc-server-runtime: 2.0.0
  • JDK Version: 11

ByteBufToProtoMessageConverter does not respect @Consumes annotation

ByteBufToProtoMessageConverter throws IllegalStateException, but TypeConverter::convert method javadoc says that * @return The converted type or empty if the conversion is not possible

throw new IllegalStateException("Error parsing: " + e.getMessage());

This behavior makes impossible to deserialize body for websocket handlers by using @Consumes annotation with Protobuf generated java classes. AbstractNettyWebSocketHandler tries to convert object using conversion service which determines converter by argument type. Converter tries to call ByteBufToProtoMessageConverter and fails (body is json string). ByteBufToProtoMessageConverter should return Optional.empty() here and then AbstractNettyWebSocketHandler will read MediaType from @Consumes annotation and deserialize with proper converter.

TTL check for grpc endpoint failed when http endpoint is present

I'm creating grpc microservice. My service also exposes some metrics for prometheus via http endpoint and uses consul for service discovery. When I start application, both grpc and http endpoints automatically registered into consul with TTL check. But after some time, consul mark grpc endpoint as failed because application didn't sent any ttl checks about it.

Not sure, but this is may be because HeartbeatTask is a @Singleton and grpc check was overwritten by http check.

Is it possible to disable auto registration for http endpoint? Can't find any configuration properties for this.

Steps to Reproduce

Start consul, create test project:

docker run -d -p 8500:8500 consul
mn create-grpc-app hello -f discovery-consul

Apply patch:

diff --git a/build.gradle b/build.gradle
index ae11713..f132a62 100644
--- a/build.gradle
+++ b/build.gradle
@@ -39,6 +39,7 @@ dependencies {
     testImplementation("org.junit.jupiter:junit-jupiter-api")
     testImplementation("io.micronaut.test:micronaut-test-junit5")
     testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
+    implementation("io.micronaut:micronaut-http-server-netty")
 }
 
 test.classpath += configurations.developmentOnly
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 856a624..b55ed60 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,6 +1,13 @@
 micronaut:
   application:
     name: hello
+  server:
+    port: 1234
+
+grpc:
+  server:
+    port: 4321
+
 consul:
   client:
     registration:

Start server:

$ ./gradlew run

> Task :run
16:02:50.775 [main] INFO  i.m.g.s.GrpcEmbeddedServerListener - GRPC started on port 4321
16:02:50.781 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 1145ms. Server Running: http://localhost:1234
16:02:51.056 [nioEventLoopGroup-1-2] INFO  i.m.d.registration.AutoRegistration - Registered service [hello] with Consul
16:02:51.056 [nioEventLoopGroup-1-3] INFO  i.m.d.registration.AutoRegistration - Registered service [hello] with Consul

Ensure service registered:

$ curl http://127.0.0.1:8500/v1/agent/checks
{
    "service:hello:1234": {
        "Node": "f821e823f2ee",
        "CheckID": "service:hello:1234",
        "Name": "Service 'hello' check",
        "Status": "passing",
        "Notes": "",
        "Output": "",
        "ServiceID": "hello:1234",
        "ServiceName": "hello",
        "ServiceTags": [],
        "Type": "ttl",
        "Definition": {},
        "CreateIndex": 0,
        "ModifyIndex": 0
    },
    "service:hello:4321": {
        "Node": "f821e823f2ee",
        "CheckID": "service:hello:4321",
        "Name": "Service 'hello' check",
        "Status": "passing",
        "Notes": "",
        "Output": "",
        "ServiceID": "hello:4321",
        "ServiceName": "hello",
        "ServiceTags": [],
        "Type": "ttl",
        "Definition": {},
        "CreateIndex": 0,
        "ModifyIndex": 0
    }
}

After some time status for grpc endpoint would be changed from passing to critical.

Also at debug log level, micronaut reports only http endpoint checks:

02:14:35.781 [pool-1-thread-1] DEBUG - Resolved existing bean [io.micronaut.health.HeartbeatTask@72825400] for type [class io.micronaut.health.HeartbeatTask] and qualifier [null]
02:14:35.781 [pool-1-thread-1] DEBUG - Publishing event: io.micronaut.health.HeartbeatEvent[source=io.micronaut.http.server.netty.NettyEmbeddedServerInstance@7e7f3cfd]
02:14:35.782 [pool-1-thread-1] DEBUG - Resolving beans for type: <HeartbeatEvent> io.micronaut.context.event.ApplicationEventListener 
02:14:35.782 [pool-1-thread-1] DEBUG - Qualifying bean [io.micronaut.context.event.ApplicationEventListener] from candidates [Definition: io.micronaut.health.HeartbeatTask, Definition: io.micronaut.logging.PropertiesLoggingLevelsConfigurer, Definition: io.micronaut.grpc.server.GrpcEmbeddedServerListener, Definition: io.micronaut.discovery.consul.registration.ConsulAutoRegistration, Definition: io.micronaut.runtime.context.scope.refresh.RefreshScope, Definition: io.micronaut.runtime.http.scope.RequestCustomScope, Definition: io.micronaut.discovery.consul.ConsulServiceInstanceList] for qualifier: <HeartbeatEvent> 
02:14:35.782 [pool-1-thread-1] DEBUG - Registering singleton bean io.micronaut.discovery.consul.registration.ConsulAutoRegistration@11f406f8 for type [<HeartbeatEvent> io.micronaut.context.event.ApplicationEventListener] using bean key <HeartbeatEvent> io.micronaut.discovery.consul.registration.ConsulAutoRegistration
02:14:35.782 [pool-1-thread-1] DEBUG - Found 1 beans for type [<HeartbeatEvent> io.micronaut.context.event.ApplicationEventListener]: [io.micronaut.discovery.consul.registration.ConsulAutoRegistration@11f406f8] 
02:14:35.783 [pool-1-thread-1] DEBUG - Reporting status for Check ID [service:hello:1234]: UP
02:14:35.792 [nioEventLoopGroup-1-4] DEBUG - Sending HTTP Request: PUT /v1/agent/check/pass/service%3Ahello%3A1234
02:14:35.792 [nioEventLoopGroup-1-4] DEBUG - Chosen Server: localhost(8500)
02:14:35.800 [nioEventLoopGroup-1-4] DEBUG - Successfully reported passing state to Consul
02:14:50.790 [pool-1-thread-2] DEBUG - Resolved existing bean [io.micronaut.health.HeartbeatTask@72825400] for type [class io.micronaut.health.HeartbeatTask] and qualifier [null]
02:14:50.790 [pool-1-thread-2] DEBUG - Publishing event: io.micronaut.health.HeartbeatEvent[source=io.micronaut.http.server.netty.NettyEmbeddedServerInstance@7e7f3cfd]
02:14:50.790 [pool-1-thread-2] DEBUG - Resolving beans for type: <HeartbeatEvent> io.micronaut.context.event.ApplicationEventListener 
02:14:50.791 [pool-1-thread-2] DEBUG - Reporting status for Check ID [service:hello:1234]: UP
02:14:50.797 [nioEventLoopGroup-1-5] DEBUG - Sending HTTP Request: PUT /v1/agent/check/pass/service%3Ahello%3A1234
02:14:50.797 [nioEventLoopGroup-1-5] DEBUG - Chosen Server: localhost(8500)
02:14:50.807 [nioEventLoopGroup-1-5] DEBUG - Successfully reported passing state to Consul

Environment Information

  • Operating System: ubuntu 18.04
  • Micronaut Version: 2.0.0.M3
  • JDK Version: 11

loadbalancing support

Is there any mechanism to support grpc loadbalancing?

I tried 3 grpc server (greeter-service), and 1 grpc client with Consul discovery.

See Below.

greeter-service

Service discovery itself was worked as expected.
But once client connected to one specific server, following requests were handled only that server, until I shutdown that server.

I turned off service discovery cache (mictonaut.caches.discovery-client.enabled: false), and set server keep-alive-time to 3s (grpc.server.keep-alive-time: 3s).

When it comes to REST API servers with @client annotation, it acted with round-robin strategy.

Happy Micronauting!

Integration with Security

For now, security seems to be completely up to the developer. It does not really feel like writing a Micronaut service where we usually just extend the AuthenticationProvider and that's it.

It would be nice if micronaut-grpc would also "just apply" the micronaut-security stuff. But I guess micronaut-security is somewhat closely coupled to the HTTP calls (e.g. the AuthenticationProvider.authenticate() wants a HttpRequest) and this would need some rewrite.

GrpcServerHealthIndicator breaks the /health endpoint when gRPC Server is DOWN

The GrpcServerHealthIndicator breaks with an unexpected error when the gRPC Server is DOWN. After the gRPC Server is stopped the /health endpoint starts to return a 500 Internal Server Error instead of 503 Service Unavailable.

Steps to Reproduce

  1. Run a Micronaut application with a gRPC Server enabled;
  2. Stop the gRPC Server programmatically (for example: create a controller with an endpoint to stop the server);
  3. Consume the /health endpoint and receive the error 500 Internal Server Error:
curl -i --request GET --url http://localhost:8080/health

HTTP/1.1 500 Internal Server Error
Content-Type: application/json
content-length: 55
connection: close

{"message":"Internal Server Error: Already terminated"}
  1. See the exception and stacktrace in the console:
12:18:49.235 [ForkJoinPool.commonPool-worker-5] ERROR i.m.h.s.netty.RoutingInBoundHandler - Unexpected error occurred: Already terminated
java.lang.IllegalStateException: Already terminated
	at com.google.common.base.Preconditions.checkState(Preconditions.java:511)
	at io.grpc.internal.ServerImpl.getPort(ServerImpl.java:197)
	at io.micronaut.grpc.server.GrpcEmbeddedServer.getPort(GrpcEmbeddedServer.java:123)
	at io.micronaut.grpc.server.health.GrpcServerHealthIndicator.getHealthResult(GrpcServerHealthIndicator.java:70)
	at io.micronaut.core.async.publisher.AsyncSingleResultPublisher$ExecutorServiceSubscription.lambda$request$1(AsyncSingleResultPublisher.java:100)
	at java.base/java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1407)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)

Expected Behaviour

The /health endpoint should return an error 503 Service Unavailable informing that the grpc-server service is DOWN instead of a 500 Internal Server Error.

Actual Behaviour

The /health endpoint is retuning an HTTP status 500 with with the following payload:

{
  "message": "Internal Server Error: Already terminated"
}

Environment Information

  • Operating System: Windows 10
  • Micronaut Version: 2.2.3
  • JDK Version: Java 11

Example Application

  • This repository shows the issue and the fix. To verify the error you must first disable the CustomGrpcServerHealthIndicator (just comment out its annotations) and run the application.

Unable to create native image for GRPC project

Building a native image for a Micronaut GRPC project fails due to the absence of SubstrateVM substitutions for Netty. When the Micronaut HTTP client dependency is added (which pulls in the substitutions) the building of the images fails due to the detection of a direct/mapped ByteBuffer in the image heap, which seems to be caused by the Netty GRPC shutdown handler.

The following steps reproduce the issue:

The project is created with the Micronaut 1.1.3 (but I've also tried 1.2.0 RC1) command line tool:

$ mn create-app micronaut-grpc-graal --build=maven --profile grpc --features=graal-native-image,java

Building that project fails due to the absence of the SubstrateVM substitutes for Netty:

$ ./mvnw clean package && ./docker-build.sh
...
Warning: RecomputeFieldValue.ArrayIndexScale automatic substitution failed. The automatic substitution registration was attempted because a call to sun.misc.Unsafe.arrayIndexScale(Class) was detected in the static initializer of io.netty.util.internal.PlatformDependent0. Detailed failure reason(s): The field java.lang.Long.value, where the value produced by the array index scale computation is stored, is not static.
Warning: RecomputeFieldValue.FieldOffset automatic substitution failed. The automatic substitution registration was attempted because a call to sun.misc.Unsafe.objectFieldOffset(Field) was detected in the static initializer of io.netty.util.internal.PlatformDependent0. Add a RecomputeFieldValue.FieldOffset manual substitution for io.netty.util.internal.PlatformDependent0.ADDRESS_FIELD_OFFSET. Detailed failure reason(s): The argument of Unsafe.objectFieldOffset(Field) is not a constant field.
Warning: RecomputeFieldValue.ArrayIndexScale automatic substitution failed. The automatic substitution registration was attempted because a call to sun.misc.Unsafe.arrayIndexScale(Class) was detected in the static initializer of io.micronaut.caffeine.cache.UnsafeRefArrayAccess. Detailed failure reason(s): Could not determine the field where the value produced by the call to sun.misc.Unsafe.arrayIndexScale(Class) for the array index scale computation is stored. The call is not directly followed by a field store or by a sign extend node followed directly by a field store.
Warning: RecomputeFieldValue.FieldOffset automatic substitution failed. The automatic substitution registration was attempted because a call to sun.misc.Unsafe.objectFieldOffset(Field) was detected in the static initializer of io.netty.util.internal.CleanerJava6. Detailed failure reason(s): The argument of Unsafe.objectFieldOffset(Field) is not a constant field., Could not determine the field where the value produced by the call to sun.misc.Unsafe.objectFieldOffset(Field) for the field offset computation is stored. The call is not directly followed by a field store or by a sign extend node followed directly by a field store.
Warning: RecomputeFieldValue.ArrayIndexScale automatic substitution failed. The automatic substitution registration was attempted because a call to sun.misc.Unsafe.arrayIndexScale(Class) was detected in the static initializer of io.netty.util.internal.shaded.org.jctools.util.UnsafeRefArrayAccess. Detailed failure reason(s): Could not determine the field where the value produced by the call to sun.misc.Unsafe.arrayIndexScale(Class) for the array index scale computation is stored. The call is not directly followed by a field store or by a sign extend node followed directly by a field store.
Warning: RecomputeFieldValue.FieldOffset automatic substitution failed. The automatic substitution registration was attempted because a call to sun.misc.Unsafe.objectFieldOffset(Field) was detected in the static initializer of io.netty.buffer.AbstractReferenceCountedByteBuf. Detailed failure reason(s): Could not determine the field where the value produced by the call to sun.misc.Unsafe.objectFieldOffset(Field) for the field offset computation is stored. The call is not directly followed by a field store or by a sign extend node followed directly by a field store.
Warning: class initialization of class io.netty.util.internal.logging.Log4JLogger failed with exception java.lang.NoClassDefFoundError: org/apache/log4j/Priority. This class will be initialized at run time because either option --report-unsupported-elements-at-runtime or option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=io.netty.util.internal.logging.Log4JLogger to explicitly request delayed initialization of this class.
Warning: class initialization of class io.netty.handler.ssl.ConscryptAlpnSslEngine failed with exception java.lang.NoClassDefFoundError: org/conscrypt/BufferAllocator. This class will be initialized at run time because either option --report-unsupported-elements-at-runtime or option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=io.netty.handler.ssl.ConscryptAlpnSslEngine to explicitly request delayed initialization of this class.
...

Micronaut supplies those substitutes with their Netty components, which are also needed when using the Micronaut rest client.
The micronaut-http-client dependency is added to the pom file:

<dependency>
  <groupId>io.micronaut</groupId>
  <artifactId>micronaut-http-client</artifactId>
  <scope>compile</scope>
</dependency>

This gives a different error when building the native image:

$ ./mvnw clean package && ./docker-build.sh
...
Warning: Aborting stand-alone image build. Detected a direct/mapped ByteBuffer in the image heap. A direct ByteBuffer has a pointer to unmanaged C memory, and C memory from the image generator is not available at image run time. A mapped ByteBuffer references a file descriptor, which is no longer open and mapped at run time. The object was probably created by a class initializer and is reachable from a static field. By default, all class initialization is done during native image building.You can manually delay class initialization to image run time by using the option -H:ClassInitialization=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point.
Detailed message:
Trace: 	object io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf
	object io.netty.buffer.ReadOnlyByteBuf
	object io.netty.buffer.UnreleasableByteBuf
	method io.netty.handler.codec.http2.DefaultHttp2FrameWriter.writeContinuationFrames(ChannelHandlerContext, int, ByteBuf, int, Http2CodecUtil$SimpleChannelPromiseAggregator)
Call path from entry point to io.netty.handler.codec.http2.DefaultHttp2FrameWriter.writeContinuationFrames(ChannelHandlerContext, int, ByteBuf, int, Http2CodecUtil$SimpleChannelPromiseAggregator):
	at io.netty.handler.codec.http2.DefaultHttp2FrameWriter.writeContinuationFrames(DefaultHttp2FrameWriter.java:555)
	at io.netty.handler.codec.http2.DefaultHttp2FrameWriter.writeHeadersInternal(DefaultHttp2FrameWriter.java:534)
	at io.netty.handler.codec.http2.DefaultHttp2FrameWriter.writeHeaders(DefaultHttp2FrameWriter.java:266)
	at io.netty.handler.codec.http2.Http2OutboundFrameLogger.writeHeaders(Http2OutboundFrameLogger.java:60)
	at io.netty.handler.codec.http2.DecoratingHttp2FrameWriter.writeHeaders(DecoratingHttp2FrameWriter.java:53)
	at io.grpc.netty.NettyServerHandler$WriteMonitoringFrameWriter.writeHeaders(NettyServerHandler.java:958)
	at io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder.writeHeaders(DefaultHttp2ConnectionEncoder.java:205)
	at io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder.writeHeaders(DefaultHttp2ConnectionEncoder.java:146)
	at io.netty.handler.codec.http2.Http2ConnectionHandler.handleServerHeaderDecodeSizeError(Http2ConnectionHandler.java:720)
	at io.netty.handler.codec.http2.Http2ConnectionHandler.onStreamError(Http2ConnectionHandler.java:696)
	at io.grpc.netty.NettyServerHandler.onStreamError(NettyServerHandler.java:506)
	at io.netty.handler.codec.http2.Http2ConnectionHandler.onError(Http2ConnectionHandler.java:610)
	at io.grpc.netty.NettyServerHandler$GracefulShutdown.secondGoAwayAndClose(NettyServerHandler.java:921)
	at io.grpc.netty.NettyServerHandler$GracefulShutdown$1.run(NettyServerHandler.java:885)
	at com.oracle.svm.core.jdk.RuntimeSupport.executeHooks(RuntimeSupport.java:144)
	at com.oracle.svm.core.jdk.RuntimeSupport.executeStartupHooks(RuntimeSupport.java:89)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:145)
	at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0)

Warning: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception

It seems like it tries to access a ByteBuffer when the shutdown handler is being invoked.

The GraalVM native image support in Micronaut works great for REST services, I would expect the same for GRPC services.

I've created a sample repo to reproduce the issue, see https://github.com/auke-/micronaut-grpc-graal.

gRPC Client does not read the Channel configuration from the properties file

Micronaut gRPC Client does not read the channel configuration from the properties file.

Task List

  • Steps to reproduce provided
  • Stacktrace (if present) provided
  • Example that reproduces the problem uploaded to Github
  • Full description of the issue provided (see Expected Behaviour)

Steps to Reproduce

  1. There is a unit test in ClientServiceTest.java to reproduce the exception, running that unit test will call the gRPC client and will give you the attached (at the end) exception.

Expected Behaviour

As per the Micronaut gRPC Client documentation you can define channel configuration in properties file such as below.

grpc:
  channels:
    clientaddress:
      address: 'http://localhost:8186'
      plaintext: true
      max-retry-attempts: 10

Once you define the above configuration then you can use it inside the annotation @GrpcChannel("clientaddress") it will eliminate the need of writing server address inside the annotation and will read it from the properties file.

Actual Behaviour

It is not able to read the clientaddress configuration from the properties file and throw a runtime exception with the below message (Stacktrace at the end of the file).

UNAVAILABLE: Unable to resolve host clientaddress

However if I define the server address inside annotation @GrpcChannel("http://localhost:8186") then it works completely fine.

Environment Information

  • Operating System: MacOS v11.2.3
  • Micronaut Version: 2.5.0
  • JDK Version: 11

Example Application

Stacktrace

io.grpc.StatusRuntimeException: UNAVAILABLE: Unable to resolve host clientaddress
        at io.grpc.stub.ClientCalls.toStatusRuntimeException(ClientCalls.java:262)
        at io.grpc.stub.ClientCalls.getUnchecked(ClientCalls.java:243)
        at io.grpc.stub.ClientCalls.blockingUnaryCall(ClientCalls.java:156)
        at com.issue.GrpcClientServiceGrpc$GrpcClientServiceBlockingStub.send(GrpcClientServiceGrpc.java:169)
        at com.issue.ClientService.callStub(ClientService.java:15)
        at com.issue.ClientServiceTest.verifyCleintOk(ClientServiceTest.java:17)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
        at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
        at org.junit.jupiter.api.extension.InvocationInterceptor.interceptTestMethod(InvocationInterceptor.java:117)
        at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
        at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
        at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
        at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
        at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
        at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
        at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
        at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
        at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
        at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
        at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
        at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
        at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
        at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
        at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
        at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
        at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
        at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
        at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
        at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
        at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
        at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
        at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
        at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
        at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
        at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
        at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
        at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
        at com.sun.proxy.$Proxy2.stop(Unknown Source)
        at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:135)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
        at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
        at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
        at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
        at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
        at java.base/java.lang.Thread.run(Thread.java:834)
        Caused by: java.lang.RuntimeException: java.net.UnknownHostException: clientaddress: nodename nor servname provided, or not known
        at io.grpc.internal.DnsNameResolver.resolveAddresses(DnsNameResolver.java:223)
        at io.grpc.internal.DnsNameResolver.doResolve(DnsNameResolver.java:282)
        at io.grpc.internal.DnsNameResolver$Resolve.run(DnsNameResolver.java:318)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        ... 1 more
        Caused by: java.net.UnknownHostException: clientaddress: nodename nor servname provided, or not known
        at java.base/java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method)
        at java.base/java.net.InetAddress$PlatformNameService.lookupAllHostAddr(InetAddress.java:929)
        at java.base/java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1515)
        at java.base/java.net.InetAddress$NameServiceAddresses.get(InetAddress.java:848)
        at java.base/java.net.InetAddress.getAllByName0(InetAddress.java:1505)
        at java.base/java.net.InetAddress.getAllByName(InetAddress.java:1364)
        at java.base/java.net.InetAddress.getAllByName(InetAddress.java:1298)
        at io.grpc.internal.DnsNameResolver$JdkAddressResolver.resolveAddress(DnsNameResolver.java:631)
        at io.grpc.internal.DnsNameResolver.resolveAddresses(DnsNameResolver.java:219)
        ... 5 more

Using -1 as Random Port fail

Hi !

I try to run a gRPC test suite so it require a random port.
Documentation state to use in application.yml

grpc:
  server:
    port: -1

But it lead to

Message: port out of range:-1

Thank you.

Client cancelation of streaming request

Hello!

I can mention I'm fairly new to gRPC overall so I might have missed something. But, Iโ€™m looking into how to cancel a gRPC stream request, normally if I understand the gRPC documentation correctly that would be done on the channel context or converting the StreamObserver to a ClientCallStreamObserver.
Neither solution would work right now as far as I can see, as I do not have access to the context and ClientCallStreamObserver is not even part of micronaut gRPC.

I did find that you can throw and exception in the client which will cancel the stream, but that seems like bad control flow and I think it would be better to have a more framework supported solution.

Grpc Server can't start on Amazon ECS

I am trying to use micronaut (v2.4.0) with Kotlin (v1.5.0) on AWS. In AWS I have setup an Elastic Container Service cluster using Fargate (AWS hosts the server cluster, not me).

My server fails to start because with an error java.net.SocketException originiating from io.micronaut.discovery.cloud.ComputeInstanceMetadataResolverUtils.readMetadataUrl(...) and exits causing the task to fail.

Task List

  • Steps to reproduce provided
  • Stacktrace (if present) provided
  • Example that reproduces the problem uploaded to Github
  • Full description of the issue provided (see below)

Steps to Reproduce

  1. Create a hello world gRPC project with micronaut-grpc v2.4.0 (I also reproed this on 2.2 and 2.3 and I believe the issue exists in every version)
  2. Create a docker container using the dockerBuild gradle task
  3. In AWS create an ECR repo for the container and an ECS cluster backed with Fargate
  4. docker push your container to the ECR repo
  5. Create a task definition in ECS using your container. Configure a log group for your task here.
    e.g. like this
{
  "ipcMode": null,
  "executionRoleArn": "arn:aws:iam::xxxxxx:role/execRole",
  "containerDefinitions": [
    {
      "dnsSearchDomains": null,
      "environmentFiles": null,
      "logConfiguration": {
        "logDriver": "awslogs",
        "secretOptions": null,
        "options": {
          "awslogs-group": "frontend-app",
          "awslogs-region": "us-west-1",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "portMappings": [
        {
          "hostPort": 50051,
          "protocol": "tcp",
          "containerPort": 50051
        },
        {
          "hostPort": 8080,
          "protocol": "tcp",
          "containerPort": 8080
        }
      ],
      "command": null,
      "linuxParameters": null,
      "cpu": 0,
      "environment": [],
      "resourceRequirements": null,
      "ulimits": null,
      "dnsServers": null,
      "mountPoints": [],
      "workingDirectory": null,
      "secrets": null,
      "dockerSecurityOptions": null,
      "memory": null,
      "memoryReservation": null,
      "volumesFrom": [],
      "stopTimeout": null,
      "image": "956515734762.dkr.ecr.us-west-1.amazonaws.com/elodie/skies-frontend-app:0.1-TEST2",
      "startTimeout": null,
      "firelensConfiguration": null,
      "dependsOn": null,
      "disableNetworking": null,
      "interactive": null,
      "healthCheck": null,
      "essential": true,
      "links": null,
      "hostname": null,
      "extraHosts": null,
      "pseudoTerminal": null,
      "user": null,
      "readonlyRootFilesystem": null,
      "dockerLabels": null,
      "systemControls": null,
      "privileged": null,
      "name": "app"
    }
  ],
  "placementConstraints": [],
  "memory": "2048",
  "taskRoleArn": "arn:aws:iam::xxxxx:role/taskRole",
  "compatibilities": [
    "EC2",
    "FARGATE"
  ],
  "taskDefinitionArn": "arn:aws:ecs:us-west-1:xxxxxxx:task-definition/service:8",
  "family": "service",
  "requiresAttributes": [
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.execution-role-awslogs"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.ecr-auth"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.task-iam-role"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.execution-role-ecr-pull"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.task-eni"
    }
  ],
  "pidMode": null,
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "networkMode": "awsvpc",
  "cpu": "512",
  "revision": 8,
  "status": "ACTIVE",
  "inferenceAccelerators": null,
  "proxyConfiguration": null,
  "volumes": []
}
  1. Create a service in ECS to run your task definition with a count of 1 instance

Expected Behaviour

The service should run and the greet endpoint should be reachable and callable

Actual Behaviour

Your ECS task fails to start because the primary container exited in error
Look at the logs for the task to see a similar stacktrace to below

Stacktrace: https://gist.github.com/jmc-elodie/82037e7bb12a7376ca4085b14a3b7a52

Environment Information

  • Operating System: Alpine linux in Docker on ECS Fargate
  • Micronaut Version: 2.4.0
  • JDK Version: 16

Example Application

https://github.com/jmc-elodie/micronaut-grpc-repro

You will need to change the image in the gradle file to use your own ECR repo

Referencing channel specific configuration not working

The documentation mentions a way to provide channel specific configuration in application.yml. But if I try to access such an entry as described (like @GrpcChannel("greeter")) I only get UknownHostException: greeter. Has the syntax for the feature or the configuration entries changed?

Service Discovery - Service name not known until runtime

As long as I know the service name ahead of time (e.g. @GrpcChannel("greeter")), all is well, but I don't see a way to handle the scenario where the app doesn't get the service name until runtime.

Is there a way to utilize Micronaut service discovery to discover a service whose name is not known by the application until runtime?

Thanks!

HTTP2 Protocol Error / DSPE / Invalid Header

In a Kubenetes deployment with Istio or Linkerd, the grpc client created with @GrpcChannel("servicename") fails with a downstream protocol error (DSPE) / Invalid HTTP Header when calling the service.

If the grpc client is created with ManagedChannelBuilder.forTarget("servicename:port"), there are no errors.

While a Consul sidecar does not report the problem, both Istio and Linkerd sidecars (outbound) report the problem and kill the stream.

micronautVersion=2.0.2
micronautGrpcVersion = 2.0.5
kotlinVersion=1.3.72
kotlinxCoroutinesVersion=1.3.3
protocVersion=3.12.2
grpcVersion=1.29.0
grpcKotlinVersion=0.1.2

An example application (based on micronaut-grpc/examples/hello-world-kotlin), with additional detail and steps to produce, can be found here: https://github.com/SUMGlobal/dspe

Micrometer metrics

Are there any built-in metrics? Shouldn't micronaut grpc integrate with micrometer?

java.nio.channels.UnresolvedAddressException when using gRPC clients configured with micronaut-grpc

When using gRPC clients configured with micronaut-grpc sometimes everything works and sometimes you get java.nio.channels.UnresolvedAddressException.

After some checking, I found the issue is because there are two different converters for SocketAddress with different behavior.

Converter provided by micronaut-http creates unresolved SocketAddress instances:

        conversionService.addConverter(
                CharSequence.class,
                SocketAddress.class,
                (object, targetType, context) -> {
                    String[] parts = object.toString().split(":");
                    if (parts.length == 2) {
                        int port = Integer.parseInt(parts[1]);
                        return Optional.of(InetSocketAddress.createUnresolved(parts[0], port));
                    } else {
                        return Optional.empty();
                    }
                }
        );

Converter provided by micronaut-grpc-client-runtime creates resolved SocketAddress instances:

        conversionService.addConverter(CharSequence.class, SocketAddress.class, charSequence -> {
            String[] parts = charSequence.toString().split(":");
            if (parts.length == 2) {
                int port = Integer.parseInt(parts[1]);
                return new InetSocketAddress(parts[0], port);
            } else {
                return null;
            }
        });

Depending on the order those converters are registered gRPC clients will work or will fail throwing java.nio.channels.UnresolvedAddressException.

Task List

  • Steps to reproduce provided
  • Stacktrace (if present) provided
  • Example that reproduces the problem uploaded to Github
  • Full description of the issue provided (see below)

Steps to Reproduce

  1. Run example application several times
  2. Check log output

Expected Behaviour

gRPC clients work and do not throw java.nio.channels.UnresolvedAddressException if properly configured.

Actual Behaviour

gRPC clients sometimes work but other times throw java.nio.channels.UnresolvedAddressException and the only solution is to restart the application and hope for converters to be registered in proper order.

Environment Information

  • Operating System: Mac OS X Big Sur
  • Micronaut Version: 2.2.0
  • JDK Version: 11.0.9

Example Application

https://github.com/jmpalomar/micronaut-grpc-unresolved-issue

This is an small application that simply converts property grpc.channels.test.address value to SocketAddress. When executing the application several times you will see sometimes the address is resolved but others not:

% gradle run

> Task :run
08:02:02.876 [main] INFO  io.energiaplus.micronaut.Application - Optional[localhost/127.0.0.1:8080]

BUILD SUCCESSFUL in 4s
3 actionable tasks: 2 executed, 1 up-to-date
% gradle run

> Task :run
08:02:09.331 [main] INFO  io.energiaplus.micronaut.Application - Optional[localhost:8080]

BUILD SUCCESSFUL in 5s
3 actionable tasks: 1 executed, 2 up-to-date

GRPC server doesn't call awaitTermination on shutdown

The gRPC server offers a https://grpc.github.io/grpc-java/javadoc/io/grpc/Server.html#awaitTermination-- to ensure that the server is properly terminated. Currently only shutdownNow is being invoked:

It'd be great to add either an option or change default behavior to call awaitTermination with some configurable timeout.

Task List

  • Steps to reproduce provided
  • Stacktrace (if present) provided
  • Example that reproduces the problem uploaded to Github
  • Full description of the issue provided (see below)

Steps to Reproduce

  1. TODO
  2. TODO
  3. TODO

Expected Behaviour

Tell us what should happen

Actual Behaviour

Tell us what happens instead

Environment Information

  • Operating System: TODO
  • Micronaut Version: TODO
  • JDK Version: TODO

Example Application

  • TODO: link to github repository with example that reproduces the issue

Regarding gRPC with SSL

How can I configure gRPC server to use the ssl cert from the file system?

And what if the cert is updated, how can I configure gRPC server to reload and use the latest certificates?

Remove GraalVM warnings

Remove the warnings when creating a native-image:

Warning: class initialization of class io.netty.util.internal.logging.Log4JLogger failed with exception java.lang.NoClassDefFoundError: org/apache/log4j/Priority. This class will be initialized at run time because option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=io.netty.util.internal.logging.Log4JLogger to explicitly request delayed initialization of this class.
Warning: class initialization of class io.netty.handler.ssl.ReferenceCountedOpenSslContext failed with exception java.lang.NoClassDefFoundError: io/netty/internal/tcnative/SSLPrivateKeyMethod. This class will be initialized at run time because option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=io.netty.handler.ssl.ReferenceCountedOpenSslContext to explicitly request delayed initialization of this class.
Warning: class initialization of class io.netty.handler.ssl.JdkNpnApplicationProtocolNegotiator failed with exception java.lang.NoClassDefFoundError: org/eclipse/jetty/npn/NextProtoNego$Provider. This class will be initialized at run time because option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=io.netty.handler.ssl.JdkNpnApplicationProtocolNegotiator to explicitly request delayed initialization of this class.
Warning: class initialization of class io.netty.handler.ssl.ReferenceCountedOpenSslEngine failed with exception java.lang.NoClassDefFoundError: io/netty/internal/tcnative/SSL. This class will be initialized at run time because option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=io.netty.handler.ssl.ReferenceCountedOpenSslEngine to explicitly request delayed initialization of this class.
Warning: class initialization of class io.netty.handler.ssl.ConscryptAlpnSslEngine failed with exception java.lang.NoClassDefFoundError: org/conscrypt/BufferAllocator. This class will be initialized at run time because option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=io.netty.handler.ssl.ConscryptAlpnSslEngine to explicitly request delayed initialization of this class.
Warning: class initialization of class io.netty.handler.ssl.JettyAlpnSslEngine$ClientEngine failed with exception java.lang.NoClassDefFoundError: org/eclipse/jetty/alpn/ALPN$Provider. This class will be initialized at run time because option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=io.netty.handler.ssl.JettyAlpnSslEngine$ClientEngine to explicitly request delayed initialization of this class.
Warning: class initialization of class io.netty.handler.ssl.JettyAlpnSslEngine$ServerEngine failed with exception java.lang.NoClassDefFoundError: org/eclipse/jetty/alpn/ALPN$Provider. This class will be initialized at run time because option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=io.netty.handler.ssl.JettyAlpnSslEngine$ServerEngine to explicitly request delayed initialization of this class.
Warning: class initialization of class io.netty.handler.ssl.JettyNpnSslEngine failed with exception java.lang.NoClassDefFoundError: org/eclipse/jetty/npn/NextProtoNego$Provider. This class will be initialized at run time because option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=io.netty.handler.ssl.JettyNpnSslEngine to explicitly request delayed initialization of this class.

Feature Request: Support Server Interceptor Ordering

It would be nice for the GrpcServerBuilder class to support ordering of interceptors for predictable results.

Make use of Ordered interface to determine order of addition of interceptors to server builder.

Support for disabling gRPC server

Hi,

Is there any way to disable a gRPC server to start? In my case, I am using micronaut-grpc for grpc clients only, and it is starting a grpc server unnecessarily.

Shouldn't we have a grpc.server.enabled config?

Thanks!

Allow creating named clients for all configured channels

In our setup, we have multiple services that provide the same interface. e.g.

grpc:
  channels:
    service-a:
      address: ...
    service-b:
      address: ...

There seems to be no way to create a factory that produces named stubs without compile time knowledge about which channels to create stubs for, due to internal use of parameterized factories and a custom scope that handles @GrpcChannel.

Ideally, something like this should be possible:

@Factory
class NamedStubFactory {
   @EachBean(ManagedChannel.class)
   public CommonInterfaceStub stub(ManagedChannel channel) {
     return CommonInterfaceGrpc.newStub(channel);
   }
}

and then somewhere else

@Inject
@Named("service-a")

This does not work, because ManagedChannel beans are not @Named. Maybe there's an alternative solution?

Error using @GrpcChannel with native-image

I've set up a project that uses the micronaut grpc client and everything works fine until I try to run it from the native-image binary.

This is the exception I get after making a request to an HTTP controller method, that depends on another class which depends on the gRPC stub from the factory class:

Message: Missing bean argument [String target] for type: io.grpc.ManagedChannel. Required arguments: String target
Path Taken: ExampleController.exampleService --> ExampleService.exampleService --> ExampleAPIFutureStub.createStub([ManagedChannel channel])
	at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1573)
	at io.micronaut.context.DefaultBeanContext.getScopedBeanForDefinition(DefaultBeanContext.java:2076)
	at io.micronaut.context.DefaultBeanContext.getBeanForDefinition(DefaultBeanContext.java:1991)
	at io.micronaut.context.DefaultBeanContext.getBeanInternal(DefaultBeanContext.java:1963)
	at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:1082)
	at io.micronaut.context.AbstractBeanDefinition.getBeanForConstructorArgument(AbstractBeanDefinition.java:1007)
	at example.micronaut.$ChannelFactory$CreateStub0Definition.build(Unknown Source)
	at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1598)
	at io.micronaut.context.DefaultBeanContext.createAndRegisterSingleton(DefaultBeanContext.java:2307)
	at io.micronaut.context.DefaultBeanContext.getBeanForDefinition(DefaultBeanContext.java:1989)
	at io.micronaut.context.DefaultBeanContext.getBeanInternal(DefaultBeanContext.java:1963)
	at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:1082)
	at io.micronaut.context.AbstractBeanDefinition.getBeanForField(AbstractBeanDefinition.java:1401)
	at io.micronaut.context.AbstractBeanDefinition.injectBeanField(AbstractBeanDefinition.java:736)
	at example.micronaut.$ExampleServiceDefinition.injectBean(Unknown Source)
	at example.micronaut.$ExampleServiceDefinition.build(Unknown Source)
	at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1598)
	at io.micronaut.context.DefaultBeanContext.createAndRegisterSingleton(DefaultBeanContext.java:2307)
	at io.micronaut.context.DefaultBeanContext.getBeanForDefinition(DefaultBeanContext.java:1989)
	at io.micronaut.context.DefaultBeanContext.getBeanInternal(DefaultBeanContext.java:1963)
	at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:1082)
	at io.micronaut.context.AbstractBeanDefinition.getBeanForField(AbstractBeanDefinition.java:1401)
	at io.micronaut.context.AbstractBeanDefinition.injectBeanField(AbstractBeanDefinition.java:736)
	at example.micronaut.$ExampleControllerDefinition.injectBean(Unknown Source)
	at example.micronaut.$ExampleControllerDefinition.build(Unknown Source)
	at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1598)
	at io.micronaut.context.DefaultBeanContext.createAndRegisterSingleton(DefaultBeanContext.java:2307)
	at io.micronaut.context.DefaultBeanContext.getBeanForDefinition(DefaultBeanContext.java:1989)
	at io.micronaut.context.DefaultBeanContext.getBeanInternal(DefaultBeanContext.java:1963)
	at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:605)
	at io.micronaut.context.DefaultBeanContext$BeanExecutionHandle.getTarget(DefaultBeanContext.java:2821)
	at io.micronaut.context.DefaultBeanContext$BeanExecutionHandle.invoke(DefaultBeanContext.java:2842)
	at io.micronaut.web.router.AbstractRouteMatch.execute(AbstractRouteMatch.java:294)
	at io.micronaut.web.router.RouteMatch.execute(RouteMatch.java:122)
	at io.micronaut.http.server.netty.RoutingInBoundHandler.lambda$buildResultEmitter$15(RoutingInBoundHandler.java:1370)
	at io.reactivex.internal.operators.flowable.FlowableDefer.subscribeActual(FlowableDefer.java:35)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.internal.operators.flowable.FlowableMap.subscribeActual(FlowableMap.java:37)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.internal.operators.flowable.FlowableSwitchIfEmpty.subscribeActual(FlowableSwitchIfEmpty.java:32)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14868)
	at io.micronaut.http.context.ServerRequestTracingPublisher.lambda$subscribe$0(ServerRequestTracingPublisher.java:52)
	at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:52)
	at io.micronaut.http.context.ServerRequestTracingPublisher.subscribe(ServerRequestTracingPublisher.java:52)
	at io.reactivex.internal.operators.flowable.FlowableFromPublisher.subscribeActual(FlowableFromPublisher.java:29)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.micronaut.reactive.rxjava2.RxInstrumentedFlowable.subscribeActual(RxInstrumentedFlowable.java:68)
	at io.reactivex.Flowable.subscribe(Flowable.java:14918)
	at io.reactivex.Flowable.subscribe(Flowable.java:14865)
	at io.reactivex.internal.operators.flowable.FlowableSubscribeOn$SubscribeOnSubscriber.run(FlowableSubscribeOn.java:82)
	at io.reactivex.internal.schedulers.ExecutorScheduler$ExecutorWorker$BooleanRunnable.run(ExecutorScheduler.java:288)
	at io.reactivex.internal.schedulers.ExecutorScheduler$ExecutorWorker.run(ExecutorScheduler.java:253)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:510)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:518)
	at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1050)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:748)
	at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:479)
	at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:193)

This is the class that defines the channel:

package example.micronaut;

import com.example.ExampleAPIGrpc;
import io.grpc.ManagedChannel;
import io.micronaut.context.annotation.Factory;
import io.micronaut.grpc.annotation.GrpcChannel;

import javax.inject.Singleton;

@Factory
public class ChannelFactory {
    @Singleton
    public ExampleAPIGrpc.ExampleAPIFutureStub createStub(@GrpcChannel("example") ManagedChannel channel) {
        return ExampleAPIGrpc.newFutureStub(channel);
    }
}

Then in my application.yml, I have:

grpc:
  channels:
    example:
      target: localhost:6565
      plaintext: true
      max-retry-attempts: 10

Add a health check for the grpc-server

Add a grpc health check with the following configuration:
grpc.server.health.enabled

By default the value will be true.

Feature will require
io.micronaut:micronaut-management

Fix GraalVM warning in grpc-server

See https://gitlab.com/micronaut-projects/micronaut-graal-tests/-/jobs/774107689#L76

[grpc-server:214]    classlist:   4,450.33 ms,  2.28 GB
[grpc-server:214]        (cap):     557.02 ms,  2.28 GB
[grpc-server:214]        setup:   1,879.55 ms,  2.28 GB
Warning: class initialization of class io.netty.handler.ssl.JettyAlpnSslEngine$ClientEngine failed with exception java.lang.NoClassDefFoundError: org/eclipse/jetty/alpn/ALPN$Provider. This class will be initialized at run time because option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=io.netty.handler.ssl.JettyAlpnSslEngine$ClientEngine to explicitly request delayed initialization of this class.
[grpc-server:214]     (clinit):   1,090.36 ms,  3.33 GB
[grpc-server:214]   (typeflow):  25,060.21 ms,  3.33 GB
[grpc-server:214]    (objects):  28,952.95 ms,  3.33 GB
[grpc-server:214]   (features):   1,364.80 ms,  3.33 GB
[grpc-server:214]     analysis:  58,037.02 ms,  3.33 GB
[grpc-server:214]     universe:   1,912.26 ms,  3.28 GB
[grpc-server:214]      (parse):   7,996.58 ms,  2.96 GB
[grpc-server:214]     (inline):   6,101.57 ms,  3.15 GB
[grpc-server:214]    (compile):  46,340.75 ms,  4.81 GB
[grpc-server:214]      compile:  62,835.94 ms,  4.81 GB
[grpc-server:214]        image:   4,503.95 ms,  4.88 GB
[grpc-server:214]        write:     678.59 ms,  4.88 GB
[grpc-server:214]      [total]: 134,481.42 ms,  4.88 GB

GRPC netty epoll startup errors

I'm trying to get working micronaut-grpc with netty epoll.

Task List

  • Steps to reproduce provided
  • Stacktrace (if present) provided
  • Example that reproduces the problem uploaded to Github
  • Full description of the issue provided (see below)

Steps to Reproduce

build.gradle

dependencies {    
    implementation platform("io.micronaut:micronaut-bom:${micronautVersion}")
    implementation "io.micronaut.grpc:micronaut-grpc-runtime"
    implementation "io.netty:netty-transport-native-epoll"
}

dependencyInsight --dependency io.netty:netty-transport-native-epoll

io.netty:netty-transport-native-epoll:4.1.41.Final (by constraint)
   variant "compile" [
      org.gradle.status             = release (not requested)
      org.gradle.usage              = java-api
      org.gradle.component.category = library (not requested)
   ]

io.netty:netty-transport-native-epoll:4.1.41.Final
\--- io.micronaut:micronaut-bom:1.2.3
     \--- compileClasspath

io.netty:netty-transport-native-epoll -> 4.1.41.Final
\--- compileClasspath
  1. application.yml
grpc:
  server:
    host: 0.0.0.0
    port: 8182
    boss-event-loop-group: io.netty.channel.epoll.EpollEventLoopGroup
    worker-event-loop-group: io.netty.channel.epoll.EpollEventLoopGroup
    channel-type: io.netty.channel.epoll.EpollServerSocketChannel
  1. gradle run
  2. stacktrace
2019-10-04 15:58:29,356 DEBUG [main] io.micronaut.core.reflect.ClassUtils: Attempting to dynamically load class io.netty.channel.epoll.EpollServerSocketChannel
2019-10-04 15:58:29,364 DEBUG [main] io.micronaut.core.reflect.ClassUtils: Successfully loaded class io.netty.channel.epoll.EpollServerSocketChannel
2019-10-04 15:58:29,451 ERROR [main] io.micronaut.runtime.Micronaut: Error starting Micronaut server: Unable to start GRPC server: Failed to bind
io.micronaut.runtime.exceptions.ApplicationStartupException: Unable to start GRPC server: Failed to bind
	at io.micronaut.grpc.server.GrpcEmbeddedServer.start(GrpcEmbeddedServer.java:173)
	at io.micronaut.grpc.server.GrpcEmbeddedServer.start(GrpcEmbeddedServer.java:60)
	at io.micronaut.runtime.Micronaut.lambda$start$2(Micronaut.java:75)
	at java.util.Optional.ifPresent(Optional.java:159)
	at io.micronaut.runtime.Micronaut.start(Micronaut.java:73)
	at io.micronaut.runtime.Micronaut.run(Micronaut.java:303)
	at io.micronaut.runtime.Micronaut.run(Micronaut.java:289)
	at loc.dataplatform.server.Application.main(Application.java:15)
Caused by: java.io.IOException: Failed to bind
Caused by: java.io.IOException: Failed to bind

	at io.grpc.netty.NettyServer.start(NettyServer.java:251)
	at io.grpc.internal.ServerImpl.start(ServerImpl.java:177)
	at io.grpc.internal.ServerImpl.start(ServerImpl.java:85)
	at io.micronaut.grpc.server.GrpcEmbeddedServer.start(GrpcEmbeddedServer.java:149)
	... 7 common frames omitted
Caused by: io.netty.channel.ChannelException: Unable to create Channel from class class io.netty.channel.epoll.EpollServerSocketChannel
Caused by: io.netty.channel.ChannelException: Unable to create Channel from class class io.netty.channel.epoll.EpollServerSocketChannel

	at io.netty.channel.ReflectiveChannelFactory.newChannel(ReflectiveChannelFactory.java:46)
	at io.netty.bootstrap.AbstractBootstrap.initAndRegister(AbstractBootstrap.java:298)
	at io.netty.bootstrap.AbstractBootstrap.doBind(AbstractBootstrap.java:260)
	at io.netty.bootstrap.AbstractBootstrap.bind(AbstractBootstrap.java:256)
	at io.grpc.netty.NettyServer.start(NettyServer.java:243)
	... 10 common frames omitted
Caused by: java.lang.reflect.InvocationTargetException: null
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
Caused by: java.lang.reflect.InvocationTargetException: null

	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at io.netty.channel.ReflectiveChannelFactory.newChannel(ReflectiveChannelFactory.java:44)
	... 14 common frames omitted
Caused by: java.lang.UnsatisfiedLinkError: io.netty.channel.unix.LimitsStaticallyReferencedJniMethods.udsSunPathSize()I
Caused by: java.lang.UnsatisfiedLinkError: io.netty.channel.unix.LimitsStaticallyReferencedJniMethods.udsSunPathSize()I

	at io.netty.channel.unix.LimitsStaticallyReferencedJniMethods.udsSunPathSize(Native Method)
	at io.netty.channel.unix.Socket.<clinit>(Socket.java:49)
	at io.netty.channel.epoll.EpollServerSocketChannel.<init>(EpollServerSocketChannel.java:43)
	... 19 common frames omitted

Expected Behaviour

Succefully started grpc server with epoll support.

Actual Behaviour

Different errors. See stacktrace.

Environment Information

  • Operating System:
5.0.0-29-generic 18.04.1-Ubuntu
  • Micronaut Version:
Micronaut 1.2.3
  • JDK Version:
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (build 1.8.0_222-20190711120915.buildslave.jdk8u-src-tar--b08)
OpenJDK 64-Bit GraalVM CE 19.2.0.1 (build 25.222-b08-jvmci-19.2-b02, mixed mode)

Example Application

  • TODO: not provided

Bla-bla

Everything works good while not setup grpc.server.channel-type in micronaut configuration file (application.yml) but it works with NIO support.

Provide ability to disable gRPC client(s) using properties

I'd like to disable gRPC client(s) using properties, similar to that available for the server grpc.server.enabled=false. grpc.client.enabled=false would disable all gRPC client stub creation, and grpc.client.greeter.enabled=false would disable greeter client stub creation. The corresponding injection points (fields) would be Optional.empty or null if using Kotlin null type. If not Optional or null type, an exception would be thrown.

This allows for specific slices of the application to be tested. In my case, I've a client-A that calls service-A that calls client-B that calls external-service-B. Depending on the test scenario, I substitute in process versions for service-A and external-service-B to test the flow, or use the real ones.

What I'd like to do now is use the same test for calling service-A deployed in my integration environment. That means, I only need client-A, and service-A and client-B beans don't need to be created.

One way is to use Requires, but it makes the Production code coupled with test code, and also duplicates code.

Request for Kotest version of GreetingServiceTest.kt

There is a gRPC service test example for Kotlin using JUnit: https://github.com/micronaut-projects/micronaut-grpc/blob/master/examples/hello-world-kotlin/src/test/kotlin/helloworld/GreetingServiceTest.kt .

It would be great to have this example with Kotest, either instead or in addition.

I'm not sure if micronaut-grpc examples use kotest at all / what the build changes required would be; I think it may just be changing testRuntime to kotest in the build.gradle and adding a ProjectConfig.kt (docs at https://kotest.io/project_config/ and note it can now be a sibling of the other test files instead of being in the provided package).

The bean factory can be added to the ProjectConfig.kt or can be in its own file that's a peer of the test file. It didn't work for me to have the @Factory in the same file as the test. But I think the code changes are relatively minor:

GreetingServiceFactory.kt:

@Factory
class GreetingServiceFactory {
    @Singleton
    fun greetingClient( @GrpcChannel(GrpcServerChannel.NAME) channel : ManagedChannel ) : GreeterGrpcKt.GreeterCoroutineStub {
        return GreeterGrpcKt.GreeterCoroutineStub(
                channel
        )
    }
}

The test structure changes slightly:

GreetingServiceTest.kt:

import io.kotest.core.spec.style.StringSpec
import io.micronaut.test.extensions.kotest.annotation.MicronautTest
import io.kotest.matchers.shouldBe

@MicronautTest
class GreetingServiceTest: StringSpec() {

    @Inject
    lateinit var greetingClient : GreeterGrpcKt.GreeterCoroutineStub

    init {
        "test a response" {
            val actual = greetingClient.sayHello(
                            HelloRequest.newBuilder().setName("John").build()
                    ).message
            actual shouldBe "Hello John"
        }
    }
}

http-client is required for gRPC managed channel configuration

Background
We are using Micronaut to create a service that will call other services via gRPC.
Given the example from the documentation

grpc:
    channels:
        greeter:
            address: '${my.server}:${my.port}'
            plaintext: true
            max-retry-attempts: 10

The address of greeter is not resolved correctly

Steps to Reproduce

  1. Setup a simple project using the "micronaut-grpc-runtime"
  2. Configure a managed channel like so
grpc:
    channels:
        greeter:
            address: 'localhost:8082'
            plaintext: true
            max-retry-attempts: 10
  1. Create a gRPC stub
@Singleton
GreeterGrpc.GreeterStub reactiveStub(
    @GrpcChannel("greeter")
    ManagedChannel channel) {
    return GreeterGrpc.newStub(
            channel
    );
}

Expected Behaviour

The stub will try to connect to localhost:8082 for the greeter service endpoint

Actual Behaviour

Micronaut will try to service discover the service with name "greeter"

Workaround / Cause

The cause is that the GrpcManagedChannelConfiguration (

final Optional<SocketAddress> socketAddress = env.getProperty(PREFIX + '.' + name + SETTING_URL, SocketAddress.class);
) can not convert "localhost:8082" to a SocketAddress.
The type converter for SocketAddress is located in the http-client https://github.com/micronaut-projects/micronaut-core/blob/e9378a1adf1873bbb30889b74a5498c6cab89eca/http-client/src/main/java/io/micronaut/http/client/converters/SocketAddressConverter.java

The workaround is to also add a dependency on http-client module and everything works as it should

Make instanceId configurable

This is a follow up on #33.

Make instanceId configurable so when an application uses both HTTP and gRPC with service discovery, users can define a different name for the gRPC application to register to service discovery.

Feature Request: Server Side JWT Security Support

Allow for integration with micronaut-security to provide server interceptors to enforce different types of authentication

Base use case would be micronaut-security-jwt to be used for validating JWT tokens. Example below:

package com.example;

import io.grpc.ForwardingServerCallListener;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.micronaut.context.annotation.Value;
import lombok.extern.slf4j.Slf4j;
import lombok.val;

import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.security.Key;
import java.util.Base64;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Singleton
public class JwtServerInterceptor implements ServerInterceptor {

    private static final Metadata.Key<String> JWT_HEADER_KEY = Metadata.Key.of("JWT", Metadata.ASCII_STRING_MARSHALLER);
    private final Set<Key> jwtSignatureKeys;

    /**
     * Create the interceptor with a set of all the provided signature keys
     *
     * @param jwtServerSignatureKeyCsv a CSV string of all the base64 encoded signature keys
     */
    @Inject
    public JwtServerInterceptor(@Value("${jwt.server.signature.key}") final String jwtServerSignatureKeyCsv) {
        this.jwtSignatureKeys = Stream.of(jwtServerSignatureKeyCsv.split(","))
                .map(Base64.getDecoder()::decode)
                .map(keyBytes -> new SecretKeySpec(keyBytes, SignatureAlgorithm.HS512.getJcaName()))
                .collect(Collectors.toSet());
    }

    /** {@inheritDoc} */
    @Override
    public <T, S> ServerCall.Listener<T> interceptCall(final ServerCall<T, S> call, final Metadata headers, final ServerCallHandler<T, S> next) {
        if (!headers.containsKey(JWT_HEADER_KEY)) {
            log.error("JWT token missing in gRPC headers");
            throw new StatusRuntimeException(Status.UNAUTHENTICATED);
        }
        val listener = next.startCall(call, headers);
        try {
            val jwt = jwtSignatureKeys.stream()
                    .map(Jwts.parser()::setSigningKey)
                    .map(parser -> parser.parse(headers.get(JWT_HEADER_KEY)))
                    .findAny()
                    .orElseThrow(Status.PERMISSION_DENIED.withDescription("Unable to validate JWT with any signature keys")::asRuntimeException);
            if (log.isDebugEnabled()) {
                log.debug("JWT: {}", jwt);
            }
        } catch (final Exception e) {
            throw Status.PERMISSION_DENIED.withCause(e).asRuntimeException();
        }
        return new ForwardingServerCallListener.SimpleForwardingServerCallListener<T>(listener) { };
    }

}

Although I can generate the stubs with io.grpc:protoc-gen-grpc-kotlin I can't implement it

Issue: I can't implement an object autogenerated from protobuf.

How reproduce: generated a project from micronaut initializer with: gRPC Application type, Java 11, Kotlin. Add io.grpc:protoc-gen-grpc-kotlin on build.gradle, build it, add a controller and try implement GrpcdemoServiceGrpcKt.

image

build.gradle

plugins {
    id "org.jetbrains.kotlin.jvm" version "1.4.10"
    id "org.jetbrains.kotlin.kapt" version "1.4.10"
    id "org.jetbrains.kotlin.plugin.allopen" version "1.4.10"
    id "com.github.johnrengelman.shadow" version "6.1.0"
    id "io.micronaut.application" version '1.0.5'
    id "com.google.protobuf" version "0.8.13"
}

version "0.1"
group "com.mybank"

repositories {
    mavenCentral()
    jcenter()
}

micronaut {
    testRuntime "junit5"
    processing {
        incremental true
        annotations "com.mybank.*"
    }
}

dependencies {
    implementation("io.micronaut:micronaut-validation")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
    implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
    implementation("io.micronaut.kotlin:micronaut-kotlin-runtime")
    implementation("io.micronaut:micronaut-runtime")
    implementation("io.micronaut.grpc:micronaut-grpc-runtime")
    implementation("javax.annotation:javax.annotation-api")
    runtimeOnly("ch.qos.logback:logback-classic")
    runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin")
    testImplementation("io.micronaut:micronaut-http-client")

    implementation("io.grpc:grpc-kotlin-stub:${grpcKotlinVersion}")
}

mainClassName = "com.mybank.ApplicationKt"
java {
    sourceCompatibility = JavaVersion.toVersion('11')
}

compileKotlin {
    kotlinOptions {
        jvmTarget = '11'
    }
}
compileTestKotlin {
    kotlinOptions {
        jvmTarget = '11'
    }
}



sourceSets {
    main {
        java {
            srcDirs 'build/generated/source/proto/main/grpc'
            srcDirs 'build/generated/source/proto/main/java'
        }
    }
}

protobuf {
    protoc { artifact = "com.google.protobuf:protoc:3.13.0" }
    plugins {
        grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.32.1" }
        grpckt { artifact = "io.grpc:protoc-gen-grpc-kotlin:${grpcKotlinVersion}" }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {}
            grpckt {}
        }
    }
}

gradle.properties

micronautVersion=2.1.3
kotlinVersion=1.4.10
grpcKotlinVersion=0.1.2

auto generated stubs while gradle build

package com.mybank

import com.mybank.GrpcdemoServiceGrpc.getServiceDescriptor
import io.grpc.CallOptions
import io.grpc.CallOptions.DEFAULT
import io.grpc.Channel
import io.grpc.Metadata
import io.grpc.MethodDescriptor
import io.grpc.ServerServiceDefinition
import io.grpc.ServerServiceDefinition.builder
import io.grpc.ServiceDescriptor
import io.grpc.Status.UNIMPLEMENTED
import io.grpc.StatusException
import io.grpc.kotlin.AbstractCoroutineServerImpl
import io.grpc.kotlin.AbstractCoroutineStub
import io.grpc.kotlin.ClientCalls.unaryRpc
import io.grpc.kotlin.ServerCalls.unaryServerMethodDefinition
import io.grpc.kotlin.StubFor
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmStatic

/**
 * Holder for Kotlin coroutine-based client and server APIs for com.mybank.GrpcdemoService.
 */
object GrpcdemoServiceGrpcKt {
  @JvmStatic
  val serviceDescriptor: ServiceDescriptor
    get() = GrpcdemoServiceGrpc.getServiceDescriptor()

  val sendMethod: MethodDescriptor<GrpcdemoRequest, GrpcdemoReply>
    @JvmStatic
    get() = GrpcdemoServiceGrpc.getsendMethod()

  /**
   * A stub for issuing RPCs to a(n) com.mybank.GrpcdemoService service as suspending coroutines.
   */
  @StubFor(GrpcdemoServiceGrpc::class)
  class GrpcdemoServiceCoroutineStub @JvmOverloads constructor(
    channel: Channel,
    callOptions: CallOptions = DEFAULT
  ) : AbstractCoroutineStub<GrpcdemoServiceCoroutineStub>(channel, callOptions) {
    override fun build(channel: Channel, callOptions: CallOptions): GrpcdemoServiceCoroutineStub =
        GrpcdemoServiceCoroutineStub(channel, callOptions)

    /**
     * Executes this RPC and returns the response message, suspending until the RPC completes
     * with [`Status.OK`][io.grpc.Status].  If the RPC completes with another status, a
     * corresponding
     * [StatusException] is thrown.  If this coroutine is cancelled, the RPC is also cancelled
     * with the corresponding exception as a cause.
     *
     * @param request The request message to send to the server.
     *
     * @return The single response from the server.
     */
    suspend fun send(request: GrpcdemoRequest): GrpcdemoReply = unaryRpc(
      channel,
      GrpcdemoServiceGrpc.getSendMethod(),
      request,
      callOptions,
      Metadata()
    )}

  /**
   * Skeletal implementation of the com.mybank.GrpcdemoService service based on Kotlin coroutines.
   */
  abstract class GrpcdemoServiceCoroutineImplBase(
    coroutineContext: CoroutineContext = EmptyCoroutineContext
  ) : AbstractCoroutineServerImpl(coroutineContext) {
    /**
     * Returns the response to an RPC for com.mybank.GrpcdemoService.send.
     *
     * If this method fails with a [StatusException], the RPC will fail with the corresponding
     * [io.grpc.Status].  If this method fails with a [java.util.concurrent.CancellationException],
     * the RPC will fail
     * with status `Status.CANCELLED`.  If this method fails for any other reason, the RPC will
     * fail with `Status.UNKNOWN` with the exception as a cause.
     *
     * @param request The request from the client.
     */
    open suspend fun send(request: GrpcdemoRequest): GrpcdemoReply = throw
        StatusException(UNIMPLEMENTED.withDescription("Method com.mybank.GrpcdemoService.send is unimplemented"))

    final override fun bindService(): ServerServiceDefinition = builder(getServiceDescriptor())
      .addMethod(unaryServerMethodDefinition(
      context = this.context,
      descriptor = GrpcdemoServiceGrpc.getSendMethod(),
      implementation = ::send
    )).build()
  }
}

All the rest are exactly the same from micronaut.launch

Possible solution: there is an example I downloaded and staret it successsfuly and called it from BloomRPC. It is from oficial examples. Looking at it I see a much more complex gradle.

plugins {
    id "org.jetbrains.kotlin.jvm" version "1.3.72"
    id "org.jetbrains.kotlin.kapt" version "1.3.72"
    id "org.jetbrains.kotlin.plugin.allopen" version "1.3.72"
    id "application"
    id 'com.google.protobuf' version '0.8.13'
}

version "0.2"
group "helloworld"

repositories {
    mavenLocal()
    jcenter()
}

configurations {
    // for dependencies that are needed for development only
    developmentOnly
}

dependencies {
    kapt(enforcedPlatform("io.micronaut:micronaut-bom:$micronautVersion"))
    kapt("io.micronaut:micronaut-inject-java")
    kapt("io.micronaut:micronaut-validation")

    implementation(enforcedPlatform("io.micronaut:micronaut-bom:$micronautVersion"))
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
    implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion")
    implementation("io.micronaut:micronaut-runtime")
//    implementation("io.micronaut.grpc:micronaut-grpc-runtime")
    implementation("io.micronaut.grpc:micronaut-grpc-server-runtime:$micronautGrpcVersion")
    implementation("io.micronaut.grpc:micronaut-grpc-client-runtime:$micronautGrpcVersion")
    implementation("io.grpc:grpc-kotlin-stub:${grpcKotlinVersion}")

    runtimeOnly("ch.qos.logback:logback-classic:1.2.3")
    runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8")

    kaptTest("io.micronaut:micronaut-inject-java")

    testImplementation enforcedPlatform("io.micronaut:micronaut-bom:$micronautVersion")
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.0")
    testImplementation("io.micronaut.test:micronaut-test-junit5")
    testImplementation("org.mockito:mockito-junit-jupiter:2.22.0")

    testRuntime("org.junit.jupiter:junit-jupiter-engine:5.3.0")
    testRuntime("org.jetbrains.spek:spek-junit-platform-engine:1.1.5")
}

test.classpath += configurations.developmentOnly

mainClassName = "helloworld.Application"

test {
    useJUnitPlatform()
}

allOpen {
	annotation("io.micronaut.aop.Around")
}

compileKotlin {
	kotlinOptions {
	    jvmTarget = '1.8' 
	    //Will retain parameter names for Java reflection
	    javaParameters = true 
	}
}
//compileKotlin.dependsOn(generateProto)

compileTestKotlin {
	kotlinOptions {
	    jvmTarget = '1.8' 
	    javaParameters = true 
	}
}

tasks.withType(JavaExec) {
    classpath += configurations.developmentOnly
    jvmArgs('-XX:TieredStopAtLevel=1', '-Dcom.sun.management.jmxremote')
}

sourceSets {
    main {
        java {
            srcDirs 'build/generated/source/proto/main/grpc'
            srcDirs 'build/generated/source/proto/main/grpckt'
            srcDirs 'build/generated/source/proto/main/java'
        }
    }
}

protobuf {
    protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" }
    plugins {
        grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
        grpckt { artifact = "io.grpc:protoc-gen-grpc-kotlin:${grpcKotlinVersion}" }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {}
            grpckt {}
        }
    }
}

Question: what I am missing in order to implement the autogenerated stubs? Do I need more gradle dependencies beyond io.grpc:protoc-gen-grpc-kotlin? Am I in right direction? If not, what should I do in order to implement the send rpc method from demo project downloaded from Micronaut.launch?

PS.: when I tried the most recent version from io.grpc:protoc-gen-grpc-kotlin gradle complains so I just use 0.1.2 which is the same from official example. This is not an issue for me as long as it is not related to my problem.

GrpcServerHealthIndicator tries to start up even with gRPC server is disabled.

We've disabled our gRPC server on some API-only instances by setting grpc.server.enabled to false. However the newly added GrpcServerHealthIndicator still tries to start up and throws this error:

Task List

  • Steps to reproduce provided
  • Stacktrace (if present) provided
  • Example that reproduces the problem uploaded to Github
  • Full description of the issue provided (see below)

Steps to Reproduce

  1. Run a micronaut server with gRPC disabled
  2. Health indicator fails periodically

Expected Behaviour

I'm not sure if there's a way to set defaults to another property but for now we've disabled it by setting grpc.server.health.enabled to false

Actual Behaviour

When gRPC server is disabled, the gRPC health indicator tries to start and fails with this

io.micronaut.context.exceptions.DependencyInjectionException: Failed to inject value for parameter [server] of class: io.micronaut.grpc.server.health.GrpcServerHealthIndicator
Message: No bean of type [io.micronaut.grpc.server.GrpcEmbeddedServer] exists. Make sure the bean is not disabled by bean requirements (enable trace logging for 'io.micronaut.context.condition' to check) and if the bean is enabled then ensure the class is declared a bean and annotation processing is enabled (for Java and Kotlin the 'micronaut-inject-java' dependency should be configured as an annotation processor).
Path Taken: new HealthResultFilter([HealthEndpoint healthEndpoint]) --> new HealthEndpoint(HealthAggregator healthAggregator,[HealthIndicator[] healthIndicators],HealthIndicator[] livenessHealthIndicators) --> new GrpcServerHealthIndicator([GrpcEmbeddedServer server])

Environment Information

  • Operating System: N/A
  • Micronaut Version: 2.10
  • JDK Version: N/A

Example Application

  • TODO: link to github repository with example that reproduces the issue

io.grpc:protoc-gen-grpc-java:1.36.0+ incompatibility

Steps to Reproduce

I've got a build.gradle

// gRPC
protobuf {
    protoc { artifact = "com.google.protobuf:protoc:$protocVersion" }
    plugins {
        grpc { artifact = "io.grpc:protoc-gen-grpc-java:$grpcVersion" }
        grpckt { artifact = "io.grpc:protoc-gen-grpc-kotlin:$grpcKotlinVersion:jdk7@jar" }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {}
            grpckt {}
        }
    }
}

with gradle.properties

protocVersion=3.17.0
grpcVersion=1.35.1 # works fine
#grpcVersion=1.36.0 # breaks things
grpcKotlinVersion=1.1.0

which worked fined. After upgrading grpcVersion=1.35.1 to grpcVersion=1.36.0 the application startup breaks:


Error starting Micronaut server: Error starting Micronaut server: Error instantiating bean of type [io.micronaut.grpc.server.GrpcEmbeddedServer]: io.grpc.netty.NettyServerBuilder$NettyClientTransportServersBuilder.buildClientTransportServers(Ljava/util/List;)Lio/grpc/internal/InternalServer;
io.micronaut.runtime.exceptions.ApplicationStartupException: Error starting Micronaut server: Error starting Micronaut server: Error instantiating bean of type [io.micronaut.grpc.server.GrpcEmbeddedServer]: io.grpc.netty.NettyServerBuilder$NettyClientTransportServersBuilder.buildClientTransportServers(Ljava/util/List;)Lio/grpc/internal/InternalServer;
	at io.micronaut.runtime.Micronaut.handleStartupException(Micronaut.java:330)
	at io.micronaut.runtime.Micronaut.start(Micronaut.java:161)
	at de.debuglevel.greeter.Application.main(Application.kt:37)
	at de.debuglevel.greeter.ApplicationTests.standalone startup(ApplicationTests.kt:16)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy2.stop(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:133)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
	at java.lang.Thread.run(Thread.java:748)
Caused by: io.micronaut.runtime.exceptions.ApplicationStartupException: Error starting Micronaut server: Error instantiating bean of type [io.micronaut.grpc.server.GrpcEmbeddedServer]: io.grpc.netty.NettyServerBuilder$NettyClientTransportServersBuilder.buildClientTransportServers(Ljava/util/List;)Lio/grpc/internal/InternalServer;
	at io.micronaut.runtime.Micronaut.handleStartupException(Micronaut.java:330)
	at io.micronaut.runtime.Micronaut.lambda$start$2(Micronaut.java:152)
	at java.util.Optional.ifPresent(Optional.java:159)
	at io.micronaut.runtime.Micronaut.start(Micronaut.java:75)
	... 92 more
Caused by: io.micronaut.context.exceptions.BeanInstantiationException: Error instantiating bean of type [io.micronaut.grpc.server.GrpcEmbeddedServer]: io.grpc.netty.NettyServerBuilder$NettyClientTransportServersBuilder.buildClientTransportServers(Ljava/util/List;)Lio/grpc/internal/InternalServer;
	at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:2008)
	at io.micronaut.context.DefaultBeanContext.createAndRegisterSingletonInternal(DefaultBeanContext.java:2770)
	at io.micronaut.context.DefaultBeanContext.createAndRegisterSingleton(DefaultBeanContext.java:2756)
	at io.micronaut.context.DefaultBeanContext.getBeanForDefinition(DefaultBeanContext.java:2427)
	at io.micronaut.context.DefaultBeanContext.getBeanInternal(DefaultBeanContext.java:2401)
	at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:744)
	at io.micronaut.grpc.server.GrpcEmbeddedServerListener.onApplicationEvent(GrpcEmbeddedServerListener.java:58)
	at io.micronaut.grpc.server.GrpcEmbeddedServerListener.onApplicationEvent(GrpcEmbeddedServerListener.java:37)
	at io.micronaut.context.DefaultBeanContext.notifyEventListeners(DefaultBeanContext.java:1326)
	at io.micronaut.context.DefaultBeanContext.publishEvent(DefaultBeanContext.java:1311)
	at io.micronaut.http.server.netty.NettyHttpServer.fireStartupEvents(NettyHttpServer.java:512)
	at io.micronaut.http.server.netty.NettyHttpServer.start(NettyHttpServer.java:355)
	at io.micronaut.http.server.netty.NettyHttpServer.start(NettyHttpServer.java:114)
	at io.micronaut.runtime.Micronaut.lambda$start$2(Micronaut.java:77)
	... 94 more
Caused by: java.lang.AbstractMethodError: io.grpc.netty.NettyServerBuilder$NettyClientTransportServersBuilder.buildClientTransportServers(Ljava/util/List;)Lio/grpc/internal/InternalServer;
	at io.grpc.internal.ServerImplBuilder.build(ServerImplBuilder.java:231)
	at io.grpc.internal.AbstractServerImplBuilder.build(AbstractServerImplBuilder.java:166)
	at io.micronaut.grpc.server.GrpcEmbeddedServer.<init>(GrpcEmbeddedServer.java:103)
	at io.micronaut.grpc.server.$GrpcEmbeddedServerDefinition.build(Unknown Source)
	at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1979)
	... 107 more

Unfortunately, I'm quite a gRPC novice and do not really know what's going on at all ๐Ÿ˜…. As https://micronaut-projects.github.io/micronaut-grpc/latest/guide/index.html states older version, I can just guess, it's just not supported yet. But I wanted to leave a note nevertheless :)

Environment Information

  • Operating System: Windows 10
  • Micronaut Version: 2.5.3
  • JDK Version: OpenJDK8

Use InProcessServerBuilder for testing

Micronaut gRPC creates a NettyServerBuilder automatically. How do I use an InProcessServerBuilder for testing?

One way is to set grpc.server.enabled=false, and then create the Server myself.

Server server = InProcessServerBuilder.forName(uniqueName)
      .directExecutor()
      .addService(new MyService())
      .build().start();

But then, I've to start and stop it. It'd be nice to have the framework manage it on my behalf instead.

Separate health + tracing into individual modules

The server tracing and health, and client tracing, should not be part of the core runtime. Tracing introduces unwanted coupling, since there's no telling that the user would want to use OpenTracing. Also, OpenTracing has been superceded by OpenTelemetry.
As for gRPC health service, it should be separate too and only enabled if grpc.server.health.enabled=true.

Declarative Client consuming Protobuf as raw byte array not working

Looks like declarative Client is not able to support raw byte[] for Protobuf type

I've created this SO question https://stackoverflow.com/questions/55916216/micronaut-api-and-client-with-controller-consuming-protobuf/56045199#56045199
and James Kleeh addressed me here.

Here is a repo with a test (https://github.com/dcalap/micronaut-http-client/blob/master/complete/src/test/java/example/micronaut/ProtobufTest.java) that maybe can help you https://github.com/dcalap/micronaut-http-client.git

And here is the test error log:

io.micronaut.http.client.exceptions.HttpClientResponseException: Required argument [byte[] data] not specified at io.micronaut.http.client.DefaultHttpClient$10.channelRead0(DefaultHttpClient.java:1771) at io.micronaut.http.client.DefaultHttpClient$10.channelRead0(DefaultHttpClient.java:1723) at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:105) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.micronaut.http.netty.stream.HttpStreamsHandler.channelRead(HttpStreamsHandler.java:185) at io.micronaut.http.netty.stream.HttpStreamsClientHandler.channelRead(HttpStreamsClientHandler.java:180) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:102) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:102) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438) at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:323) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:297) at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965) at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:644) at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:579) at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:496) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:458) at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.lang.Thread.run(Thread.java:748)

Hope you can fix it! Thanks in advance.

error if you set grpc.server.port to -1

Docs:

The server by default runs on port 50051, however you can configure which port the server runs on by setting grpc.server.port to whichever value you wish (a value of -1 will use a random port).

Steps to reproduce

% mn --version
Micronaut Version: 2.0.0.M3
JVM Version: 11.0.6
% mn create-grpc-app foo
| Application created at /Users/sdelamo/Downloads/demo-12/foo
% cd foo 
foo % vi src/main/resources/application.yml 
foo % cat src/main/resources/application.yml 
micronaut:
  application:
    name: foo
grpc:
  server:
    port: -1
% ./gradlew run

> Task :compileJava
Note: /Users/sdelamo/Downloads/demo-12/foo/build/generated/source/proto/main/java/foo/Foo.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

> Task :run FAILED
10:58:27.659 [main] ERROR io.micronaut.runtime.Micronaut - Error starting Micronaut server: Error instantiating bean of type  [io.micronaut.grpc.server.GrpcEmbeddedServer]

Message: port out of range:-1
Path Taken: new GrpcEmbeddedServer(ApplicationContext applicationContext,ApplicationConfiguration applicationConfiguration,[GrpcServerConfiguration grpcServerConfiguration],ServerBuilder serverBuilder,ApplicationEventPublisher eventPublisher,ComputeInstanceMetadataResolver computeInstanceMetadataResolver,List metadataContributors)
io.micronaut.context.exceptions.BeanInstantiationException: Error instantiating bean of type  [io.micronaut.grpc.server.GrpcEmbeddedServer]

Message: port out of range:-1
Path Taken: new GrpcEmbeddedServer(ApplicationContext applicationContext,ApplicationConfiguration applicationConfiguration,[GrpcServerConfiguration grpcServerConfiguration],ServerBuilder serverBuilder,ApplicationEventPublisher eventPublisher,ComputeInstanceMetadataResolver computeInstanceMetadataResolver,List metadataContributors)
        at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1840)
        at io.micronaut.context.DefaultBeanContext.createAndRegisterSingletonInternal(DefaultBeanContext.java:2549)
        at io.micronaut.context.DefaultBeanContext.createAndRegisterSingleton(DefaultBeanContext.java:2535)
        at io.micronaut.context.DefaultBeanContext.getBeanForDefinition(DefaultBeanContext.java:2222)
        at io.micronaut.context.DefaultBeanContext.getBeanInternal(DefaultBeanContext.java:2196)
        at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:1198)
        at io.micronaut.context.AbstractBeanDefinition.getBeanForConstructorArgument(AbstractBeanDefinition.java:1039)
        at io.micronaut.grpc.server.$GrpcEmbeddedServerDefinition.build(Unknown Source)
        at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1814)
        at io.micronaut.context.DefaultBeanContext.createAndRegisterSingletonInternal(DefaultBeanContext.java:2549)
        at io.micronaut.context.DefaultBeanContext.createAndRegisterSingleton(DefaultBeanContext.java:2535)
        at io.micronaut.context.DefaultBeanContext.getBeanForDefinition(DefaultBeanContext.java:2222)
        at io.micronaut.context.DefaultBeanContext.getBeanInternal(DefaultBeanContext.java:2196)
        at io.micronaut.context.DefaultBeanContext.findBean(DefaultBeanContext.java:1218)
        at io.micronaut.context.DefaultBeanContext.findBean(DefaultBeanContext.java:713)
        at io.micronaut.context.BeanLocator.findBean(BeanLocator.java:149)
        at io.micronaut.runtime.Micronaut.start(Micronaut.java:66)
        at io.micronaut.runtime.Micronaut.run(Micronaut.java:294)
        at io.micronaut.runtime.Micronaut.run(Micronaut.java:280)
        at foo.Application.main(Application.java:8)
Caused by: java.lang.IllegalArgumentException: port out of range:-1
        at java.base/java.net.InetSocketAddress.checkPort(InetSocketAddress.java:143)
        at java.base/java.net.InetSocketAddress.<init>(InetSocketAddress.java:188)
        at java.base/java.net.InetSocketAddress.<init>(InetSocketAddress.java:166)
        at io.grpc.netty.NettyServerBuilder.<init>(NettyServerBuilder.java:131)
        at io.grpc.netty.NettyServerBuilder.forPort(NettyServerBuilder.java:115)
        at io.micronaut.grpc.server.GrpcServerConfiguration.<init>(GrpcServerConfiguration.java:83)
        at io.micronaut.grpc.server.$GrpcServerConfigurationDefinition.build(Unknown Source)
        at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1814)
        ... 19 common frames omitted

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':run'.
> Process 'command '/Users/sdelamo/.sdkman/candidates/java/11.0.6-amzn/bin/java'' finished with non-zero exit value 1

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.3/userguide/command_line_interface.html#sec:command_line_warnings

BUILD FAILED in 3s

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.