async-grpc 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/context/getting-started.md +6 -9
- data/design.md +11 -6
- data/lib/async/grpc/client.rb +18 -38
- data/lib/async/grpc/{dispatcher_middleware.rb → dispatcher.rb} +67 -31
- data/lib/async/grpc/remote_error.rb +19 -0
- data/lib/async/grpc/service.rb +5 -29
- data/lib/async/grpc/stub.rb +5 -6
- data/lib/async/grpc/version.rb +2 -1
- data/lib/async/grpc.rb +1 -1
- data/readme.md +16 -3
- data/releases.md +14 -0
- data.tar.gz.sig +0 -0
- metadata +35 -5
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 33e1af099cdab3092bc722b175b4dc16cc4fdacbbe7c59c12d1f186cfec95b0b
|
|
4
|
+
data.tar.gz: 70c6f471fde84394a3a6aa5cb014d98a16be1746c132b0e932f516a6c175f8f3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a243cbd19d766995b1e63037f21357d97ff3878cd8020227df2652cd11be2bbbb367f753d8182ee319a0e47e7ecddc335ce38b4dca03209873ae869aee150acc
|
|
7
|
+
data.tar.gz: 9096b9bccc77a7860f164325dadc427c0e4ebc8ce4f9ff1411f87e3ccaa9c094c2502075b83ba21c5c9e458ac10e90034198e85d6f41d1d26f2b6e436010cc9b
|
checksums.yaml.gz.sig
ADDED
|
Binary file
|
data/context/getting-started.md
CHANGED
|
@@ -23,7 +23,7 @@ $ bundle add protocol-grpc async-http
|
|
|
23
23
|
- {ruby Async::GRPC::Client} - An asynchronous gRPC client that wraps `Async::HTTP::Client`.
|
|
24
24
|
- {ruby Async::GRPC::Stub} - A method-based stub for making RPC calls.
|
|
25
25
|
- {ruby Async::GRPC::Service} - A concrete service implementation that uses a `Protocol::GRPC::Interface`.
|
|
26
|
-
- {ruby Async::GRPC::
|
|
26
|
+
- {ruby Async::GRPC::Dispatcher} - Middleware that routes requests to registered services.
|
|
27
27
|
|
|
28
28
|
## Client Usage
|
|
29
29
|
|
|
@@ -104,12 +104,12 @@ end
|
|
|
104
104
|
### Registering Services
|
|
105
105
|
|
|
106
106
|
``` ruby
|
|
107
|
-
require "async/grpc/
|
|
107
|
+
require "async/grpc/dispatcher"
|
|
108
108
|
|
|
109
|
-
dispatcher = Async::GRPC::
|
|
109
|
+
dispatcher = Async::GRPC::Dispatcher.new
|
|
110
110
|
|
|
111
111
|
service = GreeterService.new(GreeterInterface, "hello.Greeter")
|
|
112
|
-
dispatcher.register(
|
|
112
|
+
dispatcher.register(service)
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
### Running a Server
|
|
@@ -151,8 +151,8 @@ end
|
|
|
151
151
|
Async do
|
|
152
152
|
# Setup server
|
|
153
153
|
endpoint = Async::HTTP::Endpoint.parse("http://localhost:50051")
|
|
154
|
-
dispatcher = Async::GRPC::
|
|
155
|
-
dispatcher.register(
|
|
154
|
+
dispatcher = Async::GRPC::Dispatcher.new
|
|
155
|
+
dispatcher.register(GreeterService.new(GreeterInterface, "hello.Greeter"))
|
|
156
156
|
server = Async::HTTP::Server.for(endpoint, dispatcher)
|
|
157
157
|
|
|
158
158
|
# Setup client
|
|
@@ -169,6 +169,3 @@ Async do
|
|
|
169
169
|
server.run
|
|
170
170
|
end
|
|
171
171
|
```
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
data/design.md
CHANGED
|
@@ -318,13 +318,18 @@ module Async
|
|
|
318
318
|
end
|
|
319
319
|
end
|
|
320
320
|
|
|
321
|
-
|
|
321
|
+
# Check gRPC status and raise error if not OK
|
|
322
322
|
def check_status!(response)
|
|
323
323
|
status = Protocol::GRPC::Metadata.extract_status(response.headers)
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
324
|
+
|
|
325
|
+
return if status == Protocol::GRPC::Status::OK
|
|
326
|
+
|
|
327
|
+
message = Protocol::GRPC::Metadata.extract_message(response.headers)
|
|
328
|
+
metadata = Protocol::GRPC::Methods.extract_metadata(response.headers)
|
|
329
|
+
|
|
330
|
+
remote_error = RemoteError.for(message, metadata)
|
|
331
|
+
|
|
332
|
+
raise Protocol::GRPC::Error.for(status, metadata: metadata), cause: remote_error
|
|
328
333
|
end
|
|
329
334
|
end
|
|
330
335
|
end
|
|
@@ -1001,7 +1006,7 @@ This enables async-grpc to be used as a drop-in replacement for the standard `gr
|
|
|
1001
1006
|
### Phase 1: Core Client (✅ Designed)
|
|
1002
1007
|
- `Async::GRPC::Client` with all four RPC types
|
|
1003
1008
|
- `Async::GRPC::ServerCall` context object (enhances Protocol::GRPC::Call)
|
|
1004
|
-
-
|
|
1009
|
+
- Error handling with backtrace support via `RemoteError` and exception chaining
|
|
1005
1010
|
- Response body wrapping pattern
|
|
1006
1011
|
- **Server**: Just use `Protocol::GRPC::Middleware` with `Async::HTTP::Server` (no wrapper needed!)
|
|
1007
1012
|
|
data/lib/async/grpc/client.rb
CHANGED
|
@@ -16,6 +16,7 @@ require "protocol/grpc/body/writable_body"
|
|
|
16
16
|
require "protocol/grpc/metadata"
|
|
17
17
|
require "protocol/grpc/error"
|
|
18
18
|
require_relative "stub"
|
|
19
|
+
require_relative "remote_error"
|
|
19
20
|
|
|
20
21
|
module Async
|
|
21
22
|
module GRPC
|
|
@@ -100,7 +101,9 @@ module Async
|
|
|
100
101
|
def call(request)
|
|
101
102
|
request.headers = @headers.merge(request.headers)
|
|
102
103
|
|
|
103
|
-
super
|
|
104
|
+
super.tap do |response|
|
|
105
|
+
response.headers.policy = Protocol::GRPC::HEADER_POLICY
|
|
106
|
+
end
|
|
104
107
|
end
|
|
105
108
|
|
|
106
109
|
# Make a gRPC call.
|
|
@@ -166,19 +169,17 @@ module Async
|
|
|
166
169
|
begin
|
|
167
170
|
# Read body first - trailers are only available after body is consumed
|
|
168
171
|
response_encoding = response.headers["grpc-encoding"]
|
|
169
|
-
|
|
170
|
-
response.body,
|
|
171
|
-
message_class: response_class,
|
|
172
|
-
encoding: response_encoding
|
|
173
|
-
)
|
|
172
|
+
response_body = Protocol::GRPC::Body::ReadableBody.wrap(response, message_class: response_class, encoding: response_encoding)
|
|
174
173
|
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
if response_body
|
|
175
|
+
response_value = response_body.read
|
|
176
|
+
response_body.close
|
|
177
|
+
end
|
|
177
178
|
|
|
178
179
|
# Check status after reading body (trailers are now available)
|
|
179
180
|
check_status!(response)
|
|
180
181
|
|
|
181
|
-
|
|
182
|
+
return response_value
|
|
182
183
|
ensure
|
|
183
184
|
response.close
|
|
184
185
|
end
|
|
@@ -203,32 +204,19 @@ module Async
|
|
|
203
204
|
response = call(http_request)
|
|
204
205
|
|
|
205
206
|
begin
|
|
206
|
-
# Set gRPC policy BEFORE reading body so trailers are processed correctly:
|
|
207
|
-
unless response.headers.policy == Protocol::GRPC::HEADER_POLICY
|
|
208
|
-
response.headers.policy = Protocol::GRPC::HEADER_POLICY
|
|
209
|
-
end
|
|
210
|
-
|
|
211
207
|
# Read body first - trailers are only available after body is consumed:
|
|
212
208
|
response_encoding = response.headers["grpc-encoding"]
|
|
213
|
-
|
|
214
|
-
response.body,
|
|
215
|
-
message_class: response_class,
|
|
216
|
-
encoding: response_encoding
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
return readable_body unless block_given?
|
|
209
|
+
response_body = Protocol::GRPC::Body::ReadableBody.wrap(response, message_class: response_class, encoding: response_encoding)
|
|
220
210
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
ensure
|
|
224
|
-
readable_body.close
|
|
211
|
+
if block_given? and response_body
|
|
212
|
+
response_body.each(&block)
|
|
225
213
|
end
|
|
226
214
|
|
|
227
215
|
# Check status after reading all body chunks (trailers are now available):
|
|
228
216
|
check_status!(response)
|
|
229
217
|
|
|
230
|
-
|
|
231
|
-
rescue
|
|
218
|
+
return response_body
|
|
219
|
+
rescue
|
|
232
220
|
response.close
|
|
233
221
|
raise
|
|
234
222
|
end
|
|
@@ -327,24 +315,16 @@ module Async
|
|
|
327
315
|
# @parameter response [Protocol::HTTP::Response]
|
|
328
316
|
# @raises [Protocol::GRPC::Error] If status is not OK
|
|
329
317
|
def check_status!(response)
|
|
330
|
-
# Policy should already be set before calling this method:
|
|
331
|
-
# But ensure it's set just in case
|
|
332
|
-
unless response.headers.policy == Protocol::GRPC::HEADER_POLICY
|
|
333
|
-
response.headers.policy = Protocol::GRPC::HEADER_POLICY
|
|
334
|
-
end
|
|
335
|
-
|
|
336
318
|
status = Protocol::GRPC::Metadata.extract_status(response.headers)
|
|
337
319
|
|
|
338
|
-
# If status is UNKNOWN (not found), default to OK:
|
|
339
|
-
# This handles cases where trailers aren't available or status wasn't set
|
|
340
|
-
status = Protocol::GRPC::Status::OK if status == Protocol::GRPC::Status::UNKNOWN
|
|
341
|
-
|
|
342
320
|
return if status == Protocol::GRPC::Status::OK
|
|
343
321
|
|
|
344
322
|
message = Protocol::GRPC::Metadata.extract_message(response.headers)
|
|
345
323
|
metadata = Protocol::GRPC::Methods.extract_metadata(response.headers)
|
|
346
324
|
|
|
347
|
-
|
|
325
|
+
remote_error = RemoteError.for(message, metadata)
|
|
326
|
+
|
|
327
|
+
raise Protocol::GRPC::Error.for(status, metadata: metadata), cause: remote_error
|
|
348
328
|
end
|
|
349
329
|
end
|
|
350
330
|
end
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2025, by Samuel Williams.
|
|
5
5
|
|
|
6
|
+
require "async"
|
|
7
|
+
require "async/deadline"
|
|
8
|
+
|
|
6
9
|
require "protocol/grpc/middleware"
|
|
7
10
|
require "protocol/grpc/methods"
|
|
8
11
|
require "protocol/grpc/call"
|
|
@@ -14,16 +17,16 @@ require "protocol/grpc/status"
|
|
|
14
17
|
|
|
15
18
|
module Async
|
|
16
19
|
module GRPC
|
|
17
|
-
#
|
|
20
|
+
# Dispatches gRPC requests to registered services.
|
|
18
21
|
# Handles routing based on service name from the request path.
|
|
19
22
|
#
|
|
20
23
|
# @example Registering services:
|
|
21
|
-
# dispatcher =
|
|
22
|
-
# dispatcher.register(
|
|
23
|
-
# dispatcher.register(
|
|
24
|
+
# dispatcher = Dispatcher.new
|
|
25
|
+
# dispatcher.register(GreeterService.new(GreeterInterface, "hello.Greeter"))
|
|
26
|
+
# dispatcher.register(WorldService.new(WorldInterface, "world.Greeter"))
|
|
24
27
|
#
|
|
25
28
|
# server = Async::HTTP::Server.for(endpoint, dispatcher)
|
|
26
|
-
class
|
|
29
|
+
class Dispatcher < Protocol::GRPC::Middleware
|
|
27
30
|
# Initialize the dispatcher.
|
|
28
31
|
# @parameter app [#call | Nil] The next middleware in the chain
|
|
29
32
|
# @parameter services [Hash] Optional initial services hash (service_name => service_instance)
|
|
@@ -32,15 +35,47 @@ module Async
|
|
|
32
35
|
@services = services
|
|
33
36
|
end
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
# Register a service.
|
|
39
|
+
# @parameter service [Async::GRPC::Service] Service instance
|
|
40
|
+
# @parameter name [String] Service name (defaults to service.service_name)
|
|
41
|
+
def register(service, name: service.service_name)
|
|
42
|
+
@services[name] = service
|
|
43
|
+
end
|
|
41
44
|
|
|
42
45
|
protected
|
|
43
46
|
|
|
47
|
+
def invoke_service(service, handler_method, input, output, call)
|
|
48
|
+
begin
|
|
49
|
+
service.send(handler_method, input, output, call)
|
|
50
|
+
ensure
|
|
51
|
+
# Close input stream:
|
|
52
|
+
input.close
|
|
53
|
+
|
|
54
|
+
# Close output stream:
|
|
55
|
+
output.close_write unless output.closed?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Mark trailers and add status (if not already set by handler):
|
|
59
|
+
if call.response&.headers
|
|
60
|
+
call.response.headers.trailer!
|
|
61
|
+
|
|
62
|
+
# Only add OK status if grpc-status hasn't been set by the handler:
|
|
63
|
+
unless call.response.headers["grpc-status"]
|
|
64
|
+
Protocol::GRPC::Metadata.add_status!(call.response.headers, status: Protocol::GRPC::Status::OK)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def dispatch_to_service(service, handler_method, input, output, call, deadline, parent: Async::Task.current)
|
|
70
|
+
if deadline
|
|
71
|
+
parent.with_timeout(deadline) do
|
|
72
|
+
invoke_service(service, handler_method, input, output, call)
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
invoke_service(service, handler_method, input, output, call)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
44
79
|
# Dispatch the request to the appropriate service.
|
|
45
80
|
# @parameter request [Protocol::HTTP::Request] The HTTP request
|
|
46
81
|
# @returns [Protocol::HTTP::Response] The HTTP response
|
|
@@ -66,9 +101,9 @@ module Async
|
|
|
66
101
|
raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Method not found: #{method_name}")
|
|
67
102
|
end
|
|
68
103
|
|
|
69
|
-
handler_method = rpc_descriptor
|
|
70
|
-
request_class = rpc_descriptor
|
|
71
|
-
response_class = rpc_descriptor
|
|
104
|
+
handler_method = rpc_descriptor.method
|
|
105
|
+
request_class = rpc_descriptor.request_class
|
|
106
|
+
response_class = rpc_descriptor.response_class
|
|
72
107
|
|
|
73
108
|
# Verify handler method exists:
|
|
74
109
|
unless service.respond_to?(handler_method, true)
|
|
@@ -80,32 +115,33 @@ module Async
|
|
|
80
115
|
input = Protocol::GRPC::Body::ReadableBody.new(request.body, message_class: request_class, encoding: encoding)
|
|
81
116
|
output = Protocol::GRPC::Body::WritableBody.new(message_class: response_class, encoding: encoding)
|
|
82
117
|
|
|
83
|
-
# Create
|
|
118
|
+
# Create response headers:
|
|
84
119
|
response_headers = Protocol::HTTP::Headers.new([], nil, policy: Protocol::GRPC::HEADER_POLICY)
|
|
85
120
|
response_headers["content-type"] = "application/grpc+proto"
|
|
86
121
|
response_headers["grpc-encoding"] = encoding if encoding
|
|
87
122
|
|
|
123
|
+
# Create response object:
|
|
124
|
+
response = Protocol::HTTP::Response[200, response_headers, output]
|
|
125
|
+
|
|
88
126
|
# Parse deadline from timeout header:
|
|
89
|
-
|
|
90
|
-
deadline = if
|
|
91
|
-
|
|
92
|
-
require "async/deadline"
|
|
93
|
-
Async::Deadline.start(timeout_seconds) if timeout_seconds
|
|
127
|
+
timeout = Protocol::GRPC::Methods.parse_timeout(request.headers["grpc-timeout"])
|
|
128
|
+
deadline = if timeout
|
|
129
|
+
Async::Deadline.start(timeout)
|
|
94
130
|
end
|
|
95
131
|
|
|
96
|
-
call
|
|
132
|
+
# Create call context with request and response:
|
|
133
|
+
call = Protocol::GRPC::Call.new(request, response, deadline: deadline)
|
|
97
134
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
Protocol::GRPC::Metadata.add_status_trailer!(response_headers, status: Protocol::GRPC::Status::OK)
|
|
135
|
+
if rpc_descriptor.streaming?
|
|
136
|
+
Async do |task|
|
|
137
|
+
dispatch_to_service(service, handler_method, input, output, call, deadline, parent: task)
|
|
138
|
+
end
|
|
139
|
+
else
|
|
140
|
+
# Unary call:
|
|
141
|
+
dispatch_to_service(service, handler_method, input, output, call, deadline)
|
|
142
|
+
end
|
|
107
143
|
|
|
108
|
-
|
|
144
|
+
response
|
|
109
145
|
end
|
|
110
146
|
end
|
|
111
147
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
module Async
|
|
7
|
+
module GRPC
|
|
8
|
+
class RemoteError < StandardError
|
|
9
|
+
def self.for(message, metadata)
|
|
10
|
+
self.new(message).tap do |error|
|
|
11
|
+
if backtrace = metadata.delete("backtrace")
|
|
12
|
+
# Backtrace is always an array (Split header format):
|
|
13
|
+
error.set_backtrace(backtrace)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/async/grpc/service.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Async
|
|
|
9
9
|
module GRPC
|
|
10
10
|
# Represents a concrete service implementation that uses an Interface.
|
|
11
11
|
# Subclass this and implement the RPC methods defined in the interface.
|
|
12
|
-
# Services are registered with
|
|
12
|
+
# Services are registered with Dispatcher for routing.
|
|
13
13
|
#
|
|
14
14
|
# @example Example service implementation:
|
|
15
15
|
# class GreeterInterface < Protocol::GRPC::Interface
|
|
@@ -32,8 +32,8 @@ module Async
|
|
|
32
32
|
# end
|
|
33
33
|
#
|
|
34
34
|
# # Register with dispatcher:
|
|
35
|
-
# dispatcher =
|
|
36
|
-
# dispatcher.register(
|
|
35
|
+
# dispatcher = Dispatcher.new
|
|
36
|
+
# dispatcher.register(GreeterService.new(GreeterInterface, "hello.Greeter"))
|
|
37
37
|
# server = Async::HTTP::Server.for(endpoint, dispatcher)
|
|
38
38
|
class Service
|
|
39
39
|
# Initialize a new service instance.
|
|
@@ -58,36 +58,12 @@ module Async
|
|
|
58
58
|
descriptions = {}
|
|
59
59
|
|
|
60
60
|
@interface_class.rpcs.each do |pascal_case_name, rpc|
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
rpc.method
|
|
64
|
-
else
|
|
65
|
-
snake_case_name = pascal_case_to_snake_case(pascal_case_name.to_s)
|
|
66
|
-
snake_case_name.to_sym
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
descriptions[pascal_case_name.to_s] = {
|
|
70
|
-
method: ruby_method_name,
|
|
71
|
-
request_class: rpc.request_class,
|
|
72
|
-
response_class: rpc.response_class,
|
|
73
|
-
streaming: rpc.streaming
|
|
74
|
-
}
|
|
61
|
+
# rpc.method is always set (either explicitly or auto-converted in Interface.rpc)
|
|
62
|
+
descriptions[pascal_case_name.to_s] = rpc
|
|
75
63
|
end
|
|
76
64
|
|
|
77
65
|
descriptions
|
|
78
66
|
end
|
|
79
|
-
|
|
80
|
-
private
|
|
81
|
-
|
|
82
|
-
# Convert PascalCase to snake_case.
|
|
83
|
-
# @parameter pascal_case [String] PascalCase string (e.g., "SayHello")
|
|
84
|
-
# @returns [String] snake_case string (e.g., "say_hello")
|
|
85
|
-
def pascal_case_to_snake_case(pascal_case)
|
|
86
|
-
pascal_case
|
|
87
|
-
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') # Insert underscore before capital letters followed by lowercase
|
|
88
|
-
.gsub(/([a-z\d])([A-Z])/, '\1_\2') # Insert underscore between lowercase/digit and uppercase
|
|
89
|
-
.downcase
|
|
90
|
-
end
|
|
91
67
|
end
|
|
92
68
|
end
|
|
93
69
|
end
|
data/lib/async/grpc/stub.rb
CHANGED
|
@@ -24,8 +24,8 @@ module Async
|
|
|
24
24
|
# rpc.method is always set (either explicit or auto-converted from PascalCase)
|
|
25
25
|
snake_case_method = rpc.method
|
|
26
26
|
|
|
27
|
-
# Index by snake_case method name, storing RPC
|
|
28
|
-
@rpcs_by_method[snake_case_method] =
|
|
27
|
+
# Index by snake_case method name, storing RPC (which includes name field)
|
|
28
|
+
@rpcs_by_method[snake_case_method] = rpc
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -53,8 +53,7 @@ module Async
|
|
|
53
53
|
encoding = options.delete(:encoding)
|
|
54
54
|
|
|
55
55
|
# Delegate to client.invoke with PascalCase method name (for interface lookup):
|
|
56
|
-
@client.invoke(@interface, interface_method_name, request, metadata: metadata, timeout: timeout, encoding: encoding,
|
|
57
|
-
&block)
|
|
56
|
+
@client.invoke(@interface, interface_method_name, request, metadata: metadata, timeout: timeout, encoding: encoding, &block)
|
|
58
57
|
else
|
|
59
58
|
super
|
|
60
59
|
end
|
|
@@ -78,8 +77,8 @@ module Async
|
|
|
78
77
|
# @returns [Array(Protocol::GRPC::RPC, Symbol) | Array(Nil, Nil)] RPC definition and PascalCase method name, or nil if not found
|
|
79
78
|
def lookup_rpc(method_name)
|
|
80
79
|
if @rpcs_by_method.key?(method_name)
|
|
81
|
-
rpc
|
|
82
|
-
return [rpc,
|
|
80
|
+
rpc = @rpcs_by_method[method_name]
|
|
81
|
+
return [rpc, rpc.name]
|
|
83
82
|
end
|
|
84
83
|
|
|
85
84
|
[nil, nil]
|
data/lib/async/grpc/version.rb
CHANGED
data/lib/async/grpc.rb
CHANGED
data/readme.md
CHANGED
|
@@ -10,10 +10,9 @@ Asynchronous gRPC client and server implementation built on top of `protocol-grp
|
|
|
10
10
|
|
|
11
11
|
- **Asynchronous client** - Wraps `Async::HTTP::Client` to provide gRPC-specific call methods with automatic message framing and status handling.
|
|
12
12
|
- **Method-based stubs** - Create type-safe stubs from `Protocol::GRPC::Interface` definitions. Accepts both PascalCase and snake\_case method names for convenience.
|
|
13
|
-
- **Server middleware** - `
|
|
13
|
+
- **Server middleware** - `Dispatcher` routes requests to registered services based on path.
|
|
14
14
|
- **All RPC patterns** - Supports unary, server streaming, client streaming, and bidirectional streaming RPCs.
|
|
15
|
-
- **
|
|
16
|
-
- **HTTP/2 transport** - Built on `async-http` with automatic HTTP/2 multiplexing and connection pooling.
|
|
15
|
+
- **HTTP/1 and HTTP/2 transport** - Built on `async-http` with automatic HTTP/2 multiplexing and connection pooling.
|
|
17
16
|
|
|
18
17
|
## Usage
|
|
19
18
|
|
|
@@ -25,8 +24,22 @@ Please see the [project documentation](https://socketry.github.io/async-grpc/) f
|
|
|
25
24
|
|
|
26
25
|
Please see the [project releases](https://socketry.github.io/async-grpc/releases/index) for all releases.
|
|
27
26
|
|
|
27
|
+
### v0.3.0
|
|
28
|
+
|
|
29
|
+
- **Breaking**: Renamed `DispatcherMiddleware` to `Dispatcher` for cleaner API.
|
|
30
|
+
- **Breaking**: Simplified `Dispatcher#register` API to `register(service, name: service.service_name)`, eliminating redundant service name specification.
|
|
31
|
+
|
|
32
|
+
### v0.2.0
|
|
33
|
+
|
|
34
|
+
- Added `Async::GRPC::RemoteError` class to encapsulate remote error details including message and backtrace extracted from response headers.
|
|
35
|
+
- Client-side error handling now extracts backtraces from response metadata and sets them on `RemoteError`, which is chained as the `cause` of `Protocol::GRPC::Error` for better debugging.
|
|
36
|
+
- Updated to use `Protocol::GRPC::Metadata.add_status!` instead of deprecated `add_status_trailer!` method.
|
|
37
|
+
- Tidy up request and response body handling.
|
|
38
|
+
|
|
28
39
|
### v0.1.0
|
|
29
40
|
|
|
41
|
+
- Initial hack.
|
|
42
|
+
|
|
30
43
|
## See Also
|
|
31
44
|
|
|
32
45
|
- [protocol-grpc](https://github.com/socketry/protocol-grpc) — Protocol abstractions for gRPC that this gem builds upon.
|
data/releases.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.3.0
|
|
4
|
+
|
|
5
|
+
- **Breaking**: Renamed `DispatcherMiddleware` to `Dispatcher` for cleaner API.
|
|
6
|
+
- **Breaking**: Simplified `Dispatcher#register` API to `register(service, name: service.service_name)`, eliminating redundant service name specification.
|
|
7
|
+
|
|
8
|
+
## v0.2.0
|
|
9
|
+
|
|
10
|
+
- Added `Async::GRPC::RemoteError` class to encapsulate remote error details including message and backtrace extracted from response headers.
|
|
11
|
+
- Client-side error handling now extracts backtraces from response metadata and sets them on `RemoteError`, which is chained as the `cause` of `Protocol::GRPC::Error` for better debugging.
|
|
12
|
+
- Updated to use `Protocol::GRPC::Metadata.add_status!` instead of deprecated `add_status_trailer!` method.
|
|
13
|
+
- Tidy up request and response body handling.
|
|
14
|
+
|
|
3
15
|
## v0.1.0
|
|
16
|
+
|
|
17
|
+
- Initial hack.
|
data.tar.gz.sig
ADDED
|
Binary file
|
metadata
CHANGED
|
@@ -1,12 +1,41 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: async-grpc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
8
8
|
bindir: bin
|
|
9
|
-
cert_chain:
|
|
9
|
+
cert_chain:
|
|
10
|
+
- |
|
|
11
|
+
-----BEGIN CERTIFICATE-----
|
|
12
|
+
MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
|
|
13
|
+
ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
|
|
14
|
+
CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
|
|
15
|
+
MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
|
|
16
|
+
MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
|
|
17
|
+
bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
|
|
18
|
+
igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
|
|
19
|
+
9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
|
|
20
|
+
sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
|
|
21
|
+
e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
|
|
22
|
+
XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
|
|
23
|
+
RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
|
|
24
|
+
tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
|
|
25
|
+
zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
|
|
26
|
+
xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
|
|
27
|
+
BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
|
|
28
|
+
aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
|
|
29
|
+
aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
|
|
30
|
+
cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
|
|
31
|
+
xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
|
|
32
|
+
c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
|
|
33
|
+
8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
|
|
34
|
+
JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
|
|
35
|
+
eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
|
|
36
|
+
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
|
37
|
+
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
|
38
|
+
-----END CERTIFICATE-----
|
|
10
39
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
40
|
dependencies:
|
|
12
41
|
- !ruby/object:Gem::Dependency
|
|
@@ -29,14 +58,14 @@ dependencies:
|
|
|
29
58
|
requirements:
|
|
30
59
|
- - "~>"
|
|
31
60
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '0.
|
|
61
|
+
version: '0.5'
|
|
33
62
|
type: :runtime
|
|
34
63
|
prerelease: false
|
|
35
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
65
|
requirements:
|
|
37
66
|
- - "~>"
|
|
38
67
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '0.
|
|
68
|
+
version: '0.5'
|
|
40
69
|
executables: []
|
|
41
70
|
extensions: []
|
|
42
71
|
extra_rdoc_files: []
|
|
@@ -47,7 +76,8 @@ files:
|
|
|
47
76
|
- design.md
|
|
48
77
|
- lib/async/grpc.rb
|
|
49
78
|
- lib/async/grpc/client.rb
|
|
50
|
-
- lib/async/grpc/
|
|
79
|
+
- lib/async/grpc/dispatcher.rb
|
|
80
|
+
- lib/async/grpc/remote_error.rb
|
|
51
81
|
- lib/async/grpc/service.rb
|
|
52
82
|
- lib/async/grpc/stub.rb
|
|
53
83
|
- lib/async/grpc/version.rb
|
metadata.gz.sig
ADDED
|
Binary file
|