msgpack-rpc-stack 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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 921c6f0470a8adf12e9afb27c6629edee9a81b0266c920570ba756eea2486229
4
+ data.tar.gz: 76e6bb755bdba72d9c5c0c06f789f614b7c1c71005a36fa342966696d4258188
5
+ SHA512:
6
+ metadata.gz: 52e3c72c2d512bf23e24f8ec37843abb07e61e5a213eb7b3e7f37b21e5b4cd18f8f752108b305414178bc51b73035c9b3bb5adbdd260021bb2e6c6cc2d211bca
7
+ data.tar.gz: 1238e7737c3cfddcfbb3782ea6b82151981dd40de03aa590e03b5f50754b0206ae38c66aa752ca02a5633d52da66976c3d4dccb3090906b66342291a9c58ff53
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in msgpack-rpc-stack.gemspec
6
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Hiroshi Kuwagata
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,305 @@
1
+ # msgpack-rpc-stack
2
+ A module of implementation for MessagePack-RPC protocol stack.
3
+
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'msgpack-rpc-stack'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install msgpack-rpc-stack
20
+
21
+ ## Example
22
+
23
+ ### server side
24
+
25
+ #### with EventMachine
26
+
27
+ ```ruby
28
+ #! /usr/bin/env ruby
29
+ # coding: utf-8
30
+ require 'logger'
31
+
32
+ require 'eventmachine'
33
+ require 'msgpack/rpc/server'
34
+
35
+ $logger = Logger.new(STDOUT)
36
+ $logger.datetime_format = "%Y-%m-%dT%H:%M:%S"
37
+
38
+ class RpcServer < EM::Connection
39
+ include MessagePack::Rpc::Server
40
+
41
+ # EM::Connection#receive_dataをMessagePack::Rpc::Server#receive_streamの
42
+ # aliasにすることによって自動的にデータが流し込まれるようにしている。
43
+ alias :receive_data :receive_stream
44
+
45
+ def post_init
46
+ info = Socket.unpack_sockaddr_in(get_peername())
47
+
48
+ @addr = info[1]
49
+ @port = info[0]
50
+
51
+ $logger.info("connection from #{@addr}:#{@port}")
52
+ end
53
+
54
+ def unbind
55
+ $logger.info("connection close #{@addr}:#{@port}")
56
+ end
57
+
58
+ #
59
+ # require for MessagePack::Rpc::Server
60
+ #
61
+
62
+ # send_dataメソッドはEM::Connectionで提供されるので定義なし
63
+
64
+ def on_error(msg)
65
+ $logger.error(msg)
66
+ end
67
+
68
+ #
69
+ # declar procedure
70
+ #
71
+
72
+ def bar
73
+ $logger.info("call `bar` from #{@addr}:#{@port}")
74
+ return "hello"
75
+ end
76
+ remote_public :bar
77
+
78
+ def foo(df)
79
+ $logger.info("call `foo` from #{@addr}:#{@port}")
80
+ EM.defer {
81
+ sleep 3
82
+ df.resolve("timeout")
83
+ }
84
+ end
85
+ remote_async :foo
86
+
87
+ def bye
88
+ $logger.info("receive notify `bye` from #{@addr}:#{@port}")
89
+ EM.stop
90
+ end
91
+ remote_public :bye
92
+ end
93
+
94
+ #
95
+ # main process
96
+ #
97
+
98
+ EM.run {
99
+ $logger.info("start dummy server")
100
+ EM.start_server("localhost", 9001, RpcServer)
101
+ }
102
+ ```
103
+
104
+ #### use raw TCP socket
105
+ ```ruby
106
+ require 'logger'
107
+ require 'socket'
108
+ require 'msgpack/rpc/server'
109
+
110
+ $logger = Logger.new(STDOUT)
111
+ $logger.datetime_format = "%Y-%m-%dT%H:%M:%S"
112
+
113
+ #
114
+ # 簡略化のため、個別にスレッドを立ち上げる実装にしています。本来であれば、
115
+ # いちいちスレッドを立ち上げるのはリソースがもったいないので、ちゃんとし
116
+ # たコードを書く場合はIO.selectで多重化するようにして下さい。
117
+ #
118
+
119
+ class Server
120
+ include MessagePack::Rpc::Server
121
+
122
+ class Exit < Exception; end
123
+
124
+ class << self
125
+ def up(host, port)
126
+ $logger.info("start dummy server")
127
+
128
+ @server = TCPServer.open(host, port)
129
+ @thread = Thread.current
130
+ loop {
131
+ begin
132
+ sock = @server.accept
133
+ self.new(sock)
134
+
135
+ rescue Exit
136
+ break
137
+ end
138
+ }
139
+ end
140
+
141
+ def down
142
+ @thread.raise(Exit)
143
+ @thread.join
144
+ @server.close
145
+ $logger.info("stop dummy server")
146
+ end
147
+ end
148
+
149
+ def initialize(sock)
150
+ @sock = sock
151
+
152
+ info = @sock.peeraddr
153
+ @addr = info[2]
154
+ @port = info[1]
155
+
156
+ $logger.info("connection from #{@addr}:#{@port}")
157
+
158
+ Thread.new {
159
+ until @sock.eof?
160
+ # 受け取ったデータをMessagePack::Rpc::Server#receive_streamで
161
+ # モジュールにデータを流し込む
162
+ receive_stream(@sock.readpartial(1024))
163
+ end
164
+
165
+ $logger.info("connection close #{@addr}:#{@port}")
166
+ @sock.close
167
+ }
168
+ end
169
+
170
+ #
171
+ # require for MessagePack::Rpc::Server
172
+ #
173
+
174
+ def send_data(data)
175
+ @sock.write(data)
176
+ end
177
+ private :send_data
178
+
179
+ def on_error(msg)
180
+ $logger.error(msg)
181
+ end
182
+ private :on_error
183
+
184
+ #
185
+ # declar procedures
186
+ #
187
+
188
+ def bar
189
+ $logger.info("call `bar` from #{@addr}:#{@port}")
190
+ return "hello"
191
+ end
192
+ remote_public :bar
193
+
194
+ def foo(df)
195
+ $logger.info("call `foo` from #{@addr}:#{@port}")
196
+ Thread.new {
197
+ sleep 3
198
+ df.resolve("timeout")
199
+ }
200
+ end
201
+ remote_async :foo
202
+
203
+ def bye
204
+ $logger.info("receive notify `bye` from #{@addr}:#{@port}")
205
+ self.class.down
206
+ end
207
+ remote_public :bye
208
+ end
209
+
210
+ #
211
+ # main process
212
+ #
213
+
214
+ Server.up("localhost", 9001)
215
+ ```
216
+
217
+ ### client side
218
+
219
+ ```ruby
220
+ #! /usr/bin/env ruby
221
+ # coding: utf-8
222
+
223
+ require 'socket'
224
+ require 'msgpack/rpc/client'
225
+
226
+ class SampleClient
227
+ include MessagePack::Rpc::Client
228
+
229
+ class Exit < Exception; end
230
+
231
+ class Error < Exception
232
+ def initialize(data)
233
+ @data = data
234
+ end
235
+
236
+ attr_reader :data
237
+ end
238
+
239
+ class << self
240
+ def open(host, port)
241
+ ret = self.allocate
242
+
243
+ ret.instance_eval {
244
+ @sock = TCPSocket.open(host, port)
245
+ @sock.sync
246
+
247
+ @thread = Thread.fork {
248
+ begin
249
+ loop {
250
+ # 受け取ったデータをMessagePack::Rpc::Client#receive_streamで
251
+ # モジュールにデータを流し込む
252
+ receive_stream(@sock.readpartial(1024))
253
+ }
254
+
255
+ rescue Exit
256
+ end
257
+ }
258
+ }
259
+
260
+ return ret
261
+ end
262
+ end
263
+
264
+ def close
265
+ @thread.raise(Exit)
266
+ @thread.join
267
+ @sock.close
268
+ end
269
+
270
+ #
271
+ # require for MessagePack::Rpc::Client
272
+ #
273
+
274
+ def send_data(data)
275
+ @sock.write(data)
276
+ end
277
+ private :send_data
278
+ end
279
+
280
+ #
281
+ # main process
282
+ #
283
+
284
+ port = SampleClient.open("localhost", 9001)
285
+ que = Queue.new
286
+
287
+ port.call(:foo) { |resp, error|
288
+ que << [:foo, resp, error]
289
+ }
290
+
291
+ port.call(:bar) { |resp, error|
292
+ que << [:bar, resp, error]
293
+ }
294
+
295
+ port.call(:baz) { |resp, error|
296
+ que << [:baz, resp, error]
297
+ }
298
+
299
+ p que.deq
300
+ p que.deq
301
+ p que.deq
302
+
303
+ port.notify(:bye)
304
+ port.close
305
+ ```
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList['test/test_*.rb']
7
+ end
8
+
9
+ task :default => :spec
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "msgpack/rpc/server"
5
+ require "msgpack/rpc/client"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,85 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require 'socket'
5
+ require 'msgpack/rpc/client'
6
+
7
+ class SampleClient
8
+ include MessagePack::Rpc::Client
9
+
10
+ class Exit < Exception; end
11
+
12
+ class Error < Exception
13
+ def initialize(data)
14
+ @data = data
15
+ end
16
+
17
+ attr_reader :data
18
+ end
19
+
20
+ class << self
21
+ def open(host, port)
22
+ ret = self.allocate
23
+
24
+ ret.instance_eval {
25
+ @sock = TCPSocket.open(host, port)
26
+ @sock.sync
27
+
28
+ @thread = Thread.fork {
29
+ begin
30
+ loop {
31
+ # 受け取ったデータをMessagePack::Rpc::Client#receive_streamで
32
+ # モジュールにデータを流し込む
33
+ receive_stream(@sock.readpartial(1024))
34
+ }
35
+
36
+ rescue Exit
37
+ end
38
+ }
39
+ }
40
+
41
+ return ret
42
+ end
43
+ end
44
+
45
+ def close
46
+ @thread.raise(Exit)
47
+ @thread.join
48
+ @sock.close
49
+ end
50
+
51
+ #
52
+ # require for MessagePack::Rpc::Server
53
+ #
54
+
55
+ def send_data(data)
56
+ @sock.write(data)
57
+ end
58
+ private :send_data
59
+ end
60
+
61
+ #
62
+ # main process
63
+ #
64
+
65
+ port = SampleClient.open("localhost", 9001)
66
+ que = Queue.new
67
+
68
+ port.call(:foo) { |resp, error|
69
+ que << [:foo, resp, error]
70
+ }
71
+
72
+ port.call(:bar) { |resp, error|
73
+ que << [:bar, resp, error]
74
+ }
75
+
76
+ port.call(:baz) { |resp, error|
77
+ que << [:baz, resp, error]
78
+ }
79
+
80
+ p que.deq
81
+ p que.deq
82
+ p que.deq
83
+
84
+ port.notify(:bye)
85
+ port.close
@@ -0,0 +1,109 @@
1
+ require 'logger'
2
+ require 'socket'
3
+ require 'msgpack/rpc/server'
4
+
5
+ $logger = Logger.new(STDOUT)
6
+ $logger.datetime_format = "%Y-%m-%dT%H:%M:%S"
7
+
8
+ #
9
+ # 簡略化のため、個別にスレッドを立ち上げる実装にしています。本来であれば、
10
+ # いちいちスレッドを立ち上げるのはリソースがもったいないので、ちゃんとし
11
+ # たコードを書く場合はIO.selectで多重化するようにして下さい。
12
+ #
13
+
14
+ class Server
15
+ include MessagePack::Rpc::Server
16
+
17
+ class Exit < Exception; end
18
+
19
+ class << self
20
+ def up(host, port)
21
+ $logger.info("start dummy server")
22
+
23
+ @server = TCPServer.open(host, port)
24
+ @thread = Thread.current
25
+ loop {
26
+ begin
27
+ sock = @server.accept
28
+ self.new(sock)
29
+
30
+ rescue Exit
31
+ break
32
+ end
33
+ }
34
+ end
35
+
36
+ def down
37
+ @thread.raise(Exit)
38
+ @thread.join
39
+ @server.close
40
+ $logger.info("stop dummy server")
41
+ end
42
+ end
43
+
44
+ def initialize(sock)
45
+ @sock = sock
46
+
47
+ info = @sock.peeraddr
48
+ @addr = info[2]
49
+ @port = info[1]
50
+
51
+ $logger.info("connection from #{@addr}:#{@port}")
52
+
53
+ Thread.new {
54
+ until @sock.eof?
55
+ # 受け取ったデータをMessagePack::Rpc::Server#receive_streamで
56
+ # モジュールにデータを流し込む
57
+ receive_stream(@sock.readpartial(1024))
58
+ end
59
+
60
+ $logger.info("connection close #{@addr}:#{@port}")
61
+ @sock.close
62
+ }
63
+ end
64
+
65
+ #
66
+ # require for MessagePack::Rpc::Server
67
+ #
68
+
69
+ def send_data(data)
70
+ @sock.write(data)
71
+ end
72
+ private :send_data
73
+
74
+ def on_error(msg)
75
+ $logger.error(msg)
76
+ end
77
+ private :on_error
78
+
79
+ #
80
+ # declar procedures
81
+ #
82
+
83
+ def bar
84
+ $logger.info("call `bar` from #{@addr}:#{@port}")
85
+ return "hello"
86
+ end
87
+ remote_public :bar
88
+
89
+ def foo(df)
90
+ $logger.info("call `foo` from #{@addr}:#{@port}")
91
+ Thread.new {
92
+ sleep 3
93
+ df.resolve("timeout")
94
+ }
95
+ end
96
+ remote_async :foo
97
+
98
+ def bye
99
+ $logger.info("receive notify `bye` from #{@addr}:#{@port}")
100
+ self.class.down
101
+ end
102
+ remote_public :bye
103
+ end
104
+
105
+ #
106
+ # main process
107
+ #
108
+
109
+ Server.up("localhost", 9001)
@@ -0,0 +1,74 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+ require 'logger'
4
+
5
+ require 'eventmachine'
6
+ require 'msgpack/rpc/server'
7
+
8
+ $logger = Logger.new(STDOUT)
9
+ $logger.datetime_format = "%Y-%m-%dT%H:%M:%S"
10
+
11
+ class RpcServer < EM::Connection
12
+ include MessagePack::Rpc::Server
13
+
14
+ # EM::Connection#receive_dataをMessagePack::Rpc::Server#receive_streamの
15
+ # aliasにすることによって自動的にデータが流し込まれるようにしている。
16
+ alias :receive_data :receive_stream
17
+
18
+ def post_init
19
+ info = Socket.unpack_sockaddr_in(get_peername())
20
+
21
+ @addr = info[1]
22
+ @port = info[0]
23
+
24
+ $logger.info("connection from #{@addr}:#{@port}")
25
+ end
26
+
27
+ def unbind
28
+ $logger.info("connection close #{@addr}:#{@port}")
29
+ end
30
+
31
+ #
32
+ # require for MessagePack::Rpc::Server
33
+ #
34
+
35
+ # send_dataメソッドはEM::Connectionで提供されるので定義なし
36
+
37
+ def on_error(msg)
38
+ $logger.error(msg)
39
+ end
40
+
41
+ #
42
+ # declar procedure
43
+ #
44
+
45
+ def bar
46
+ $logger.info("call `bar` from #{@addr}:#{@port}")
47
+ return "hello"
48
+ end
49
+ remote_public :bar
50
+
51
+ def foo(df)
52
+ $logger.info("call `foo` from #{@addr}:#{@port}")
53
+ EM.defer {
54
+ sleep 3
55
+ df.resolve("timeout")
56
+ }
57
+ end
58
+ remote_async :foo
59
+
60
+ def bye
61
+ $logger.info("receive notify `bye` from #{@addr}:#{@port}")
62
+ EM.stop
63
+ end
64
+ remote_public :bye
65
+ end
66
+
67
+ #
68
+ # main process
69
+ #
70
+
71
+ EM.run {
72
+ $logger.info("start dummy server")
73
+ EM.start_server("localhost", 9001, RpcServer)
74
+ }
@@ -0,0 +1,23 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ #
5
+ # module of MessagePack-RPC protocol stack
6
+ #
7
+ # Copyright (C) 2017 Hiroshi Kuwagata <kgt9221@gmail.com>
8
+ #
9
+
10
+ require 'msgpack'
11
+
12
+ module MessagePack
13
+ module Rpc
14
+ class ProtocolError < StandardError
15
+ def initialize(msg, data = nil)
16
+ super(msg)
17
+ @data = data
18
+ end
19
+
20
+ attr_reader :data
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,132 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ #
5
+ # module of MessagePack-RPC protocol stack
6
+ #
7
+ # Copyright (C) 2017 Hiroshi Kuwagata <kgt9221@gmail.com>
8
+ #
9
+
10
+ require 'msgpack'
11
+ require 'msgpack/rpc'
12
+
13
+ module MessagePack
14
+ module Rpc
15
+ module Client
16
+ class << self
17
+ def included(klass)
18
+ m = Module.new {
19
+ klass.instance_variable_set(:@msgpack_options, {})
20
+
21
+ def msgpack_options(opts = nil)
22
+ return (@msgpack_options = opts)
23
+ end
24
+
25
+ def new_unpacker
26
+ return MessagePack::Unpacker.new(@msgpack_options || {})
27
+ end
28
+ }
29
+
30
+ klass.extend(m)
31
+ end
32
+ end
33
+
34
+ def new_id
35
+ @session_id ||= -1
36
+ return (@session_id += 1)
37
+ end
38
+ private :new_id
39
+
40
+ def session_map
41
+ return (@session_map ||= {})
42
+ end
43
+ private :session_map
44
+
45
+ def notify_handler
46
+ return (@notify_handler ||= {})
47
+ end
48
+ private :notify_handler
49
+
50
+ def unpacker
51
+ return (@unpacker ||= self.class.new_unpacker)
52
+ end
53
+ private :unpacker
54
+
55
+ def call(meth, *args, &blk)
56
+ raise ArgumentError.new("handler is not spcified") if not blk
57
+
58
+ id = new_id
59
+
60
+ session_map[id] = blk
61
+ send_data([0, id, meth, args].to_msgpack)
62
+ end
63
+
64
+ def notify(meth, *args)
65
+ send_data([2, meth, args].to_msgpack)
66
+ end
67
+
68
+ def eval_response(resp)
69
+ if not resp.kind_of?(Array)
70
+ raise ProtocolError.new("responce is not array")
71
+ end
72
+
73
+ case resp.shift
74
+ when 1 # as response
75
+ id, error, result = resp
76
+
77
+ if not session_map.include?(id)
78
+ raise ProtocolError.new("unknwon responce id is received.")
79
+ end
80
+
81
+ if error.nil?
82
+ # when success
83
+ session_map.delete(id).(result, nil)
84
+
85
+ elsif result.nil?
86
+ # when error occurred
87
+ session_map.delete(id).(nil, error)
88
+
89
+ else
90
+ raise ProtocolError.new("unknwon responce id is received.")
91
+ end
92
+
93
+ when 2 # as notification
94
+ meth = resp.shift.to_sym
95
+
96
+ if notify_handler.include?(meth)
97
+ notify_handler[meth].(*resp)
98
+
99
+ else
100
+ STDERR.print("unhandled notification '#{meth}' received.\n")
101
+ end
102
+
103
+ else
104
+ raise ProtocolError.new("unknown response received")
105
+ end
106
+ end
107
+ private :eval_response
108
+
109
+ def receive_dgram(data)
110
+ eval_eval_response(MessagePack.unpack(data, self.class.msgpack_options))
111
+ end
112
+
113
+ def receive_stream(data)
114
+ begin
115
+ unpacker.feed_each(data) {|resp| eval_response(resp)}
116
+
117
+ rescue MessagePack::UnpackError => e
118
+ unpacker.reset
119
+ raise(e)
120
+
121
+ rescue => e
122
+ raise(e)
123
+ end
124
+ end
125
+
126
+ def on(name, &blk)
127
+ raise ArgumentError.new("handler is not spcified") if not blk
128
+ notify_handler[name] = blk
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,205 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ #
5
+ # module of MessagePack-RPC protocol stack
6
+ #
7
+ # Copyright (C) 2017 Hiroshi Kuwagata <kgt9221@gmail.com>
8
+ #
9
+
10
+ require 'msgpack'
11
+ require 'msgpack/rpc'
12
+
13
+ module MessagePack
14
+ module Rpc
15
+ module Server
16
+ class Error < Exception
17
+ def initialize(data)
18
+ @data = data
19
+ end
20
+
21
+ attr_reader :data
22
+ end
23
+
24
+ class << self
25
+ def included(klass)
26
+ m = Module.new {
27
+ @@deferred = Class.new {
28
+ def initialize(id, klass)
29
+ @id = id
30
+ @klass = klass
31
+ end
32
+
33
+ def resolve(result)
34
+ packet = [1, @id, nil, result].to_msgpack
35
+ @klass.instance_eval {send_data(packet)}
36
+ end
37
+
38
+ def reject(error)
39
+ packet = [1, @id, error, nil].to_msgpack
40
+ @klass.instance_eval {send_data(packet)}
41
+ end
42
+ }
43
+
44
+ klass.instance_variable_set(:@remote_public, [])
45
+ klass.instance_variable_set(:@remote_async, [])
46
+ klass.instance_variable_set(:@msgpack_options, {})
47
+
48
+ def remote_public(meth = nil)
49
+ @remote_public << meth if meth
50
+ return @remote_public
51
+ end
52
+
53
+ def remote_async(meth = nil)
54
+ @remote_async << meth if meth
55
+ return @remote_async
56
+ end
57
+
58
+ def msgpack_options(opts = nil)
59
+ return (@msgpack_options = opts)
60
+ end
61
+
62
+ def new_unpacker
63
+ return MessagePack::Unpacker.new(@msgpack_options || {})
64
+ end
65
+ }
66
+
67
+ klass.extend(m)
68
+ end
69
+ end
70
+
71
+ def unpacker
72
+ return (@unpacker ||= self.class.new_unpacker)
73
+ end
74
+
75
+ def reset_unpacker
76
+ @unpacker = nil
77
+ end
78
+
79
+ def do_async_call(id, meth, para)
80
+ deferred = @@deferred.new(id, self)
81
+
82
+ if not para
83
+ self.__send__(meth, deferred)
84
+
85
+ elsif para.kind_of?(Array)
86
+ self.__send__(meth, deferred, *para)
87
+
88
+ else
89
+ self.__send__(meth, deferred, para)
90
+ end
91
+ end
92
+ private :do_async_call
93
+
94
+ def do_call(id, meth, para)
95
+ if not para
96
+ ret = self.__send__(meth)
97
+
98
+ elsif para.kind_of?(Array)
99
+ ret = self.__send__(meth, *para)
100
+
101
+ else
102
+ ret = self.__send__(meth, para)
103
+ end
104
+
105
+ return ret
106
+ end
107
+ private :do_call
108
+
109
+ def do_notify(meth, para)
110
+ if para.kind_of?(Array)
111
+ self.__send__(meth.to_sym, *para)
112
+
113
+ else
114
+ self.__send__(meth.to_sym, para)
115
+ end
116
+ end
117
+ private :do_notify
118
+
119
+ def error_occured(msg)
120
+ if self.respond_to?(:on_error, true)
121
+ __send__(:on_error, msg)
122
+ else
123
+ STDERR.print("#{msg}")
124
+ end
125
+ end
126
+ private :error_occured
127
+
128
+ def eval_message(msg)
129
+ case msg[0]
130
+ when 0
131
+ #
132
+ # when call
133
+ #
134
+ id = msg[1]
135
+ meth = msg[2].to_sym
136
+ args = msg[3]
137
+
138
+ if self.class.remote_async.include?(meth)
139
+ do_async_call(id, meth, args)
140
+
141
+ elsif self.class.remote_public.include?(meth)
142
+ result = do_call(id, meth, args)
143
+ send_data([1, id, nil, result].to_msgpack)
144
+
145
+ else
146
+ raise("procedure `#{meth}` is not callable from remote")
147
+ end
148
+
149
+ when 2
150
+ #
151
+ # when notify
152
+ #
153
+ meth = msg[1].to_sym
154
+ args = msg[2]
155
+
156
+ if self.class.remote_public.include?(meth)
157
+ do_notify(meth, args);
158
+
159
+ else
160
+ raise("notify `#{meth}` is unhandled")
161
+ end
162
+
163
+ else
164
+ raise ProtocolError.new("unknown message type #{msg[0]} recived.")
165
+ end
166
+
167
+ rescue => e
168
+ if msg[0] == 0
169
+ error = e.data rescue String.new(e.message, encoding:"UTF-8")
170
+ send_data([1, id, error, nil].to_msgpack)
171
+ end
172
+
173
+ error_occured(e.message)
174
+ end
175
+ private :eval_message
176
+
177
+ def notify(meth, *args)
178
+ send_data([2, meth, args].to_msgpack)
179
+ end
180
+
181
+ def receive_dgram(data)
182
+ msg = MessagePack.unpack(data, self.class.msgpack_options)
183
+
184
+ if not msg.kind_of?(Array)
185
+ error_occured("not array message is received")
186
+ end
187
+
188
+ eval_message(msg)
189
+ end
190
+
191
+ def receive_stream(data)
192
+ begin
193
+ unpacker.feed_each(data) {|msg| eval_message(msg)}
194
+
195
+ rescue MessagePack::UnpackError => e
196
+ unpacker.reset
197
+ error_occured(e.message)
198
+
199
+ rescue => e
200
+ error_occured(e.message)
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,5 @@
1
+ module MessagePack
2
+ module Rpc
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,39 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "msgpack/rpc/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "msgpack-rpc-stack"
8
+ spec.version = MessagePack::Rpc::VERSION
9
+ spec.authors = ["Hiroshi Kuwagata"]
10
+ spec.email = ["kgt9221@gmail.com"]
11
+
12
+ spec.summary = %q{MessagePack-RPC module}
13
+ spec.description = %q{A module of implementation MessagePack-RPC stack}
14
+ spec.homepage = "https://github.com/kwgt/msgpack-rpc-stack"
15
+ spec.license = "MIT"
16
+
17
+ if spec.respond_to?(:metadata)
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ else
20
+ raise "RubyGems 2.0 or newer is required to protect against " \
21
+ "public gem pushes."
22
+ end
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
26
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
27
+ (`git ls-files -z`).split("\x0").reject { |f|
28
+ f.match(%r{^(test|spec|features)/})
29
+ }
30
+ end
31
+
32
+ spec.bindir = "exe"
33
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
+ spec.require_paths = ["lib"]
35
+
36
+ spec.add_development_dependency "bundler", "~> 1.17"
37
+ spec.add_development_dependency "rake", "~> 10.0"
38
+ spec.add_development_dependency "msgpack", "~> 1.2.6"
39
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: msgpack-rpc-stack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Hiroshi Kuwagata
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-01-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: msgpack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.2.6
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.2.6
55
+ description: A module of implementation MessagePack-RPC stack
56
+ email:
57
+ - kgt9221@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - LICENSE
65
+ - README.md
66
+ - Rakefile
67
+ - bin/console
68
+ - bin/setup
69
+ - example/client.rb
70
+ - example/server.rb
71
+ - example/server_with_eventmachin.rb
72
+ - lib/msgpack/rpc.rb
73
+ - lib/msgpack/rpc/client.rb
74
+ - lib/msgpack/rpc/server.rb
75
+ - lib/msgpack/rpc/version.rb
76
+ - msgpack-rpc-stack.gemspec
77
+ homepage: https://github.com/kwgt/msgpack-rpc-stack
78
+ licenses:
79
+ - MIT
80
+ metadata:
81
+ homepage_uri: https://github.com/kwgt/msgpack-rpc-stack
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 2.7.6
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: MessagePack-RPC module
102
+ test_files: []