arf 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.ruby-version +1 -0
- data/LICENSE.txt +21 -0
- data/Rakefile +12 -0
- data/lib/arf/configuration.rb +67 -0
- data/lib/arf/context.rb +170 -0
- data/lib/arf/errors.rb +10 -0
- data/lib/arf/io/buffer.rb +25 -0
- data/lib/arf/io/compression.rb +44 -0
- data/lib/arf/io/limit_reader.rb +30 -0
- data/lib/arf/observer.rb +29 -0
- data/lib/arf/proto/array.rb +31 -0
- data/lib/arf/proto/boolean.rb +15 -0
- data/lib/arf/proto/bytes.rb +35 -0
- data/lib/arf/proto/decoder.rb +31 -0
- data/lib/arf/proto/encoder.rb +71 -0
- data/lib/arf/proto/float.rb +48 -0
- data/lib/arf/proto/map.rb +49 -0
- data/lib/arf/proto/registry.rb +27 -0
- data/lib/arf/proto/scalar.rb +55 -0
- data/lib/arf/proto/string.rb +36 -0
- data/lib/arf/proto/struct.rb +84 -0
- data/lib/arf/proto/types.rb +67 -0
- data/lib/arf/proto/union.rb +25 -0
- data/lib/arf/proto.rb +17 -0
- data/lib/arf/reactor.rb +270 -0
- data/lib/arf/rpc/base_message.rb +119 -0
- data/lib/arf/rpc/client_base.rb +110 -0
- data/lib/arf/rpc/end_stream.rb +9 -0
- data/lib/arf/rpc/enum.rb +51 -0
- data/lib/arf/rpc/message_kind.rb +27 -0
- data/lib/arf/rpc/metadata.rb +120 -0
- data/lib/arf/rpc/method_meta.rb +42 -0
- data/lib/arf/rpc/request.rb +39 -0
- data/lib/arf/rpc/responder.rb +186 -0
- data/lib/arf/rpc/response.rb +46 -0
- data/lib/arf/rpc/service_base.rb +137 -0
- data/lib/arf/rpc/start_stream.rb +9 -0
- data/lib/arf/rpc/stream_error.rb +23 -0
- data/lib/arf/rpc/stream_item.rb +16 -0
- data/lib/arf/rpc/stream_metadata.rb +16 -0
- data/lib/arf/rpc/struct.rb +255 -0
- data/lib/arf/rpc.rb +19 -0
- data/lib/arf/server.rb +123 -0
- data/lib/arf/status.rb +75 -0
- data/lib/arf/types/array_type.rb +24 -0
- data/lib/arf/types/base_type.rb +14 -0
- data/lib/arf/types/coercion.rb +36 -0
- data/lib/arf/types/in_out_stream.rb +28 -0
- data/lib/arf/types/input_stream.rb +8 -0
- data/lib/arf/types/map_type.rb +32 -0
- data/lib/arf/types/mixin.rb +29 -0
- data/lib/arf/types/output_stream.rb +8 -0
- data/lib/arf/types/streamer.rb +21 -0
- data/lib/arf/types.rb +69 -0
- data/lib/arf/version.rb +5 -0
- data/lib/arf/wire/base_connection.rb +177 -0
- data/lib/arf/wire/client.rb +101 -0
- data/lib/arf/wire/encoding.rb +49 -0
- data/lib/arf/wire/error_code.rb +35 -0
- data/lib/arf/wire/errors.rb +88 -0
- data/lib/arf/wire/frame.rb +111 -0
- data/lib/arf/wire/frame_kind.rb +23 -0
- data/lib/arf/wire/frame_reader.rb +104 -0
- data/lib/arf/wire/frames/base_frame.rb +108 -0
- data/lib/arf/wire/frames/configuration_frame.rb +33 -0
- data/lib/arf/wire/frames/data_frame.rb +21 -0
- data/lib/arf/wire/frames/go_away_frame.rb +29 -0
- data/lib/arf/wire/frames/make_stream_frame.rb +15 -0
- data/lib/arf/wire/frames/ping_frame.rb +18 -0
- data/lib/arf/wire/frames/reset_stream_frame.rb +19 -0
- data/lib/arf/wire/frames.rb +9 -0
- data/lib/arf/wire/server/peer.rb +85 -0
- data/lib/arf/wire/server.rb +63 -0
- data/lib/arf/wire/stream/state.rb +69 -0
- data/lib/arf/wire/stream.rb +128 -0
- data/lib/arf/wire/wait_signal.rb +24 -0
- data/lib/arf/wire.rb +14 -0
- data/lib/arf.rb +46 -0
- metadata +195 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
module RPC
|
5
|
+
class Response < BaseMessage
|
6
|
+
kind :response
|
7
|
+
has_status
|
8
|
+
has_metadata
|
9
|
+
has_streaming
|
10
|
+
attr_accessor :params
|
11
|
+
|
12
|
+
def encode
|
13
|
+
flags = 0x00
|
14
|
+
flags |= (0x01 << 0x00) if streaming?
|
15
|
+
|
16
|
+
params = @params.map { Proto.encode(_1) }
|
17
|
+
|
18
|
+
IO::Buffer.new
|
19
|
+
.write_raw(encode_uint16(@status))
|
20
|
+
.write(flags)
|
21
|
+
.write_raw(@metadata.encode)
|
22
|
+
.write_raw(encode_uint16(params.length))
|
23
|
+
.write_raw(params.join)
|
24
|
+
.string
|
25
|
+
end
|
26
|
+
|
27
|
+
def ok? = status == :ok
|
28
|
+
|
29
|
+
def decode(data)
|
30
|
+
@status = decode_uint16(data)
|
31
|
+
flags = data.readbyte
|
32
|
+
@streaming = !flags.nobits?((0x01 << 0x00))
|
33
|
+
@metadata = Metadata.new.decode(data)
|
34
|
+
len = decode_uint16(data)
|
35
|
+
@params = []
|
36
|
+
len.times { @params << Proto.decode(data) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def result
|
40
|
+
return @params if status == :ok
|
41
|
+
|
42
|
+
raise Status::BadStatus.new(@status, @metadata.get("arf-status-description"))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
module RPC
|
5
|
+
class ServiceBase
|
6
|
+
include Arf::Types::Mixin
|
7
|
+
|
8
|
+
class YieldWithoutRespondError < Arf::Error
|
9
|
+
def initialize
|
10
|
+
super("This RPC method has response fields that must be sent before " \
|
11
|
+
"starting a stream. Please invoke #respond with the response " \
|
12
|
+
"parameters before yielding a value to the stream.")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class YieldWithoutStreamError < Arf::Error
|
17
|
+
def initialize
|
18
|
+
super("This RPC method does not have an output stream and cannot " \
|
19
|
+
"yield values.")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class YieldOnClosedStream < Arf::Error
|
24
|
+
def initialize
|
25
|
+
super("Cannot call yield after closing the output stream.")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.register(id, cls)
|
30
|
+
@services ||= {}
|
31
|
+
@services[id] = cls
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.by_id(id)
|
35
|
+
@services ||= {}
|
36
|
+
@services[id]
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.arf_service_id(id)
|
40
|
+
@arf_service_id = id
|
41
|
+
::Arf::RPC::ServiceBase.register(id, self)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.respond_to_rpc?(name)
|
45
|
+
return true if @service_methods&.key?(name.to_sym)
|
46
|
+
|
47
|
+
if superclass != Arf::RPC::ServiceBase && superclass.respond_to?(:respond_to_rpc?)
|
48
|
+
return superclass.respond_to_rpc?(name)
|
49
|
+
end
|
50
|
+
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.rpc_method_meta(name)
|
55
|
+
name = name.to_sym unless name.is_a? Symbol
|
56
|
+
return @service_methods[name] if @service_methods&.key?(name)
|
57
|
+
|
58
|
+
if superclass != Arf::RPC::ServiceBase && superclass.respond_to?(:rpc_method_meta)
|
59
|
+
return superclass.rpc_method_meta(name)
|
60
|
+
end
|
61
|
+
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.rpc(name, inputs: nil, outputs: nil)
|
66
|
+
inputs ||= {}
|
67
|
+
outputs = [outputs].compact unless outputs.is_a? Array
|
68
|
+
|
69
|
+
@service_methods ||= {}
|
70
|
+
@service_methods[name] = MethodMeta.new(inputs, outputs)
|
71
|
+
define_method(name) { unimplemented! }
|
72
|
+
end
|
73
|
+
|
74
|
+
attr_reader :log, :request
|
75
|
+
|
76
|
+
def arf_execute_request(ctx)
|
77
|
+
@context = ctx
|
78
|
+
@request = ctx.request
|
79
|
+
@metadata = @request.metadata
|
80
|
+
@service = @request.service
|
81
|
+
@method = @request.method
|
82
|
+
@log = ctx.log
|
83
|
+
@method_meta = self.class.rpc_method_meta(@method)
|
84
|
+
needs_response = @method_meta.output?
|
85
|
+
@context.prepare
|
86
|
+
if (str = @method_meta.output_stream)
|
87
|
+
@context.has_send_stream = true
|
88
|
+
@context.send_stream_type = str
|
89
|
+
end
|
90
|
+
|
91
|
+
@context.response.metadata.attach_observer(self)
|
92
|
+
|
93
|
+
output_type = @method_meta.output_stream&.resolved_type(self.class)
|
94
|
+
|
95
|
+
result = nil
|
96
|
+
begin
|
97
|
+
result = send(@method, *@request.params) do |val|
|
98
|
+
raise YieldWithoutStreamError unless ctx.has_send_stream
|
99
|
+
raise YieldWithoutRespondError if !ctx.has_sent_response && needs_response
|
100
|
+
raise YieldOnClosedStream if ctx.send_stream_finished
|
101
|
+
|
102
|
+
ctx.stream_send(Arf::Types.coerce_value(val, output_type))
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
rescue Exception => e
|
106
|
+
@log.error("Failed executing ##{@method}", e)
|
107
|
+
error!
|
108
|
+
end
|
109
|
+
@log.debug("Finished executing request", method: @method, result:)
|
110
|
+
result
|
111
|
+
end
|
112
|
+
|
113
|
+
def observer_changed(_observer)
|
114
|
+
# We just have a single observer, it will be response metadata.
|
115
|
+
return unless ctx.send_stream_started && !ctx.send_stream_finished
|
116
|
+
|
117
|
+
ctx.stream_send_metadata
|
118
|
+
end
|
119
|
+
|
120
|
+
def recv = @context.recv
|
121
|
+
|
122
|
+
def set_meta(k, v) = @context.response.metadata.set(k, v)
|
123
|
+
def add_meta(k, v) = @context.response.metadata.add(k, v)
|
124
|
+
|
125
|
+
def respond(value, code: nil)
|
126
|
+
code ||= :ok
|
127
|
+
|
128
|
+
@context.response.params = @method_meta.coerce_result(value, self.class)
|
129
|
+
@context.response.status = code
|
130
|
+
@context.send_response
|
131
|
+
end
|
132
|
+
|
133
|
+
def unimplemented! = raise Status::BadStatus, :unimplemented
|
134
|
+
def error! = raise Status::BadStatus, :internal_error
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
module RPC
|
5
|
+
class StreamError < BaseMessage
|
6
|
+
kind :stream_error
|
7
|
+
has_status
|
8
|
+
has_metadata
|
9
|
+
|
10
|
+
def encode
|
11
|
+
IO::Buffer.new
|
12
|
+
.write_raw(encode_uint16(@status))
|
13
|
+
.write_raw(metadata.encode)
|
14
|
+
.string
|
15
|
+
end
|
16
|
+
|
17
|
+
def decode(data)
|
18
|
+
@status = decode_uint16(data)
|
19
|
+
@metadata = Metadata.new.decode(data)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
module RPC
|
5
|
+
class StreamItem < BaseMessage
|
6
|
+
kind :stream_item
|
7
|
+
attr_accessor :value
|
8
|
+
|
9
|
+
def encode = Proto.encode(@value)
|
10
|
+
|
11
|
+
def decode(data)
|
12
|
+
@value = Proto.decode(data)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
module RPC
|
5
|
+
class StreamMetadata < BaseMessage
|
6
|
+
kind :stream_metadata
|
7
|
+
has_metadata
|
8
|
+
|
9
|
+
def encode = metadata.encode
|
10
|
+
|
11
|
+
def decode(data)
|
12
|
+
@metadata = Metadata.new.decode(data)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
module RPC
|
5
|
+
class Struct
|
6
|
+
include Arf::Types::Mixin
|
7
|
+
|
8
|
+
def self.arf_struct_id(v = nil)
|
9
|
+
return @arf_struct_id if v.nil?
|
10
|
+
|
11
|
+
@arf_struct_id = v
|
12
|
+
Arf::Proto::Registry.register! self
|
13
|
+
v
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.validate_subclass(type, v)
|
17
|
+
cls = find_type(type)
|
18
|
+
if cls.ancestors.include?(Arf::RPC::Enum)
|
19
|
+
!cls.to_i(v).nil?
|
20
|
+
else
|
21
|
+
v.is_a?(cls) || v.is_a?(Hash)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.validator_for_type(type, optional:)
|
26
|
+
validator = if ::Arf::Types::INTEGERS.include?(type)
|
27
|
+
->(v) { v.is_a?(Float) || v.is_a?(Integer) }
|
28
|
+
elsif ::Arf::Types::FLOATS.include?(type)
|
29
|
+
->(v) { v.is_a?(Float) || v.is_a?(Integer) }
|
30
|
+
elsif type == :string
|
31
|
+
->(v) { v.is_a?(String) }
|
32
|
+
elsif type == :bool
|
33
|
+
->(v) { v.is_a?(TrueClass) || v.is_a?(FalseClass) }
|
34
|
+
elsif type == :bytes
|
35
|
+
->(v) { v.is_a?(StringIO) || v.is_a?(String) }
|
36
|
+
elsif type.is_a? MapType
|
37
|
+
->(v) { v.is_a? Hash }
|
38
|
+
elsif type.is_a? ArrayType
|
39
|
+
->(v) { v.is_a? Array }
|
40
|
+
elsif type.is_a? String
|
41
|
+
# Validation for nested classes must be done at time of assignment
|
42
|
+
# since we can't rely on it being available during the field's
|
43
|
+
# definition.
|
44
|
+
->(v) { validate_subclass(type, v) }
|
45
|
+
elsif type.ancestors.include?(Arf::RPC::Enum)
|
46
|
+
->(v) { !type.to_i(v).nil? }
|
47
|
+
else
|
48
|
+
raise ArgumentError, "Invalid type #{type.inspect} (#{type.class.name})"
|
49
|
+
end
|
50
|
+
|
51
|
+
if optional
|
52
|
+
->(v) { v.nil? || validator.call(v) }
|
53
|
+
else
|
54
|
+
validator
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.default_for_type(type)
|
59
|
+
if ::Arf::Types::INTEGERS.include?(type)
|
60
|
+
0
|
61
|
+
elsif ::Arf::Types::FLOATS.include?(type)
|
62
|
+
0.0
|
63
|
+
elsif type == :string
|
64
|
+
""
|
65
|
+
elsif type == :bool
|
66
|
+
false
|
67
|
+
elsif type == :bytes
|
68
|
+
""
|
69
|
+
elsif type.is_a? MapType
|
70
|
+
{}
|
71
|
+
elsif type.is_a? ArrayType
|
72
|
+
[]
|
73
|
+
elsif type.is_a? String
|
74
|
+
# Validation for nested classes must be done at time of assignment
|
75
|
+
# since we can't rely on it being available during the field's
|
76
|
+
# definition.
|
77
|
+
cls = find_type(type)
|
78
|
+
if cls.ancestors.include?(Arf::RPC::Enum)
|
79
|
+
default_for_type(cls)
|
80
|
+
else
|
81
|
+
cls.new
|
82
|
+
end
|
83
|
+
elsif type.ancestors.include?(Arf::RPC::Enum)
|
84
|
+
type.options.keys.first
|
85
|
+
else
|
86
|
+
raise ArgumentError, "Invalid type #{type.inspect} (#{type.class.name})"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.field(id, name, type, optional: false)
|
91
|
+
@fields ||= []
|
92
|
+
@fields << { id:, name: name.to_s, type:, optional: }
|
93
|
+
attr_reader(name)
|
94
|
+
|
95
|
+
validator = validator_for_type(type, optional:)
|
96
|
+
define_method("#{name}=") do |v|
|
97
|
+
unless validator.call(v)
|
98
|
+
raise ArgumentError, "Invalid value for #{self.class.name}.#{name}, type #{type}: #{v.inspect}"
|
99
|
+
end
|
100
|
+
|
101
|
+
v = _coerce(v, type)
|
102
|
+
|
103
|
+
instance_variable_set("@__arf_union_set_id", id) if union? && !@skip_union_setter
|
104
|
+
instance_variable_set("@#{name}", v)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.all_fields
|
109
|
+
@all_fields ||= @fields
|
110
|
+
.map do |v|
|
111
|
+
next v unless v[:id] == :union
|
112
|
+
|
113
|
+
find_type(v[:type])
|
114
|
+
.all_fields
|
115
|
+
.map { _1.merge(via: v[:name]) }
|
116
|
+
end
|
117
|
+
.flatten
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.fields = @fields || []
|
121
|
+
def self.field_by_id(id) = @fields.find { _1[:id] == id }
|
122
|
+
def self.field_by_name(name) = name.to_s.then { |n| @fields.find { _1[:name] == n } }
|
123
|
+
|
124
|
+
def self.union!
|
125
|
+
@union = true
|
126
|
+
attr_reader(:__arf_union_set_id)
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.union? = @union || false
|
130
|
+
|
131
|
+
def arf_struct_id = self.class.arf_struct_id
|
132
|
+
def union? = self.class.union?
|
133
|
+
|
134
|
+
def initialize(**kwargs)
|
135
|
+
cls = self.class
|
136
|
+
kwargs.each_pair do |k, v|
|
137
|
+
field = cls.field_by_name(k)
|
138
|
+
raise ArgumentError, "#{cls.name}: Unknown field #{k}" unless field
|
139
|
+
|
140
|
+
v = _coerce(v, field[:type])
|
141
|
+
send("#{k}=", v)
|
142
|
+
end
|
143
|
+
|
144
|
+
@skip_union_setter = true
|
145
|
+
# Initialize missing variables
|
146
|
+
cls.fields.each do |f|
|
147
|
+
next if f[:optional]
|
148
|
+
|
149
|
+
send("#{f[:name]}=", cls.default_for_type(f[:type])) if instance_variable_get("@#{f[:name]}").nil?
|
150
|
+
end
|
151
|
+
|
152
|
+
if union? && __arf_union_set_id.nil?
|
153
|
+
@skip_union_setter = false
|
154
|
+
# Set the first field
|
155
|
+
f = cls.fields.first
|
156
|
+
send("#{f[:name]}=", cls.default_for_type(f[:type]))
|
157
|
+
end
|
158
|
+
ensure
|
159
|
+
@skip_union_setter = false
|
160
|
+
end
|
161
|
+
|
162
|
+
def _coerce(value, type)
|
163
|
+
cls = self.class
|
164
|
+
case type
|
165
|
+
when String
|
166
|
+
type = cls.find_type(type)
|
167
|
+
if type.ancestors.include?(Arf::RPC::Enum)
|
168
|
+
type.to_sym(value)
|
169
|
+
else
|
170
|
+
case value
|
171
|
+
when Hash then type.new(**value)
|
172
|
+
when type then value
|
173
|
+
else
|
174
|
+
raise ArgumentError,
|
175
|
+
"Cannot initialize #{type} with #{value.inspect} (#{value.class}). Did you mean to use a Hash?"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
when ArrayType
|
179
|
+
type.coerce(value)
|
180
|
+
when MapType
|
181
|
+
type.coerce(value)
|
182
|
+
else
|
183
|
+
::Arf::Types.coerce_value(value, type)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def decode_fields(fields)
|
188
|
+
fields.each_pair do |id, value|
|
189
|
+
meta = self.class.all_fields.find { _1[:id] == id }
|
190
|
+
unless meta
|
191
|
+
puts "WARNING: Skipping field ID #{id} without matching ID on target type #{self.class.name}"
|
192
|
+
next
|
193
|
+
end
|
194
|
+
if meta[:via]
|
195
|
+
union_field = self.class.all_fields.find { _1[:id] == value[:id] }
|
196
|
+
instance_variable_get("@#{meta[:via]}")
|
197
|
+
.send("#{union_field[:name]}=", value[:value])
|
198
|
+
next
|
199
|
+
end
|
200
|
+
|
201
|
+
next if meta[:optional] && value.nil?
|
202
|
+
|
203
|
+
instance_variable_set("@#{meta[:name]}", _coerce(value, meta[:type]))
|
204
|
+
end
|
205
|
+
self
|
206
|
+
end
|
207
|
+
|
208
|
+
def ==(other)
|
209
|
+
return false unless other.is_a? self.class
|
210
|
+
|
211
|
+
return eq_union(other) if union?
|
212
|
+
|
213
|
+
eq_struct(other)
|
214
|
+
end
|
215
|
+
|
216
|
+
def eq_struct(other)
|
217
|
+
self.class.fields.each do |v|
|
218
|
+
return false unless send(v[:name]) == other.send(v[:name])
|
219
|
+
end
|
220
|
+
true
|
221
|
+
end
|
222
|
+
|
223
|
+
def eq_union(other)
|
224
|
+
return false unless other.__arf_union_set_id == __arf_union_set_id
|
225
|
+
|
226
|
+
self.class.fields.each do |v|
|
227
|
+
return false if instance_variable_get("@#{v[:name]}") != other.instance_variable_get("@#{v[:name]}")
|
228
|
+
end
|
229
|
+
|
230
|
+
true
|
231
|
+
end
|
232
|
+
|
233
|
+
def hashify(value)
|
234
|
+
case value
|
235
|
+
when nil then nil
|
236
|
+
when Arf::RPC::Struct then value.to_h
|
237
|
+
when Array then value.map { hashify(_1) }
|
238
|
+
when Hash then value.transform_values { |v| hashify(v) }
|
239
|
+
else value
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def to_h
|
244
|
+
if union?
|
245
|
+
set = self.class.fields.find { _1[:id] == __arf_union_set_id }
|
246
|
+
return { set[:name] => hashify(instance_variable_get("@#{set[:name]}")) }
|
247
|
+
end
|
248
|
+
|
249
|
+
self.class.fields
|
250
|
+
.to_h { |f| [f[:name], hashify(instance_variable_get("@#{f[:name]}"))] }
|
251
|
+
.compact
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
data/lib/arf/rpc.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "rpc/struct"
|
4
|
+
require_relative "rpc/enum"
|
5
|
+
require_relative "rpc/message_kind"
|
6
|
+
require_relative "rpc/base_message"
|
7
|
+
require_relative "rpc/metadata"
|
8
|
+
require_relative "rpc/request"
|
9
|
+
require_relative "rpc/response"
|
10
|
+
require_relative "rpc/start_stream"
|
11
|
+
require_relative "rpc/end_stream"
|
12
|
+
require_relative "rpc/stream_error"
|
13
|
+
require_relative "rpc/stream_item"
|
14
|
+
require_relative "rpc/stream_metadata"
|
15
|
+
require_relative "rpc/method_meta"
|
16
|
+
|
17
|
+
require_relative "rpc/service_base"
|
18
|
+
require_relative "rpc/client_base"
|
19
|
+
require_relative "rpc/responder"
|
data/lib/arf/server.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arf
|
4
|
+
class Server
|
5
|
+
def self.pseudo_uuid
|
6
|
+
hostname = Socket.gethostname
|
7
|
+
lambda do
|
8
|
+
"#{hostname}-#{SecureRandom.hex(8)}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_options
|
13
|
+
@default_options ||= {
|
14
|
+
max_concurrent_streams: 0,
|
15
|
+
logger: Arf.configuration.logger,
|
16
|
+
id_generator: Server.pseudo_uuid
|
17
|
+
}.merge
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(**opts)
|
21
|
+
opts = default_options.merge(opts)
|
22
|
+
opts[:id_generator] ||= Server.pseudo_uuid
|
23
|
+
@logger = opts[:logger]
|
24
|
+
@id_generator = opts[:id_generator]
|
25
|
+
@max_concurrent_streams = opts[:max_concurrent_streams]
|
26
|
+
@server = Arf::Wire::Server.new(self)
|
27
|
+
@interceptors = []
|
28
|
+
end
|
29
|
+
|
30
|
+
def register_interceptor(callable, &block)
|
31
|
+
callable ||= block
|
32
|
+
@interceptors << callable
|
33
|
+
end
|
34
|
+
|
35
|
+
def run = @server.run
|
36
|
+
def shutdown = @server.shutdown
|
37
|
+
|
38
|
+
def handle_stream(stream)
|
39
|
+
req_id = @id_generator.call
|
40
|
+
stream.external_id = req_id
|
41
|
+
log = @logger.with_fields(request_id: req_id)
|
42
|
+
log.debug("Servicing stream")
|
43
|
+
ctx = Context.new(req_id, log, stream)
|
44
|
+
log.debug("Reading request...", thread: Thread.current.name)
|
45
|
+
msg = RPC::BaseMessage.initialize_from(stream.read_blocking)
|
46
|
+
unless msg.is_a? RPC::Request
|
47
|
+
log.info("Rejecting stream as it does not start with a Request frame")
|
48
|
+
stream.write_data(RPC::BaseMessage.encode(RPC::Response.new(
|
49
|
+
status: :failed_precondition,
|
50
|
+
metadata: {
|
51
|
+
"arf-request-id" => req_id,
|
52
|
+
"arf-status-description" => "Missing Request frame"
|
53
|
+
}
|
54
|
+
)))
|
55
|
+
stream.close_local
|
56
|
+
return
|
57
|
+
end
|
58
|
+
|
59
|
+
log.debug("Request read OK")
|
60
|
+
ctx.request = msg
|
61
|
+
# chain interceptors
|
62
|
+
call_next(0, ctx)
|
63
|
+
handle_request(ctx)
|
64
|
+
end
|
65
|
+
|
66
|
+
def handle_request(ctx)
|
67
|
+
base_svc = Arf::RPC::ServiceBase.by_id(ctx.request.service)
|
68
|
+
svc = base_svc&.subclasses&.first
|
69
|
+
|
70
|
+
if svc.nil?
|
71
|
+
ctx.log.info("Rejecting request as there's no service registered with the requested name",
|
72
|
+
service: ctx.request.service, method: ctx.request.method)
|
73
|
+
failure(ctx, :unimplemented)
|
74
|
+
return false
|
75
|
+
end
|
76
|
+
|
77
|
+
unless svc.respond_to_rpc?(ctx.request.method)
|
78
|
+
ctx.log.info("Rejecting request as service does not respond to the requested method",
|
79
|
+
service: ctx.request.service, method: ctx.request.method)
|
80
|
+
failure(ctx, :unimplemented)
|
81
|
+
return false
|
82
|
+
end
|
83
|
+
|
84
|
+
ctx.log.debug("Handler got request")
|
85
|
+
|
86
|
+
svc_inst = svc.new
|
87
|
+
|
88
|
+
begin
|
89
|
+
result = svc_inst.arf_execute_request(ctx)
|
90
|
+
rescue Status::BadStatus => e
|
91
|
+
failure(ctx, e.code, e.message)
|
92
|
+
return false
|
93
|
+
rescue Exception => e
|
94
|
+
ctx.log.error("Failed invoking method #{ctx.request.method}", e)
|
95
|
+
failure(ctx, :internal_error)
|
96
|
+
return false
|
97
|
+
end
|
98
|
+
|
99
|
+
ctx.end_send if ctx.has_send_stream
|
100
|
+
svc_inst.respond(result) unless ctx.has_sent_response
|
101
|
+
true
|
102
|
+
end
|
103
|
+
|
104
|
+
def failure(ctx, status, message = nil)
|
105
|
+
resp = RPC::Response.new(
|
106
|
+
status: Status::FROM_SYMBOL[status],
|
107
|
+
metadata: {
|
108
|
+
"arf-status-description" => message || Status::STATUS_TEXT[status]
|
109
|
+
}
|
110
|
+
)
|
111
|
+
ctx.stream.write_data(RPC::BaseMessage.encode(resp), end_stream: true)
|
112
|
+
ctx.has_sent_response = true
|
113
|
+
end
|
114
|
+
|
115
|
+
def cancel_stream(stream); end
|
116
|
+
|
117
|
+
def call_next(index, ctx)
|
118
|
+
return unless index < @interceptors.size
|
119
|
+
|
120
|
+
@interceptors[index].call(ctx) { call_next(index + 1, ctx) }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|