grpc_kit 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.
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'grpc_kit/errors'
4
+ require 'grpc_kit/rpc_desc'
5
+ require 'grpc_kit/client'
6
+ require 'grpc_kit/grpc/stream'
7
+
8
+ module GrpcKit
9
+ module GRPC
10
+ module Dsl
11
+ attr_accessor :service_name
12
+
13
+ attr_writer :marshal_class_method, :unmarshal_class_method
14
+
15
+ def inherited(subclass)
16
+ subclass.rpc_descs.merge!(rpc_descs)
17
+ subclass.service_name = service_name
18
+ end
19
+
20
+ def rpc(name, input, output)
21
+ if rpc_descs.key?(name)
22
+ raise "rpc (#{name}) is already defined"
23
+ end
24
+
25
+ unless input.respond_to?(@marshal_class_method)
26
+ raise "#{marshal} must implement #{marshal}.#{@marshal_class_method}"
27
+ end
28
+
29
+ unless output.respond_to?(@unmarshal_class_method)
30
+ raise "#{unmarshal} must implement #{unmarshal}.#{@unmarshal_class_method}"
31
+ end
32
+
33
+ # create StreamDesc?
34
+ rpc_descs[name] = GrpcKit::RpcDesc.new(
35
+ name: name,
36
+ input: input,
37
+ output: output,
38
+ marshal_method: @marshal_class_method,
39
+ unmarshal_method: @unmarshal_class_method,
40
+ )
41
+
42
+ define_method(rpc_descs[name].ruby_style_name) do |_, _|
43
+ raise GrpcKit::Errors::Unimplemented, name.to_s
44
+ end
45
+ end
46
+
47
+ def stream(cls)
48
+ GrpcKit::GRPC::Stream.new(cls)
49
+ end
50
+
51
+ def rpc_stub_class
52
+ rpc_descs_ = rpc_descs
53
+ service_name_ = service_name
54
+ Class.new(GrpcKit::Client) do
55
+ rpc_descs_.each_pair do |_, rpc_desc|
56
+ method_name = rpc_desc.ruby_style_name
57
+ path = rpc_desc.path(service_name_)
58
+
59
+ if rpc_desc.request_response?
60
+ define_method(method_name) do |request, opts = {}|
61
+ request_response(path, request, rpc_desc, opts)
62
+ end
63
+ elsif rpc_desc.client_streamer?
64
+ define_method(method_name) do |requests, opts = {}|
65
+ client_streamer(path, requests, rpc_desc, opts)
66
+ end
67
+ elsif rpc_desc.server_streamer?
68
+ define_method(method_name) do |request, opts = {}, &blk|
69
+ server_streamer(path, request, rpc_desc, opts, &blk)
70
+ end
71
+ elsif rpc_desc.bidi_streamer?
72
+ define_method(method_name) do |requests, opts = {}, &blk|
73
+ bidi_streamer(path, requests, rpc_desc, opts, &blk)
74
+ end
75
+ else
76
+ raise "unknown #{rpc_desc}"
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def rpc_descs
83
+ @rpc_descs ||= {}
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'grpc_kit/grpc/dsl'
4
+
5
+ module GrpcKit
6
+ module GRPC
7
+ module GenericService
8
+ def self.included(obj)
9
+ obj.extend(GrpcKit::GRPC::Dsl)
10
+ return unless obj.service_name.nil?
11
+
12
+ # if obj.name.nil?
13
+ # obj.service_name = 'GenericService'
14
+ # else
15
+ # modules = obj.name.split('::')
16
+ # obj.service_name =
17
+ # if modules.length > 2
18
+ # modules[modules.length - 2]
19
+ # else
20
+ # modules.first
21
+ # end
22
+ # end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'grpc_kit/grpc/dsl'
4
+ require 'forwardable'
5
+
6
+ module GrpcKit
7
+ module GRPC
8
+ class Stream
9
+ extend Forwardable
10
+ delegate %i[encode decode] => :@klass
11
+
12
+ def initialize(klass)
13
+ @klass = klass
14
+ end
15
+
16
+ # FIXME: Do not use method_missing...
17
+ def method_missing(name, *args, &block)
18
+ @klass.send(name, *args, &block)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ds9'
4
+
5
+ module GrpcKit
6
+ module IO
7
+ class Basic
8
+ def initialize(reader, writer)
9
+ @reader = reader
10
+ @writer = writer
11
+ end
12
+
13
+ def read(length)
14
+ data = @reader.read_nonblock(length, nil, exception: false)
15
+
16
+ case data
17
+ when :wait_readable
18
+ DS9::ERR_WOULDBLOCK
19
+ when nil
20
+ ''
21
+ else
22
+ data
23
+ end
24
+ end
25
+
26
+ def write(data)
27
+ @writer.write(data)
28
+ end
29
+
30
+ def wait_readable
31
+ @reader.wait_writable
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'grpc_kit/grpc/stream'
4
+
5
+ module GrpcKit
6
+ class RpcDesc
7
+ def initialize(name:, input:, output:, marshal_method:, unmarshal_method:)
8
+ @name = name
9
+ @input = input
10
+ @output = output
11
+ @marshal_method = marshal_method
12
+ @unmarshal_method = unmarshal_method
13
+ end
14
+
15
+ def encode(val)
16
+ @output.send(@marshal_method, val)
17
+ end
18
+
19
+ def decode(val)
20
+ @input.send(@unmarshal_method, val)
21
+ end
22
+
23
+ def encode2(val)
24
+ @input.send(@marshal_method, val)
25
+ end
26
+
27
+ def decode2(val)
28
+ @output.send(@unmarshal_method, val)
29
+ end
30
+
31
+ def invoke(rpc, val)
32
+ args = decode(val)
33
+ ret = rpc.send(to_underscore(@name), args, nil) # nil is GRPC::Call object
34
+ encode(ret)
35
+ end
36
+
37
+ def ruby_style_name
38
+ @ruby_style_name ||= to_underscore(@name).to_sym
39
+ end
40
+
41
+ def path(service_name)
42
+ "/#{service_name}/#{@name}".to_sym
43
+ end
44
+
45
+ def request_response?
46
+ !@input.is_a?(GrpcKit::GRPC::Stream) && !@output.is_a?(GrpcKit::GRPC::Stream)
47
+ end
48
+
49
+ def client_streamer?
50
+ @input.is_a?(GrpcKit::GRPC::Stream) && !@output.is_a?(GrpcKit::GRPC::Stream)
51
+ end
52
+
53
+ def server_streamer?
54
+ !@input.is_a?(GrpcKit::GRPC::Stream) && @output.is_a?(GrpcKit::GRPC::Stream)
55
+ end
56
+
57
+ def bidi_streamer?
58
+ @input.is_a?(GrpcKit::GRPC::Stream) && @output.is_a?(GrpcKit::GRPC::Stream)
59
+ end
60
+
61
+ private
62
+
63
+ def to_underscore(val)
64
+ val
65
+ .to_s
66
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
67
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
68
+ .tr('-', '_')
69
+ .downcase
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'grpc_kit/io/basic'
4
+ require 'grpc_kit/session/server'
5
+
6
+ module GrpcKit
7
+ class Server
8
+ def initialize
9
+ @sessions = []
10
+ @handler = {}
11
+ @rpc_descs = {}
12
+ end
13
+
14
+ # @params handler [object]
15
+ def handle(handler)
16
+ klass = handler.class
17
+
18
+ klass.rpc_descs.values.each do |rpc_desc|
19
+ path = rpc_desc.path(klass.service_name)
20
+ if @rpc_descs[path]
21
+ raise "Duplicated method registered #{key}, class: #{handler}"
22
+ end
23
+
24
+ @rpc_descs[path] = [rpc_desc, handler]
25
+ end
26
+ end
27
+
28
+ def run
29
+ GrpcKit.logger.info("Start grpc_kit v#{GrpcKit::VERSION}")
30
+ # XXX
31
+ end
32
+
33
+ def stop
34
+ GrpcKit.logger.info('Stop grpc_kit')
35
+
36
+ @sessions.each(&:stop)
37
+ end
38
+
39
+ def session_start(conn, io = GrpcKit::IO::Basic)
40
+ session = GrpcKit::Session::Server.new(io.new(conn, conn), self) # TODO: change self to proper object
41
+ @sessions << session
42
+
43
+ session.submit_settings([])
44
+ session.start # blocking
45
+ end
46
+
47
+ def on_data_chunk_recv(stream, data)
48
+ compressed, length, buf = data.unpack('CNa*')
49
+ if compressed == 0 # TODO: not
50
+ if length != buf.size
51
+ raise 'recived data inconsistent'
52
+ end
53
+
54
+ stream.recv(buf)
55
+ else
56
+ raise 'not supported'
57
+ end
58
+ end
59
+
60
+ def on_frame_data_recv(stream)
61
+ return unless stream.exist_data?
62
+
63
+ path = stream.headers[':path']
64
+ rpc = @rpc_descs[path.to_sym]
65
+ if rpc
66
+ resp = rpc[0].invoke(rpc[1], stream.data)
67
+ buf = [0, resp.length, resp].pack('CNa*')
68
+ stream.data = ''
69
+ stream.send(StringIO.new(buf))
70
+ else
71
+ # TODO: 404
72
+ raise "unkown path #{path}"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ds9'
4
+ require 'grpc_kit/session/stream'
5
+
6
+ module GrpcKit
7
+ module Session
8
+ class Client < DS9::Client
9
+ # @io [GrpcKit::IO::XXX]
10
+ def initialize(io, handler)
11
+ super() # initialize DS9::Session
12
+
13
+ @io = io
14
+ @streams = {}
15
+ @handler = handler
16
+ end
17
+
18
+ def start(stream_id)
19
+ stream = GrpcKit::Session::Stream.new(stream_id: stream_id)
20
+ @streams[stream_id] = stream
21
+
22
+ while want_read? || want_write?
23
+ if stream.closed?
24
+ break
25
+ elsif !stream.exist_data?
26
+ receive
27
+
28
+ send
29
+ else
30
+ break
31
+ # GrpcKit.logger.info("unknown #{stream}")
32
+ end
33
+ end
34
+ # invalid if receive and send are not called
35
+ end
36
+
37
+ private
38
+
39
+ # for nghttp2_session_callbacks_set_send_callback
40
+ # override
41
+ def send_event(string)
42
+ @io.write(string)
43
+ end
44
+
45
+ # for nghttp2_session_callbacks_set_recv_callback
46
+ # override
47
+ def recv_event(length)
48
+ @io.read(length)
49
+ end
50
+
51
+ # for nghttp2_session_callbacks_set_on_data_chunk_recv_callback
52
+ def on_data_chunk_recv(stream_id, data, flags)
53
+ @handler.on_data_chunk_recv(@streams[stream_id], data)
54
+ end
55
+
56
+ # provider for nghttp2_submit_response
57
+ # def on_data_source_read(stream_id, length)
58
+ # end
59
+
60
+ # for nghttp2_session_callbacks_set_on_frame_send_callback
61
+ # def on_frame_recv(frame)
62
+ # GrpcKit.logger.debug("on_frame_recv #{frame}")
63
+ # end
64
+
65
+ # # for nghttp2_session_callbacks_set_on_frame_not_send_callback
66
+ # def on_frame_not_send(frame, reason)
67
+ # end
68
+
69
+ # # for nghttp2_session_callbacks_set_on_frame_send_callback
70
+ # def on_frame_send(frame, reason)
71
+ # end
72
+
73
+ # # for nghttp2_session_callbacks_set_on_header_callback
74
+ # def on_header(name, value, frame, flags)
75
+ # end
76
+
77
+ # # for nghttp2_session_callbacks_set_on_begin_headers_callback
78
+ # def on_begin_header(name, value, frame, flags)
79
+ # end
80
+
81
+ # # for nghttp2_session_callbacks_set_on_begin_frame_callback
82
+ # def on_begin_frame(frame_header)
83
+ # end
84
+
85
+ # # for nghttp2_session_callbacks_set_on_invalid_frame_recv_callback
86
+ # def on_invalid_frame_recv(frame, error_code)
87
+ # end
88
+
89
+ # for nghttp2_session_callbacks_set_on_stream_close_callback
90
+ def on_stream_close(stream_id, error_code)
91
+ GrpcKit.logger.debug("on_stream_close stream_id=#{stream_id}, error_code=#{error_code}")
92
+ stream = @streams.delete(stream_id)
93
+ if stream
94
+ stream.close
95
+ end
96
+ end
97
+
98
+ # # for nghttp2_session_callbacks_set_on_data_chunk_recv_callback
99
+ # def on_data_chunk_recv(id, data, flags)
100
+ # end
101
+
102
+ # # nghttp2_session_callbacks_set_before_frame_send_callback
103
+ # def before_frame_send(frame)
104
+ # end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ds9'
4
+ require 'grpc_kit/session/stream'
5
+
6
+ module GrpcKit
7
+ module Session
8
+ class Server < DS9::Server
9
+ # @io [GrpcKit::IO::XXX]
10
+ def initialize(io, handler)
11
+ super() # initialize DS9::Server
12
+
13
+ @io = io
14
+ @streams = {}
15
+ @stop = false
16
+ @handler = handler
17
+ end
18
+
19
+ def start
20
+ @io.wait_readable
21
+ until @stop && (want_read? || want_write?)
22
+ if want_write?
23
+ send
24
+ end
25
+
26
+ if want_read?
27
+ receive
28
+ end
29
+ end
30
+ end
31
+
32
+ def stop
33
+ @stop = true
34
+ end
35
+
36
+ private
37
+
38
+ # provider for nghttp2_submit_response
39
+ # override
40
+ def on_data_source_read(stream_id, length)
41
+ GrpcKit.logger.debug("on_data_source_read #{stream_id}, lenght=#{length}")
42
+
43
+ data = @streams[stream_id].read(length)
44
+ if data.nil?
45
+ submit_trailer(stream_id, 'grpc-status' => '0')
46
+ false # nil mean END_STREAM
47
+ else
48
+ data
49
+ end
50
+ end
51
+
52
+ # for nghttp2_session_callbacks_set_send_callback
53
+ # override
54
+ def send_event(string)
55
+ GrpcKit.logger.debug('send_event')
56
+
57
+ @io.write(string)
58
+ end
59
+
60
+ # for nghttp2_session_callbacks_set_recv_callback
61
+ # override
62
+ def recv_event(length)
63
+ # GrpcKit.logger.debug("recv_event #{length}")
64
+ @io.read(length)
65
+ end
66
+
67
+ # for nghttp2_session_callbacks_set_on_frame_send_callback
68
+ def on_frame_recv(frame)
69
+ GrpcKit.logger.debug("on_frame_recv #{frame}")
70
+ case frame
71
+ when DS9::Frames::Data
72
+ # need to port NGHTTP2_FLAG_END_STREAM to check frame.flag has it
73
+ stream = @streams[frame.stream_id]
74
+ resp = @handler.on_frame_data_recv(stream)
75
+ unless resp
76
+ return # TODO
77
+ end
78
+
79
+ submit_response(
80
+ frame.stream_id,
81
+ ':status' => '200',
82
+ 'content-type' => 'application/grpc',
83
+ 'accept-encoding' => 'identity,gzip',
84
+ )
85
+ # when DS9::Frames::Headers
86
+ # need to port NGHTTP2_FLAG_END_STREAM to check frame.flag has it
87
+ # when DS9::Frames::Goaway
88
+ # when DS9::Frames::RstStream
89
+ else
90
+ GrpcKit.logger.info("unsupport frame #{frame}")
91
+ end
92
+
93
+ true
94
+ end
95
+
96
+ # for nghttp2_session_callbacks_set_on_frame_send_callback
97
+ def on_frame_send(frame)
98
+ GrpcKit.logger.debug("on_frame_send #{frame}")
99
+ true
100
+ end
101
+
102
+ # for nghttp2_session_callbacks_set_on_frame_not_send_callback
103
+ def on_frame_not_send(frame, reason)
104
+ GrpcKit.logger.debug("on_frame_not_send frame=#{frame}, reason=#{reason}")
105
+ true
106
+ end
107
+
108
+ # for nghttp2_session_callbacks_set_on_begin_headers_callback
109
+ def on_begin_headers(header)
110
+ stream_id = header.stream_id
111
+ GrpcKit.logger.debug("on_begin_header stream_id=#{stream_id}")
112
+
113
+ if @streams[stream_id]
114
+ raise "#{stream_id} is already existed"
115
+ end
116
+
117
+ @streams[stream_id] = GrpcKit::Session::Stream.new(stream_id: stream_id)
118
+ end
119
+
120
+ # for nghttp2_session_callbacks_set_on_header_callback
121
+ def on_header(name, value, frame, _flags)
122
+ @streams[frame.stream_id].headers[name] = value
123
+ end
124
+
125
+ # for nghttp2_session_callbacks_set_on_begin_frame_callback
126
+ def on_begin_frame(frame_header)
127
+ GrpcKit.logger.debug("on_begin_frame #{frame_header}")
128
+ true
129
+ end
130
+
131
+ # for nghttp2_session_callbacks_set_on_invalid_frame_recv_callback
132
+ def on_invalid_frame_recv(frame, error_code)
133
+ GrpcKit.logger.debug("on_invalid_frame_recv #{error_code}")
134
+ true
135
+ end
136
+
137
+ # for nghttp2_session_callbacks_set_on_stream_close_callback
138
+ def on_stream_close(stream_id, error_code)
139
+ GrpcKit.logger.debug("on_stream_close stream_id=#{stream_id}, error_code=#{error_code}")
140
+ @streams.delete(stream_id)
141
+ end
142
+
143
+ # for nghttp2_session_callbacks_set_on_data_chunk_recv_callback
144
+ def on_data_chunk_recv(stream_id, data, _flags)
145
+ @handler.on_data_chunk_recv(@streams[stream_id], data)
146
+ end
147
+
148
+ # nghttp2_session_callbacks_set_before_frame_send_callback
149
+ def before_frame_send(frame)
150
+ GrpcKit.logger.debug("before_frame_send frame=#{frame}")
151
+ true
152
+ end
153
+ end
154
+ end
155
+ end