NIO-based gRPC Swift

YES please, I don't see any reason not to add it. Especially given that the implementation looks really good!

4 Likes

A huge +1 from me: gRPC is an extremely important protocol, and it is exciting to have a good, almost entirely Swift implementation. It seems like a no-brainer to me.

6 Likes

Yes gRPC is definitely on-topic here!

Great to see that the package already uses the swift-log package we created. Is using swift-metrics on the roadmap too?

One topic of conversation might be about the level of API provided. I haven't looked at grpc-swift in detail but I see it offers a public class Server for starting a gRPC server. SwiftNIO doesn't provide that level of API for HTTP, rather it provides the building blocks for others to build an API for an HTTP server. So as well as providing the ChannelHandlers etc. for gRPC this package also provides some higher level API. I'll be interested to hear whether others think that's OK for SSWG packages.

4 Likes

Plus one from me :-) Useful addition to the sswg recommended projects portfolio. I hope to muster up some more time to go over the APIs in some more depth in the near future...

umm, I am not for this but am for the need of data center rpc services. the idea of gRPC is yes, gr8, but proto buff, no as swift's codable types are very powerful on its own which is contingent upon community to create solutions.

+1 this would be a great addition. it extends the application level protocol options and aligned with the “cloud native” nature of swift

2 Likes

Not at the moment; down the line I think it would be a useful addition though.

True, although AsyncHTTPClient does this, albeit for a client. We haven't gone out of our way to preclude users from taking the handlers we provide and creating their own gRPC server but I imagine there are a bunch of ways we could make this easier (i.e. something similar to configureHTTP2Pipeline in NIO HTTP2).

We have a handy tutorial which covers most of it if you can't wait until the proposal :wink:

3 Likes

Big +1, gRPC is often an obvious protocol choice and having this part of SSWG makes perfect sense.

1 Like

This is a strong addition to SSWG, great to see grpc-swift hit the milestone when it can be pitched. Well done everyone involved.

2 Likes

it's will be great !

Yes! I've been waiting a long time for this. Congratulations to the grpc-swift team as the project nears the 1.0 milestone.

2 Likes

I have written a small library called CombineGRPC that provides Combine framework integration for gRPC Swift. I would love to have some feedback if anybody is willing to take a look.

You can use it to make gRPC calls and implement server side RPC handlers using Combine publishers. All RPC types are supported. Here is a simple bidirectional streaming example. Protobuf:

syntax = "proto3";

service EchoService {
  rpc SayItBack (stream EchoRequest) returns (stream EchoResponse);
}

message EchoRequest {
  string message = 1;
}

message EchoResponse {
  string message = 1;
}

Server side implementation, where EchoProvider is generated by gRPC Swift:

class EchoServiceProvider: EchoProvider {  

  // Bidirectional streaming RPC that echoes back each request message
  func sayItBack(context: StreamingResponseCallContext<EchoResponse>)
    -> EventLoopFuture<(StreamEvent<EchoRequest>) -> Void>
  {
    handle(context) { requests in
      requests
        .map { req in
          EchoResponse.with { $0.message = req.message }
        }
        .setFailureType(to: GRPCStatus.self)
        .eraseToAnyPublisher()
    }
  }
}

Here's how you make RPC calls on the client side. EchoServiceClient is generated by gRPC Swift:

let echoClient = EchoServiceClient(connection: ClientConnection(configuration: configuration))
let grpc = GRPCExecutor()  // Can optionally be configured with retry policy and CallOptions

let requests = Publishers
  .Sequence(sequence: repeatElement(EchoRequest.with { $0.message = "hello"}, count: 10))
  .eraseToAnyPublisher()

grpc.call(echoClient.sayItBack)(requests)
  .filter { $0.message == "hello" }
  .count()
  .sink(receiveValue: { count in
    assert(count == 10)
  })

The proposal is here: [Discussion] gRPC Swift

2 Likes

I had a chance to test a generated client out with Vapor 4 and it works great! I've included the code I used below for reference.

The biggest issue I ran into getting this up and running was configuring protoc. I noticed you have a Makefile but I couldn't really figure out how to get that working. It seems like it's built specifically for the grpc-swift repo and it was difficult (for me at least) to parse what was happening inside due to all the variables.

I ended up installing SwiftProtobuf via brew install swift-protobuf. That worked great, but I still needed protoc-gen-grpc-swift. After a lot of debugging strange errors, I finally realized if I build grpc-swift in release mode, then put that executable in my bin, protoc can find it automatically. Once all said and done, I was able to use this command:

protoc HelloWorld.proto \
    --proto_path=Sources/App \
    --swift_out=Sources/App \
    --grpc-swift_out=Sources/App

That finally generated both the HelloWorld.pb.swift and HelloWorld.grpc.swift files where I wanted them.

Two takeaways from setup:

  • It would be awesome if you could brew install swift-grpc or at least clone grpc/swift-grpc and do make install
  • The docs I found seemed more geared toward running the grpc-swift repo's examples, and not toward using it in your own project. In fairness, I might have just glossed over those accidentally.

Some takeaways from the actual code:

  • I'm initializing a new ClientConnection each request. Is this the recommended pattern or do I need to keep a shared client for connection pooling?
  • I got a strange error Missing package product 'Logging' if I didn't explicitly include apple/swift-log in my project. I haven't seen this before and I'm not sure why I'd need to do that. @weissi any ideas?

Here's the main.swift file I used.

import GRPC
import Vapor

var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)

let app = Application(env)
defer { app.shutdown() }

app.get("greeting") { req -> EventLoopFuture<String> in
    // Provide some basic configuration for the connection, in this case we connect to an endpoint on
    // localhost at the given port.
    let configuration = ClientConnection.Configuration(
      target: .hostAndPort("localhost", 50323),
      eventLoopGroup: req.eventLoop
    )

    // Create a connection using the configuration.
    let connection = ClientConnection(configuration: configuration)

    // Provide the connection to the generated client.
    let greeter = Helloworld_GreeterServiceClient(connection: connection)

    // Form the request with the name, if one was provided.
    let request = Helloworld_HelloRequest.with {
        $0.name = req.query["name"] ?? ""
    }

    // Make the RPC call to the server.
    let sayHello = greeter.sayHello(request)

    // map response
    return sayHello.response.map { reply in
        reply.message
    }
}

try app.run()
$ http get localhost:8080/greeting name==Vapor
HTTP/1.1 200 OK
content-length: 12
content-type: text/plain; charset=utf-8

Hello Vapor!

Finally, the Package.swift:

// swift-tools-version:5.1
import PackageDescription

let package = Package(
    name: "foo",
    platforms: [
       .macOS(.v10_14)
    ],
    dependencies: [
        .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-beta"),
        .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.0.0-alpha"),
        // Not sure why this one is needed. 
        .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
    ],
    targets: [
        .target(name: "App", dependencies: ["Logging", "GRPC", "Vapor"])
    ]
)
2 Likes

Thanks for the feedback @tanner0101 – super helpful!

That's completely fair: I had the same issues when I started out with gRPC! How to invoke protoc to do the right thing isn't obvious. We have some documentation on the different options available to the protoc-gen-grpc-swift plugin (https://github.com/grpc/grpc-swift/blob/nio/docs/plugin.md) but nothing that really explains how to use without prior knowledge of protoc. The idea of a "cookbook" was suggested recently which could be a good way to show people how to get started, I think this would alleviate the problem that the docs are quite tightly coupled to the examples. What do you think?

Something I just noticed is that the "Using gRPC Swift" section of the README is buried right at the bottom, bring that up to the top and highlighting the different documentation may also help.

Couldn't agree more! There's already a formula for grpc-swift but it provides the plugin for the non-NIO based version. We'll update this before we tag 1.0.0.

It would be better to keep a connection open instead of creating one per request: gRPC runs over HTTP/2 and each RPC runs in a separate HTTP/2 stream so you can have many concurrent RPCs per connection. A single connection may well be sufficient, but of course this depends on what your service is doing!

2 Likes

Hello Swift Community,

We are in the process of migrating our application from gRPC C library (which was included via CocoaPods) to SwiftNIO gRPC (integrated via SPM). We aim to support a gradual transition, therefore we intend to have both versions of gRPC coexisting in the app until we've fully migrated all calls to the new implementation.

We've managed to compile the application with both gRPC libraries. However, during runtime, while the new SwiftNIO gRPC implementation is functioning as expected, the legacy gRPC C library is encountering a type conformance issue.

The error we receive is as follows:

failed to demangle superclass of Db_logdbgetuseridfromloginemailwithsignatureCallBase from mangled name '\^Bs\M-y ': subject type x does not conform to protocol Message

As far as we understand, this suggests a problem with Swift's type system, potentially related to the Protobuf Message protocol conformance.

Any insights on how we might go about resolving this issue would be greatly appreciated. We're particularly interested in understanding the underlying cause of this type conformance problem and any steps we could take to ensure a smooth coexistence of the two gRPC versions during our transitional phase.

Thank you for your help.

I think we might need to page in @Joe_Groff on this one.

It's possible that that conformance doesn't otherwise appear used by the program, so a fully static linked build might think it's dead. Does the problem go away if you introduce some artificial use of the conformance? One way to do that might be to do print(protobuf as any Message) to construct an any Message value containing the conformance and make it appear used by printing it.

the message is printing just fine, it seem like the issue is on the auto generated class Db_logdbgetuseridfromloginemailwithsignatureCallBase initialization



as shown on the screenshot. the first screenshot is my code, the next are auto-generated.