arf 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/.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
|