protocol-grpc 0.2.0 → 0.4.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 +4 -0
- data/context/getting-started.md +0 -1
- data/design.md +4 -4
- data/lib/protocol/grpc/body/readable_body.rb +20 -62
- data/lib/protocol/grpc/call.rb +6 -1
- data/lib/protocol/grpc/header.rb +16 -4
- data/lib/protocol/grpc/interface.rb +12 -3
- data/lib/protocol/grpc/metadata.rb +16 -2
- data/lib/protocol/grpc/version.rb +1 -1
- data/readme.md +11 -1
- data/releases.md +11 -1
- data.tar.gz.sig +0 -0
- metadata +31 -2
- 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: 301c1d74991d960be7af4b50850ffa24aab9e4c2c7aedc97759518027aacfe18
|
|
4
|
+
data.tar.gz: ac2fe4b32aa211ef8dbf4329a8677ae09deced47ab21816c0b6fb361d346dcd2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2467aaa36d36c6d011ee4913984defc1974d008bf25081777863d83b4c3c7c7825556ae571a99cc74858a7eece0c99cf253953230bbb7220244082a2c32bd95f
|
|
7
|
+
data.tar.gz: 222fe2052157790f288d72f6f6feff1ad42d507ab188e2bce23cbf0f2e9fa63d7eb0966f774935e5ae0ad3e5a497170f8b126f9dfa4d10ccf53b7f385d7b8437
|
checksums.yaml.gz.sig
ADDED
data/context/getting-started.md
CHANGED
data/design.md
CHANGED
|
@@ -779,10 +779,10 @@ module Protocol
|
|
|
779
779
|
# Handle the RPC
|
|
780
780
|
begin
|
|
781
781
|
handle_rpc(request, handler, handler_method, request_class, response_class)
|
|
782
|
-
rescue Error =>
|
|
783
|
-
trailers_only_error(e.status_code,
|
|
784
|
-
rescue =>
|
|
785
|
-
trailers_only_error(Status::INTERNAL,
|
|
782
|
+
rescue Error => error
|
|
783
|
+
trailers_only_error(e.status_code, error.message)
|
|
784
|
+
rescue => error
|
|
785
|
+
trailers_only_error(Status::INTERNAL, error.message)
|
|
786
786
|
end
|
|
787
787
|
end
|
|
788
788
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# Copyright, 2025, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require "protocol/http"
|
|
7
|
+
require "protocol/http/body/wrapper"
|
|
7
8
|
require "zlib"
|
|
8
9
|
|
|
9
10
|
module Protocol
|
|
@@ -12,56 +13,41 @@ module Protocol
|
|
|
12
13
|
module Body
|
|
13
14
|
# Represents a readable body for gRPC messages with length-prefixed framing.
|
|
14
15
|
# This is the standard readable body for gRPC - all gRPC responses use message framing.
|
|
15
|
-
|
|
16
|
+
# Wraps the underlying HTTP body and transforms raw chunks into decoded gRPC messages.
|
|
17
|
+
class ReadableBody < Protocol::HTTP::Body::Wrapper
|
|
18
|
+
# Wrap the body of a message.
|
|
19
|
+
#
|
|
20
|
+
# @parameter message [Request | Response] The message to wrap.
|
|
21
|
+
# @parameter options [Hash] The options to pass to the initializer.
|
|
22
|
+
# @returns [ReadableBody | Nil] The wrapped body or `nil` if the message has no body.
|
|
23
|
+
def self.wrap(message, **options)
|
|
24
|
+
if body = message.body
|
|
25
|
+
message.body = self.new(body, **options)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
return message.body
|
|
29
|
+
end
|
|
30
|
+
|
|
16
31
|
# Initialize a new readable body for gRPC messages.
|
|
17
32
|
# @parameter body [Protocol::HTTP::Body::Readable] The underlying HTTP body
|
|
18
33
|
# @parameter message_class [Class | Nil] Protobuf message class with .decode method.
|
|
19
34
|
# If `nil`, returns raw binary data (useful for channel adapters)
|
|
20
35
|
# @parameter encoding [String | Nil] Compression encoding (from grpc-encoding header)
|
|
21
36
|
def initialize(body, message_class: nil, encoding: nil)
|
|
22
|
-
|
|
37
|
+
super(body)
|
|
23
38
|
@message_class = message_class
|
|
24
39
|
@encoding = encoding
|
|
25
40
|
@buffer = String.new.force_encoding(Encoding::BINARY)
|
|
26
|
-
@closed = false
|
|
27
41
|
end
|
|
28
42
|
|
|
29
|
-
# @attribute [Protocol::HTTP::Body::Readable] The underlying HTTP body.
|
|
30
|
-
attr_reader :body
|
|
31
|
-
|
|
32
43
|
# @attribute [String | Nil] The compression encoding.
|
|
33
44
|
attr_reader :encoding
|
|
34
45
|
|
|
35
|
-
# Close the input body.
|
|
36
|
-
# @parameter error [Exception | Nil] Optional error that caused the close
|
|
37
|
-
# @returns [Nil]
|
|
38
|
-
def close(error = nil)
|
|
39
|
-
@closed = true
|
|
40
|
-
|
|
41
|
-
if @body
|
|
42
|
-
@body.close(error)
|
|
43
|
-
@body = nil
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
nil
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Check if the stream has been closed.
|
|
50
|
-
# @returns [Boolean] `true` if the stream is closed, `false` otherwise
|
|
51
|
-
def closed?
|
|
52
|
-
@closed or @body.nil?
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Check if there are any input chunks remaining.
|
|
56
|
-
# @returns [Boolean] `true` if the body is empty, `false` otherwise
|
|
57
|
-
def empty?
|
|
58
|
-
@body.nil?
|
|
59
|
-
end
|
|
60
|
-
|
|
61
46
|
# Read the next gRPC message.
|
|
47
|
+
# Overrides Wrapper#read to transform raw HTTP body chunks into decoded gRPC messages.
|
|
62
48
|
# @returns [Object | String | Nil] Decoded message, raw binary, or `Nil` if stream ended
|
|
63
49
|
def read
|
|
64
|
-
return nil if
|
|
50
|
+
return nil if @body.nil? || @body.empty?
|
|
65
51
|
|
|
66
52
|
# Read 5-byte prefix: 1 byte compression flag + 4 bytes length
|
|
67
53
|
prefix = read_exactly(5)
|
|
@@ -87,24 +73,6 @@ module Protocol
|
|
|
87
73
|
end
|
|
88
74
|
end
|
|
89
75
|
|
|
90
|
-
# Enumerate all messages until finished, then invoke {close}.
|
|
91
|
-
# @yields {|message| ...} The block to call with each message.
|
|
92
|
-
def each
|
|
93
|
-
return to_enum unless block_given?
|
|
94
|
-
|
|
95
|
-
error = nil
|
|
96
|
-
begin
|
|
97
|
-
while (message = read)
|
|
98
|
-
yield message
|
|
99
|
-
end
|
|
100
|
-
rescue StandardError => e
|
|
101
|
-
error = e
|
|
102
|
-
raise
|
|
103
|
-
ensure
|
|
104
|
-
close(error)
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
76
|
private
|
|
109
77
|
|
|
110
78
|
# Read exactly n bytes from the underlying body.
|
|
@@ -113,28 +81,18 @@ module Protocol
|
|
|
113
81
|
def read_exactly(n)
|
|
114
82
|
# Fill buffer until we have enough data:
|
|
115
83
|
while @buffer.bytesize < n
|
|
116
|
-
return nil if
|
|
84
|
+
return nil if @body.nil? || @body.empty?
|
|
117
85
|
|
|
118
86
|
# Read chunk from underlying body:
|
|
119
87
|
chunk = @body.read
|
|
120
88
|
|
|
121
89
|
if chunk.nil?
|
|
122
90
|
# End of stream:
|
|
123
|
-
if @body && !@closed
|
|
124
|
-
@body.close
|
|
125
|
-
@closed = true
|
|
126
|
-
end
|
|
127
91
|
return nil
|
|
128
92
|
end
|
|
129
93
|
|
|
130
94
|
# Append to buffer:
|
|
131
95
|
@buffer << chunk.force_encoding(Encoding::BINARY)
|
|
132
|
-
|
|
133
|
-
# Check if body is empty and close if needed:
|
|
134
|
-
if @body.empty?
|
|
135
|
-
@body.close
|
|
136
|
-
@closed = true
|
|
137
|
-
end
|
|
138
96
|
end
|
|
139
97
|
|
|
140
98
|
# Extract the required data:
|
data/lib/protocol/grpc/call.rb
CHANGED
|
@@ -12,9 +12,11 @@ module Protocol
|
|
|
12
12
|
class Call
|
|
13
13
|
# Initialize a new RPC call context.
|
|
14
14
|
# @parameter request [Protocol::HTTP::Request] The HTTP request
|
|
15
|
+
# @parameter response [Protocol::HTTP::Response | Nil] The HTTP response (for setting metadata and trailers)
|
|
15
16
|
# @parameter deadline [Async::Deadline | Nil] Deadline for the call
|
|
16
|
-
def initialize(request, deadline: nil)
|
|
17
|
+
def initialize(request, response = nil, deadline: nil)
|
|
17
18
|
@request = request
|
|
19
|
+
@response = response
|
|
18
20
|
@deadline = deadline
|
|
19
21
|
@cancelled = false
|
|
20
22
|
end
|
|
@@ -22,6 +24,9 @@ module Protocol
|
|
|
22
24
|
# @attribute [Protocol::HTTP::Request] The underlying HTTP request.
|
|
23
25
|
attr_reader :request
|
|
24
26
|
|
|
27
|
+
# @attribute [Protocol::HTTP::Response | Nil] The HTTP response.
|
|
28
|
+
attr_reader :response
|
|
29
|
+
|
|
25
30
|
# @attribute [Async::Deadline | Nil] The deadline for this call.
|
|
26
31
|
attr_reader :deadline
|
|
27
32
|
|
data/lib/protocol/grpc/header.rb
CHANGED
|
@@ -20,9 +20,9 @@ module Protocol
|
|
|
20
20
|
class Status
|
|
21
21
|
# Initialize the status header with the given value.
|
|
22
22
|
#
|
|
23
|
-
# @parameter value [String, Integer] The status code as a string or
|
|
23
|
+
# @parameter value [String, Integer, Array] The status code as a string, integer, or array (takes first element).
|
|
24
24
|
def initialize(value)
|
|
25
|
-
@value = value
|
|
25
|
+
@value = normalize_value(value)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
# Get the status code as an integer.
|
|
@@ -40,12 +40,24 @@ module Protocol
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# Merge another status value (takes the new value, as status should only appear once)
|
|
43
|
-
# @parameter value [String, Integer] The new status code
|
|
43
|
+
# @parameter value [String, Integer, Array] The new status code
|
|
44
44
|
def <<(value)
|
|
45
|
-
@value = value
|
|
45
|
+
@value = normalize_value(value)
|
|
46
46
|
self
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# Normalize a value to an integer status code.
|
|
52
|
+
# Handles arrays (from external clients), strings, and integers.
|
|
53
|
+
# @parameter value [String, Integer, Array] The raw value
|
|
54
|
+
# @returns [Integer] The normalized status code
|
|
55
|
+
def normalize_value(value)
|
|
56
|
+
# Handle Array case (may occur with external clients)
|
|
57
|
+
actual_value = value.is_a?(Array) ? value.flatten.compact.first : value
|
|
58
|
+
actual_value.to_i
|
|
59
|
+
end
|
|
60
|
+
|
|
49
61
|
# Whether this header is acceptable in HTTP trailers.
|
|
50
62
|
# The `grpc-status` header can appear in trailers as per the gRPC specification.
|
|
51
63
|
# @returns [Boolean] `true`, as grpc-status can appear in trailers.
|
|
@@ -11,10 +11,17 @@ module Protocol
|
|
|
11
11
|
# Can be used by both client stubs and server implementations.
|
|
12
12
|
class Interface
|
|
13
13
|
# RPC method definition
|
|
14
|
-
RPC = Struct.new(:request_class, :response_class, :streaming, :method, keyword_init: true) do
|
|
15
|
-
def initialize(request_class:, response_class:, streaming: :unary, method: nil)
|
|
14
|
+
RPC = Struct.new(:name, :request_class, :response_class, :streaming, :method, keyword_init: true) do
|
|
15
|
+
def initialize(name:, request_class:, response_class:, streaming: :unary, method: nil)
|
|
16
16
|
super
|
|
17
17
|
end
|
|
18
|
+
|
|
19
|
+
# Check if this RPC is a streaming RPC (server, client, or bidirectional).
|
|
20
|
+
# Server-side handlers for streaming RPCs are expected to block until all messages are sent.
|
|
21
|
+
# @returns [Boolean] `true` if streaming, `false` if unary
|
|
22
|
+
def streaming?
|
|
23
|
+
streaming != :unary
|
|
24
|
+
end
|
|
18
25
|
end
|
|
19
26
|
|
|
20
27
|
# Hook called when a subclass is created.
|
|
@@ -33,6 +40,8 @@ module Protocol
|
|
|
33
40
|
# @parameter streaming [Symbol] Streaming type (:unary, :server_streaming, :client_streaming, :bidirectional)
|
|
34
41
|
# @parameter method [Symbol | Nil] Optional explicit Ruby method name (snake_case). If not provided, automatically converts PascalCase to snake_case.
|
|
35
42
|
def self.rpc(name, **options)
|
|
43
|
+
options[:name] = name
|
|
44
|
+
|
|
36
45
|
# Ensure snake_case method name is always available
|
|
37
46
|
options[:method] ||= pascal_case_to_snake_case(name.to_s).to_sym
|
|
38
47
|
|
|
@@ -104,4 +113,4 @@ module Protocol
|
|
|
104
113
|
end
|
|
105
114
|
end
|
|
106
115
|
end
|
|
107
|
-
end
|
|
116
|
+
end
|
|
@@ -33,8 +33,22 @@ module Protocol
|
|
|
33
33
|
status.to_i
|
|
34
34
|
else
|
|
35
35
|
# Fallback for when header policy isn't used
|
|
36
|
-
|
|
37
|
-
status_value.
|
|
36
|
+
# Handle Array case (may occur with external clients)
|
|
37
|
+
status_value = if status.is_a?(Array)
|
|
38
|
+
# Flatten and take first non-nil value, recursively handle nested arrays
|
|
39
|
+
flattened = status.flatten.compact.first
|
|
40
|
+
# If still an array, take first element
|
|
41
|
+
flattened.is_a?(Array) ? flattened.first : flattened
|
|
42
|
+
else
|
|
43
|
+
status
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Convert to string then integer to handle various types
|
|
47
|
+
# Handle case where status_value might still be an array somehow
|
|
48
|
+
if status_value.is_a?(Array)
|
|
49
|
+
status_value = status_value.first
|
|
50
|
+
end
|
|
51
|
+
status_value.to_s.to_i
|
|
38
52
|
end
|
|
39
53
|
end
|
|
40
54
|
|
data/readme.md
CHANGED
|
@@ -28,9 +28,19 @@ Please see the [project documentation](https://socketry.github.io/protocol-grpc/
|
|
|
28
28
|
|
|
29
29
|
Please see the [project releases](https://socketry.github.io/protocol-grpc/releases/index) for all releases.
|
|
30
30
|
|
|
31
|
+
### v0.4.0
|
|
32
|
+
|
|
33
|
+
- Add `RPC#name`.
|
|
34
|
+
|
|
35
|
+
### v0.3.0
|
|
36
|
+
|
|
37
|
+
- **Breaking**: `Protocol::GRPC::Call` now takes a `response` object parameter instead of separate `response_headers`.
|
|
38
|
+
- **Breaking**: Removed `Call#response_headers` method. Use `call.response.headers` directly.
|
|
39
|
+
- Added `RPC#streaming?` method to check if an RPC is streaming.
|
|
40
|
+
|
|
31
41
|
### v0.2.0
|
|
32
42
|
|
|
33
|
-
- `
|
|
43
|
+
- `RPC#method` is always defined (snake case).
|
|
34
44
|
|
|
35
45
|
### v0.1.0
|
|
36
46
|
|
data/releases.md
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.4.0
|
|
4
|
+
|
|
5
|
+
- Add `RPC#name`.
|
|
6
|
+
|
|
7
|
+
## v0.3.0
|
|
8
|
+
|
|
9
|
+
- **Breaking**: `Protocol::GRPC::Call` now takes a `response` object parameter instead of separate `response_headers`.
|
|
10
|
+
- **Breaking**: Removed `Call#response_headers` method. Use `call.response.headers` directly.
|
|
11
|
+
- Added `RPC#streaming?` method to check if an RPC is streaming.
|
|
12
|
+
|
|
3
13
|
## v0.2.0
|
|
4
14
|
|
|
5
|
-
- `
|
|
15
|
+
- `RPC#method` is always defined (snake case).
|
|
6
16
|
|
|
7
17
|
## v0.1.0
|
|
8
18
|
|
data.tar.gz.sig
ADDED
|
Binary file
|
metadata
CHANGED
|
@@ -1,12 +1,41 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: protocol-grpc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.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
|
metadata.gz.sig
ADDED
|
Binary file
|