grpc-dc 0.37.1
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 +201 -0
- data/README.md +10 -0
- data/lib/grpc_dc.rb +1 -0
- data/lib/grpcdc/channel.rb +327 -0
- data/lib/grpcdc/codec.rb +262 -0
- data/lib/grpcdc/dynamic.rb +677 -0
- data/lib/grpcdc/endpoint.rb +397 -0
- data/lib/grpcdc/eventbus.rb +250 -0
- data/lib/grpcdc/eventbus_git.rb +132 -0
- data/lib/grpcdc/events_grpc_dc.rb +232 -0
- data/lib/grpcdc/events_pb.rb +432 -0
- data/lib/grpcdc/loopback.rb +43 -0
- data/lib/grpcdc/server.rb +106 -0
- data/lib/grpcdc/sidecar.rb +63 -0
- data/lib/grpcdc/wire_gen.rb +58 -0
- data/lib/grpcdc.rb +12 -0
- metadata +61 -0
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
# @generated by grpc-dc codegen. DO NOT EDIT.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
# Dynamic, descriptor-driven client and server.
|
|
4
|
+
#
|
|
5
|
+
# Parses a FileDescriptorSet (as produced by
|
|
6
|
+
# `protoc --descriptor_set_out`) at runtime and builds clients
|
|
7
|
+
# and servers whose messages are generic field maps — no
|
|
8
|
+
# generated stubs required. This is the runtime-generation
|
|
9
|
+
# counterpart of the protoc-plugin surfaces: schemas arrive as
|
|
10
|
+
# data (descriptor.proto), not as code.
|
|
11
|
+
#
|
|
12
|
+
# Generic values by field type: varint family -> Integer,
|
|
13
|
+
# string -> String, bytes/message -> String / GenericMessage,
|
|
14
|
+
# fixed/float -> Integer / Float, bool -> true/false. Repeated
|
|
15
|
+
# fields are arrays in field-number order.
|
|
16
|
+
|
|
17
|
+
require_relative "codec"
|
|
18
|
+
require_relative "server"
|
|
19
|
+
require_relative "wire_gen"
|
|
20
|
+
|
|
21
|
+
module Grpcdc
|
|
22
|
+
# FieldDescriptorProto.Type values (descriptor.proto).
|
|
23
|
+
TYPE_DOUBLE = 1
|
|
24
|
+
TYPE_FLOAT = 2
|
|
25
|
+
TYPE_INT64 = 3
|
|
26
|
+
TYPE_UINT64 = 4
|
|
27
|
+
TYPE_INT32 = 5
|
|
28
|
+
TYPE_FIXED64 = 6
|
|
29
|
+
TYPE_FIXED32 = 7
|
|
30
|
+
TYPE_BOOL = 8
|
|
31
|
+
TYPE_STRING = 9
|
|
32
|
+
TYPE_GROUP = 10
|
|
33
|
+
TYPE_MESSAGE = 11
|
|
34
|
+
TYPE_BYTES = 12
|
|
35
|
+
TYPE_UINT32 = 13
|
|
36
|
+
TYPE_ENUM = 14
|
|
37
|
+
TYPE_SFIXED32 = 15
|
|
38
|
+
TYPE_SFIXED64 = 16
|
|
39
|
+
TYPE_SINT32 = 17
|
|
40
|
+
TYPE_SINT64 = 18
|
|
41
|
+
|
|
42
|
+
VARINT_TYPES = [TYPE_INT64, TYPE_UINT64, TYPE_INT32, TYPE_BOOL,
|
|
43
|
+
TYPE_UINT32, TYPE_ENUM, TYPE_SINT32, TYPE_SINT64].freeze
|
|
44
|
+
LEN_TYPES = [TYPE_STRING, TYPE_BYTES, TYPE_MESSAGE, TYPE_GROUP].freeze
|
|
45
|
+
|
|
46
|
+
WIRE_TYPE = {}.tap do |table|
|
|
47
|
+
VARINT_TYPES.each { |t| table[t] = 0 }
|
|
48
|
+
[TYPE_FIXED64, TYPE_SFIXED64, TYPE_DOUBLE].each { |t| table[t] = 1 }
|
|
49
|
+
LEN_TYPES.each { |t| table[t] = 2 }
|
|
50
|
+
[TYPE_FIXED32, TYPE_SFIXED32, TYPE_FLOAT].each { |t| table[t] = 5 }
|
|
51
|
+
end.freeze
|
|
52
|
+
|
|
53
|
+
LABEL_REPEATED = 3
|
|
54
|
+
|
|
55
|
+
# Minimal protobuf reader over a binary String.
|
|
56
|
+
class PbReader
|
|
57
|
+
attr_reader :last_chunk
|
|
58
|
+
|
|
59
|
+
def initialize(data)
|
|
60
|
+
@buf = data.b
|
|
61
|
+
@pos = 0
|
|
62
|
+
@last_chunk = nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def varint
|
|
66
|
+
result = 0
|
|
67
|
+
shift = 0
|
|
68
|
+
loop do
|
|
69
|
+
raise ArgumentError, "truncated varint" if @pos >= @buf.bytesize
|
|
70
|
+
|
|
71
|
+
b = @buf.getbyte(@pos)
|
|
72
|
+
@pos += 1
|
|
73
|
+
result |= (b & 0x7F) << shift
|
|
74
|
+
return result if (b & 0x80).zero?
|
|
75
|
+
|
|
76
|
+
shift += 7
|
|
77
|
+
raise ArgumentError, "varint too long" if shift >= 70
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def bytes_field
|
|
82
|
+
n = varint
|
|
83
|
+
raise ArgumentError, "truncated bytes field" if @pos + n > @buf.bytesize
|
|
84
|
+
|
|
85
|
+
out = @buf.byteslice(@pos, n)
|
|
86
|
+
@pos += n
|
|
87
|
+
out
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Yield [number, wire_type, value] until end of buffer.
|
|
91
|
+
# Sets last_chunk to the raw bytes of the yielded field
|
|
92
|
+
# (captured before the yield, so consumers may read it while
|
|
93
|
+
# processing this field).
|
|
94
|
+
def each_field
|
|
95
|
+
return enum_for(:each_field) unless block_given?
|
|
96
|
+
|
|
97
|
+
while @pos < @buf.bytesize
|
|
98
|
+
start = @pos
|
|
99
|
+
tag = varint
|
|
100
|
+
number = tag >> 3
|
|
101
|
+
wire = tag & 7
|
|
102
|
+
value =
|
|
103
|
+
case wire
|
|
104
|
+
when 0 then varint
|
|
105
|
+
when 2 then bytes_field
|
|
106
|
+
when 1
|
|
107
|
+
raise ArgumentError, "truncated fixed64" if @pos + 8 > @buf.bytesize
|
|
108
|
+
|
|
109
|
+
v = @buf.byteslice(@pos, 8)
|
|
110
|
+
@pos += 8
|
|
111
|
+
v
|
|
112
|
+
when 5
|
|
113
|
+
raise ArgumentError, "truncated fixed32" if @pos + 4 > @buf.bytesize
|
|
114
|
+
|
|
115
|
+
v = @buf.byteslice(@pos, 4)
|
|
116
|
+
@pos += 4
|
|
117
|
+
v
|
|
118
|
+
else
|
|
119
|
+
raise ArgumentError, "unsupported wire type #{wire}"
|
|
120
|
+
end
|
|
121
|
+
@last_chunk = @buf.byteslice(start...@pos)
|
|
122
|
+
yield number, wire, value
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
class FieldModel
|
|
128
|
+
attr_reader :number, :name, :type, :type_name
|
|
129
|
+
|
|
130
|
+
def initialize(number, name, field_type, label, type_name)
|
|
131
|
+
@number = number
|
|
132
|
+
@name = name
|
|
133
|
+
@type = field_type
|
|
134
|
+
@repeated = label == LABEL_REPEATED
|
|
135
|
+
@type_name = type_name # for TYPE_MESSAGE, e.g. ".echo.EchoRequest"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def repeated?
|
|
139
|
+
@repeated
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class MessageModel
|
|
144
|
+
attr_reader :full_name, :fields, :by_name
|
|
145
|
+
|
|
146
|
+
def initialize(full_name, fields)
|
|
147
|
+
@full_name = full_name
|
|
148
|
+
@fields = fields # number -> FieldModel
|
|
149
|
+
@by_name = {}
|
|
150
|
+
fields.each_value { |f| @by_name[f.name] = f }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def refresh_by_name
|
|
154
|
+
@by_name = {}
|
|
155
|
+
@fields.each_value { |f| @by_name[f.name] = f }
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
class MethodModel
|
|
160
|
+
attr_reader :name, :path, :kind, :input, :output
|
|
161
|
+
|
|
162
|
+
def initialize(name, path, kind, input_model, output_model)
|
|
163
|
+
@name = name
|
|
164
|
+
@path = path
|
|
165
|
+
@kind = kind
|
|
166
|
+
@input = input_model
|
|
167
|
+
@output = output_model
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
class ServiceModel
|
|
172
|
+
attr_reader :full_name, :methods
|
|
173
|
+
|
|
174
|
+
def initialize(full_name, methods)
|
|
175
|
+
@full_name = full_name
|
|
176
|
+
@methods = methods # name -> MethodModel
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Parsed descriptor set: messages and services by full name.
|
|
181
|
+
class FileModel
|
|
182
|
+
attr_reader :messages, :services
|
|
183
|
+
|
|
184
|
+
def initialize
|
|
185
|
+
@messages = {} # ".echo.EchoRequest" -> MessageModel
|
|
186
|
+
@services = {} # "echo.Echo" -> ServiceModel
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def message(full_name)
|
|
190
|
+
@messages.fetch(full_name)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def service(full_name)
|
|
194
|
+
@services.fetch(full_name)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# A message as field number -> list of values (descriptor order).
|
|
199
|
+
#
|
|
200
|
+
# Fields not in the model are retained as raw encoded chunks in
|
|
201
|
+
# `unknown` (protobuf unknown-field retention): a decode ->
|
|
202
|
+
# encode round trip re-emits them after the known fields, so
|
|
203
|
+
# intermediate hops do not strip newer fields.
|
|
204
|
+
class GenericMessage
|
|
205
|
+
attr_accessor :model
|
|
206
|
+
attr_reader :values, :unknown
|
|
207
|
+
|
|
208
|
+
def initialize(model = nil)
|
|
209
|
+
@model = model
|
|
210
|
+
@values = {}
|
|
211
|
+
@unknown = []
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def set(number, value)
|
|
215
|
+
@values[number] = [value]
|
|
216
|
+
self
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def add(number, value)
|
|
220
|
+
(@values[number] ||= []) << value
|
|
221
|
+
self
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def get_all(number)
|
|
225
|
+
(@values[number] || []).dup
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def get(number, default = nil)
|
|
229
|
+
items = @values[number]
|
|
230
|
+
items && !items.empty? ? items[0] : default
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def get_string(number, default = "")
|
|
234
|
+
value = get(number)
|
|
235
|
+
value.is_a?(String) ? value : default
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def get_int(number, default = 0)
|
|
239
|
+
value = get(number)
|
|
240
|
+
value.is_a?(Integer) ? value : default
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def ==(other)
|
|
244
|
+
other.is_a?(GenericMessage) && @values == other.values
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def inspect
|
|
248
|
+
"GenericMessage(#{@values.inspect})"
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
class << self
|
|
253
|
+
def encode_varint(value)
|
|
254
|
+
out = String.new(encoding: Encoding::BINARY)
|
|
255
|
+
v = value & 0xFFFFFFFFFFFFFFFF
|
|
256
|
+
while v > 0x7F
|
|
257
|
+
out << ((v & 0x7F) | 0x80)
|
|
258
|
+
v >>= 7
|
|
259
|
+
end
|
|
260
|
+
out << v
|
|
261
|
+
out
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def encode_generic(model, message, file_model)
|
|
265
|
+
out = String.new(encoding: Encoding::BINARY)
|
|
266
|
+
message.values.keys.sort.each do |number|
|
|
267
|
+
field = model.fields[number]
|
|
268
|
+
if field.nil?
|
|
269
|
+
raise StatusError.new(CODE_INVALID_ARGUMENT, "unknown field #{number} for #{model.full_name}")
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
values = message.values[number]
|
|
273
|
+
if field.repeated?
|
|
274
|
+
values.each { |value| out << encode_value(field, value, file_model) }
|
|
275
|
+
elsif !values.empty?
|
|
276
|
+
out << encode_value(field, values[0], file_model)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
(message.unknown || []).each { |chunk| out << chunk }
|
|
280
|
+
out
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def decode_generic(model, data, file_model)
|
|
284
|
+
message = GenericMessage.new(model)
|
|
285
|
+
reader = PbReader.new(data)
|
|
286
|
+
reader.each_field do |number, wire, raw|
|
|
287
|
+
field = model.fields[number]
|
|
288
|
+
if field.nil?
|
|
289
|
+
# Unknown field: retain the raw encoded chunk verbatim.
|
|
290
|
+
message.unknown << reader.last_chunk
|
|
291
|
+
next
|
|
292
|
+
end
|
|
293
|
+
(message.values[number] ||= []) << decode_value(field, wire, raw, file_model)
|
|
294
|
+
end
|
|
295
|
+
message
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Parse FileDescriptorSet bytes into a FileModel.
|
|
299
|
+
def parse_descriptor_set(data)
|
|
300
|
+
file_model = FileModel.new
|
|
301
|
+
pending_services = []
|
|
302
|
+
PbReader.new(data).each_field do |number, _wire, file_bytes|
|
|
303
|
+
next unless number == 1
|
|
304
|
+
|
|
305
|
+
package = ""
|
|
306
|
+
service_types = []
|
|
307
|
+
fields_of_file = PbReader.new(file_bytes).each_field.to_a
|
|
308
|
+
fields_of_file.each do |fnum, _w, fval|
|
|
309
|
+
package = fval.dup.force_encoding(Encoding::UTF_8) if fnum == 2
|
|
310
|
+
end
|
|
311
|
+
fields_of_file.each do |fnum, _w, fval|
|
|
312
|
+
case fnum
|
|
313
|
+
when 4 then parse_message(package, fval, file_model)
|
|
314
|
+
when 6 then service_types << [package, fval]
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
pending_services.concat(service_types)
|
|
318
|
+
end
|
|
319
|
+
pending_services.each { |package, bytes| parse_service(package, bytes, file_model) }
|
|
320
|
+
file_model
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Register a runtime-built service on a Grpcdc::Server.
|
|
324
|
+
#
|
|
325
|
+
# handler is an object with methods by kind:
|
|
326
|
+
# unary(ctx, method, request) -> GenericMessage
|
|
327
|
+
# server_stream(ctx, method, request, send) -> nil
|
|
328
|
+
# client_stream(ctx, method, request_enum) -> GenericMessage
|
|
329
|
+
# bidi(ctx, method, request_enum, send) -> nil
|
|
330
|
+
# where send is a callable taking a GenericMessage and
|
|
331
|
+
# request_enum yields GenericMessages until half-close.
|
|
332
|
+
def register_dynamic_service(server, file_model, service_name, handler)
|
|
333
|
+
service = file_model.services.fetch(service_name)
|
|
334
|
+
methods = service.methods.values.map do |method|
|
|
335
|
+
Method.new(method.name, method.path, method.kind, make_handler(handler, method, file_model))
|
|
336
|
+
end
|
|
337
|
+
server.register_service(ServiceDesc.new(service.full_name, methods))
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
private
|
|
341
|
+
|
|
342
|
+
def to_signed(value, bits)
|
|
343
|
+
value &= (1 << bits) - 1
|
|
344
|
+
value >= (1 << (bits - 1)) ? value - (1 << bits) : value
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def zigzag_decode(value)
|
|
348
|
+
(value >> 1) ^ -(value & 1)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def zigzag_encode(value)
|
|
352
|
+
((value << 1) ^ (value >> 63)) & 0xFFFFFFFFFFFFFFFF
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def encode_value(field, value, file_model)
|
|
356
|
+
wire = WIRE_TYPE.fetch(field.type)
|
|
357
|
+
out = encode_varint((field.number << 3) | wire)
|
|
358
|
+
case wire
|
|
359
|
+
when 0
|
|
360
|
+
if [TYPE_SINT32, TYPE_SINT64].include?(field.type)
|
|
361
|
+
bits = field.type == TYPE_SINT32 ? 32 : 64
|
|
362
|
+
out << encode_varint(zigzag_encode(value) & ((1 << bits) - 1))
|
|
363
|
+
elsif field.type == TYPE_BOOL
|
|
364
|
+
out << encode_varint(value ? 1 : 0)
|
|
365
|
+
else
|
|
366
|
+
out << encode_varint(value & 0xFFFFFFFFFFFFFFFF)
|
|
367
|
+
end
|
|
368
|
+
when 1
|
|
369
|
+
out << if field.type == TYPE_DOUBLE
|
|
370
|
+
[value].pack("E")
|
|
371
|
+
elsif field.type == TYPE_SFIXED64
|
|
372
|
+
[value].pack("q<")
|
|
373
|
+
else
|
|
374
|
+
[value & 0xFFFFFFFFFFFFFFFF].pack("Q<")
|
|
375
|
+
end
|
|
376
|
+
when 5
|
|
377
|
+
out << if field.type == TYPE_FLOAT
|
|
378
|
+
[value].pack("e")
|
|
379
|
+
elsif field.type == TYPE_SFIXED32
|
|
380
|
+
[value].pack("l<")
|
|
381
|
+
else
|
|
382
|
+
[value & 0xFFFFFFFF].pack("V")
|
|
383
|
+
end
|
|
384
|
+
else
|
|
385
|
+
data =
|
|
386
|
+
case field.type
|
|
387
|
+
when TYPE_STRING then value.encode(Encoding::UTF_8).b
|
|
388
|
+
when TYPE_MESSAGE then encode_generic(file_model.messages.fetch(field.type_name), value, file_model)
|
|
389
|
+
else value.b
|
|
390
|
+
end
|
|
391
|
+
out << encode_varint(data.bytesize)
|
|
392
|
+
out << data
|
|
393
|
+
end
|
|
394
|
+
out
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def decode_value(field, wire, raw, file_model)
|
|
398
|
+
case wire
|
|
399
|
+
when 0
|
|
400
|
+
case field.type
|
|
401
|
+
when TYPE_BOOL then raw != 0
|
|
402
|
+
when TYPE_SINT32, TYPE_SINT64 then zigzag_decode(raw)
|
|
403
|
+
when TYPE_INT32, TYPE_ENUM then to_signed(raw, 32)
|
|
404
|
+
when TYPE_INT64 then to_signed(raw, 64)
|
|
405
|
+
else raw
|
|
406
|
+
end
|
|
407
|
+
when 1
|
|
408
|
+
case field.type
|
|
409
|
+
when TYPE_DOUBLE then raw.unpack1("E")
|
|
410
|
+
when TYPE_SFIXED64 then raw.unpack1("q<")
|
|
411
|
+
else raw.unpack1("Q<")
|
|
412
|
+
end
|
|
413
|
+
when 5
|
|
414
|
+
case field.type
|
|
415
|
+
when TYPE_FLOAT then raw.unpack1("e")
|
|
416
|
+
when TYPE_SFIXED32 then raw.unpack1("l<")
|
|
417
|
+
else raw.unpack1("V")
|
|
418
|
+
end
|
|
419
|
+
else
|
|
420
|
+
# length-delimited
|
|
421
|
+
case field.type
|
|
422
|
+
when TYPE_STRING then raw.dup.force_encoding(Encoding::UTF_8)
|
|
423
|
+
when TYPE_MESSAGE then decode_generic(file_model.messages.fetch(field.type_name), raw, file_model)
|
|
424
|
+
else raw.b
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def parse_message(package, data, file_model)
|
|
430
|
+
name = ""
|
|
431
|
+
raw_fields = []
|
|
432
|
+
nested = []
|
|
433
|
+
PbReader.new(data).each_field do |fnum, _w, fval|
|
|
434
|
+
case fnum
|
|
435
|
+
when 1 then name = fval.dup.force_encoding(Encoding::UTF_8)
|
|
436
|
+
when 2 then raw_fields << fval
|
|
437
|
+
when 3 then nested << fval
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
full_name = package.empty? ? name : "#{package}.#{name}"
|
|
441
|
+
fields = {}
|
|
442
|
+
model = MessageModel.new(full_name, fields)
|
|
443
|
+
file_model.messages["." + full_name] = model
|
|
444
|
+
nested.each { |bytes| parse_nested(package, name, bytes, file_model) }
|
|
445
|
+
raw_fields.each do |field_bytes|
|
|
446
|
+
field = parse_field(field_bytes)
|
|
447
|
+
fields[field.number] = field
|
|
448
|
+
end
|
|
449
|
+
model.refresh_by_name
|
|
450
|
+
model
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def parse_nested(package, outer, data, file_model)
|
|
454
|
+
name = ""
|
|
455
|
+
raw_fields = []
|
|
456
|
+
PbReader.new(data).each_field do |fnum, _w, fval|
|
|
457
|
+
case fnum
|
|
458
|
+
when 1 then name = fval.dup.force_encoding(Encoding::UTF_8)
|
|
459
|
+
when 2 then raw_fields << fval
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
full_name = package.empty? ? "#{outer}.#{name}" : "#{package}.#{outer}.#{name}"
|
|
463
|
+
fields = {}
|
|
464
|
+
model = MessageModel.new(full_name, fields)
|
|
465
|
+
file_model.messages["." + full_name] = model
|
|
466
|
+
raw_fields.each do |field_bytes|
|
|
467
|
+
field = parse_field(field_bytes)
|
|
468
|
+
fields[field.number] = field
|
|
469
|
+
end
|
|
470
|
+
model.refresh_by_name
|
|
471
|
+
model
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def parse_field(data)
|
|
475
|
+
name = ""
|
|
476
|
+
number = 0
|
|
477
|
+
label = 1
|
|
478
|
+
field_type = 0
|
|
479
|
+
type_name = ""
|
|
480
|
+
PbReader.new(data).each_field do |fnum, _w, fval|
|
|
481
|
+
case fnum
|
|
482
|
+
when 1 then name = fval.dup.force_encoding(Encoding::UTF_8)
|
|
483
|
+
when 3 then number = fval
|
|
484
|
+
when 4 then label = fval
|
|
485
|
+
when 5 then field_type = fval
|
|
486
|
+
when 6 then type_name = fval.dup.force_encoding(Encoding::UTF_8)
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
FieldModel.new(number, name, field_type, label, type_name)
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def kind_of(client_streaming, server_streaming)
|
|
493
|
+
if client_streaming && server_streaming
|
|
494
|
+
KIND_BIDI
|
|
495
|
+
elsif server_streaming
|
|
496
|
+
KIND_SERVER_STREAMING
|
|
497
|
+
elsif client_streaming
|
|
498
|
+
KIND_CLIENT_STREAMING
|
|
499
|
+
else
|
|
500
|
+
KIND_UNARY
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def parse_service(package, data, file_model)
|
|
505
|
+
name = ""
|
|
506
|
+
raw_methods = []
|
|
507
|
+
PbReader.new(data).each_field do |fnum, _w, fval|
|
|
508
|
+
case fnum
|
|
509
|
+
when 1 then name = fval.dup.force_encoding(Encoding::UTF_8)
|
|
510
|
+
when 2 then raw_methods << fval
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
full_name = package.empty? ? name : "#{package}.#{name}"
|
|
514
|
+
methods = {}
|
|
515
|
+
raw_methods.each do |method_bytes|
|
|
516
|
+
mname = ""
|
|
517
|
+
input_type = ""
|
|
518
|
+
output_type = ""
|
|
519
|
+
client_streaming = false
|
|
520
|
+
server_streaming = false
|
|
521
|
+
PbReader.new(method_bytes).each_field do |fnum, _w, fval|
|
|
522
|
+
case fnum
|
|
523
|
+
when 1 then mname = fval.dup.force_encoding(Encoding::UTF_8)
|
|
524
|
+
when 2 then input_type = fval.dup.force_encoding(Encoding::UTF_8)
|
|
525
|
+
when 3 then output_type = fval.dup.force_encoding(Encoding::UTF_8)
|
|
526
|
+
when 5 then client_streaming = fval != 0
|
|
527
|
+
when 6 then server_streaming = fval != 0
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
methods[mname] = MethodModel.new(
|
|
531
|
+
mname, "/#{full_name}/#{mname}", kind_of(client_streaming, server_streaming),
|
|
532
|
+
file_model.messages.fetch(input_type), file_model.messages.fetch(output_type)
|
|
533
|
+
)
|
|
534
|
+
end
|
|
535
|
+
service = ServiceModel.new(full_name, methods)
|
|
536
|
+
file_model.services[full_name] = service
|
|
537
|
+
service
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def make_handler(handler, method, file_model)
|
|
541
|
+
lambda do |ctx, reqs, sink|
|
|
542
|
+
send = lambda do |generic|
|
|
543
|
+
sink.send(encode_generic(method.output, generic, file_model))
|
|
544
|
+
end
|
|
545
|
+
request_enum = Enumerator.new do |yielder|
|
|
546
|
+
loop do
|
|
547
|
+
begin
|
|
548
|
+
payload = reqs.recv_raw
|
|
549
|
+
rescue EOFError
|
|
550
|
+
break
|
|
551
|
+
end
|
|
552
|
+
yielder << decode_generic(method.input, payload, file_model)
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
case method.kind
|
|
556
|
+
when KIND_UNARY
|
|
557
|
+
send.call(handler.unary(ctx, method, request_enum.next))
|
|
558
|
+
when KIND_SERVER_STREAMING
|
|
559
|
+
handler.server_stream(ctx, method, request_enum.next, send)
|
|
560
|
+
when KIND_CLIENT_STREAMING
|
|
561
|
+
send.call(handler.client_stream(ctx, method, request_enum))
|
|
562
|
+
else
|
|
563
|
+
handler.bidi(ctx, method, request_enum, send)
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# Adapts a (MessageModel, GenericMessage) pair to the Channel
|
|
570
|
+
# marshal/unmarshal contract.
|
|
571
|
+
class BoundMessage
|
|
572
|
+
attr_reader :model, :file_model
|
|
573
|
+
attr_accessor :generic
|
|
574
|
+
|
|
575
|
+
def initialize(model, generic, file_model)
|
|
576
|
+
@model = model
|
|
577
|
+
@generic = generic || GenericMessage.new(model)
|
|
578
|
+
@file_model = file_model
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
def marshal_dc
|
|
582
|
+
Grpcdc.encode_generic(@model, @generic, @file_model)
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
def unmarshal_dc(data)
|
|
586
|
+
@generic = Grpcdc.decode_generic(@model, data, @file_model)
|
|
587
|
+
self
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
class DynamicRecvStream
|
|
592
|
+
include Enumerable
|
|
593
|
+
|
|
594
|
+
def initialize(stream, model, file_model)
|
|
595
|
+
@stream = stream
|
|
596
|
+
@model = model
|
|
597
|
+
@file_model = file_model
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
def recv
|
|
601
|
+
@stream.recv(BoundMessage.new(@model, nil, @file_model)).generic
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
# Abandon the call early: RST unless it already terminated.
|
|
605
|
+
def cancel
|
|
606
|
+
@stream.cancel
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def close
|
|
610
|
+
@stream.cancel
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def each
|
|
614
|
+
return enum_for(:each) unless block_given?
|
|
615
|
+
|
|
616
|
+
begin
|
|
617
|
+
loop do
|
|
618
|
+
begin
|
|
619
|
+
yield recv
|
|
620
|
+
rescue EOFError
|
|
621
|
+
return
|
|
622
|
+
end
|
|
623
|
+
end
|
|
624
|
+
ensure
|
|
625
|
+
# Breaking out of the block abandons the call: RST so
|
|
626
|
+
# the server stops producing (no-op once terminated).
|
|
627
|
+
close
|
|
628
|
+
end
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
# Runtime-built client: methods resolve from the FileModel.
|
|
633
|
+
class DynamicClient
|
|
634
|
+
def initialize(channel, file_model)
|
|
635
|
+
@channel = channel
|
|
636
|
+
@model = file_model
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def unary(service_name, method_name, request, metadata = nil, timeout: nil)
|
|
640
|
+
method = resolve(service_name, method_name)
|
|
641
|
+
resp = BoundMessage.new(method.output, nil, @model)
|
|
642
|
+
@channel.unary(method.path, BoundMessage.new(method.input, request, @model), resp, metadata, timeout: timeout)
|
|
643
|
+
resp.generic
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
def server_stream(service_name, method_name, request, metadata = nil, timeout: nil)
|
|
647
|
+
method = resolve(service_name, method_name)
|
|
648
|
+
stream = @channel.server_stream(
|
|
649
|
+
method.path, BoundMessage.new(method.input, request, @model), metadata, timeout: timeout
|
|
650
|
+
)
|
|
651
|
+
DynamicRecvStream.new(stream, method.output, @model)
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
def client_stream(service_name, method_name, requests, metadata = nil, timeout: nil)
|
|
655
|
+
method = resolve(service_name, method_name)
|
|
656
|
+
resp = BoundMessage.new(method.output, nil, @model)
|
|
657
|
+
@channel.client_stream(
|
|
658
|
+
method.path, requests.map { |r| BoundMessage.new(method.input, r, @model) }, resp, metadata, timeout: timeout
|
|
659
|
+
)
|
|
660
|
+
resp.generic
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
def bidi(service_name, method_name, requests, metadata = nil, timeout: nil)
|
|
664
|
+
method = resolve(service_name, method_name)
|
|
665
|
+
stream = @channel.bidi(
|
|
666
|
+
method.path, requests.map { |r| BoundMessage.new(method.input, r, @model) }, metadata, timeout: timeout
|
|
667
|
+
)
|
|
668
|
+
DynamicRecvStream.new(stream, method.output, @model)
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
private
|
|
672
|
+
|
|
673
|
+
def resolve(service_name, method_name)
|
|
674
|
+
@model.services.fetch(service_name).methods.fetch(method_name)
|
|
675
|
+
end
|
|
676
|
+
end
|
|
677
|
+
end
|