connectrpc 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/LICENSE +21 -0
- data/README.md +501 -0
- data/docs/design.md +504 -0
- data/docs/development.md +566 -0
- data/docs/grpc-web.md +165 -0
- data/docs/grpc.md +75 -0
- data/docs/rbs.md +469 -0
- data/docs/streaming.md +251 -0
- data/exe/protoc-gen-connectrpc-ruby +23 -0
- data/lib/connectrpc/bindings/descriptors/status.bin +15 -0
- data/lib/connectrpc/bindings/google_protobuf.rb +92 -0
- data/lib/connectrpc/cancellation.rb +220 -0
- data/lib/connectrpc/client.rb +260 -0
- data/lib/connectrpc/compression.rb +120 -0
- data/lib/connectrpc/deadline.rb +81 -0
- data/lib/connectrpc/error.rb +72 -0
- data/lib/connectrpc/generator/names.rb +105 -0
- data/lib/connectrpc/generator/rbs_renderer.rb +93 -0
- data/lib/connectrpc/generator/renderer.rb +115 -0
- data/lib/connectrpc/generator/runtime_names.rb +84 -0
- data/lib/connectrpc/generator/schema.rb +218 -0
- data/lib/connectrpc/generator/streaming_methods.rb +29 -0
- data/lib/connectrpc/generator.rb +42 -0
- data/lib/connectrpc/get_query.rb +159 -0
- data/lib/connectrpc/grpc.rb +200 -0
- data/lib/connectrpc/grpc_client.rb +23 -0
- data/lib/connectrpc/grpc_server.rb +158 -0
- data/lib/connectrpc/grpc_web.rb +131 -0
- data/lib/connectrpc/grpc_web_cors.rb +135 -0
- data/lib/connectrpc/grpc_web_io.rb +156 -0
- data/lib/connectrpc/grpc_web_server.rb +207 -0
- data/lib/connectrpc/instrumentation.rb +216 -0
- data/lib/connectrpc/message_io.rb +47 -0
- data/lib/connectrpc/metadata.rb +105 -0
- data/lib/connectrpc/method.rb +68 -0
- data/lib/connectrpc/protocol.rb +189 -0
- data/lib/connectrpc/server.rb +398 -0
- data/lib/connectrpc/streaming/client.rb +104 -0
- data/lib/connectrpc/streaming/client_execution.rb +87 -0
- data/lib/connectrpc/streaming/client_scope.rb +105 -0
- data/lib/connectrpc/streaming/framing.rb +123 -0
- data/lib/connectrpc/streaming/framing_io.rb +61 -0
- data/lib/connectrpc/streaming/method.rb +33 -0
- data/lib/connectrpc/streaming/server.rb +122 -0
- data/lib/connectrpc/streaming/server_execution.rb +88 -0
- data/lib/connectrpc/streaming/server_terminal.rb +70 -0
- data/lib/connectrpc/streaming/session.rb +143 -0
- data/lib/connectrpc/streaming/session_completion.rb +134 -0
- data/lib/connectrpc/streaming/session_io.rb +107 -0
- data/lib/connectrpc/streaming/session_messages.rb +119 -0
- data/lib/connectrpc/streaming/strategy.rb +95 -0
- data/lib/connectrpc/streaming/streams.rb +116 -0
- data/lib/connectrpc/streaming/terminal.rb +52 -0
- data/lib/connectrpc/transports/net_http.rb +246 -0
- data/lib/connectrpc/version.rb +5 -0
- data/lib/connectrpc.rb +38 -0
- data/rbs/google_protobuf_4_36/runtime.rbs +41 -0
- data/sig/connectrpc.rbs +772 -0
- data/sig/manifest.yaml +2 -0
- data/third_party/README.md +16 -0
- data/third_party/google/LICENSE +201 -0
- data/third_party/google/rpc/status.proto +48 -0
- data/third_party/protobuf-LICENSE +32 -0
- metadata +106 -0
data/docs/grpc-web.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# gRPC-Web servers
|
|
2
|
+
|
|
3
|
+
The [supported 0.x contract](support.md) defines the target compatibility
|
|
4
|
+
boundary and the remaining adoption gates.
|
|
5
|
+
|
|
6
|
+
Enable gRPC-Web explicitly on the existing server. Generated declarations,
|
|
7
|
+
message bindings, unary invokers and server-stream handlers remain protocol neutral.
|
|
8
|
+
There is no Ruby gRPC-Web client or new runtime dependency.
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
binding = ConnectRPC::Bindings::GoogleProtobuf.new
|
|
12
|
+
cors = ConnectRPC::GRPCWebCORS.new(
|
|
13
|
+
origins: ["https://app.example"], credentials: true,
|
|
14
|
+
request_headers: %w[authorization x-csrf-token], expose_headers: ["x-request-id"]
|
|
15
|
+
)
|
|
16
|
+
server = ConnectRPC::Server.new(
|
|
17
|
+
services: [service], handlers: handlers, binding: binding,
|
|
18
|
+
protocol: %i[connect grpc grpc_web], cors: cors
|
|
19
|
+
)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`protocol: :grpc_web` selects Web alone. A protocol array is copied and frozen;
|
|
23
|
+
it must contain distinct `:connect`, `:grpc` or `:grpc_web` symbols. The default
|
|
24
|
+
is `:connect`. `:auto` remains exactly Connect and native gRPC. Both gRPC families
|
|
25
|
+
require `_GRPCStatusCodec`, supplied by the Google binding or a separate
|
|
26
|
+
`status_codec:` composed with a custom protobuf binding. Enabling Web on a server
|
|
27
|
+
does not change `Client(protocol: :connect | :grpc)` or its default transport.
|
|
28
|
+
|
|
29
|
+
When Connect is enabled, an actual GET selects Connect before observation,
|
|
30
|
+
native publication and Web CORS. Query controls override ignored POST content
|
|
31
|
+
headers, including a supplied Web Content-Type. Safe unary methods use the
|
|
32
|
+
existing Connect GET path; unsafe methods return 405 without handler dispatch.
|
|
33
|
+
A Web-only server still classifies Web GET as Web and rejects it with 405.
|
|
34
|
+
|
|
35
|
+
| Host | Unary binary/text | Server-stream binary/text wire | Incremental browser stream |
|
|
36
|
+
| --- | --- | --- | --- |
|
|
37
|
+
| Async native H2 | Yes | Yes | Text |
|
|
38
|
+
| Falcon native H2 and H1 | Yes | Yes | Text |
|
|
39
|
+
| Rack with rackup/WEBrick | Yes | Rejected before input | No |
|
|
40
|
+
| Native Rails controller | Yes | Rejected before callbacks | No |
|
|
41
|
+
| Testing manual encoded `TransportRequest` | Yes | Rejected before input | No |
|
|
42
|
+
|
|
43
|
+
Official grpc-web 2.1.1 browser clients support binary unary, text unary and
|
|
44
|
+
incremental text server streaming. Binary XHR buffers server-stream responses.
|
|
45
|
+
Binary server-stream wire interoperability uses independent connect-go. Client
|
|
46
|
+
streaming, bidi, JSON, per-message compression and whole-body compression are
|
|
47
|
+
outside this profile. Puma has not been verified. Browser TLS fixtures use an
|
|
48
|
+
explicit test-only verification exception; verified TLS/mTLS wire checks are
|
|
49
|
+
separate. Browser client-certificate selection is not promised.
|
|
50
|
+
|
|
51
|
+
## Media, framing and errors
|
|
52
|
+
|
|
53
|
+
Requests accept exactly `application/grpc-web[+proto]` or
|
|
54
|
+
`application/grpc-web-text[+proto]`, case-insensitively, without parameters.
|
|
55
|
+
Responses include `+proto`. POST is required. An independent bounded Accept
|
|
56
|
+
list supports these aliases, `application/*`, `*/*` and standard q values. The
|
|
57
|
+
most specific match determines each representation's quality. Highest quality
|
|
58
|
+
wins; the request representation wins ties. Responses vary on Accept.
|
|
59
|
+
|
|
60
|
+
Exactly one framed protobuf request and request EOF precede invocation. Empty
|
|
61
|
+
protobuf is a message; missing or excess requests are UNIMPLEMENTED. Excess
|
|
62
|
+
framing is detected without decoding a second message. Reserved flags, request
|
|
63
|
+
terminal, truncation, malformed protobuf/base64 and malformed Content-Length
|
|
64
|
+
are INVALID_ARGUMENT. Unnegotiated compressed DATA is INTERNAL. Unsupported
|
|
65
|
+
valid grpc-encoding tokens are UNIMPLEMENTED; malformed/repeated encoding is
|
|
66
|
+
INTERNAL. Web remains identity-only on a mixed server configured with gzip.
|
|
67
|
+
|
|
68
|
+
Before negotiation, 405/Allow, 415/Accept-Post and 406 are bare HTTP responses.
|
|
69
|
+
After negotiation, RPC errors use HTTP 200 and one 0x80 body terminal. The
|
|
70
|
+
terminal contains lower-case CRLF field lines, exactly one grpc-status and no
|
|
71
|
+
extra empty line. Repeated trailers remain separate wire lines; the official
|
|
72
|
+
JS runtime collapses duplicate trailer keys. Rich errors use the canonical
|
|
73
|
+
private Any/Status codec described in [native gRPC](grpc.md). All six grpc-timeout
|
|
74
|
+
units work; zero expires immediately and absence means no logical expiry.
|
|
75
|
+
|
|
76
|
+
## CORS and Rails
|
|
77
|
+
|
|
78
|
+
CORS is disabled by default. One owner controls each mount. `GRPCWebCORS` is an
|
|
79
|
+
immutable configuration value with exact serialized ASCII HTTP(S) origins.
|
|
80
|
+
It accepts no wildcard, regex, callback or null origin. At most 32 distinct
|
|
81
|
+
origins of 2 KiB each and 32 additional token header names of 128 bytes each are
|
|
82
|
+
accepted. Credential sharing defaults false. Explicit additions are required
|
|
83
|
+
for auth, CSRF and trace request headers.
|
|
84
|
+
|
|
85
|
+
Owned OPTIONS preflight returns empty 204 or 403 before RPC observation, body
|
|
86
|
+
reads, binding or application callbacks. Requested method must be POST and
|
|
87
|
+
requested names must be allowed. Max-Age is zero. Actual Web requests without
|
|
88
|
+
Origin are allowed; disallowed or ambiguous origins receive 403 before input.
|
|
89
|
+
Allowed-origin policy also decorates RPC errors, negotiation failures and native
|
|
90
|
+
admission 503. Vary merges Origin, Accept and preflight controls without replacing
|
|
91
|
+
application cache, cookie or security fields. Conflicting application
|
|
92
|
+
access-control fields fail closed before publication. Set `cors: nil` when outer
|
|
93
|
+
middleware already owns CORS. CORS does not authenticate or replace CSRF checks.
|
|
94
|
+
|
|
95
|
+
Native Rails overrides stay private:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
def connect_protocol = %i[connect grpc_web]
|
|
99
|
+
def connect_status_codec = connect_binding
|
|
100
|
+
def connect_cors = application_web_cors
|
|
101
|
+
def connect_limits = { grpc_status_wire_limit: 65_536, grpc_status_limit: 65_536 }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Explicitly route the intended POST and OPTIONS paths. The normal controller
|
|
105
|
+
invoker, callbacks, authentication, sessions, cookies, CSRF and security headers
|
|
106
|
+
remain active. Preflight does not bypass actual POST checks. A valid CSRF token
|
|
107
|
+
does not override Rails' origin policy. No CSRF exemption is installed.
|
|
108
|
+
|
|
109
|
+
## Bounds and host SPI
|
|
110
|
+
|
|
111
|
+
Message and independent message-wire limits default to 4 MiB. Framing adds five
|
|
112
|
+
bytes. Binary request entity cap is `5 + request_wire_limit`; text entity cap is
|
|
113
|
+
`4 * (5 + request_wire_limit)` because independently padded one-byte groups can
|
|
114
|
+
expand fourfold. Declared and actual encoded bytes, decoded framing and message
|
|
115
|
+
bytes are checked independently. Incremental decoding retains at most three
|
|
116
|
+
encoded carry characters plus a bounded decoded fragment. Whitespace, bad
|
|
117
|
+
alphabet, padding, pad bits and incomplete quartets are rejected.
|
|
118
|
+
|
|
119
|
+
`grpc_status_wire_limit` and `grpc_status_limit` default to 64 KiB. Terminal
|
|
120
|
+
serialization checks both actual CRLF bytes and name/value/32-byte field
|
|
121
|
+
accounting. Rich status has an independent decoded cap. Oversize status becomes
|
|
122
|
+
bounded RESOURCE_EXHAUSTED, or aborts if even the minimal terminal cannot fit.
|
|
123
|
+
Text terminal staging is separately bounded by base64 expansion. Portable unary
|
|
124
|
+
uses a finite aggregate response cap; native streaming never aggregates a whole
|
|
125
|
+
response. Native output retains one queued and one in-flight 16 KiB entity
|
|
126
|
+
fragment. Text uses raw slices of at most 12,288 bytes and flushes padded groups
|
|
127
|
+
at existing write boundaries, including the first small prefix.
|
|
128
|
+
|
|
129
|
+
Native H2 ingress remains 65,536 bytes/five entries with 16 KiB chunks and
|
|
130
|
+
consumption-based stream credit. The H1 input adapter may retain one pinned
|
|
131
|
+
backend chunk of up to 64 KiB. During rejected-input cleanup this remainder can
|
|
132
|
+
coexist with one discard read of at most 64 KiB. Complete parsed field blocks are checked before application copies;
|
|
133
|
+
H2 includes pseudo/status fields. Parser/readahead, HPACK/CONTINUATION temporaries,
|
|
134
|
+
codec allocations, CPU and total RSS are outside these retention bounds.
|
|
135
|
+
|
|
136
|
+
Custom hosts call `Server#call_grpc_web(path:, http_method:, headers:, input:,
|
|
137
|
+
output:, publish:, capabilities: [:unary], lifecycle: nil, cancellation: nil)`.
|
|
138
|
+
Streaming requires explicit `:server_stream` capability. Input and publication
|
|
139
|
+
reuse `_StreamingInput` and `StreamHead`; `_GRPCWebOutput` provides bounded
|
|
140
|
+
`write(bytes, timeout:)`, `finish(terminal:)` and `abort`. Finish only stages
|
|
141
|
+
prepared bytes. Hosts must hold **all terminal bytes** until the original
|
|
142
|
+
processor and outer application cleanup succeed, then release those bytes.
|
|
143
|
+
Body consumers perform no handler, codec, status or base64 work. Cleanup failure
|
|
144
|
+
discards the candidate terminal and aborts delivery. Admission includes original
|
|
145
|
+
processing and wire/callback completion. H1 has explicit resources, not fake H2
|
|
146
|
+
streams. Only an actual H2 CANCEL reset signals Context cancellation. H1 EOF,
|
|
147
|
+
disconnect and host shutdown do not. There is no Connect one-second tail policy.
|
|
148
|
+
Falcon native H1 now has a separate explicit rejected-input resource policy:
|
|
149
|
+
one absolute 100 ms allowance to finish the original HTTP body framing, shortened
|
|
150
|
+
by the original valid grpc-timeout and subject to earlier cancellation, drain
|
|
151
|
+
and owner abort. Each discard read is at most 64 KiB; progress does not reset the
|
|
152
|
+
budget and a following request is never consumed. Expiry or malformed framing
|
|
153
|
+
retires that connection raw-first, preserving the original RPC outcome and
|
|
154
|
+
independent aborted delivery. This best effort does not promise delivery for
|
|
155
|
+
arbitrarily slow uploads or extend existing cleanup allowances. See the
|
|
156
|
+
[Falcon guide](../gems/connectrpc-falcon/README.md#grpc-web).
|
|
157
|
+
|
|
158
|
+
`Server#cors_response(path:, http_method:, headers:, response: nil)` is a pure
|
|
159
|
+
typed host SPI for early decisions and idempotent decoration, including admission
|
|
160
|
+
503. Call it before lifecycle start for preflight or denied requests.
|
|
161
|
+
|
|
162
|
+
The optional OTel adapter maps Web to the project's gRPC semconv 1.44 mapping,
|
|
163
|
+
with `rpc.system.name=grpc` and bounded `connectrpc.protocol=grpc_web`. Call.protocol
|
|
164
|
+
is fixed before subscriber start. Processing, admission, delivery and original
|
|
165
|
+
failures remain separate; SDK/exporter ownership and privacy are unchanged.
|
data/docs/grpc.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Native gRPC
|
|
2
|
+
|
|
3
|
+
The [supported 0.x contract](support.md) defines the target compatibility
|
|
4
|
+
boundary and the remaining adoption gates.
|
|
5
|
+
|
|
6
|
+
Configure `Client(protocol: :grpc)` with an Async transport and a protobuf
|
|
7
|
+
binding. Unary, server streaming, client streaming and bidi use the existing
|
|
8
|
+
method declarations, generated clients and scoped stream endpoints. Configure
|
|
9
|
+
`Server(protocol: :grpc)` for gRPC alone, or `protocol: :auto` for exactly Connect
|
|
10
|
+
and gRPC. Async and Falcon HTTP/2 hosts share the native execution engine.
|
|
11
|
+
h2c, verified TLS and mTLS use the existing backend configuration.
|
|
12
|
+
|
|
13
|
+
The default remains Connect. gRPC accepts `application/grpc` and
|
|
14
|
+
`application/grpc+proto`, and emits the latter. It supports protobuf, identity and
|
|
15
|
+
opt-in per-message gzip. JSON and GET are rejected eagerly. NetHTTP, Rack, Rails,
|
|
16
|
+
Testing and Falcon HTTP/1 reject gRPC before message encoding or handler input.
|
|
17
|
+
Native handlers are plain handlers; native execution provides no Rails callback
|
|
18
|
+
or authentication execution contract.
|
|
19
|
+
|
|
20
|
+
Enabling gRPC requires a capable status codec at construction. A binding may
|
|
21
|
+
implement `_GRPCStatusCodec`, or callers can pass a separate
|
|
22
|
+
`status_codec:` with `encode_status(Error) -> String` and
|
|
23
|
+
`decode_status(String, code: Integer) -> Error`. The optional Google binding
|
|
24
|
+
provides it using canonical Any and Status descriptors in a private pool.
|
|
25
|
+
Opaque detail types and bytes survive without registering their descriptors.
|
|
26
|
+
Core has no protobuf runtime dependency.
|
|
27
|
+
|
|
28
|
+
`grpc_status_wire_limit` bounds the complete terminal field block, counting
|
|
29
|
+
name bytes, value bytes and 32 bytes per occurrence. `grpc_status_limit` bounds
|
|
30
|
+
decoded rich status bytes before protobuf parsing. Both default to 65,536.
|
|
31
|
+
Base64 expansion means a decoded status may exceed the independent wire limit.
|
|
32
|
+
Native gRPC always limits each complete decoded HEADERS block to 65,536 bytes,
|
|
33
|
+
including custom and informational fields, even with larger core configuration.
|
|
34
|
+
These are retention bounds applied before application copies, excluding HPACK,
|
|
35
|
+
CONTINUATION, parser temporary allocation, CPU and RSS. Message and independent
|
|
36
|
+
wire limits remain 4 MiB per message. Ingress and output bounds are unchanged.
|
|
37
|
+
|
|
38
|
+
Clients require actual final HTTP/2 HEADERS and stream EOF. Trailers-only means
|
|
39
|
+
initial final-response HEADERS with END_STREAM. Ordinary initial grpc-status is
|
|
40
|
+
not terminal. Server terminal fields are staged behind processor completion;
|
|
41
|
+
late application cleanup failure prevents successful delivery. Success includes
|
|
42
|
+
transport-owned cleanup, never peer receipt or TLS shutdown.
|
|
43
|
+
|
|
44
|
+
Status accepts a nonempty ASCII digit sequence with leading zeros, bounded by
|
|
45
|
+
the field block budget. Numeric values above uint32 map to unknown; there is no
|
|
46
|
+
10-character spelling limit or unbounded integer parse. Output is canonical.
|
|
47
|
+
Rich status codes compare numerically before unknown-code normalization, and
|
|
48
|
+
rich messages take precedence over grpc-message. Invalid percent escapes remain
|
|
49
|
+
literal; decoded invalid UTF8 is scrubbed. As a deliberate stricter policy than
|
|
50
|
+
grpc-go, details on OK, malformed or duplicate details, and mismatched rich codes
|
|
51
|
+
produce safe internal instead of silently dropping rich data.
|
|
52
|
+
|
|
53
|
+
Missing server timeout means no logical expiry. gRPC timeout accepts all six
|
|
54
|
+
case-sensitive units and zero as immediately expired. Clients default to 30
|
|
55
|
+
seconds; nil omits the timeout and logical watchdog. Dispatch recomputes remaining
|
|
56
|
+
budget without extending monotonic expiry. gRPC per-operation waits have a
|
|
57
|
+
9,999,999.999-second ceiling, further shortened by real deadlines. Connect Async
|
|
58
|
+
nil waits retain their existing behavior. There is no one-second gRPC tail timer.
|
|
59
|
+
Send-I/O terminal recovery has one absolute 100ms budget and allows at most
|
|
60
|
+
262,144 additional body bytes, with separate finite header limits. It skips
|
|
61
|
+
messages without decoding or inflating them and cannot take over an active
|
|
62
|
+
reader. Only a validated remote error can replace an unhandled send failure;
|
|
63
|
+
remote success cannot. The existing destructive cleanup has a separate 100ms
|
|
64
|
+
budget. A stuck frame can fail its connection and siblings during that cleanup.
|
|
65
|
+
|
|
66
|
+
Only actual peer CANCEL resets signal server Context cancellation. Half-close,
|
|
67
|
+
EOF, other resets, connection loss and shutdown do not. No retry or replay occurs.
|
|
68
|
+
Client reset mapping follows grpc-go 1.85.0: FLOW_CONTROL_ERROR and
|
|
69
|
+
ENHANCE_YOUR_CALM map to resource_exhausted, REFUSED_STREAM to unavailable,
|
|
70
|
+
CANCEL to canceled, and INADEQUATE_SECURITY to permission_denied. Other resets
|
|
71
|
+
map to internal. The FLOW_CONTROL_ERROR choice differs from the older protocol
|
|
72
|
+
document's mapping. NO_ERROR can preserve queued data and a valid final status;
|
|
73
|
+
it cannot establish success without that status and owned transport completion.
|
|
74
|
+
Native unary executes the configured invoker with a normal Context, including
|
|
75
|
+
custom invokers that have no handler-map entry.
|