msgpack-rpc-stack 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []