protocol-grpc 0.1.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 +7 -0
- data/context/getting-started.md +132 -0
- data/context/index.yaml +12 -0
- data/design.md +1675 -0
- data/lib/protocol/grpc/body/readable_body.rb +165 -0
- data/lib/protocol/grpc/body/writable_body.rb +101 -0
- data/lib/protocol/grpc/call.rb +64 -0
- data/lib/protocol/grpc/error.rb +133 -0
- data/lib/protocol/grpc/header.rb +119 -0
- data/lib/protocol/grpc/health_check.rb +19 -0
- data/lib/protocol/grpc/interface.rb +89 -0
- data/lib/protocol/grpc/metadata.rb +117 -0
- data/lib/protocol/grpc/methods.rb +113 -0
- data/lib/protocol/grpc/middleware.rb +71 -0
- data/lib/protocol/grpc/status.rb +50 -0
- data/lib/protocol/grpc/version.rb +12 -0
- data/lib/protocol/grpc.rb +35 -0
- data/license.md +21 -0
- data/readme.md +57 -0
- data/releases.md +5 -0
- metadata +114 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "uri"
|
|
7
|
+
require_relative "header"
|
|
8
|
+
require_relative "status"
|
|
9
|
+
|
|
10
|
+
module Protocol
|
|
11
|
+
module GRPC
|
|
12
|
+
# @namespace
|
|
13
|
+
module Metadata
|
|
14
|
+
# Extract gRPC status from headers.
|
|
15
|
+
# Convenience method that handles both Header::Status instances and raw values.
|
|
16
|
+
# Returns Status::UNKNOWN if status is not present.
|
|
17
|
+
#
|
|
18
|
+
# Note: In Protocol::HTTP::Headers, trailers are merged into the headers
|
|
19
|
+
# so users just access headers["grpc-status"] regardless of whether it
|
|
20
|
+
# was sent as an initial header or trailer.
|
|
21
|
+
#
|
|
22
|
+
# @parameter headers [Protocol::HTTP::Headers]
|
|
23
|
+
# @returns [Integer] Status code (0-16)
|
|
24
|
+
def self.extract_status(headers)
|
|
25
|
+
# Ensure policy is set - setting policy clears the index (@indexed = nil)
|
|
26
|
+
# The index will be rebuilt automatically on next access via to_h
|
|
27
|
+
headers.policy = Protocol::GRPC::HEADER_POLICY unless headers.policy == Protocol::GRPC::HEADER_POLICY
|
|
28
|
+
|
|
29
|
+
status = headers["grpc-status"]
|
|
30
|
+
return Status::UNKNOWN unless status
|
|
31
|
+
|
|
32
|
+
if status.is_a?(Header::Status)
|
|
33
|
+
status.to_i
|
|
34
|
+
else
|
|
35
|
+
# Fallback for when header policy isn't used
|
|
36
|
+
status_value = status.is_a?(Array) ? status.first : status.to_s
|
|
37
|
+
status_value.to_i
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Extract gRPC status message from headers.
|
|
42
|
+
# Convenience method that handles both Header::Message instances and raw values.
|
|
43
|
+
# Returns `Nil` if message is not present.
|
|
44
|
+
#
|
|
45
|
+
# @parameter headers [Protocol::HTTP::Headers]
|
|
46
|
+
# @returns [String | Nil] Status message
|
|
47
|
+
def self.extract_message(headers)
|
|
48
|
+
# Ensure policy is set - setting policy clears the index (@indexed = nil)
|
|
49
|
+
# The index will be rebuilt automatically on next access via to_h
|
|
50
|
+
headers.policy = Protocol::GRPC::HEADER_POLICY unless headers.policy == Protocol::GRPC::HEADER_POLICY
|
|
51
|
+
|
|
52
|
+
message = headers["grpc-message"]
|
|
53
|
+
return nil unless message
|
|
54
|
+
|
|
55
|
+
if message.is_a?(Header::Message)
|
|
56
|
+
message.decode
|
|
57
|
+
else
|
|
58
|
+
# Fallback for when header policy isn't used
|
|
59
|
+
message_value = message.is_a?(Array) ? message.first : message.to_s
|
|
60
|
+
URI.decode_www_form_component(message_value)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Build headers with gRPC status and message
|
|
65
|
+
# @parameter status [Integer] gRPC status code
|
|
66
|
+
# @parameter message [String | Nil] Optional status message
|
|
67
|
+
# @parameter policy [Hash] Header policy to use
|
|
68
|
+
# @returns [Protocol::HTTP::Headers]
|
|
69
|
+
def self.build_status_headers(status: Status::OK, message: nil, policy: HEADER_POLICY)
|
|
70
|
+
headers = Protocol::HTTP::Headers.new([], nil, policy: policy)
|
|
71
|
+
headers["grpc-status"] = Header::Status.new(status)
|
|
72
|
+
headers["grpc-message"] = Header::Message.new(Header::Message.encode(message)) if message
|
|
73
|
+
headers
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Mark that trailers will follow (call after sending initial headers)
|
|
77
|
+
# @parameter headers [Protocol::HTTP::Headers]
|
|
78
|
+
# @returns [Protocol::HTTP::Headers]
|
|
79
|
+
def self.prepare_trailers!(headers)
|
|
80
|
+
headers.trailer!
|
|
81
|
+
headers
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Add status as trailers to existing headers
|
|
85
|
+
# @parameter headers [Protocol::HTTP::Headers]
|
|
86
|
+
# @parameter status [Integer] gRPC status code
|
|
87
|
+
# @parameter message [String | Nil] Optional status message
|
|
88
|
+
def self.add_status_trailer!(headers, status: Status::OK, message: nil)
|
|
89
|
+
headers.trailer! unless headers.trailer?
|
|
90
|
+
headers["grpc-status"] = Header::Status.new(status)
|
|
91
|
+
headers["grpc-message"] = Header::Message.new(Header::Message.encode(message)) if message
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Add status as initial headers (for trailers-only responses)
|
|
95
|
+
# @parameter headers [Protocol::HTTP::Headers]
|
|
96
|
+
# @parameter status [Integer] gRPC status code
|
|
97
|
+
# @parameter message [String | Nil] Optional status message
|
|
98
|
+
def self.add_status_header!(headers, status: Status::OK, message: nil)
|
|
99
|
+
headers["grpc-status"] = Header::Status.new(status)
|
|
100
|
+
headers["grpc-message"] = Header::Message.new(Header::Message.encode(message)) if message
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Build a trailers-only error response (no body, status in headers)
|
|
104
|
+
# @parameter status [Integer] gRPC status code
|
|
105
|
+
# @parameter message [String | Nil] Optional status message
|
|
106
|
+
# @parameter policy [Hash] Header policy to use
|
|
107
|
+
# @returns [Protocol::HTTP::Response]
|
|
108
|
+
def self.build_trailers_only_response(status:, message: nil, policy: HEADER_POLICY)
|
|
109
|
+
headers = Protocol::HTTP::Headers.new([], nil, policy: policy)
|
|
110
|
+
headers["content-type"] = "application/grpc+proto"
|
|
111
|
+
add_status_header!(headers, status: status, message: message)
|
|
112
|
+
|
|
113
|
+
Protocol::HTTP::Response[200, headers, nil]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "base64"
|
|
7
|
+
require "protocol/http"
|
|
8
|
+
|
|
9
|
+
module Protocol
|
|
10
|
+
module GRPC
|
|
11
|
+
# Provides utility methods for building and parsing gRPC-compatible HTTP requests.
|
|
12
|
+
module Methods
|
|
13
|
+
# Build gRPC path from service and method.
|
|
14
|
+
# @parameter service [String] e.g., "my_service.Greeter"
|
|
15
|
+
# @parameter method [String] e.g., "SayHello"
|
|
16
|
+
# @returns [String] e.g., "/my_service.Greeter/SayHello"
|
|
17
|
+
def self.build_path(service, method)
|
|
18
|
+
"/#{service}/#{method}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Parse service and method from gRPC path.
|
|
22
|
+
# @parameter path [String] e.g., "/my_service.Greeter/SayHello"
|
|
23
|
+
# @returns [Array(String, String)] [service, method]
|
|
24
|
+
def self.parse_path(path)
|
|
25
|
+
parts = path.split("/")
|
|
26
|
+
[parts[1], parts[2]]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Build gRPC request headers.
|
|
30
|
+
# @parameter metadata [Hash] Custom metadata key-value pairs
|
|
31
|
+
# @parameter timeout [Numeric | Nil] Optional timeout in seconds
|
|
32
|
+
# @parameter content_type [String] Content type (default: "application/grpc+proto")
|
|
33
|
+
# @returns [Protocol::HTTP::Headers]
|
|
34
|
+
def self.build_headers(metadata: {}, timeout: nil, content_type: "application/grpc+proto")
|
|
35
|
+
headers = Protocol::HTTP::Headers.new
|
|
36
|
+
headers["content-type"] = content_type
|
|
37
|
+
headers["te"] = "trailers"
|
|
38
|
+
headers["grpc-timeout"] = format_timeout(timeout) if timeout
|
|
39
|
+
|
|
40
|
+
metadata.each do |key, value|
|
|
41
|
+
# Binary headers end with -bin and are base64 encoded:
|
|
42
|
+
headers[key] = if key.end_with?("-bin")
|
|
43
|
+
Base64.strict_encode64(value)
|
|
44
|
+
else
|
|
45
|
+
value.to_s
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
headers
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Extract metadata from gRPC headers.
|
|
53
|
+
# @parameter headers [Protocol::HTTP::Headers]
|
|
54
|
+
# @returns [Hash] Metadata key-value pairs
|
|
55
|
+
def self.extract_metadata(headers)
|
|
56
|
+
metadata = {}
|
|
57
|
+
|
|
58
|
+
headers.each do |key, value|
|
|
59
|
+
# Skip reserved headers:
|
|
60
|
+
next if key.start_with?("grpc-") || key == "content-type" || key == "te"
|
|
61
|
+
|
|
62
|
+
# Decode binary headers:
|
|
63
|
+
metadata[key] = if key.end_with?("-bin")
|
|
64
|
+
Base64.strict_decode64(value)
|
|
65
|
+
else
|
|
66
|
+
value
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
metadata
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Format timeout for grpc-timeout header.
|
|
74
|
+
# @parameter timeout [Numeric] Timeout in seconds
|
|
75
|
+
# @returns [String] e.g., "1000m" for 1 second
|
|
76
|
+
def self.format_timeout(timeout)
|
|
77
|
+
# gRPC timeout format: value + unit (H=hours, M=minutes, S=seconds, m=milliseconds, u=microseconds, n=nanoseconds)
|
|
78
|
+
if timeout >= 3600
|
|
79
|
+
"#{(timeout / 3600).to_i}H"
|
|
80
|
+
elsif timeout >= 60
|
|
81
|
+
"#{(timeout / 60).to_i}M"
|
|
82
|
+
elsif timeout >= 1
|
|
83
|
+
"#{timeout.to_i}S"
|
|
84
|
+
elsif timeout >= 0.001
|
|
85
|
+
"#{(timeout * 1000).to_i}m"
|
|
86
|
+
elsif timeout >= 0.000001
|
|
87
|
+
"#{(timeout * 1_000_000).to_i}u"
|
|
88
|
+
else
|
|
89
|
+
"#{(timeout * 1_000_000_000).to_i}n"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Parse grpc-timeout header value.
|
|
94
|
+
# @parameter value [String] e.g., "1000m"
|
|
95
|
+
# @returns [Numeric | Nil] Timeout in seconds, or `Nil` if value is invalid
|
|
96
|
+
def self.parse_timeout(value)
|
|
97
|
+
return nil unless value
|
|
98
|
+
|
|
99
|
+
amount = value[0...-1].to_i
|
|
100
|
+
unit = value[-1]
|
|
101
|
+
|
|
102
|
+
case unit
|
|
103
|
+
when "H" then amount * 3600
|
|
104
|
+
when "M" then amount * 60
|
|
105
|
+
when "S" then amount
|
|
106
|
+
when "m" then amount / 1000.0
|
|
107
|
+
when "u" then amount / 1_000_000.0
|
|
108
|
+
when "n" then amount / 1_000_000_000.0
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "protocol/http"
|
|
7
|
+
require_relative "error"
|
|
8
|
+
require_relative "status"
|
|
9
|
+
require_relative "metadata"
|
|
10
|
+
require_relative "header"
|
|
11
|
+
|
|
12
|
+
module Protocol
|
|
13
|
+
module GRPC
|
|
14
|
+
# Represents server middleware for handling gRPC requests.
|
|
15
|
+
# Implements Protocol::HTTP::Middleware interface.
|
|
16
|
+
# Subclasses should implement {dispatch} to handle service routing and protocol details.
|
|
17
|
+
class Middleware < Protocol::HTTP::Middleware
|
|
18
|
+
# Initialize a new middleware instance.
|
|
19
|
+
# @parameter app [#call | Nil] The next middleware in the chain
|
|
20
|
+
def initialize(app = nil)
|
|
21
|
+
super(app)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Handle incoming HTTP request.
|
|
25
|
+
# @parameter request [Protocol::HTTP::Request]
|
|
26
|
+
# @returns [Protocol::HTTP::Response]
|
|
27
|
+
def call(request)
|
|
28
|
+
return super unless grpc_request?(request)
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
dispatch(request)
|
|
32
|
+
rescue Error => error
|
|
33
|
+
trailers_only_error(error.status_code, error.message)
|
|
34
|
+
rescue StandardError => error
|
|
35
|
+
trailers_only_error(Status::INTERNAL, error.message)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Dispatch the request to the service handler.
|
|
40
|
+
# Subclasses should implement this method to handle routing and protocol details.
|
|
41
|
+
# @parameter request [Protocol::HTTP::Request]
|
|
42
|
+
# @returns [Protocol::HTTP::Response]
|
|
43
|
+
# @raises [NotImplementedError] If not implemented by subclass
|
|
44
|
+
def dispatch(request)
|
|
45
|
+
raise NotImplementedError, "Subclasses must implement #dispatch"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
protected
|
|
49
|
+
|
|
50
|
+
# Check if the request is a gRPC request.
|
|
51
|
+
# @parameter request [Protocol::HTTP::Request]
|
|
52
|
+
# @returns [Boolean] `true` if the request is a gRPC request, `false` otherwise
|
|
53
|
+
def grpc_request?(request)
|
|
54
|
+
content_type = request.headers["content-type"]
|
|
55
|
+
content_type&.start_with?("application/grpc")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Build a trailers-only error response.
|
|
59
|
+
# @parameter status_code [Integer] gRPC status code
|
|
60
|
+
# @parameter message [String] Error message
|
|
61
|
+
# @returns [Protocol::HTTP::Response]
|
|
62
|
+
def trailers_only_error(status_code, message)
|
|
63
|
+
Metadata.build_trailers_only_response(
|
|
64
|
+
status: status_code,
|
|
65
|
+
message: message,
|
|
66
|
+
policy: HEADER_POLICY
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
module Protocol
|
|
7
|
+
module GRPC
|
|
8
|
+
# Provides gRPC status codes and their descriptions.
|
|
9
|
+
module Status
|
|
10
|
+
OK = 0
|
|
11
|
+
CANCELLED = 1
|
|
12
|
+
UNKNOWN = 2
|
|
13
|
+
INVALID_ARGUMENT = 3
|
|
14
|
+
DEADLINE_EXCEEDED = 4
|
|
15
|
+
NOT_FOUND = 5
|
|
16
|
+
ALREADY_EXISTS = 6
|
|
17
|
+
PERMISSION_DENIED = 7
|
|
18
|
+
RESOURCE_EXHAUSTED = 8
|
|
19
|
+
FAILED_PRECONDITION = 9
|
|
20
|
+
ABORTED = 10
|
|
21
|
+
OUT_OF_RANGE = 11
|
|
22
|
+
UNIMPLEMENTED = 12
|
|
23
|
+
INTERNAL = 13
|
|
24
|
+
UNAVAILABLE = 14
|
|
25
|
+
DATA_LOSS = 15
|
|
26
|
+
UNAUTHENTICATED = 16
|
|
27
|
+
|
|
28
|
+
# Status code descriptions
|
|
29
|
+
DESCRIPTIONS = {
|
|
30
|
+
OK => "OK",
|
|
31
|
+
CANCELLED => "Cancelled",
|
|
32
|
+
UNKNOWN => "Unknown",
|
|
33
|
+
INVALID_ARGUMENT => "Invalid Argument",
|
|
34
|
+
DEADLINE_EXCEEDED => "Deadline Exceeded",
|
|
35
|
+
NOT_FOUND => "Not Found",
|
|
36
|
+
ALREADY_EXISTS => "Already Exists",
|
|
37
|
+
PERMISSION_DENIED => "Permission Denied",
|
|
38
|
+
RESOURCE_EXHAUSTED => "Resource Exhausted",
|
|
39
|
+
FAILED_PRECONDITION => "Failed Precondition",
|
|
40
|
+
ABORTED => "Aborted",
|
|
41
|
+
OUT_OF_RANGE => "Out of Range",
|
|
42
|
+
UNIMPLEMENTED => "Unimplemented",
|
|
43
|
+
INTERNAL => "Internal",
|
|
44
|
+
UNAVAILABLE => "Unavailable",
|
|
45
|
+
DATA_LOSS => "Data Loss",
|
|
46
|
+
UNAUTHENTICATED => "Unauthenticated"
|
|
47
|
+
}.freeze
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "grpc/version"
|
|
7
|
+
|
|
8
|
+
require_relative "grpc/status"
|
|
9
|
+
require_relative "grpc/error"
|
|
10
|
+
require_relative "grpc/methods"
|
|
11
|
+
require_relative "grpc/header"
|
|
12
|
+
require_relative "grpc/metadata"
|
|
13
|
+
require_relative "grpc/call"
|
|
14
|
+
require_relative "grpc/body/readable_body"
|
|
15
|
+
require_relative "grpc/body/writable_body"
|
|
16
|
+
require_relative "grpc/interface"
|
|
17
|
+
require_relative "grpc/middleware"
|
|
18
|
+
require_relative "grpc/health_check"
|
|
19
|
+
|
|
20
|
+
module Protocol
|
|
21
|
+
# Protocol abstractions for gRPC, built on top of `protocol-http`.
|
|
22
|
+
#
|
|
23
|
+
# gRPC is an RPC framework that runs over HTTP/2. It uses Protocol Buffers for serialization
|
|
24
|
+
# and supports four types of RPC patterns:
|
|
25
|
+
#
|
|
26
|
+
# 1. **Unary RPC**: Single request, single response
|
|
27
|
+
# 2. **Client Streaming**: Stream of requests, single response
|
|
28
|
+
# 3. **Server Streaming**: Single request, stream of responses
|
|
29
|
+
# 4. **Bidirectional Streaming**: Stream of requests, stream of responses
|
|
30
|
+
#
|
|
31
|
+
# This gem provides **protocol-level abstractions only** - no networking, no client/server implementations.
|
|
32
|
+
# Those should be built on top in separate gems (e.g., `async-grpc`).
|
|
33
|
+
module GRPC
|
|
34
|
+
end
|
|
35
|
+
end
|
data/license.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright, 2025, by Samuel Williams.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/readme.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Protocol::GRPC
|
|
2
|
+
|
|
3
|
+
Provides abstractions for working with the gRPC protocol over HTTP/2.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/socketry/protocol-grpc/actions?workflow=Test)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
`protocol-grpc` provides protocol-level abstractions for building gRPC applications:
|
|
10
|
+
|
|
11
|
+
- **Protocol-level abstractions** - No networking, no client/server implementations. Focuses on gRPC protocol details.
|
|
12
|
+
- **Message framing** - Handles gRPC's 5-byte length-prefixed message format with compression support.
|
|
13
|
+
- **Status codes and error handling** - Complete gRPC status code support with error hierarchy.
|
|
14
|
+
- **Metadata and trailers** - Full support for gRPC metadata headers and HTTP trailers.
|
|
15
|
+
- **Interface definitions** - Define service contracts using `Protocol::GRPC::Interface` with PascalCase method names matching `.proto` files.
|
|
16
|
+
- **Middleware pattern** - Abstract base class for building gRPC server applications.
|
|
17
|
+
- **Call context** - Track deadlines, metadata, and request context for each RPC call.
|
|
18
|
+
|
|
19
|
+
Following the same pattern as `protocol-http`, this gem provides only protocol abstractions. Client and server implementations are built on top in separate gems (e.g., `async-grpc`).
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
Please see the [project documentation](https://socketry.github.io/protocol-grpc/) for more details.
|
|
24
|
+
|
|
25
|
+
- [Getting Started](https://socketry.github.io/protocol-grpc/guides/getting-started/index) - This guide explains how to use `protocol-grpc` for building abstract gRPC interfaces.
|
|
26
|
+
|
|
27
|
+
## Releases
|
|
28
|
+
|
|
29
|
+
Please see the [project releases](https://socketry.github.io/protocol-grpc/releases/index) for all releases.
|
|
30
|
+
|
|
31
|
+
### v0.1.0
|
|
32
|
+
|
|
33
|
+
- Initial design.
|
|
34
|
+
|
|
35
|
+
## See Also
|
|
36
|
+
|
|
37
|
+
- [async-grpc](https://github.com/socketry/async-grpc) — Asynchronous gRPC client and server implementation using this interface.
|
|
38
|
+
- [protocol-http](https://github.com/socketry/protocol-http) — HTTP protocol abstractions that gRPC builds upon.
|
|
39
|
+
- [async-http](https://github.com/socketry/async-http) — Asynchronous HTTP client and server, supporting HTTP/2 which gRPC requires.
|
|
40
|
+
|
|
41
|
+
## Contributing
|
|
42
|
+
|
|
43
|
+
We welcome contributions to this project.
|
|
44
|
+
|
|
45
|
+
1. Fork it.
|
|
46
|
+
2. Create your feature branch (`git checkout -b my-new-feature`).
|
|
47
|
+
3. Commit your changes (`git commit -am 'Add some feature'`).
|
|
48
|
+
4. Push to the branch (`git push origin my-new-feature`).
|
|
49
|
+
5. Create new Pull Request.
|
|
50
|
+
|
|
51
|
+
### Developer Certificate of Origin
|
|
52
|
+
|
|
53
|
+
In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
|
|
54
|
+
|
|
55
|
+
### Community Guidelines
|
|
56
|
+
|
|
57
|
+
This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
|
metadata
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: protocol-grpc
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Samuel Williams
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: async
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: base64
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: google-protobuf
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '4.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '4.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: protocol-http
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.28'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.28'
|
|
68
|
+
executables: []
|
|
69
|
+
extensions: []
|
|
70
|
+
extra_rdoc_files: []
|
|
71
|
+
files:
|
|
72
|
+
- context/getting-started.md
|
|
73
|
+
- context/index.yaml
|
|
74
|
+
- design.md
|
|
75
|
+
- lib/protocol/grpc.rb
|
|
76
|
+
- lib/protocol/grpc/body/readable_body.rb
|
|
77
|
+
- lib/protocol/grpc/body/writable_body.rb
|
|
78
|
+
- lib/protocol/grpc/call.rb
|
|
79
|
+
- lib/protocol/grpc/error.rb
|
|
80
|
+
- lib/protocol/grpc/header.rb
|
|
81
|
+
- lib/protocol/grpc/health_check.rb
|
|
82
|
+
- lib/protocol/grpc/interface.rb
|
|
83
|
+
- lib/protocol/grpc/metadata.rb
|
|
84
|
+
- lib/protocol/grpc/methods.rb
|
|
85
|
+
- lib/protocol/grpc/middleware.rb
|
|
86
|
+
- lib/protocol/grpc/status.rb
|
|
87
|
+
- lib/protocol/grpc/version.rb
|
|
88
|
+
- license.md
|
|
89
|
+
- readme.md
|
|
90
|
+
- releases.md
|
|
91
|
+
homepage: https://github.com/socketry/protocol-grpc
|
|
92
|
+
licenses:
|
|
93
|
+
- MIT
|
|
94
|
+
metadata:
|
|
95
|
+
documentation_uri: https://socketry.github.io/protocol-grpc/
|
|
96
|
+
source_code_uri: https://github.com/socketry/protocol-grpc.git
|
|
97
|
+
rdoc_options: []
|
|
98
|
+
require_paths:
|
|
99
|
+
- lib
|
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '3.2'
|
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
requirements: []
|
|
111
|
+
rubygems_version: 3.6.9
|
|
112
|
+
specification_version: 4
|
|
113
|
+
summary: Protocol abstractions for gRPC, built on top of protocol-http.
|
|
114
|
+
test_files: []
|