tonic-rails 0.3.0-x86_64-linux-gnu → 0.4.0-x86_64-linux-gnu

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b594b6ba6699510bedb286cab5e05fc8fa3578d200fdb704fd8335529ee0105
4
- data.tar.gz: df3ddc7f65ca300dce59a26855fcadfd88f573c9f892039afa727ec16a3dc01e
3
+ metadata.gz: 06cf21277f2b5442bae8386269a576c89515959d2144d9d8ed3e171b24e73c7d
4
+ data.tar.gz: 9862766277deb1548e0e6e88d48e611746282d430578b198d1f81430e759cf69
5
5
  SHA512:
6
- metadata.gz: 0d8da9f21bc78c95c31b708a0349d54f23e9a5395bad4de0d46937ec1fae721bd6590a74d498dd523e087b1a1c90b23af6a10c93f2cfaab262d554e3c74272a0
7
- data.tar.gz: a67219c39168b2eeae48a638b73ebdfa65a4c5a0300138577088f214868db989e36196647639753563584e49dfb256da0175f2b13aac0b8526d30cb9cee38f44
6
+ metadata.gz: 9a72581a9b168d58c71402037b41e0df2650dcad092fa1163b0bf738508da03d3db6babf9c0651b58636fe989be50431231176152b1e7ed894838871a8a706e5
7
+ data.tar.gz: 5397e3379da5008ccc472b4b3d6f3f88e649675e0290b271dde7a2cfeb1843f45287cbd4d0776750304d12bcb19179c7c2206ef0c05cd7af1744721e6d57c790
data/CHANGELOG.md CHANGED
@@ -7,6 +7,15 @@ minor versions may contain breaking changes.
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-10-11
11
+
12
+ ### Added
13
+
14
+ - gRPC-Web: `config.grpc_web = true` serves browser clients (unary and
15
+ server-streaming RPCs, binary and text encodings) without a proxy, on the
16
+ same port as native gRPC, with CORS via `grpc_web_origins`,
17
+ `grpc_web_allowed_headers` and `grpc_web_exposed_headers`. Closes #22.
18
+
10
19
  ## [0.3.0] - 2026-10-11
11
20
 
12
21
  ### Added
@@ -116,7 +125,8 @@ First public release.
116
125
  - Precompiled native gems for Linux (x86_64, aarch64; glibc and musl) and
117
126
  macOS (x86_64, arm64), Ruby 3.3 to 4.0.
118
127
 
119
- [Unreleased]: https://github.com/CodingAnarchy/tonic-rails/compare/v0.3.0...HEAD
128
+ [Unreleased]: https://github.com/CodingAnarchy/tonic-rails/compare/v0.4.0...HEAD
129
+ [0.4.0]: https://github.com/CodingAnarchy/tonic-rails/compare/v0.3.0...v0.4.0
120
130
  [0.3.0]: https://github.com/CodingAnarchy/tonic-rails/compare/v0.2.0...v0.3.0
121
131
  [0.2.0]: https://github.com/CodingAnarchy/tonic-rails/compare/v0.1.0...v0.2.0
122
132
  [0.1.0]: https://github.com/CodingAnarchy/tonic-rails/releases/tag/v0.1.0
data/README.md CHANGED
@@ -363,6 +363,44 @@ Tonic::Rails.configure do |config|
363
363
  end
364
364
  ```
365
365
 
366
+ ## Browser clients (gRPC-Web)
367
+
368
+ Browsers can't speak native gRPC, but they can speak
369
+ [gRPC-Web](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md).
370
+ Turn it on and your existing handlers serve web frontends directly, with
371
+ no Envoy or other proxy:
372
+
373
+ ```ruby
374
+ Tonic::Rails.configure do |config|
375
+ config.grpc_web = true
376
+ config.grpc_web_origins = ["https://app.example.com"] # CORS; omit for same-origin only
377
+ config.grpc_web_exposed_headers = %w[x-request-id] # response metadata browsers may read
378
+ end
379
+ ```
380
+
381
+ Then call it from the browser with any gRPC-Web client, for example
382
+ [Connect](https://connectrpc.com/docs/web/using-clients):
383
+
384
+ ```ts
385
+ import { createClient } from "@connectrpc/connect";
386
+ import { createGrpcWebTransport } from "@connectrpc/connect-web";
387
+ import { ItemService } from "./gen/demo/v1/items_pb";
388
+
389
+ const client = createClient(ItemService, createGrpcWebTransport({ baseUrl: "https://api.example.com" }));
390
+ const item = await client.getItem({ id: 1n });
391
+ ```
392
+
393
+ - Unary and server-streaming RPCs work; browsers can't do client or
394
+ bidirectional streaming.
395
+ - Errors, response headers and trailers reach the browser as usual.
396
+ - Native gRPC clients keep working on the same port. Enabling gRPC-Web
397
+ also accepts HTTP/1.1, which browsers use without TLS; over HTTPS they
398
+ negotiate HTTP/2.
399
+ - `grpc_web_origins` sets CORS: listed origins may call with credentials
400
+ (cookies); `"*"` allows any origin without credentials (logged as a
401
+ warning). `grpc_web_allowed_headers` adds request headers beyond the
402
+ standard ones (`authorization` is already allowed).
403
+
366
404
  ## Testing handlers
367
405
 
368
406
  `Tonic::Rails::Testing` calls a handler in-process, with real protobuf
@@ -441,6 +479,9 @@ end
441
479
  | `tls_client_ca_path` | `nil` | PEM CA bundle; requires client certificates (mutual TLS) |
442
480
  | `drain_timeout` | `10` | Seconds to let in-flight calls finish on shutdown |
443
481
  | `keepalive_interval`, `keepalive_timeout` | `15`, `5` | HTTP/2 PING interval and timeout, seconds |
482
+ | `grpc_web` | `false` | Serve gRPC-Web for browser clients (see [Browser clients](#browser-clients-grpc-web)) |
483
+ | `grpc_web_origins` | `[]` | Browser origins allowed via CORS; `"*"` for any (no credentials) |
484
+ | `grpc_web_allowed_headers`, `grpc_web_exposed_headers` | `[]` | Extra CORS request / response headers |
444
485
  | `health_check` | `nil` | Callable; while false or raising, health reports `NOT_SERVING` |
445
486
  | `health_check_interval` | `5` | Seconds between health checks |
446
487
  | `compression` | `false` | gzip responses over 1 KiB for clients that accept it |
@@ -66,6 +66,25 @@ module Tonic
66
66
  # API schema; keep it off in production. Default false.
67
67
  attr_accessor :reflection
68
68
 
69
+ # Accept gRPC-Web requests, so browser apps can call services
70
+ # directly (unary and server-streaming RPCs). Also enables HTTP/1.1,
71
+ # which browsers use when there's no TLS. Default false.
72
+ attr_accessor :grpc_web
73
+
74
+ # Browser origins allowed to call gRPC-Web services (CORS), e.g.
75
+ # ["https://app.example.com"]. Empty = same-origin only. "*" allows
76
+ # any origin, without credentials (cookies). Default [].
77
+ attr_accessor :grpc_web_origins
78
+
79
+ # Extra request headers browsers may send (beyond content-type,
80
+ # x-grpc-web, x-user-agent, grpc-timeout and authorization).
81
+ attr_accessor :grpc_web_allowed_headers
82
+
83
+ # Extra response headers browsers may read, e.g. custom response
84
+ # metadata (grpc-status, grpc-message and grpc-status-details-bin are
85
+ # always exposed).
86
+ attr_accessor :grpc_web_exposed_headers
87
+
69
88
  # HTTP/2 PING interval and ack timeout, in seconds. Defaults 15 / 5.
70
89
  attr_accessor :keepalive_interval, :keepalive_timeout
71
90
 
@@ -105,6 +124,10 @@ module Tonic
105
124
  @keepalive_interval = 15
106
125
  @keepalive_timeout = 5
107
126
  @reload_handlers = false
127
+ @grpc_web = false
128
+ @grpc_web_origins = []
129
+ @grpc_web_allowed_headers = []
130
+ @grpc_web_exposed_headers = []
108
131
  @health_check = nil
109
132
  @health_check_interval = 5
110
133
  end
@@ -128,12 +151,22 @@ module Tonic
128
151
  raise ArgumentError, "tls_client_ca_path requires tls_cert_path and tls_key_path" if tls_client_ca_path && !tls?
129
152
 
130
153
  validate_callables!
154
+ validate_grpc_web!
131
155
 
132
156
  self
133
157
  end
134
158
 
135
159
  private
136
160
 
161
+ def validate_grpc_web!
162
+ %i[grpc_web_origins grpc_web_allowed_headers grpc_web_exposed_headers].each do |name|
163
+ value = public_send(name)
164
+ next if value.is_a?(Array) && value.all?(String)
165
+
166
+ raise ArgumentError, "#{name} must be an Array of Strings, got #{value.inspect}"
167
+ end
168
+ end
169
+
137
170
  def validate_callables!
138
171
  raise ArgumentError, "health_check must respond to #call" if health_check && !health_check.respond_to?(:call)
139
172
  unless health_check_interval.is_a?(Numeric) && health_check_interval.positive?
@@ -130,19 +130,30 @@ module Tonic
130
130
  native = TonicRails::Native::Server.new(config.bind_address, config.max_message_size)
131
131
  native.on_request(build_callback)
132
132
  native.set_routes(@router.paths)
133
- if config.tls?
134
- native.set_tls(config.tls_cert_path, config.tls_key_path)
135
- native.set_tls_client_ca(config.tls_client_ca_path) if config.tls_client_ca_path
136
- end
133
+ apply_connection_settings(native, config)
134
+ apply_features(native, config)
135
+ native
136
+ end
137
+
138
+ def apply_connection_settings(native, config)
137
139
  native.set_max_connections(config.max_connections)
138
140
  native.set_concurrency_limit_per_connection(config.concurrency_limit_per_connection)
139
141
  native.set_worker_threads(config.worker_threads || 0)
140
142
  native.set_keepalive(config.keepalive_interval.to_f, config.keepalive_timeout.to_f)
141
143
  native.set_dispatch_queue_size(config.dispatch_queue_size)
144
+ end
145
+
146
+ def apply_features(native, config)
147
+ if config.tls?
148
+ native.set_tls(config.tls_cert_path, config.tls_key_path)
149
+ native.set_tls_client_ca(config.tls_client_ca_path) if config.tls_client_ca_path
150
+ end
142
151
  native.set_compression(config.compression ? true : false)
143
152
  native.set_reflection(config.reflection ? true : false)
144
153
  native.set_reflection_descriptors(Reflection.file_descriptors(@router.service_names)) if config.reflection
145
- native
154
+ return unless config.grpc_web
155
+
156
+ native.set_grpc_web(config.grpc_web_origins, config.grpc_web_allowed_headers, config.grpc_web_exposed_headers)
146
157
  end
147
158
 
148
159
  # The proc the transport calls for every RPC:
@@ -273,6 +284,16 @@ module Tonic
273
284
  logger&.info("tonic-rails: stopped")
274
285
  end
275
286
 
287
+ def log_grpc_web(logger, config)
288
+ return unless config.grpc_web
289
+
290
+ origins = config.grpc_web_origins
291
+ logger.info("tonic-rails: gRPC-Web enabled (#{origins.empty? ? 'same-origin only' : "origins: #{origins.join(', ')}"})")
292
+ return unless origins.include?("*")
293
+
294
+ logger.warn("tonic-rails: gRPC-Web allows any origin; any website can call these services from a visitor's browser")
295
+ end
296
+
276
297
  def log_startup
277
298
  config = @configuration
278
299
  logger = config.logger
@@ -284,6 +305,7 @@ module Tonic
284
305
  logger.warn("tonic-rails: TLS is not configured; gRPC traffic is plaintext")
285
306
  end
286
307
  logger.info("tonic-rails: reflection enabled") if config.reflection
308
+ log_grpc_web(logger, config)
287
309
  logger.info(
288
310
  "tonic-rails: serving #{@router.service_names.join(', ')} on #{config.bind_address} " \
289
311
  "(#{config.dispatch_threads} dispatch threads)",
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Tonic
4
4
  module Rails
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tonic-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: x86_64-linux-gnu
6
6
  authors:
7
7
  - Matt Tanous