YES please, I don't see any reason not to add it. Especially given that the implementation looks really good!
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.
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 ChannelHandler
s 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.
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
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).
Big +1, gRPC is often an obvious protocol choice and having this part of SSWG makes perfect sense.
This is a strong addition to SSWG, great to see grpc-swift hit the milestone when it can be pitched. Well done everyone involved.
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.
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)
})
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 domake 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 includeapple/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"])
]
)
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!
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.
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.