├── .gitignore ├── .rspec ├── .travis.yml ├── AUTHORS ├── ChangeLog ├── Gemfile ├── NOTICE ├── README ├── Rakefile ├── lib ├── msgpack-rpc.rb └── msgpack │ ├── rpc.rb │ └── rpc │ ├── address.rb │ ├── client.rb │ ├── dispatcher.rb │ ├── exception.rb │ ├── future.rb │ ├── loop.rb │ ├── message.rb │ ├── multi_future.rb │ ├── server.rb │ ├── session.rb │ ├── session_pool.rb │ ├── transport │ ├── base.rb │ ├── tcp.rb │ ├── udp.rb │ └── unix.rb │ └── version.rb ├── msgpack-rpc.gemspec ├── pkg └── msgpack-rpc-0.6.0.gem ├── spec ├── spec_helper.rb └── unit │ ├── client_spec.rb │ └── my_server.rb └── test ├── msgpack_rpc_test.rb └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | Gemfile.lock 3 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | --format d 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: false 3 | rvm: 4 | - 1.9.3 5 | - 2.0 6 | - 2.1 7 | - 2.2 8 | 9 | script: bundle exec rake test 10 | 11 | notifications: 12 | email: 13 | on_success: change 14 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | FURUHASHI Sadayuki 2 | Shuzo Kashihara 3 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2020-10-07 version 0.7.0 2 | 3 | * Upgrade cool.io-1.7.0 4 | 5 | 2017-04-06 version 0.6.0 6 | 7 | * Relax msgpack version requirements for supporting Ruby 2.4 and further 8 | 9 | 2015-01-13 version 0.5.3 10 | 11 | * Upgrade cool.io-1.2.4 for Ruby 2.2 12 | * Upgrade msgpack-0.5.10 13 | 14 | 2013-01-22 version 0.5.2 15 | 16 | * Upgrade msgpack-0.5.8 for Ruby 2.0 17 | * Use bundler for packaging 18 | 19 | 2012-01-05 version 0.5.1 20 | 21 | * Use ~> version dependency in gemspec 22 | 23 | 2012-01-05 version 0.5.0 24 | 25 | * Replaced Rev with Cool.io 26 | * MSGPACK-8 RPC::Address.parse_sockaddr(raw) checks encoding of the string on Ruby 1.9 27 | 28 | 2011-04-06 version 0.4.4 29 | 30 | * Fixes missing RuntimeError::CODE 31 | 32 | 2010-11-28 version 0.4.3 33 | 34 | * Uses MessagePack::Unpacker#feed_each implemented on msgpack-0.4.4 35 | 36 | 2010-08-28 version 0.4.2 37 | 38 | * Fixes exception.rb 39 | 40 | 2010-08-27 version 0.4.1 41 | 42 | * Adds MultiFuture class 43 | * New exception mechanism 44 | * Rescues all errors on_readable not to stop event loop 45 | * Future: doesn't wrap callback_handler but check on calling for backward compatibility 46 | * Responder: adds a guard not to send results twice 47 | * Session: adds call_apply and notify_apply 48 | * Uses jeweler and Rakefile for packaging 49 | 50 | 2010-05-28 version 0.4.0 51 | 52 | * updates dispatch mechanism 53 | * adds Session#call_apply and notify_apply 54 | * Responder prevents sending results twice 55 | 56 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | MessagePack-RPC for Ruby is developed by FURUHASHI Sadayuki, licensed under 2 | Apache License, Version 2.0. The original software and related information 3 | is available at http://msgpack.org/. 4 | 5 | MessagePack is developed by FURUHASHI Sadayuki, licensed under Apache License, 6 | Version 2.0. The original software and related information is available at 7 | http://msgpack.org/. 8 | 9 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | = MessagePack-RPC 3 | 4 | 5 | == Description 6 | 7 | 8 | == Installation 9 | 10 | === Archive Installation 11 | 12 | rake install 13 | 14 | === Gem Installation 15 | 16 | gem install msgpack-rpc 17 | 18 | 19 | == Features/Problems 20 | 21 | 22 | == Synopsis 23 | 24 | 25 | == Copyright 26 | 27 | Author:: frsyuki 28 | Copyright:: Copyright (c) 2010 frsyuki 29 | License:: Apache License, Version 2.0 30 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | require "bundler/gem_tasks" 3 | require "rspec/core/rake_task" 4 | 5 | RSpec::Core::RakeTask.new(:spec) 6 | 7 | require 'rake/testtask' 8 | require 'rake/clean' 9 | 10 | task :default => [:build] 11 | 12 | task :test => ["spec", "test:unit"] 13 | 14 | namespace :test do 15 | desc "run test" 16 | Rake::TestTask.new(:unit) do |t| 17 | t.libs << 'lib' 18 | t.pattern = 'test/*_test.rb' 19 | t.verbose = true 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/msgpack-rpc.rb: -------------------------------------------------------------------------------- 1 | require 'msgpack/rpc' 2 | -------------------------------------------------------------------------------- /lib/msgpack/rpc.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack #:nodoc: 19 | 20 | # MessagePack-RPC is an inter-process messaging library that uses 21 | # MessagePack[http://msgpack.sourceforge.net/] for object serialization. 22 | # The goal of the project is providing fast and scalable messaging system 23 | # for server, client and cluster applications. 24 | # 25 | # You can install MessagePack-RPC for Ruby using RubyGems. 26 | # 27 | # gem install msgpack-rpc 28 | # 29 | # 30 | # == Client API 31 | # 32 | # MessagePack::RPC::Client and MessagePack::RPC::SessionPool are for RPC clients. 33 | # 34 | # 35 | # === Simple usage 36 | # 37 | # Client is subclass of Session. Use Session#call method to call remote methods. 38 | # 39 | # require 'msgpack/rpc' 40 | # 41 | # client = MessagePack::RPC::Client.new('127.0.0.1', 18800) 42 | # 43 | # result = client.call(:methodName, arg1, arg2, arg3) 44 | # 45 | # # ---------- server 46 | # # ^ | 47 | # # | | 48 | # # ---+ +----- client 49 | # # call join 50 | # 51 | # 52 | # === Asynchronous call 53 | # 54 | # Use Session#call_async method to call remote methods asynchronously. It returns a Future. Use Future#get or Future#attach_callback to get actual result. 55 | # 56 | # require 'msgpack/rpc' 57 | # 58 | # client = MessagePack::RPC::Client.new('127.0.0.1', 18800) 59 | # 60 | # # call two methods concurrently 61 | # future1 = client.call_async(:method1, arg1) 62 | # future2 = client.call_async(:method2, arg1) 63 | # 64 | # # join the results 65 | # result1 = future1.get 66 | # result2 = future2.get 67 | # 68 | # # ------------------ server 69 | # # ^ | 70 | # # | ---------|-------- server 71 | # # | ^ | | 72 | # # | | | | 73 | # # ---+-------+----- +-------+----- client 74 | # # call call join join 75 | # 76 | # === Asynchronous call with multiple servers 77 | # 78 | # Loop enables you to invoke multiple asynchronous calls for multiple servers concurrently. 79 | # This is good for advanced network applications. 80 | # 81 | # require 'msgpack/rpc' 82 | # 83 | # # create a event loop 84 | # loop = MessagePack::RPC::Loop.new 85 | # 86 | # # connect to multiple servers 87 | # client1 = MessagePack::RPC::Client.new('127.0.0.1', 18801, loop) 88 | # client2 = MessagePack::RPC::Client.new('127.0.0.1', 18802, loop) 89 | # 90 | # # call two methods concurrently 91 | # future1 = client1.call_async(:method1, arg1) 92 | # future2 = client2.call_async(:method2, arg1) 93 | # 94 | # # join the results 95 | # result1 = future1.get 96 | # result2 = future2.get 97 | # 98 | # # ------------------ server-1 --- different servers 99 | # # ^ | / 100 | # # | ---------|-------- server-2 101 | # # | ^ | | 102 | # # | | | | 103 | # # ---+-------+----- +-------+----- client 104 | # # call call join join 105 | # 106 | # === Connection pooling 107 | # 108 | # SessionPool#get_session returns a Session. It pools created session and enables you to reuse established connections. 109 | # 110 | # require 'msgpack/rpc' 111 | # 112 | # sp = MessagePack::RPC::SessionPool.new 113 | # 114 | # client = sp.get_session('127.0.0.1', 18800) 115 | # 116 | # result = client.call(:methodName, arg1, arg2, arg3) 117 | # 118 | # 119 | # == Server API 120 | # 121 | # MessagePack::RPC::Server is for RPC servers. 122 | # 123 | # 124 | # === Simple usage 125 | # 126 | # The public methods of the handler class becomes callbale. 127 | # 128 | # require 'msgpack/rpc' 129 | # 130 | # class MyHandler 131 | # def methodName(arg1, arg2, arg3) 132 | # puts "received" 133 | # return "return result." 134 | # end 135 | # end 136 | # 137 | # server = MessagePack::RPC::Server.new 138 | # server.listen('0.0.0.0', 18800, MyHandler.new) 139 | # server.run 140 | # 141 | # 142 | # === Advance return 143 | # 144 | # You can use *yield* to send the result without returning. 145 | # 146 | # class MyHandler 147 | # def method1(arg1) 148 | # yield("return result.") 149 | # puts "you can do something after returning the result" 150 | # end 151 | # end 152 | # 153 | # 154 | # === Delayed return 155 | # 156 | # You can use AsyncResult to return results later. 157 | # 158 | # class MyHandler 159 | # def method2(arg1) 160 | # as = MessagePack::RPC::AsyncResult.new 161 | # Thread.new do 162 | # sleep 10 # return result 10 seconds later. 163 | # as.result("return result.") 164 | # end 165 | # return as 166 | # end 167 | # end 168 | # 169 | # 170 | # You can receive and send any objects that can be serialized by MessagePack. 171 | # This means that the objects required to implement *to_msgpack(out = '')* method. 172 | # 173 | # 174 | # == Transports 175 | # 176 | # You can use UDP and UNIX domain sockets instead of TCP. 177 | # 178 | # 179 | # === For clients 180 | # 181 | # For clients, use MessagePack::RPC::UDPTransport or MessagePack::RPC::UNIXTransport. 182 | # 183 | # require 'msgpack/rpc' 184 | # require 'msgpack/rpc/transport/udp' 185 | # 186 | # transport = MessagePack::RPC::UDPTransport.new 187 | # address = MessagePack::RPC::Address.new('127.0.0.1', 18800) 188 | # 189 | # client = MessagePack::RPC::Client.new(transport, address) 190 | # 191 | # result = client.call(:methodName, arg1, arg2, arg3) 192 | # 193 | # You can use transports for SessionPool. 194 | # 195 | # require 'msgpack/rpc' 196 | # require 'msgpack/rpc/transport/udp' 197 | # 198 | # transport = MessagePack::RPC::UDPTransport.new 199 | # 200 | # sp = MessagePack::RPC::SessionPool.new(transport) 201 | # 202 | # client = sp.get_session('127.0.0.1', 18800) 203 | # 204 | # result = client.call(:methodName, arg1, arg2, arg3) 205 | # 206 | # === For servers 207 | # 208 | # For servers, use MessagePack::RPC::UDPServerTransport or MessagePack::RPC::UNIXServerTransport. 209 | # 210 | # require 'msgpack/rpc' 211 | # require 'msgpack/rpc/transport/udp' 212 | # 213 | # class MyHandler 214 | # def methodName(arg1, arg2, arg3) 215 | # puts "received" 216 | # return "return result." 217 | # end 218 | # end 219 | # 220 | # address = MessagePack::RPC::Address.new('0.0.0.0', 18800) 221 | # listener = MessagePack::RPC::UDPServerTransport.new(address) 222 | # 223 | # server = MessagePack::RPC::Server.new 224 | # server.listen(listener, MyHandler.new) 225 | # server.run 226 | # 227 | # 228 | module RPC 229 | end 230 | 231 | end # module MessagePack 232 | 233 | 234 | require 'msgpack' 235 | require 'socket' 236 | require 'cool.io' 237 | require 'msgpack/rpc/version' 238 | require 'msgpack/rpc/address' 239 | require 'msgpack/rpc/message' 240 | require 'msgpack/rpc/exception' 241 | require 'msgpack/rpc/loop' 242 | require 'msgpack/rpc/future' 243 | require 'msgpack/rpc/multi_future' 244 | require 'msgpack/rpc/session' 245 | require 'msgpack/rpc/session_pool' 246 | require 'msgpack/rpc/dispatcher' 247 | require 'msgpack/rpc/client' 248 | require 'msgpack/rpc/server' 249 | require 'msgpack/rpc/transport/base' 250 | require 'msgpack/rpc/transport/tcp' 251 | 252 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/address.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | class Address 23 | # +--+----+ 24 | # | 2| 4 | 25 | # +--+----+ 26 | # port network byte order 27 | # IPv4 address 28 | # 29 | # +--+----------------+ 30 | # | 2| 16 | 31 | # +--+----------------+ 32 | # port network byte order 33 | # IPv6 address 34 | # 35 | 36 | test = Socket.pack_sockaddr_in(0,'0.0.0.0') 37 | if test[0] == "\0"[0] || test[1] == "\0"[0] 38 | # Linux 39 | def initialize(host, port) 40 | raw = Socket.pack_sockaddr_in(port, host) 41 | family = raw.unpack('S')[0] 42 | if family == Socket::AF_INET 43 | @serial = raw[2,6] 44 | elsif family == Socket::AF_INET6 45 | @serial = raw[2,2] + raw[8,16] 46 | else 47 | raise "Unknown address family: #{family}" 48 | end 49 | end 50 | else 51 | # BSD 52 | def initialize(host, port) 53 | raw = Socket.pack_sockaddr_in(port, host) 54 | family = raw.unpack('CC')[1] 55 | if family == Socket::AF_INET 56 | @serial = raw[2,6] 57 | elsif family == Socket::AF_INET6 58 | @serial = raw[2,2] + raw[8,16] 59 | else 60 | raise "Unknown address family: #{family}" 61 | end 62 | end 63 | end 64 | 65 | def host 66 | unpack[0] 67 | end 68 | 69 | def port 70 | unpack[1] 71 | end 72 | 73 | def connectable? 74 | port != 0 75 | end 76 | 77 | def sockaddr 78 | Address.parse_sockaddr(@serial) 79 | end 80 | 81 | def unpack 82 | Address.parse(@serial) 83 | end 84 | 85 | if "".respond_to?(:encoding) 86 | def self.parse_sockaddr(raw) 87 | raw.force_encoding('ASCII-8BIT') 88 | if raw.length == 6 89 | addr = Socket.pack_sockaddr_in(0, '0.0.0.0') 90 | addr[2,6] = raw[0,6] 91 | else 92 | addr = Socket.pack_sockaddr_in(0, '::') 93 | addr[2,2] = raw[0,2] 94 | addr[8,16] = raw[2,16] 95 | end 96 | addr 97 | end 98 | else 99 | def self.parse_sockaddr(raw) 100 | if raw.length == 6 101 | addr = Socket.pack_sockaddr_in(0, '0.0.0.0') 102 | addr[2,6] = raw[0,6] 103 | else 104 | addr = Socket.pack_sockaddr_in(0, '::') 105 | addr[2,2] = raw[0,2] 106 | addr[8,16] = raw[2,16] 107 | end 108 | addr 109 | end 110 | end 111 | 112 | def self.parse(raw) 113 | Socket.unpack_sockaddr_in(parse_sockaddr(raw)).reverse 114 | end 115 | 116 | def self.load(raw) 117 | Address.new *parse(raw) 118 | end 119 | 120 | def dump 121 | @serial 122 | end 123 | 124 | def to_msgpack(out = '') 125 | @serial.to_msgpack(out) 126 | end 127 | 128 | def to_s 129 | unpack.join(':') 130 | end 131 | 132 | def to_a 133 | unpack 134 | end 135 | 136 | def <=>(o) 137 | dump <=> o.dump 138 | end 139 | 140 | def inspect 141 | "#<#{self.class} #{to_s} @serial=#{@serial.inspect}>" 142 | end 143 | 144 | def eql?(o) 145 | o.class == Address && dump.eql?(o.dump) 146 | end 147 | 148 | def hash 149 | dump.hash 150 | end 151 | 152 | def ==(o) 153 | eql?(o) 154 | end 155 | end 156 | 157 | 158 | end 159 | end 160 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/client.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | # Client is usable for RPC client. 23 | # Note that Client includes LoopUtil. 24 | class Client < Session 25 | # 1. initialize(builder, address, loop = Loop.new) 26 | # 2. initialize(host, port, loop = Loop.new) 27 | # 28 | # Creates a client. 29 | def initialize(arg1, arg2, arg3=nil) 30 | if arg1.respond_to?(:build_transport) 31 | # 1. 32 | builder = arg1 33 | address = arg2 34 | loop = arg3 || Loop.new 35 | else 36 | # 2. 37 | builder = TCPTransport.new 38 | address = Address.new(arg1, arg2) 39 | loop = arg3 || Loop.new 40 | end 41 | 42 | super(builder, address, loop) 43 | 44 | @timer = Timer.new(1, true, &method(:step_timeout)) 45 | loop.attach(@timer) 46 | end 47 | 48 | # call-seq: 49 | # Client.open(arg1, arg2, arg3=nil) {|client| } 50 | # 51 | # 1. open(builder, address, loop = Loop.new) {|client } 52 | # 2. open(host, port, loop = Loop.new) {|client } 53 | # Creates a client, calls the block and closes the client. 54 | def self.open(*args, &block) 55 | c = new(*args) 56 | begin 57 | block.call(c) 58 | ensure 59 | c.close 60 | end 61 | end 62 | 63 | def close 64 | @timer.detach if @timer.attached? 65 | super 66 | end 67 | 68 | include LoopUtil 69 | end 70 | 71 | 72 | #:nodoc: 73 | class Client::Base 74 | def initialize(*args) 75 | @base = Client.new(*args) 76 | end 77 | attr_reader :base 78 | end 79 | 80 | 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/dispatcher.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | module Dispatcher 23 | end 24 | 25 | 26 | class ObjectDispatcher 27 | include Dispatcher 28 | 29 | def initialize(obj, accept = obj.public_methods) 30 | @obj = obj 31 | @accept = accept.map {|m| m.is_a?(Integer) ? m : m.to_s } 32 | end 33 | 34 | def dispatch(method, param, &block) 35 | unless @accept.include?(method) 36 | raise NoMethodError, "method `#{method}' is not accepted" 37 | end 38 | @obj.send(method, *param, &block) 39 | end 40 | end 41 | 42 | 43 | #:nodoc: 44 | class MethodForwarder 45 | end 46 | 47 | 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/exception.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | class Error < StandardError 23 | end 24 | 25 | 26 | ## 27 | ## MessagePack-RPC Exception 28 | ## 29 | # 30 | # RPCError 31 | # | 32 | # +-- TimeoutError 33 | # | 34 | # +-- TransportError 35 | # | | 36 | # | +-- NetworkUnreachableError 37 | # | | 38 | # | +-- ConnectionRefusedError 39 | # | | 40 | # | +-- ConnectionTimeoutError 41 | # | | 42 | # | +-- MalformedMessageError 43 | # | | 44 | # | +-- StreamClosedError 45 | # | 46 | # +-- CallError 47 | # | | 48 | # | +-- NoMethodError 49 | # | | 50 | # | +-- ArgumentError 51 | # | 52 | # +-- ServerError 53 | # | | 54 | # | +-- ServerBusyError 55 | # | 56 | # +-- RemoteError 57 | # | 58 | # +-- RuntimeError 59 | # | 60 | # +-- (user-defined errors) 61 | 62 | class RPCError < Error 63 | def initialize(code, *data) 64 | @code = code.to_s 65 | @data = data 66 | super(@data.shift || @code) 67 | end 68 | attr_reader :code 69 | attr_reader :data 70 | 71 | def is?(code) 72 | if code.is_a?(Class) && code < RPCError 73 | if code == RemoteError 74 | return @code[0] != ?. 75 | end 76 | code = code::CODE 77 | end 78 | @code == code || @code[0,code.length+1] == "#{code}." 79 | end 80 | end 81 | 82 | 83 | ## 84 | # Top Level Errors 85 | # 86 | class TimeoutError < RPCError 87 | CODE = ".TimeoutError" 88 | def initialize(msg) 89 | super(self.class::CODE, msg) 90 | end 91 | end 92 | 93 | class TransportError < RPCError 94 | CODE = ".TransportError" 95 | def initialize(msg) 96 | super(self.class::CODE, msg) 97 | end 98 | end 99 | 100 | class CallError < RPCError 101 | CODE = ".CallError" 102 | def initialize(msg) 103 | super(self.class::CODE, msg) 104 | end 105 | end 106 | 107 | class ServerError < RPCError 108 | CODE = ".ServerError" 109 | def initialize(msg) 110 | super(self.class::CODE, msg) 111 | end 112 | end 113 | 114 | class RemoteError < RPCError 115 | CODE = "" 116 | def initialize(code, *data) 117 | super(code, *data) 118 | end 119 | end 120 | 121 | 122 | ## 123 | # TransportError 124 | # 125 | class NetworkUnreachableError < TransportError 126 | CODE = ".TransportError.NetworkUnreachableError" 127 | end 128 | 129 | class ConnectionRefusedError < TransportError 130 | CODE = ".TransportError.ConnectionRefusedError" 131 | end 132 | 133 | class ConnectionTimeoutError < TransportError 134 | CODE = ".TransportError.ConnectionTimeoutError" 135 | end 136 | 137 | class MalformedMessageError < TransportError 138 | CODE = ".TransportError.ConnectionRefusedError" 139 | end 140 | 141 | class StreamClosedError < TransportError 142 | CODE = ".TransportError.StreamClosedError" 143 | end 144 | 145 | ## 146 | # CallError 147 | # 148 | class NoMethodError < CallError 149 | CODE = ".CallError.NoMethodError" 150 | end 151 | 152 | class ArgumentError < CallError 153 | CODE = ".CallError.ArgumentError" 154 | end 155 | 156 | ## 157 | # ServerError 158 | # 159 | class ServerBusyError < ServerError 160 | CODE = ".ServerError.ServerBusyError" 161 | end 162 | 163 | ## 164 | # RuntimeError 165 | # 166 | class RuntimeError < RemoteError 167 | CODE = ".RuntimeError" 168 | def initialize(msg, *data) 169 | super("RuntimeError", msg, *data) 170 | end 171 | end 172 | 173 | 174 | class RPCError 175 | def self.create(code, data) 176 | if code[0] == ?. 177 | code = code.dup 178 | while true 179 | if klass = SYSTEM_ERROR_TABLE[code] 180 | return klass.new(*data) 181 | end 182 | if code.slice!(/\.[^\.]*$/) == nil || code.empty? 183 | return RPCError.new(code, *data) 184 | end 185 | end 186 | 187 | elsif code == RuntimeError::CODE 188 | RuntimeError.new(*data) 189 | 190 | else 191 | RemoteError.new(code, *data) 192 | end 193 | end 194 | 195 | private 196 | SYSTEM_ERROR_TABLE = { 197 | TimeoutError::CODE => TimeoutError, 198 | TransportError::CODE => TransportError, 199 | CallError::CODE => CallError, 200 | ServerError::CODE => ServerError, 201 | NetworkUnreachableError::CODE => NetworkUnreachableError, 202 | ConnectionRefusedError::CODE => ConnectionRefusedError, 203 | ConnectionTimeoutError::CODE => ConnectionTimeoutError, 204 | MalformedMessageError::CODE => MalformedMessageError, 205 | StreamClosedError::CODE => StreamClosedError, 206 | NoMethodError::CODE => NoMethodError, 207 | ArgumentError::CODE => ArgumentError, 208 | ServerBusyError::CODE => ServerBusyError, 209 | } 210 | end 211 | 212 | 213 | end 214 | end 215 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/future.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | # Future describes result of remote procedure call that is initially not known, 23 | # because it is not yet received. 24 | # You can wait and get the result with get method. 25 | class Future 26 | def initialize(session, loop, callback = nil) #:nodoc: 27 | @timeout = session.timeout 28 | @loop = loop 29 | @callback_handler = callback 30 | @error_handler = nil 31 | @result_handler = nil 32 | @set = false 33 | @error = nil 34 | @result = nil 35 | end 36 | attr_reader :loop 37 | attr_accessor :result, :error 38 | 39 | # Wait for receiving result of remote procedure call and returns its result. 40 | # If the remote method raises error, then this method raises RemoteError. 41 | # If the remote procedure call failed with timeout, this method raises TimeoutError. 42 | # Otherwise this method returns the result of remote method. 43 | def get 44 | join 45 | if error.nil? 46 | if @result_handler 47 | return @result_handler.call(@result) 48 | else 49 | return @result 50 | end 51 | end 52 | if @result.nil? 53 | # compatible error 54 | raise RuntimeError.new(@error) 55 | end 56 | if @error_handler 57 | @error_handler.call(@error, @result) 58 | end 59 | raise RPCError.create(@error, @result) 60 | end 61 | 62 | # Wait for receiving result of remote procedure call. 63 | # This method returns self. 64 | # If a callback method is attached, it will be called. 65 | def join 66 | until @set 67 | @loop.run_once 68 | end 69 | self 70 | end 71 | 72 | # call-seq: 73 | # attach_callback {|future| } 74 | # 75 | # Attaches a callback method that is called when the result of remote method is received. 76 | def attach_callback(proc = nil, &block) 77 | @callback_handler = proc || block 78 | end 79 | 80 | # For IDL 81 | def attach_error_handler(proc = nil, &block) #:nodoc: 82 | @error_handler = proc || block 83 | end 84 | 85 | # For IDL 86 | def attach_result_handler(proc = nil, &block) #:nodoc: 87 | @result_handler = proc || block 88 | end 89 | 90 | def set_result(err, res) #:nodoc: 91 | @error = err 92 | @result = res 93 | @set = true 94 | if @callback_handler 95 | if @callback_handler.arity == 2 96 | # FIXME backward compatibility 97 | @callback_handler.call(error, result) 98 | else 99 | @callback_handler.call(self) 100 | end 101 | end 102 | self 103 | rescue 104 | self 105 | end 106 | 107 | def step_timeout #:nodoc: 108 | if @timeout < 1 109 | true 110 | else 111 | @timeout -= 1 112 | false 113 | end 114 | end 115 | end 116 | 117 | 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/loop.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | Loop = Cool.io::Loop 23 | 24 | 25 | module LoopUtil 26 | attr_reader :loop 27 | 28 | class Timer < Cool.io::TimerWatcher 29 | def initialize(interval, repeating, &block) 30 | @block = block 31 | super(interval, repeating) 32 | end 33 | def on_timer 34 | @block.call 35 | end 36 | end 37 | 38 | def start_timer(interval, repeating, &block) 39 | @loop.attach Timer.new(interval, repeating, &block) 40 | end 41 | 42 | class TaskQueue < Cool.io::AsyncWatcher 43 | def initialize 44 | @queue = [] 45 | super 46 | end 47 | 48 | def push(task) 49 | @queue.push(task) 50 | signal 51 | end 52 | 53 | def on_signal 54 | while task = @queue.shift 55 | begin 56 | task.call 57 | rescue 58 | end 59 | end 60 | end 61 | end 62 | 63 | def submit(task = nil, &block) 64 | task ||= block 65 | unless @queue 66 | @queue = TaskQueue.new 67 | @loop.attach(@queue) 68 | end 69 | @queue.push(task) 70 | end 71 | 72 | def run 73 | @loop.run 74 | end 75 | 76 | def stop 77 | @queue.detach if @queue && @queue.attached? 78 | @loop.stop 79 | # attach dummy timer 80 | @loop.attach Cool.io::TimerWatcher.new(0, false) 81 | nil 82 | end 83 | end 84 | 85 | 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/message.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | REQUEST = 0 # [0, msgid, method, param] 23 | RESPONSE = 1 # [1, msgid, error, result] 24 | NOTIFY = 2 # [2, method, param] 25 | 26 | NO_METHOD_ERROR = 0x01; 27 | ARGUMENT_ERROR = 0x02; 28 | 29 | 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/multi_future.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | # MultiFuture bunldes up multiple Future objects. 23 | class MultiFuture 24 | def initialize 25 | @all = [] 26 | 27 | @not_joined = [] 28 | @joined = [] 29 | @error = [] 30 | @success = [] 31 | 32 | @on_all = nil 33 | @on_error = nil 34 | @on_success = nil 35 | @on_num = {} # {num => callback} 36 | end 37 | 38 | # Gets all registered Future objects. 39 | attr_reader :all 40 | 41 | # Gets Future objects which are not joined yet. 42 | attr_reader :not_joined 43 | 44 | # Gets Future objects which are already joined. 45 | attr_reader :joined 46 | 47 | # Gets Future objects which are joined as error. 48 | attr_reader :error 49 | 50 | # Gets Future objects which are joined as success. 51 | attr_reader :success 52 | 53 | # Clears all Future objects and all callback methods. 54 | def clear 55 | @all = [] 56 | 57 | @not_joined = [] 58 | @joined = [] 59 | @error = [] 60 | @success = [] 61 | 62 | clear_callback 63 | end 64 | 65 | # Clears all callback methods registered by on_xxx methods. 66 | def clear_callback 67 | @on_all = nil 68 | @on_error = nil 69 | @on_success = nil 70 | @on_num = {} 71 | self 72 | end 73 | 74 | # Registeres new Future object. 75 | # Returns self. 76 | def add(future) 77 | future.attach_callback(&method(:callback)) 78 | @all << future 79 | @not_joined << future 80 | self 81 | end 82 | 83 | # Attaches a callback method that is called when 84 | # all Future objects are joined. 85 | # Returns self. 86 | def on_all(&block) 87 | @on_all = block 88 | self 89 | end 90 | 91 | # Attaches a callback method that is called when 92 | # Future objects are joined as success. 93 | # Returns self. 94 | def on_success(&block) 95 | @on_success = block 96 | self 97 | end 98 | 99 | # Attaches a callback method that is called when 100 | # Future objects are joined as error. 101 | # Returns self. 102 | def on_error(&block) 103 | @on_error = block 104 | self 105 | end 106 | 107 | # Attaches a callback method that is called when 108 | # specified number of Future objects are joined. 109 | # Returns self. 110 | def on_num(n, &block) 111 | @on_num[n.to_i] = block 112 | self 113 | end 114 | 115 | # Waits until all Future objects join. 116 | def join_all 117 | @not_joined.each {|future| 118 | future.join 119 | } 120 | @all 121 | end 122 | 123 | # Waits until at least one Future object joins as success. 124 | # Returns the joined Future object or nil. 125 | def join_success 126 | until @not_joined.empty? 127 | unless @success.empty? 128 | break 129 | end 130 | @not_joined.first.loop.run_once 131 | end 132 | @success.last 133 | end 134 | 135 | # Waits until at least one Future object joins as error. 136 | # Returns the joined Future object or nil. 137 | def join_error 138 | until @not_joined.empty? 139 | unless @error.empty? 140 | break 141 | end 142 | @not_joined.first.loop.run_once 143 | end 144 | @error.last 145 | end 146 | 147 | # Waits until specified number of Future objects join. 148 | # Returns the joined Future objects or nil. 149 | def join_num(n) 150 | until @not_joined.empty? 151 | unless @joined.size >= n 152 | return @joined 153 | end 154 | @not_joined.first.loop.run_once 155 | end 156 | nil 157 | end 158 | 159 | private 160 | 161 | def callback(future) 162 | if @not_joined.delete(future) 163 | @joined << future 164 | 165 | if future.error == nil 166 | @success << future 167 | if @on_success 168 | @on_success.call(future) rescue nil 169 | end 170 | else 171 | @error << future 172 | if @on_error 173 | @on_error.call(future) rescue nil 174 | end 175 | end 176 | 177 | if callback = @on_num[@joined.size] 178 | callback.call(@joined) rescue nil 179 | end 180 | 181 | if @on_all && @not_joined.empty? 182 | @on_all.call(@all) rescue nil 183 | end 184 | end 185 | end 186 | end 187 | 188 | 189 | end 190 | end 191 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/server.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | # Server is usable for RPC server. 23 | # Note that Server is a SessionPool. 24 | class Server < SessionPool 25 | # 1. initialize(builder, loop = Loop.new) 26 | # 2. initialize(loop = Loop.new) 27 | def initialize(arg1=nil, arg2=nil) 28 | super(arg1, arg2) 29 | @dispatcher = nil 30 | @listeners = [] 31 | end 32 | 33 | # 1. serve(dispatcher) 34 | # 2. serve(obj, accept = obj.public_methods) 35 | def serve(obj, accept = obj.public_methods) 36 | if obj.is_a?(Dispatcher) 37 | # 2. 38 | @dispatcher = obj 39 | else 40 | # 1. 41 | @dispatcher = ObjectDispatcher.new(obj, accept) 42 | end 43 | self 44 | end 45 | 46 | # 1. listen(listener, obj = nil, accept = obj.public_methods) 47 | # 2. listen(host, port, obj = nil, accept = obj.public_methods) 48 | def listen(arg1, arg2 = nil, arg3 = nil, arg4 = nil) 49 | if arg1.respond_to?(:listen) 50 | # 1. 51 | listener = arg1 52 | obj = arg2 53 | accept = arg3 || obj.public_methods 54 | else 55 | # 2. 56 | listener = TCPServerTransport.new(Address.new(arg1,arg2)) 57 | obj = arg3 58 | accept = arg4 || obj.public_methods 59 | end 60 | 61 | unless obj.nil? 62 | serve(obj, accept) 63 | end 64 | 65 | listener.listen(self) 66 | @listeners.push(listener) 67 | nil 68 | end 69 | 70 | def close 71 | @listeners.reject! {|listener| 72 | listener.close 73 | true 74 | } 75 | super 76 | end 77 | 78 | # from ServerTransport 79 | def on_request(sendable, msgid, method, param) #:nodoc: 80 | responder = Responder.new(sendable, msgid) 81 | dispatch_method(method, param, responder) 82 | end 83 | 84 | # from ServerTransport 85 | def on_notify(method, param) #:nodoc: 86 | responder = NullResponder.new 87 | dispatch_method(method, param, responder) 88 | end 89 | 90 | private 91 | def dispatch_method(method, param, responder) #:nodoc: 92 | begin 93 | sent = false 94 | early_result = nil 95 | result = @dispatcher.dispatch(method, param) do |result_| 96 | unless result_.is_a?(AsyncResult) 97 | responder.result(result_) 98 | sent = true 99 | end 100 | early_result = result_ 101 | end 102 | 103 | #FIXME on NoMethodError 104 | # res.error(NO_METHOD_ERROR); return 105 | 106 | #FIXME on ArgumentError 107 | # res.error(ArgumentError); return 108 | 109 | rescue 110 | responder.error($!.to_s) 111 | return 112 | end 113 | 114 | if early_result.is_a?(AsyncResult) 115 | early_result.set_responder(responder) 116 | elsif sent 117 | return 118 | elsif result.is_a?(AsyncResult) 119 | result.set_responder(responder) 120 | else 121 | responder.result(result) 122 | end 123 | end 124 | end 125 | 126 | 127 | class AsyncResult 128 | def initialize 129 | @responder = nil 130 | @sent = false 131 | end 132 | 133 | def result(retval, err = nil) 134 | unless @sent 135 | if @responder 136 | @responder.result(retval, err) 137 | else 138 | @result = [retval, err] 139 | end 140 | @sent = true 141 | end 142 | nil 143 | end 144 | 145 | def error(err) 146 | result(nil, err) 147 | nil 148 | end 149 | 150 | def set_responder(res) #:nodoc: 151 | @responder = res 152 | if @sent && @result 153 | @responder.result(*@result) 154 | @result = nil 155 | end 156 | end 157 | end 158 | 159 | 160 | class Responder 161 | def initialize(sendable, msgid) 162 | @sendable = sendable # send_message method is required 163 | @msgid = msgid 164 | @sent = false 165 | end 166 | 167 | def sent? 168 | @sent 169 | end 170 | 171 | def result(retval, err = nil) 172 | unless @sent 173 | data = [RESPONSE, @msgid, err, retval].to_msgpack 174 | @sendable.send_data(data) 175 | @sent = true 176 | end 177 | nil 178 | end 179 | 180 | def error(err, retval = nil) 181 | result(retval, err) 182 | end 183 | end 184 | 185 | 186 | class NullResponder 187 | def sent? 188 | true 189 | end 190 | 191 | def result(retval, err = nil) 192 | nil 193 | end 194 | 195 | def error(err, retval = nil) 196 | nil 197 | end 198 | end 199 | 200 | 201 | #:nodoc: 202 | class Server::Base 203 | def initialize(*args) 204 | @base = Server.new(*args) 205 | @base.serve(self) 206 | end 207 | attr_reader :base 208 | end 209 | 210 | 211 | end 212 | end 213 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/session.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | # Session is a abstract class corresponds with a remote host. 23 | # You can call remote method using call, call_async, callback or notify method. 24 | class Session 25 | def initialize(builder, address, loop) 26 | @address = address 27 | @loop = loop 28 | @reqtable = {} 29 | @timeout = 10 # FIXME default timeout time 30 | @seqid = 0 31 | @transport = builder.build_transport(self, address) 32 | end 33 | attr_reader :loop, :address 34 | 35 | # Sets and gets timeout in seconds. 36 | attr_accessor :timeout 37 | 38 | # backward compatibility 39 | def port #:nodoc: 40 | @address.port 41 | end 42 | 43 | # backward compatibility 44 | def host #:nodoc: 45 | @address.host 46 | end 47 | 48 | # call-seq: 49 | # call(symbol, *args) -> result of remote method 50 | # 51 | # Calls remote method. 52 | # This method is same as call_async(method, *args).get 53 | def call(method, *args) 54 | send_request(method, args).get 55 | end 56 | 57 | # call-seq: 58 | # call_apply(symbol, params) -> result of remote method 59 | # 60 | # Calls remote method. 61 | # This method is same as call(method, *args) excepting that 62 | # the arugment is a array. 63 | def call_apply(method, params) 64 | send_request(method, params).get 65 | end 66 | 67 | # call-seq: 68 | # call_async(symbol, *args) -> Future 69 | # 70 | # Calls remote method asynchronously. 71 | # This method is non-blocking and returns Future. 72 | def call_async(method, *args) 73 | future = send_request(method, args) 74 | end 75 | 76 | # call-seq: 77 | # call_async_apply(symbol, params) -> Future 78 | # 79 | # Calls remote method asynchronously. 80 | # This method is same as call_async(method, *args) excepting that 81 | # the arugment is a array. 82 | def call_async_apply(method, params) 83 | future = send_request(method, params) 84 | end 85 | 86 | # backward compatibility 87 | alias_method :send_without_call_async, :send 88 | def send(method, *args) 89 | if caller.first =~ /.*_test.rb/ || caller.first =~ /.*_spec.rb/ then 90 | warn "\n Don't use send method. Use 'call_async' method." 91 | end 92 | call_async(method, *args) 93 | end 94 | 95 | 96 | # call-seq: 97 | # callback(symbol, *args) {|future| } 98 | # 99 | # Calls remote method asynchronously. 100 | # The callback method is called with Future when the result is reached. 101 | # This method is same as call_async(method, *args).attach_callback {|future| } 102 | def callback(method, *args, &block) 103 | future = send_request(method, args) 104 | future.attach_callback(block) 105 | future 106 | end 107 | 108 | # call-seq: 109 | # callback_apply(symbol, params) {|future| } 110 | # 111 | # Calls remote method asynchronously. 112 | # The callback method is called with Future when the result is reached. 113 | # This method is same as callback(method, *args).attach_callback {|future| } 114 | # excepting that the argument is a array. 115 | def callback_apply(method, params, &block) 116 | future = send_request(method, params) 117 | future.attach_callback(block) 118 | future 119 | end 120 | 121 | # call-seq: 122 | # notify(symbol, *args) -> nil 123 | # 124 | # Calls remote method with NOTIFY protocol. 125 | # It doesn't require server to return results. 126 | # This method is non-blocking and returns nil. 127 | def notify(method, *args) 128 | send_notify(method, args) 129 | nil 130 | end 131 | 132 | # call-seq: 133 | # notify_apply(symbol, params) -> nil 134 | # 135 | # Calls remote method with NOTIFY protocol. 136 | # It doesn't require server to return results. 137 | # This method is non-blocking and returns nil. 138 | # This method is same as notify(method, *args) excepting that 139 | # the argument is a array. 140 | def notify_apply(method, params) 141 | send_notify(method, params) 142 | nil 143 | end 144 | 145 | # Closes underlaying Transport and destroy resources. 146 | def close 147 | @transport.close 148 | @reqtable = {} 149 | @seqid = 0 150 | self 151 | end 152 | 153 | # from ClientTransport 154 | def on_response(sock, msgid, error, result) #:nodoc: 155 | if future = @reqtable.delete(msgid) 156 | future.set_result(error, result) 157 | end 158 | end 159 | 160 | # from ClientTransport 161 | def on_connect_failed #:nodoc: 162 | @reqtable.reject! {|msgid, future| 163 | future.set_result ConnectionTimeoutError::CODE, ["connection timed out"] 164 | true 165 | } 166 | nil 167 | end 168 | 169 | # from Client, SessionPool 170 | def step_timeout #:nodoc: 171 | timedout = [] 172 | @reqtable.reject! {|msgid, future| 173 | if future.step_timeout 174 | timedout.push(future) 175 | true 176 | end 177 | } 178 | timedout.each {|future| 179 | future.set_result TimeoutError::CODE, ["request timed out"] 180 | } 181 | !@reqtable.empty? 182 | end 183 | 184 | private 185 | def send_request(method, param) 186 | method = method.to_s 187 | msgid = @seqid 188 | @seqid += 1; if @seqid >= 1<<31 then @seqid = 0 end 189 | data = [REQUEST, msgid, method, param].to_msgpack 190 | @transport.send_data(data) 191 | @reqtable[msgid] = Future.new(self, @loop) 192 | end 193 | 194 | def send_notify(method, param) 195 | method = method.to_s 196 | data = [NOTIFY, method, param].to_msgpack 197 | @transport.send_data(data) 198 | nil 199 | end 200 | end 201 | 202 | 203 | end 204 | end 205 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/session_pool.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | # SessionPool is usable for connection pooling. 23 | # You can get pooled Session using get_session method. 24 | # Note that SessionPool includes LoopUtil. 25 | class SessionPool 26 | # 1. initialize(builder, loop = Loop.new) 27 | # 2. initialize(loop = Loop.new) 28 | # 29 | # Creates an SessionPool. 30 | def initialize(arg1=nil, arg2=nil) 31 | if arg1.respond_to?(:build_transport) 32 | # 1. 33 | builder = arg1 34 | loop = arg2 || Loop.new 35 | else 36 | # 2. 37 | builder = TCPTransport.new 38 | loop = arg1 || Loop.new 39 | end 40 | 41 | @builder = builder 42 | @loop = loop 43 | @pool = {} 44 | 45 | @timer = Timer.new(1, true, &method(:step_timeout)) 46 | loop.attach(@timer) 47 | end 48 | 49 | # 1. get_session(address) 50 | # 2. get_session(host, port) 51 | # 52 | # Returns pooled Session. 53 | # If there are no pooled Session for the specified address, 54 | # this method creates the Session and pools it. 55 | def get_session(arg1, arg2=nil) 56 | if arg2.nil? 57 | # 1. 58 | addr = arg1 59 | else 60 | # 2. 61 | host = arg1 62 | port = arg2 63 | addr = Address.new(host, port) 64 | end 65 | 66 | @pool[addr] ||= Session.new(@builder, addr, @loop) 67 | end 68 | 69 | # backward compatibility 70 | alias_method :get_session_addr,:get_session #:nodoc: 71 | 72 | def close 73 | @pool.reject! {|addr, s| 74 | s.close 75 | true 76 | } 77 | @timer.detach if @timer.attached? 78 | nil 79 | end 80 | 81 | include LoopUtil 82 | 83 | private 84 | def step_timeout 85 | @pool.each_pair {|addr,s| s.step_timeout } 86 | end 87 | end 88 | 89 | 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/transport/base.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | module MessageReceiver 23 | def on_message(msg, *ctx) 24 | case msg[0] 25 | when REQUEST 26 | on_request(msg[1], msg[2], msg[3], *ctx) 27 | when RESPONSE 28 | on_response(msg[1], msg[2], msg[3], *ctx) 29 | when NOTIFY 30 | on_notify(msg[1], msg[2], *ctx) 31 | else 32 | raise RPCError.new("unknown message type #{msg[0]}") 33 | end 34 | end 35 | 36 | #def on_request(msgid, method, param) 37 | #end 38 | 39 | #def on_notify(method, param) 40 | #end 41 | 42 | #def on_response(msgid, error, result) 43 | #end 44 | end 45 | 46 | 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/transport/tcp.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby TCP transport 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | class TCPTransport 23 | def initialize 24 | @reconnect_limit = 5 # FIXME default reconnect_limit 25 | end 26 | 27 | attr_accessor :reconnect_limit 28 | 29 | # Transport interface 30 | def build_transport(session, address) 31 | TCPClientTransport.new(session, address, @reconnect_limit) 32 | end 33 | 34 | class BasicSocket < Cool.io::TCPSocket 35 | def initialize(io) 36 | super(io) 37 | @pac = MessagePack::Unpacker.new 38 | end 39 | 40 | # from Cool.io::TCPSocket 41 | def on_readable 42 | super 43 | rescue 44 | # FIXME send Connection Close message 45 | # FIXME log 46 | close 47 | end 48 | 49 | # from Cool.io::TCPSocket 50 | def on_read(data) 51 | @pac.feed_each(data) {|obj| 52 | on_message(obj) 53 | } 54 | end 55 | 56 | include MessageReceiver 57 | end 58 | end 59 | 60 | 61 | class TCPClientTransport 62 | def initialize(session, address, reconnect_limit) 63 | @session = session 64 | @address = address 65 | 66 | @pending = "" 67 | @sockpool = [] 68 | @connecting = 0 69 | @reconnect_limit = reconnect_limit 70 | end 71 | 72 | # ClientTransport interface 73 | def send_data(data) 74 | if @sockpool.empty? 75 | if @connecting == 0 76 | try_connect 77 | @connecting = 1 78 | end 79 | @pending << data 80 | else 81 | # FIXME pesudo connection load balance 82 | # sock = @sockpool.choice 83 | sock = @sockpool.first 84 | sock.send_data(data) 85 | end 86 | end 87 | 88 | # ClientTransport interface 89 | def close 90 | @sockpool.reject! {|sock| 91 | sock.detach if sock.attached? 92 | sock.close 93 | true 94 | } 95 | @sockpool = [] 96 | @connecting = 0 97 | @pending = "" 98 | self 99 | end 100 | 101 | # from TCPClientTransport::ClientSocket::on_connect 102 | def on_connect(sock) 103 | @sockpool.push(sock) 104 | sock.send_pending(@pending) 105 | @pending = "" 106 | @connecting = 0 107 | end 108 | 109 | # from TCPClientTransport::ClientSocket::on_connect_failed 110 | def on_connect_failed(sock) 111 | if @connecting < @reconnect_limit 112 | try_connect 113 | @connecting += 1 114 | else 115 | @connecting = 0 116 | @pending = "" 117 | @session.on_connect_failed 118 | end 119 | end 120 | 121 | # from TCPClientTransport::ClientSocket::on_close 122 | def on_close(sock) 123 | @sockpool.delete(sock) 124 | end 125 | 126 | private 127 | def try_connect 128 | host, port = *@address 129 | sock = ClientSocket.connect(host, port, self, @session) # async connect 130 | @session.loop.attach(sock) 131 | end 132 | 133 | class ClientSocket < TCPTransport::BasicSocket 134 | def initialize(io, transport, session) 135 | super(io) 136 | @t = transport 137 | @s = session 138 | end 139 | 140 | # MessageSendable interface 141 | def send_data(data) 142 | write data 143 | end 144 | 145 | # from TCPClientTransport::on_connect 146 | def send_pending(data) 147 | write data 148 | end 149 | 150 | # MessageReceiver interface 151 | def on_request(msgid, method, param) 152 | raise Error.new("request message on client session") 153 | end 154 | 155 | # MessageReceiver interface 156 | def on_notify(method, param) 157 | raise Error.new("notify message on client session") 158 | end 159 | 160 | # MessageReceiver interface 161 | def on_response(msgid, error, result) 162 | @s.on_response(self, msgid, error, result) 163 | end 164 | 165 | # from Cool.io::TCPSocket 166 | def on_connect 167 | return unless @t 168 | @t.on_connect(self) 169 | end 170 | 171 | # from Cool.io::TCPSocket 172 | def on_connect_failed 173 | return unless @t 174 | @t.on_connect_failed(self) 175 | rescue 176 | nil 177 | end 178 | 179 | # from Cool.io::TCPSocket 180 | def on_close 181 | return unless @t 182 | @t.on_close(self) 183 | @t = nil 184 | @s = nil 185 | rescue 186 | nil 187 | end 188 | end 189 | end 190 | 191 | 192 | class TCPServerTransport 193 | def initialize(address) 194 | @address = address 195 | @lsock = nil 196 | end 197 | 198 | # ServerTransport interface 199 | def listen(server) 200 | @server = server 201 | host, port = *@address.unpack 202 | @lsock = Cool.io::TCPServer.new(host, port, ServerSocket, @server) 203 | begin 204 | @server.loop.attach(@lsock) 205 | rescue 206 | @lsock.close 207 | raise 208 | end 209 | end 210 | 211 | # ServerTransport interface 212 | def close 213 | return unless @lsock 214 | @lsock.detach if @lsock.attached? 215 | @lsock.close 216 | end 217 | 218 | private 219 | class ServerSocket < TCPTransport::BasicSocket 220 | def initialize(io, server) 221 | super(io) 222 | @server = server 223 | end 224 | 225 | # MessageSendable interface 226 | def send_data(data) 227 | write data 228 | end 229 | 230 | # MessageReceiver interface 231 | def on_request(msgid, method, param) 232 | @server.on_request(self, msgid, method, param) 233 | end 234 | 235 | # MessageReceiver interface 236 | def on_notify(method, param) 237 | @server.on_notify(method, param) 238 | end 239 | 240 | # MessageReceiver interface 241 | def on_response(msgid, error, result) 242 | raise Error.new("response message on server session") 243 | end 244 | end 245 | end 246 | 247 | 248 | end 249 | end 250 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/transport/udp.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby UDP transport 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | class UDPTransport 23 | def initialize 24 | end 25 | 26 | # Transport interface 27 | def build_transport(session, address) 28 | UDPClientTransport.new(session, address) 29 | end 30 | 31 | class BasicSocket < Cool.io::IOWatcher 32 | HAVE_DNRL = UDPSocket.public_instance_methods.include?(:do_not_reverse_lookup) 33 | 34 | def initialize(io) 35 | io.do_not_reverse_lookup = true if HAVE_DNRL 36 | super(io) 37 | @io = io 38 | end 39 | 40 | attr_reader :io 41 | 42 | def on_readable 43 | begin 44 | data, addr = @io.recvfrom(64*1024) # FIXME buffer size 45 | rescue Errno::EAGAIN 46 | return 47 | end 48 | 49 | # FIXME multiple objects in one message 50 | obj = MessagePack.unpack(data) 51 | on_message(obj, addr) 52 | rescue 53 | # FIXME log 54 | return 55 | end 56 | 57 | include MessageReceiver 58 | end 59 | end 60 | 61 | 62 | class UDPClientTransport 63 | def initialize(session, address) 64 | io = UDPSocket.new 65 | io.connect(*address) 66 | 67 | begin 68 | @sock = ClientSocket.new(io, session) 69 | rescue 70 | io.close 71 | raise 72 | end 73 | 74 | begin 75 | session.loop.attach(@sock) 76 | rescue 77 | @sock.close 78 | raise 79 | end 80 | end 81 | 82 | # ClientTransport interface 83 | def send_data(data) 84 | @sock.send_data(data) 85 | end 86 | 87 | # ClientTransport interface 88 | def close 89 | @sock.detach if @sock.attached? 90 | @sock.close 91 | end 92 | 93 | private 94 | class ClientSocket < UDPTransport::BasicSocket 95 | def initialize(io, session) 96 | super(io) 97 | @s = session 98 | end 99 | 100 | # MessageSendable interface 101 | def send_data(data) 102 | @io.send(data, 0) 103 | end 104 | 105 | # MessageReceiver interface 106 | def on_request(msgid, method, param, addr) 107 | raise Error.new("request message on client session") 108 | end 109 | 110 | # MessageReceiver interface 111 | def on_notify(method, param, addr) 112 | raise Error.new("notify message on client session") 113 | end 114 | 115 | # MessageReceiver interface 116 | def on_response(msgid, error, result, addr) 117 | @s.on_response(self, msgid, error, result) 118 | end 119 | end 120 | end 121 | 122 | 123 | class UDPServerTransport 124 | def initialize(address) 125 | @address = address 126 | @sock = nil 127 | end 128 | 129 | # ServerTransport interface 130 | def listen(server) 131 | @server = server 132 | host, port = *@address.unpack 133 | io = UDPSocket.new 134 | io.bind(*@address) 135 | 136 | begin 137 | @sock = ServerSocket.new(io, @server) 138 | rescue 139 | io.close 140 | raise 141 | end 142 | 143 | begin 144 | @server.loop.attach(@sock) 145 | rescue 146 | @sock.close 147 | raise 148 | end 149 | end 150 | 151 | # ServerTransport interface 152 | def close 153 | return unless @lsock 154 | @lsock.detach if @lsock.attached? 155 | @lsock.close 156 | end 157 | 158 | private 159 | class ServerSocket < UDPTransport::BasicSocket 160 | def initialize(io, server) 161 | super(io) 162 | @server = server 163 | end 164 | 165 | # MessageReceiver interface 166 | def on_request(msgid, method, param, addr) 167 | sender = ResponseSender.new(@io, addr[3], addr[1]) 168 | @server.on_request(sender, msgid, method, param) 169 | end 170 | 171 | # MessageReceiver interface 172 | def on_notify(method, param, addr) 173 | @server.on_notify(method, param) 174 | end 175 | 176 | # MessageReceiver interface 177 | def on_response(msgid, error, result, addr) 178 | raise Error.new("response message on server session") 179 | end 180 | end 181 | 182 | class ResponseSender 183 | def initialize(io, host, port) 184 | @io = io 185 | @host = host 186 | @port = port 187 | end 188 | 189 | # MessageSendable interface 190 | def send_data(data) 191 | @io.send(data, 0, @host, @port) 192 | end 193 | end 194 | end 195 | 196 | 197 | end 198 | end 199 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/transport/unix.rb: -------------------------------------------------------------------------------- 1 | # 2 | # MessagePack-RPC for Ruby UNIX transport 3 | # 4 | # Copyright (C) 2010-2011 FURUHASHI Sadayuki 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | module MessagePack 19 | module RPC 20 | 21 | 22 | class UNIXTransport 23 | def initialize 24 | end 25 | 26 | # Transport interface 27 | def build_transport(session, address) 28 | UNIXClientTransport.new(session, address) 29 | end 30 | 31 | class BasicSocket < Cool.io::UNIXSocket 32 | def initialize(io) 33 | super(io) 34 | @pac = MessagePack::Unpacker.new 35 | end 36 | 37 | # from Cool.io::TCPSocket 38 | def on_readable 39 | super 40 | rescue 41 | # FIXME send Connection Close message 42 | # FIXME log 43 | close 44 | end 45 | 46 | # from Cool.io::UNIXSocket 47 | def on_read(data) 48 | @pac.feed_each(data) {|obj| 49 | on_message(obj) 50 | } 51 | end 52 | 53 | include MessageReceiver 54 | end 55 | end 56 | 57 | 58 | class UNIXClientTransport 59 | def initialize(session, address) 60 | io = UNIXSocket.new(address) 61 | 62 | begin 63 | @sock = ClientSocket.new(io, session) 64 | rescue 65 | io.close 66 | raise 67 | end 68 | 69 | begin 70 | session.loop.attach(@sock) 71 | rescue 72 | @sock.close 73 | raise 74 | end 75 | end 76 | 77 | # ClientTransport interface 78 | def send_data(data) 79 | @sock.send_data(data) 80 | end 81 | 82 | # ClientTransport interface 83 | def close 84 | @sock.detach if @sock.attached? 85 | @sock.close 86 | end 87 | 88 | class ClientSocket < UNIXTransport::BasicSocket 89 | def initialize(io, session) 90 | super(io) 91 | @s = session 92 | end 93 | 94 | # MessageSendable interface 95 | def send_data(data) 96 | write data 97 | end 98 | 99 | # MessageReceiver interface 100 | def on_request(msgid, method, param) 101 | raise Error.new("request message on client session") 102 | end 103 | 104 | # MessageReceiver interface 105 | def on_notify(method, param) 106 | raise Error.new("notify message on client session") 107 | end 108 | 109 | # MessageReceiver interface 110 | def on_response(msgid, error, result) 111 | @s.on_response(self, msgid, error, result) 112 | end 113 | end 114 | end 115 | 116 | 117 | class UNIXServerTransport 118 | def initialize(address) 119 | @address = address 120 | @sock = nil 121 | end 122 | 123 | # ServerTransport interface 124 | def listen(server) 125 | @server = server 126 | @lsock = Cool.io::UNIXServer.new(@address, ServerSocket, @server) 127 | begin 128 | @server.loop.attach(@lsock) 129 | rescue 130 | @lsock.close 131 | raise 132 | end 133 | end 134 | 135 | # ServerTransport interface 136 | def close 137 | return unless @lsock 138 | @lsock.detach if @lsock.attached? 139 | @lsock.close 140 | end 141 | 142 | private 143 | class ServerSocket < UNIXTransport::BasicSocket 144 | def initialize(io, server) 145 | super(io) 146 | @server = server 147 | end 148 | 149 | # MessageSendable interface 150 | def send_data(data) 151 | write data 152 | end 153 | 154 | # MessageReceiver interface 155 | def on_request(msgid, method, param) 156 | @server.on_request(self, msgid, method, param) 157 | end 158 | 159 | # MessageReceiver interface 160 | def on_notify(method, param) 161 | @server.on_notify(method, param) 162 | end 163 | 164 | # MessageReceiver interface 165 | def on_response(msgid, error, result) 166 | raise Error.new("response message on server session") 167 | end 168 | end 169 | end 170 | 171 | 172 | end 173 | end 174 | -------------------------------------------------------------------------------- /lib/msgpack/rpc/version.rb: -------------------------------------------------------------------------------- 1 | module MessagePack 2 | module RPC 3 | 4 | VERSION = '0.7.0' 5 | 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /msgpack-rpc.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | require 'msgpack/rpc/version' 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "msgpack-rpc" 6 | s.version = MessagePack::RPC::VERSION 7 | s.authors = ["FURUHASHI Sadayuki", "Shuzo Kashihara"] 8 | s.email = ["frsyuki@users.sourceforge.jp", "suma@users.sourceforge.jp"] 9 | s.files = `git ls-files`.split("\n") 10 | s.test_files = s.files.grep(%r{^(test|spec|features)/}) 11 | s.license = "Apache 2.0" 12 | s.homepage = "http://msgpack.org/" 13 | s.require_paths = ["lib"] 14 | s.summary = "MessagePack-RPC, asynchronous RPC library using MessagePack" 15 | 16 | s.add_runtime_dependency "msgpack" 17 | s.add_runtime_dependency "cool.io", ["~> 1.7.0"] 18 | s.add_development_dependency "rake" 19 | s.add_development_dependency "rspec" 20 | s.add_development_dependency "test-unit" 21 | s.add_development_dependency 'bundler', ["~> 1.0"] 22 | end 23 | 24 | -------------------------------------------------------------------------------- /pkg/msgpack-rpc-0.6.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msgpack-rpc/msgpack-rpc-ruby/17d9b4a48f4749a4c2d18eda1baeb948342bb834/pkg/msgpack-rpc-0.6.0.gem -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'msgpack' 2 | require 'msgpack/rpc' 3 | 4 | RSpec.configure do |config| 5 | config.expect_with :rspec do |expectations| 6 | # This option will default to `true` in RSpec 4. It makes the `description` 7 | # and `failure_message` of custom matchers include text for helper methods 8 | # defined using `chain`, e.g.: 9 | # be_bigger_than(2).and_smaller_than(4).description 10 | # # => "be bigger than 2 and smaller than 4" 11 | # ...rather than: 12 | # # => "be bigger than 2" 13 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 14 | 15 | expectations.syntax = [:should, :expect] 16 | end 17 | 18 | config.mock_with :rspec do |mocks| 19 | # Prevents you from mocking or stubbing a method that does not exist on 20 | # a real object. This is generally recommended, and will default to 21 | # `true` in RSpec 4. 22 | mocks.verify_partial_doubles = true 23 | end 24 | 25 | config.filter_run :focus 26 | config.run_all_when_everything_filtered = true 27 | 28 | config.order = :random 29 | 30 | # config.disable_monkey_patching! 31 | # config.warnings = true 32 | end 33 | -------------------------------------------------------------------------------- /spec/unit/client_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative './my_server' 3 | 4 | include MyServerTest 5 | 6 | describe 'MessagePack::RPC::Client test' do 7 | before(:each)do 8 | @svr,@client = start_server 9 | end 10 | 11 | after(:each)do 12 | @svr.stop 13 | @client.close 14 | end 15 | 16 | 17 | it 'should return "ok" value ' do 18 | @client.call(:hello).should include("ok") 19 | end 20 | 21 | it 'should return "3" value ' do 22 | @client.call(:sum,1,2).should equal 3 23 | end 24 | 25 | it 'should return "ok" and "3" when you call with call_async' do 26 | req1 = @client.call_async(:hello) 27 | req2 = @client.call_async(:sum, 1, 2) 28 | req1.join 29 | req1.result.should include("ok") 30 | req1.error.should be_nil 31 | 32 | req2.join 33 | req2.result.should equal 3 34 | req2.error.should be_nil 35 | end 36 | 37 | it 'should return "ok" when you set callback(:hello)' do 38 | req = @client.callback(:hello) do |error, result| 39 | result.should include("ok") 40 | error.should be_nil 41 | end 42 | req.join 43 | end 44 | 45 | it 'should return "3" when you set callback(:sum)' do 46 | 47 | req = @client.callback(:sum, 1, 2) do |error, result| 48 | result.should equal 3 49 | error.should be_nil 50 | end 51 | req.join 52 | end 53 | 54 | it 'should return nil values when you call notify' do 55 | @client.call(:count).should eq 0 56 | @client.notify(:increase_count).should be_nil 57 | sleep 0.5 58 | @client.call(:count).should eq 1 59 | end 60 | 61 | it 'should return error when you call private method' do 62 | lambda{@client.call(:hidden)}.should raise_error(MessagePack::RPC::RemoteError) 63 | end 64 | 65 | it 'should be throw exception message when you call exception method' do 66 | lambda{@client.call(:exception)}.should raise_error(MessagePack::RPC::RemoteError,"raised") 67 | end 68 | 69 | it 'should be return "async" when you call with :async parameter' do 70 | @client.call(:async).should include("async") 71 | 72 | end 73 | 74 | 75 | it 'should throws exception when you call with async_exception' do 76 | lambda{@client.call(:async_exception)}.should raise_error(MessagePack::RPC::RemoteError,"async") 77 | end 78 | 79 | it 'should be returns correct values when you use MessagePack::RPC::SessionPool' do 80 | sp = MessagePack::RPC::SessionPool.new 81 | s = sp.get_session('127.0.0.1', @client.port) 82 | 83 | s.call(:hello).should include("ok") 84 | s.call(:sum,1,2).should equal 3 85 | 86 | sp.close 87 | 88 | end 89 | 90 | 91 | end 92 | 93 | describe "MessagePack::RPC::TimeoutError test" do 94 | 95 | before(:each)do 96 | @client = start_client(PortHelper.find_port) 97 | @lsock = TCPServer.new("0.0.0.0",@client.port) 98 | @client.timeout = 1 99 | end 100 | 101 | it 'should return MessagePack::RPC::TimoutError' do 102 | lambda{@client.call(:hello)}.should raise_error(MessagePack::RPC::TimeoutError) 103 | end 104 | 105 | after(:each)do 106 | @client.close 107 | @lsock.close 108 | end 109 | 110 | end 111 | 112 | describe "MessagePack::RPC::Loop testing" do 113 | before(:all) do 114 | @loop = MessagePack::RPC::Loop.new 115 | 116 | @svr = MessagePack::RPC::Server.new(@loop) 117 | @svr.listen("0.0.0.0", MyServer.port, MyServer.new(@svr)) 118 | 119 | @cli = MessagePack::RPC::Client.new("127.0.0.1", MyServer.port, @loop) 120 | @cli.timeout = 10 121 | 122 | end 123 | 124 | it "should return correct values when you use MessagePack::RPC::Loop" do 125 | @cli.callback(:hello) do |error, result| 126 | result.should include("ok") 127 | error.should be_nil 128 | end 129 | 130 | @cli.callback(:sum, 1, 2) do |error, result| 131 | result.should equal 3 132 | error.should be_nil 133 | end 134 | 135 | end 136 | 137 | 138 | after(:all) do 139 | @cli.close 140 | @svr.close 141 | end 142 | 143 | end 144 | 145 | -------------------------------------------------------------------------------- /spec/unit/my_server.rb: -------------------------------------------------------------------------------- 1 | 2 | class MyServer 3 | @port = 65500 4 | attr_accessor :port 5 | 6 | 7 | def initialize(svr) 8 | @svr = svr 9 | @count = 0 10 | end 11 | 12 | def hello 13 | "ok" 14 | end 15 | 16 | def sum(a, b) 17 | a + b 18 | end 19 | 20 | def count 21 | @count 22 | end 23 | 24 | def increase_count 25 | @count += 1 26 | end 27 | 28 | def exception 29 | raise "raised" 30 | end 31 | 32 | def async 33 | as = MessagePack::RPC::AsyncResult.new 34 | @svr.start_timer(1, false) do 35 | as.result "async" 36 | end 37 | as 38 | end 39 | 40 | def async_exception 41 | as = MessagePack::RPC::AsyncResult.new 42 | @svr.start_timer(1, false) do 43 | as.error "async" 44 | end 45 | as 46 | end 47 | 48 | def self.port 49 | @port 50 | end 51 | 52 | private 53 | def hidden 54 | 55 | end 56 | end 57 | 58 | 59 | 60 | 61 | module MyServerTest 62 | def start_client(port) 63 | cli = MessagePack::RPC::Client.new("127.0.0.1", port) 64 | cli.timeout = 10 65 | return cli 66 | end 67 | 68 | def start_server 69 | port = PortHelper.find_port 70 | svr = MessagePack::RPC::Server.new 71 | svr.listen("0.0.0.0", port, MyServer.new(svr)) 72 | Thread.start do 73 | svr.run 74 | svr.close 75 | end 76 | return svr, start_client(port) 77 | end 78 | 79 | def server_start_loop 80 | port = PortHelper.find_port 81 | 82 | loop = MessagePack::RPC::Loop.new 83 | svr =MessagePack::RPC::Server.new(loop) 84 | svr.listen("0.0.0.0", port ,MyServer.new(svr)) 85 | 86 | cli = MessagePack::RPC::Client.new("127.0.0.1", port, loop) 87 | cli.timeout = 10 88 | 89 | return svr,cli 90 | end 91 | end 92 | 93 | module PortHelper 94 | def self.find_port 95 | s = TCPServer.new('127.0.0.1', 0) 96 | port = s.addr[1] 97 | port 98 | ensure 99 | s.close 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /test/msgpack_rpc_test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__)) + '/test_helper.rb' 4 | 5 | 6 | $port = 65500 7 | 8 | class MessagePackRPCTest < Test::Unit::TestCase 9 | 10 | class MyServer 11 | def initialize(svr) 12 | @svr = svr 13 | end 14 | 15 | def hello 16 | "ok" 17 | end 18 | 19 | def sum(a, b) 20 | a + b 21 | end 22 | 23 | def exception 24 | raise "raised" 25 | end 26 | 27 | def async 28 | as = MessagePack::RPC::AsyncResult.new 29 | @svr.start_timer(1, false) do 30 | as.result "async" 31 | end 32 | as 33 | end 34 | 35 | def async_exception 36 | as = MessagePack::RPC::AsyncResult.new 37 | @svr.start_timer(1, false) do 38 | as.error "async" 39 | end 40 | as 41 | end 42 | 43 | private 44 | def hidden 45 | 46 | end 47 | end 48 | 49 | 50 | def next_port 51 | port = $port += 1 52 | end 53 | 54 | 55 | def test_listen 56 | port = next_port 57 | 58 | svr = MessagePack::RPC::Server.new 59 | svr.listen("0.0.0.0", port, MyServer.new(svr)) 60 | svr.close 61 | end 62 | 63 | 64 | def start_server 65 | port = next_port 66 | 67 | svr = MessagePack::RPC::Server.new 68 | svr.listen("0.0.0.0", port, MyServer.new(svr)) 69 | Thread.start do 70 | svr.run 71 | svr.close 72 | end 73 | 74 | cli = MessagePack::RPC::Client.new("127.0.0.1", port) 75 | cli.timeout = 10 76 | 77 | return svr, cli 78 | end 79 | 80 | 81 | def test_call 82 | svr, cli = start_server 83 | 84 | result = cli.call(:hello) 85 | assert_equal(result, "ok") 86 | 87 | result = cli.call(:sum, 1, 2) 88 | assert_equal(result, 3) 89 | 90 | cli.close 91 | svr.stop 92 | end 93 | 94 | 95 | def test_send 96 | svr, cli = start_server 97 | 98 | req1 = cli.call_async(:hello) 99 | req2 = cli.call_async(:sum, 1, 2) 100 | 101 | req1.join 102 | req2.join 103 | 104 | assert_equal(req1.result, "ok") 105 | assert_nil(req1.error) 106 | assert_equal(req2.result, 3) 107 | assert_nil(req2.error) 108 | 109 | cli.close 110 | svr.stop 111 | end 112 | 113 | 114 | def test_callback 115 | svr, cli = start_server 116 | 117 | count = 0 118 | 119 | cli.callback(:hello) do |error, result| 120 | assert_equal(result, "ok") 121 | assert_nil(error) 122 | count += 1 123 | end 124 | 125 | cli.callback(:sum, 1, 2) do |error, result| 126 | assert_equal(result, 3) 127 | assert_nil(error) 128 | count += 1 129 | end 130 | 131 | while count < 2 132 | cli.loop.run_once 133 | end 134 | 135 | cli.close 136 | svr.stop 137 | end 138 | 139 | 140 | def test_notify 141 | svr, cli = start_server 142 | 143 | cli.notify(:hello) 144 | cli.notify(:sum, 1, 2) 145 | 146 | cli.close 147 | end 148 | 149 | 150 | def test_hidden 151 | svr, cli = start_server 152 | 153 | count = 0 154 | 155 | rejected = false 156 | begin 157 | cli.call(:hidden) 158 | rescue MessagePack::RPC::RemoteError 159 | rejected = true 160 | end 161 | 162 | assert_equal(rejected, true) 163 | 164 | cli.close 165 | svr.stop 166 | end 167 | 168 | 169 | def test_exception 170 | svr, cli = start_server 171 | 172 | raised = false 173 | begin 174 | cli.call(:exception) 175 | rescue MessagePack::RPC::RemoteError 176 | assert_equal($!.message, "raised") 177 | raised = true 178 | end 179 | 180 | assert_equal(raised, true) 181 | 182 | cli.close 183 | svr.stop 184 | end 185 | 186 | 187 | def test_async 188 | svr, cli = start_server 189 | 190 | result = cli.call(:async) 191 | assert_equal(result, "async") 192 | 193 | cli.close 194 | svr.stop 195 | end 196 | 197 | 198 | def test_async_exception 199 | svr, cli = start_server 200 | 201 | raised = false 202 | begin 203 | cli.call(:async_exception) 204 | rescue MessagePack::RPC::RemoteError 205 | assert_equal($!.message, "async") 206 | raised = true 207 | end 208 | 209 | assert_equal(raised, true) 210 | 211 | cli.close 212 | svr.stop 213 | end 214 | 215 | 216 | def test_pool 217 | svr, cli = start_server 218 | 219 | sp = MessagePack::RPC::SessionPool.new 220 | s = sp.get_session('127.0.0.1', cli.port) 221 | 222 | result = s.call(:hello) 223 | assert_equal(result, "ok") 224 | 225 | result = s.call(:sum, 1, 2) 226 | assert_equal(result, 3) 227 | 228 | sp.close 229 | cli.close 230 | svr.stop 231 | end 232 | 233 | 234 | def test_loop 235 | port = next_port 236 | 237 | loop = MessagePack::RPC::Loop.new 238 | 239 | svr = MessagePack::RPC::Server.new(loop) 240 | svr.listen("0.0.0.0", port, MyServer.new(svr)) 241 | 242 | cli = MessagePack::RPC::Client.new("127.0.0.1", port, loop) 243 | cli.timeout = 10 244 | 245 | count = 0 246 | 247 | cli.callback(:hello) do |error, result| 248 | assert_equal(result, "ok") 249 | assert_nil(error) 250 | count += 1 251 | end 252 | 253 | cli.callback(:sum, 1, 2) do |error, result| 254 | assert_equal(result, 3) 255 | assert_nil(error) 256 | count += 1 257 | end 258 | 259 | while count < 2 260 | loop.run_once 261 | end 262 | 263 | cli.close 264 | svr.close 265 | end 266 | 267 | 268 | def test_timeout 269 | port = next_port 270 | 271 | lsock = TCPServer.new("0.0.0.0", port) 272 | 273 | cli = MessagePack::RPC::Client.new("127.0.0.1", port) 274 | cli.timeout = 1 275 | 276 | timeout = false 277 | begin 278 | cli.call(:hello) 279 | rescue MessagePack::RPC::TimeoutError 280 | timeout = true 281 | end 282 | 283 | assert_equal(timeout, true) 284 | 285 | cli.close 286 | lsock.close 287 | end 288 | 289 | def test_address 290 | addr = MessagePack::RPC::Address.new('172.16.0.11', 18900) 291 | raw = '' 292 | addr.to_msgpack(raw) 293 | msg = MessagePack.unpack(raw) 294 | addr2 = MessagePack::RPC::Address.load(msg) 295 | assert_equal(addr, addr2) 296 | end 297 | end 298 | 299 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | begin 5 | require 'rubygems' 6 | rescue LoadError 7 | end 8 | require 'test/unit' 9 | $LOAD_PATH.unshift File.dirname(__FILE__)+'/../lib' 10 | require 'msgpack/rpc' 11 | --------------------------------------------------------------------------------