protocol-grpc 0.2.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 +1 -0
- data/lib/protocol/grpc/body/readable_body.rb +15 -62
- data/lib/protocol/grpc/call.rb +6 -1
- data/lib/protocol/grpc/header.rb +16 -4
- data/lib/protocol/grpc/interface.rb +8 -1
- data/lib/protocol/grpc/metadata.rb +16 -2
- data/lib/protocol/grpc/version.rb +2 -1
- data/readme.md +7 -1
- data/releases.md +7 -1
- data.tar.gz.sig +2 -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: a889e5eb3f6b74e8609a27a4667b62611ab8dc0515d24d9ecf70c4fee7e25b11
|
|
4
|
+
data.tar.gz: 56b5bc9e9d8a767db66ead55bcc97404e8092ef6a75a008d4bd893624391b808
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6aab2ee9f75421070a6348c39da680c7cd320e31021b9492f48d48832ab4caf580ffa77113a792e336cd87158890d22ed12d9e4650079954b33c99c7da85e422
|
|
7
|
+
data.tar.gz: 67c0e25c06ed8f5c8ad431233506f6133655d64a6e546d9767c74049d85b0fa6b54e37384e15e6458c429f0431779fad96c8b01e30821acd8f7952a260ccf3de
|
checksums.yaml.gz.sig
ADDED
|
Binary file
|
data/context/getting-started.md
CHANGED
|
@@ -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,36 @@ 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
|
+
def self.wrap(message, **options)
|
|
19
|
+
if body = message.body
|
|
20
|
+
message.body = self.new(body, **options)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
return message.body
|
|
24
|
+
end
|
|
25
|
+
|
|
16
26
|
# Initialize a new readable body for gRPC messages.
|
|
17
27
|
# @parameter body [Protocol::HTTP::Body::Readable] The underlying HTTP body
|
|
18
28
|
# @parameter message_class [Class | Nil] Protobuf message class with .decode method.
|
|
19
29
|
# If `nil`, returns raw binary data (useful for channel adapters)
|
|
20
30
|
# @parameter encoding [String | Nil] Compression encoding (from grpc-encoding header)
|
|
21
31
|
def initialize(body, message_class: nil, encoding: nil)
|
|
22
|
-
|
|
32
|
+
super(body)
|
|
23
33
|
@message_class = message_class
|
|
24
34
|
@encoding = encoding
|
|
25
35
|
@buffer = String.new.force_encoding(Encoding::BINARY)
|
|
26
|
-
@closed = false
|
|
27
36
|
end
|
|
28
37
|
|
|
29
|
-
# @attribute [Protocol::HTTP::Body::Readable] The underlying HTTP body.
|
|
30
|
-
attr_reader :body
|
|
31
|
-
|
|
32
38
|
# @attribute [String | Nil] The compression encoding.
|
|
33
39
|
attr_reader :encoding
|
|
34
40
|
|
|
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
41
|
# Read the next gRPC message.
|
|
42
|
+
# Overrides Wrapper#read to transform raw HTTP body chunks into decoded gRPC messages.
|
|
62
43
|
# @returns [Object | String | Nil] Decoded message, raw binary, or `Nil` if stream ended
|
|
63
44
|
def read
|
|
64
|
-
return nil if
|
|
45
|
+
return nil if @body.nil? || @body.empty?
|
|
65
46
|
|
|
66
47
|
# Read 5-byte prefix: 1 byte compression flag + 4 bytes length
|
|
67
48
|
prefix = read_exactly(5)
|
|
@@ -87,24 +68,6 @@ module Protocol
|
|
|
87
68
|
end
|
|
88
69
|
end
|
|
89
70
|
|
|
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
71
|
private
|
|
109
72
|
|
|
110
73
|
# Read exactly n bytes from the underlying body.
|
|
@@ -113,28 +76,18 @@ module Protocol
|
|
|
113
76
|
def read_exactly(n)
|
|
114
77
|
# Fill buffer until we have enough data:
|
|
115
78
|
while @buffer.bytesize < n
|
|
116
|
-
return nil if
|
|
79
|
+
return nil if @body.nil? || @body.empty?
|
|
117
80
|
|
|
118
81
|
# Read chunk from underlying body:
|
|
119
82
|
chunk = @body.read
|
|
120
83
|
|
|
121
84
|
if chunk.nil?
|
|
122
85
|
# End of stream:
|
|
123
|
-
if @body && !@closed
|
|
124
|
-
@body.close
|
|
125
|
-
@closed = true
|
|
126
|
-
end
|
|
127
86
|
return nil
|
|
128
87
|
end
|
|
129
88
|
|
|
130
89
|
# Append to buffer:
|
|
131
90
|
@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
91
|
end
|
|
139
92
|
|
|
140
93
|
# 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.
|
|
@@ -15,6 +15,13 @@ module Protocol
|
|
|
15
15
|
def initialize(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.
|
|
@@ -104,4 +111,4 @@ module Protocol
|
|
|
104
111
|
end
|
|
105
112
|
end
|
|
106
113
|
end
|
|
107
|
-
end
|
|
114
|
+
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,15 @@ 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.3.0
|
|
32
|
+
|
|
33
|
+
- **Breaking**: `Protocol::GRPC::Call` now takes a `response` object parameter instead of separate `response_headers`.
|
|
34
|
+
- **Breaking**: Removed `Call#response_headers` method. Use `call.response.headers` directly.
|
|
35
|
+
- Added `RPC#streaming?` method to check if an RPC is streaming.
|
|
36
|
+
|
|
31
37
|
### v0.2.0
|
|
32
38
|
|
|
33
|
-
- `
|
|
39
|
+
- `RPC#method` is always defined (snake case).
|
|
34
40
|
|
|
35
41
|
### v0.1.0
|
|
36
42
|
|
data/releases.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.3.0
|
|
4
|
+
|
|
5
|
+
- **Breaking**: `Protocol::GRPC::Call` now takes a `response` object parameter instead of separate `response_headers`.
|
|
6
|
+
- **Breaking**: Removed `Call#response_headers` method. Use `call.response.headers` directly.
|
|
7
|
+
- Added `RPC#streaming?` method to check if an RPC is streaming.
|
|
8
|
+
|
|
3
9
|
## v0.2.0
|
|
4
10
|
|
|
5
|
-
- `
|
|
11
|
+
- `RPC#method` is always defined (snake case).
|
|
6
12
|
|
|
7
13
|
## v0.1.0
|
|
8
14
|
|
data.tar.gz.sig
ADDED
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.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
|
metadata.gz.sig
ADDED
|
Binary file
|