fd-bertrpc 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/History.txt +33 -0
- data/LICENSE +20 -0
- data/README.md +71 -0
- data/Rakefile +60 -0
- data/VERSION +1 -0
- data/bertrpc.gemspec +70 -0
- data/lib/bertrpc.rb +20 -0
- data/lib/bertrpc/action.rb +113 -0
- data/lib/bertrpc/encodes.rb +38 -0
- data/lib/bertrpc/errors.rb +76 -0
- data/lib/bertrpc/mod.rb +14 -0
- data/lib/bertrpc/request.rb +15 -0
- data/lib/bertrpc/service.rb +40 -0
- data/test/action_test.rb +126 -0
- data/test/encodes_test.rb +58 -0
- data/test/error_test.rb +33 -0
- data/test/mod_test.rb +32 -0
- data/test/request_test.rb +24 -0
- data/test/service_test.rb +47 -0
- data/test/test_helper.rb +12 -0
- metadata +110 -0
data/.document
ADDED
data/History.txt
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
= 1.3.0 / 2010-02-24
|
2
|
+
* Enhancements
|
3
|
+
* Raise BERTRPC::ReadTimeout if remote connection is closed
|
4
|
+
|
5
|
+
= 1.2.1 / 2010-02-10
|
6
|
+
* Bug fixes
|
7
|
+
* Restrict to IPv4
|
8
|
+
|
9
|
+
= 1.2.0 / 2010-02-09
|
10
|
+
* Enhancements
|
11
|
+
* Better timeouts courtesy of select(2)
|
12
|
+
* BERTRPCError gives self for original_exception if nil
|
13
|
+
|
14
|
+
= 1.1.2 / 2009-11-27
|
15
|
+
* Minor Changes
|
16
|
+
* Add useful debugging information to ReadTimeoutError
|
17
|
+
* Switch to using raw socket timeouts over buffered io (Linux only)
|
18
|
+
* Add BERTRPC::VERSION and BERTRPC.version
|
19
|
+
|
20
|
+
= 1.1.1 / 2009-10-28
|
21
|
+
Major Changes
|
22
|
+
* Require bert-1.1.0 or greater
|
23
|
+
* Remove dependency on Erlectricity
|
24
|
+
|
25
|
+
= 1.1.0 / 2009-10-27
|
26
|
+
* Add read socket timeout
|
27
|
+
|
28
|
+
= 1.0.0 / 2009-10-19
|
29
|
+
* No changes. Production ready!
|
30
|
+
|
31
|
+
= 0.4.0 / 2009-10-08
|
32
|
+
* Major changes
|
33
|
+
* Convert to use BERT gem.
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Tom Preston-Werner
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
BERTRPC
|
2
|
+
=======
|
3
|
+
|
4
|
+
By Tom Preston-Werner (tom@mojombo.com)
|
5
|
+
|
6
|
+
BERT-RPC client library for Ruby. Makes it ridiculously simple to interface
|
7
|
+
with BERT-RPC servers.
|
8
|
+
|
9
|
+
See the full BERT-RPC specification at [bert-rpc.org](http://bert-rpc.org).
|
10
|
+
|
11
|
+
This library currently only supports the following BERT-RPC features:
|
12
|
+
|
13
|
+
* `call` requests
|
14
|
+
* `cast` requests
|
15
|
+
|
16
|
+
BERTRPC was developed for GitHub and is currently in production use performing
|
17
|
+
millions of RPC requests every day. The stability and performance have been
|
18
|
+
exemplary.
|
19
|
+
|
20
|
+
|
21
|
+
Installation
|
22
|
+
------------
|
23
|
+
|
24
|
+
$ gem install bertrpc -s http://gemcutter.org
|
25
|
+
|
26
|
+
|
27
|
+
Examples
|
28
|
+
--------
|
29
|
+
|
30
|
+
Require the library and create a service:
|
31
|
+
|
32
|
+
require 'bertrpc'
|
33
|
+
svc = BERTRPC::Service.new('localhost', 9999)
|
34
|
+
|
35
|
+
Make a call:
|
36
|
+
|
37
|
+
svc.call.calc.add(1, 2)
|
38
|
+
# => 3
|
39
|
+
|
40
|
+
The underlying BERT-RPC transaction of the above call is:
|
41
|
+
|
42
|
+
-> {call, calc, add, [1, 2]}
|
43
|
+
<- {reply, 3}
|
44
|
+
|
45
|
+
Make a cast:
|
46
|
+
|
47
|
+
svc.cast.stats.incr
|
48
|
+
# => nil
|
49
|
+
|
50
|
+
The underlying BERT-RPC transaction of the above cast is:
|
51
|
+
|
52
|
+
-> {cast, stats, incr, []}
|
53
|
+
<- {noreply}
|
54
|
+
|
55
|
+
|
56
|
+
Documentation
|
57
|
+
-------------
|
58
|
+
|
59
|
+
Creating a service:
|
60
|
+
|
61
|
+
# No timeout
|
62
|
+
svc = BERTRPC::Service.new('localhost', 9999)
|
63
|
+
|
64
|
+
# 10s socket read timeout, raises BERTRPC::ReadTimeoutError
|
65
|
+
svc = BERTRPC::Service.new('localhost', 9999, 10)
|
66
|
+
|
67
|
+
|
68
|
+
Copyright
|
69
|
+
---------
|
70
|
+
|
71
|
+
Copyright (c) 2009 Tom Preston-Werner. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "fd-bertrpc"
|
8
|
+
gem.summary = %Q{BERTRPC is a Ruby BERT-RPC client library.}
|
9
|
+
gem.email = "tom@mojombo.com"
|
10
|
+
gem.homepage = "http://github.com/mojombo/bertrpc"
|
11
|
+
gem.authors = ["Tom Preston-Werner"]
|
12
|
+
gem.add_dependency('bert', '>= 1.1.0', '< 2.0.0')
|
13
|
+
# gem is a Gem::Specification...
|
14
|
+
# see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/*_test.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/*_test.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :default => :test
|
42
|
+
|
43
|
+
require 'rake/rdoctask'
|
44
|
+
Rake::RDocTask.new do |rdoc|
|
45
|
+
if File.exist?('VERSION.yml')
|
46
|
+
config = YAML.load(File.read('VERSION.yml'))
|
47
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
48
|
+
else
|
49
|
+
version = ""
|
50
|
+
end
|
51
|
+
|
52
|
+
rdoc.rdoc_dir = 'rdoc'
|
53
|
+
rdoc.title = "bertrpc #{version}"
|
54
|
+
rdoc.rdoc_files.include('README*')
|
55
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
56
|
+
end
|
57
|
+
|
58
|
+
task :console do
|
59
|
+
exec('irb -Ilib -rbertrpc')
|
60
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.3.0
|
data/bertrpc.gemspec
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{fd-bertrpc}
|
8
|
+
s.version = "1.3.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Tom Preston-Werner"]
|
12
|
+
s.date = %q{2010-02-24}
|
13
|
+
s.email = %q{tom@mojombo.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE",
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".document",
|
20
|
+
".gitignore",
|
21
|
+
"History.txt",
|
22
|
+
"LICENSE",
|
23
|
+
"README.md",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"bertrpc.gemspec",
|
27
|
+
"lib/bertrpc.rb",
|
28
|
+
"lib/bertrpc/action.rb",
|
29
|
+
"lib/bertrpc/encodes.rb",
|
30
|
+
"lib/bertrpc/errors.rb",
|
31
|
+
"lib/bertrpc/mod.rb",
|
32
|
+
"lib/bertrpc/request.rb",
|
33
|
+
"lib/bertrpc/service.rb",
|
34
|
+
"test/action_test.rb",
|
35
|
+
"test/encodes_test.rb",
|
36
|
+
"test/error_test.rb",
|
37
|
+
"test/mod_test.rb",
|
38
|
+
"test/request_test.rb",
|
39
|
+
"test/service_test.rb",
|
40
|
+
"test/test_helper.rb"
|
41
|
+
]
|
42
|
+
s.homepage = %q{http://github.com/mojombo/bertrpc}
|
43
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
44
|
+
s.require_paths = ["lib"]
|
45
|
+
s.rubygems_version = %q{1.3.5}
|
46
|
+
s.summary = %q{BERTRPC is a Ruby BERT-RPC client library.}
|
47
|
+
s.test_files = [
|
48
|
+
"test/action_test.rb",
|
49
|
+
"test/encodes_test.rb",
|
50
|
+
"test/error_test.rb",
|
51
|
+
"test/mod_test.rb",
|
52
|
+
"test/request_test.rb",
|
53
|
+
"test/service_test.rb",
|
54
|
+
"test/test_helper.rb"
|
55
|
+
]
|
56
|
+
|
57
|
+
if s.respond_to? :specification_version then
|
58
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
59
|
+
s.specification_version = 3
|
60
|
+
|
61
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
62
|
+
s.add_runtime_dependency(%q<bert>, [">= 1.1.0", "< 2.0.0"])
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<bert>, [">= 1.1.0", "< 2.0.0"])
|
65
|
+
end
|
66
|
+
else
|
67
|
+
s.add_dependency(%q<bert>, [">= 1.1.0", "< 2.0.0"])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
data/lib/bertrpc.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'bert'
|
2
|
+
require 'socket'
|
3
|
+
require 'net/protocol'
|
4
|
+
|
5
|
+
require 'bertrpc/service'
|
6
|
+
require 'bertrpc/request'
|
7
|
+
require 'bertrpc/mod'
|
8
|
+
require 'bertrpc/encodes'
|
9
|
+
require 'bertrpc/action'
|
10
|
+
require 'bertrpc/errors'
|
11
|
+
|
12
|
+
module BERTRPC
|
13
|
+
def self.version
|
14
|
+
File.read(File.join(File.dirname(__FILE__), *%w[.. VERSION])).chomp
|
15
|
+
rescue
|
16
|
+
'unknown'
|
17
|
+
end
|
18
|
+
|
19
|
+
VERSION = self.version
|
20
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module BERTRPC
|
2
|
+
class Action
|
3
|
+
include Encodes
|
4
|
+
|
5
|
+
def initialize(svc, req, mod, fun, args)
|
6
|
+
@svc = svc
|
7
|
+
@req = req
|
8
|
+
@mod = mod
|
9
|
+
@fun = fun
|
10
|
+
@args = args
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute
|
14
|
+
bert_request = encode_ruby_request(t[@req.kind, @mod, @fun, @args])
|
15
|
+
bert_response = transaction(bert_request)
|
16
|
+
decode_bert_response(bert_response)
|
17
|
+
end
|
18
|
+
|
19
|
+
#private
|
20
|
+
|
21
|
+
def write(sock, bert)
|
22
|
+
sock.write([bert.length].pack("N"))
|
23
|
+
sock.write(bert)
|
24
|
+
end
|
25
|
+
|
26
|
+
def read(sock, len, timeout)
|
27
|
+
data, size = [], 0
|
28
|
+
while size < len
|
29
|
+
r, w, e = IO.select([sock], [], [], timeout)
|
30
|
+
raise Errno::EAGAIN if r.nil?
|
31
|
+
msg, sender = sock.recvfrom(len - size)
|
32
|
+
raise Errno::ECONNRESET if msg.size == 0
|
33
|
+
size += msg.size
|
34
|
+
data << msg
|
35
|
+
end
|
36
|
+
data.join ''
|
37
|
+
end
|
38
|
+
|
39
|
+
def transaction(bert_request)
|
40
|
+
timeout = @svc.timeout && Float(@svc.timeout)
|
41
|
+
sock = connect_to(@svc.host, @svc.port, timeout)
|
42
|
+
|
43
|
+
if @req.options
|
44
|
+
if @req.options[:cache] && @req.options[:cache][0] == :validation
|
45
|
+
token = @req.options[:cache][1]
|
46
|
+
info_bert = encode_ruby_request(t[:info, :cache, [:validation, token]])
|
47
|
+
write(sock, info_bert)
|
48
|
+
end
|
49
|
+
|
50
|
+
if @req.options[:stream]
|
51
|
+
info_bert = encode_ruby_request(t[:info, :stream, []])
|
52
|
+
write(sock, info_bert)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
write(sock, bert_request)
|
57
|
+
|
58
|
+
case (@req.options||{})[:stream]
|
59
|
+
when IO then
|
60
|
+
while blob = stream.read(1024)
|
61
|
+
write(sock, blob)
|
62
|
+
end
|
63
|
+
write(sock, "")
|
64
|
+
when String
|
65
|
+
File.open(stream) do |f|
|
66
|
+
while blob = f.read(1024)
|
67
|
+
write(sock, blob)
|
68
|
+
end
|
69
|
+
write(sock, "")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
lenheader = read(sock, 4, timeout)
|
74
|
+
raise ProtocolError.new(ProtocolError::NO_HEADER) unless lenheader
|
75
|
+
len = lenheader.unpack('N').first
|
76
|
+
bert_response = read(sock, len, timeout)
|
77
|
+
raise ProtocolError.new(ProtocolError::NO_DATA) unless bert_response
|
78
|
+
sock.close
|
79
|
+
bert_response
|
80
|
+
rescue Errno::ECONNREFUSED
|
81
|
+
raise ConnectionError.new(@svc.host, @svc.port)
|
82
|
+
rescue Errno::EAGAIN
|
83
|
+
raise ReadTimeoutError.new(@svc.host, @svc.port, @svc.timeout)
|
84
|
+
rescue Errno::ECONNRESET
|
85
|
+
raise ReadError.new(@svc.host, @svc.port)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Creates a socket object which does speedy, non-blocking reads
|
89
|
+
# and can perform reliable read timeouts.
|
90
|
+
#
|
91
|
+
# Raises Timeout::Error on timeout.
|
92
|
+
#
|
93
|
+
# +host+ String address of the target TCP server
|
94
|
+
# +port+ Integer port of the target TCP server
|
95
|
+
# +timeout+ Optional Integer (in seconds) of the read timeout
|
96
|
+
def connect_to(host, port, timeout = nil)
|
97
|
+
addr = Socket.getaddrinfo(host, nil, Socket::AF_INET)
|
98
|
+
sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
99
|
+
sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
100
|
+
|
101
|
+
if timeout
|
102
|
+
secs = Integer(timeout)
|
103
|
+
usecs = Integer((timeout - secs) * 1_000_000)
|
104
|
+
optval = [secs, usecs].pack("l_2")
|
105
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
106
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
107
|
+
end
|
108
|
+
|
109
|
+
sock.connect(Socket.pack_sockaddr_in(port, addr[0][3]))
|
110
|
+
sock
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module BERTRPC
|
2
|
+
module Encodes
|
3
|
+
def encode_ruby_request(ruby_request)
|
4
|
+
BERT.encode(ruby_request)
|
5
|
+
end
|
6
|
+
|
7
|
+
def decode_bert_response(bert_response)
|
8
|
+
ruby_response = BERT.decode(bert_response)
|
9
|
+
case ruby_response[0]
|
10
|
+
when :reply
|
11
|
+
ruby_response[1]
|
12
|
+
when :noreply
|
13
|
+
nil
|
14
|
+
when :error
|
15
|
+
error(ruby_response[1])
|
16
|
+
else
|
17
|
+
raise
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def error(err)
|
22
|
+
level, code, klass, message, backtrace = err
|
23
|
+
|
24
|
+
case level
|
25
|
+
when :protocol
|
26
|
+
raise ProtocolError.new([code, message], klass, backtrace)
|
27
|
+
when :server
|
28
|
+
raise ServerError.new([code, message], klass, backtrace)
|
29
|
+
when :user
|
30
|
+
raise UserError.new([code, message], klass, backtrace)
|
31
|
+
when :proxy
|
32
|
+
raise ProxyError.new([code, message], klass, backtrace)
|
33
|
+
else
|
34
|
+
raise
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module BERTRPC
|
2
|
+
class BERTRPCError < StandardError
|
3
|
+
attr_accessor :code, :original_exception
|
4
|
+
|
5
|
+
def initialize(msg = nil, klass = nil, bt = [])
|
6
|
+
case msg
|
7
|
+
when Array
|
8
|
+
code, message = msg
|
9
|
+
else
|
10
|
+
code, message = [0, msg]
|
11
|
+
end
|
12
|
+
|
13
|
+
if klass
|
14
|
+
self.original_exception = RemoteError.new("#{klass}: #{message}")
|
15
|
+
self.original_exception.set_backtrace(bt)
|
16
|
+
else
|
17
|
+
self.original_exception = self
|
18
|
+
end
|
19
|
+
|
20
|
+
self.code = code
|
21
|
+
super(message)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class RemoteError < StandardError
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
class ConnectionError < BERTRPCError
|
30
|
+
attr_reader :host, :port
|
31
|
+
def initialize(host, port)
|
32
|
+
@host, @port = host, port
|
33
|
+
super("Unable to connect to #{host}:#{port}")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Raised when we don't get a response from a server in a timely
|
38
|
+
# manner. This typically occurs in spite of a successful connection.
|
39
|
+
class ReadTimeoutError < BERTRPCError
|
40
|
+
attr_reader :host, :port, :timeout
|
41
|
+
def initialize(host, port, timeout)
|
42
|
+
@host, @port, @timeout = host, port, timeout
|
43
|
+
super("No response from #{host}:#{port} in #{timeout}s")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Raised when unexpected EOF is reached on the socket.
|
48
|
+
class ReadError < BERTRPCError
|
49
|
+
attr_reader :host, :port
|
50
|
+
def initialize(host, port)
|
51
|
+
@host, @port = host, port
|
52
|
+
super("Unable to read from #{host}:#{port}")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class ProtocolError < BERTRPCError
|
57
|
+
NO_HEADER = [0, "Unable to read length header from server."]
|
58
|
+
NO_DATA = [1, "Unable to read data from server."]
|
59
|
+
end
|
60
|
+
|
61
|
+
class ServerError < BERTRPCError
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
class UserError < BERTRPCError
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
class ProxyError < BERTRPCError
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
class InvalidOption < BERTRPCError
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
data/lib/bertrpc/mod.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module BERTRPC
|
2
|
+
class Service
|
3
|
+
attr_accessor :host, :port, :timeout
|
4
|
+
|
5
|
+
def initialize(host, port, timeout = nil)
|
6
|
+
@host = host
|
7
|
+
@port = port
|
8
|
+
@timeout = timeout
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(options = nil)
|
12
|
+
verify_options(options)
|
13
|
+
Request.new(self, :call, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def cast(options = nil)
|
17
|
+
verify_options(options)
|
18
|
+
Request.new(self, :cast, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
# private
|
22
|
+
|
23
|
+
def verify_options(options)
|
24
|
+
(options||{}).each do |key, value|
|
25
|
+
case key
|
26
|
+
when :stream
|
27
|
+
unless (String === value and File.file?(value))
|
28
|
+
raise InvalidOption.new("Valid :stream args are IO or path")
|
29
|
+
end
|
30
|
+
when :case
|
31
|
+
unless value[0] == :validation && value[1].is_a?(String)
|
32
|
+
raise InvalidOption.new("Valid :cache args are [:validation, String]")
|
33
|
+
end
|
34
|
+
else
|
35
|
+
raise InvalidOption.new("Valid options are :cache and :stream")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/test/action_test.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ActionTest < Test::Unit::TestCase
|
4
|
+
context "An Action" do
|
5
|
+
setup do
|
6
|
+
@svc = BERTRPC::Service.new('localhost', 9941)
|
7
|
+
@req = @svc.call
|
8
|
+
end
|
9
|
+
|
10
|
+
should "be created with a Service, module name, fun name, and args" do
|
11
|
+
assert BERTRPC::Action.new(@svc, @req, :mymod, :myfun, [1, 2]).is_a?(BERTRPC::Action)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "An Action instance" do
|
16
|
+
setup do
|
17
|
+
@svc = BERTRPC::Service.new('localhost', 9941)
|
18
|
+
@req = @svc.call
|
19
|
+
@enc = Enc.new
|
20
|
+
end
|
21
|
+
|
22
|
+
should "call with single-arity" do
|
23
|
+
req = @enc.encode_ruby_request(t[:call, :mymod, :myfun, [1]])
|
24
|
+
res = @enc.encode_ruby_request(t[:reply, 2])
|
25
|
+
call = BERTRPC::Action.new(@svc, @req, :mymod, :myfun, [1])
|
26
|
+
call.expects(:transaction).with(req).returns(res)
|
27
|
+
assert_equal 2, call.execute
|
28
|
+
end
|
29
|
+
|
30
|
+
should "call with single-arity array" do
|
31
|
+
req = @enc.encode_ruby_request(t[:call, :mymod, :myfun, [[1, 2, 3]]])
|
32
|
+
res = @enc.encode_ruby_request(t[:reply, [4, 5, 6]])
|
33
|
+
call = BERTRPC::Action.new(@svc, @req, :mymod, :myfun, [[1, 2, 3]])
|
34
|
+
call.expects(:transaction).with(req).returns(res)
|
35
|
+
assert_equal [4, 5, 6], call.execute
|
36
|
+
end
|
37
|
+
|
38
|
+
should "call with multi-arity" do
|
39
|
+
req = @enc.encode_ruby_request(t[:call, :mymod, :myfun, [1, 2, 3]])
|
40
|
+
res = @enc.encode_ruby_request(t[:reply, [4, 5, 6]])
|
41
|
+
call = BERTRPC::Action.new(@svc, @req, :mymod, :myfun, [1, 2, 3])
|
42
|
+
call.expects(:transaction).with(req).returns(res)
|
43
|
+
assert_equal [4, 5, 6], call.execute
|
44
|
+
end
|
45
|
+
|
46
|
+
context "sync_request" do
|
47
|
+
setup do
|
48
|
+
@svc = BERTRPC::Service.new('localhost', 9941)
|
49
|
+
@req = @svc.call
|
50
|
+
@call = BERTRPC::Action.new(@svc, @req, :mymod, :myfun, [])
|
51
|
+
end
|
52
|
+
|
53
|
+
should "read and write BERT-Ps from the socket" do
|
54
|
+
io = stub()
|
55
|
+
io.expects(:write).with("\000\000\000\003")
|
56
|
+
io.expects(:write).with("foo")
|
57
|
+
@call.expects(:read).with(io, 4, nil).returns("\000\000\000\003")
|
58
|
+
@call.expects(:read).with(io, 3, nil).returns("bar")
|
59
|
+
io.expects(:close)
|
60
|
+
@call.expects(:connect_to).returns(io)
|
61
|
+
assert_equal "bar", @call.transaction("foo")
|
62
|
+
end
|
63
|
+
|
64
|
+
should "raise a ProtocolError when the length is invalid" do
|
65
|
+
io = stub()
|
66
|
+
io.expects(:write).with("\000\000\000\003")
|
67
|
+
io.expects(:write).with("foo")
|
68
|
+
@call.expects(:read).with(io, 4, nil).returns(nil)
|
69
|
+
@call.expects(:connect_to).returns(io)
|
70
|
+
begin
|
71
|
+
@call.transaction("foo")
|
72
|
+
fail "Should have thrown an error"
|
73
|
+
rescue BERTRPC::ProtocolError => e
|
74
|
+
assert_equal 0, e.code
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
should "raise a ProtocolError when the data is invalid" do
|
79
|
+
io = stub()
|
80
|
+
io.expects(:write).with("\000\000\000\003")
|
81
|
+
io.expects(:write).with("foo")
|
82
|
+
@call.expects(:read).with(io, 4, nil).returns("\000\000\000\003")
|
83
|
+
@call.expects(:read).with(io, 3, nil).returns(nil)
|
84
|
+
@call.expects(:connect_to).returns(io)
|
85
|
+
begin
|
86
|
+
@call.transaction("foo")
|
87
|
+
fail "Should have thrown an error"
|
88
|
+
rescue BERTRPC::ProtocolError => e
|
89
|
+
assert_equal 1, e.code
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
should "raise a ReadTimeoutError when the connection times out" do
|
94
|
+
io = stub()
|
95
|
+
io.expects(:write).with("\000\000\000\003")
|
96
|
+
io.expects(:write).with("foo")
|
97
|
+
@call.expects(:read).with(io, 4, nil).raises(Errno::EAGAIN)
|
98
|
+
@call.expects(:connect_to).returns(io)
|
99
|
+
begin
|
100
|
+
@call.transaction("foo")
|
101
|
+
fail "Should have thrown an error"
|
102
|
+
rescue BERTRPC::ReadTimeoutError => e
|
103
|
+
assert_equal 0, e.code
|
104
|
+
assert_equal 'localhost', e.host
|
105
|
+
assert_equal 9941, e.port
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
should "raise a ReadError when the socket becomes unreadable" do
|
110
|
+
io = stub()
|
111
|
+
io.expects(:write).with("\000\000\000\003")
|
112
|
+
io.expects(:write).with("foo")
|
113
|
+
@call.expects(:read).with(io, 4, nil).raises(Errno::ECONNRESET)
|
114
|
+
@call.expects(:connect_to).returns(io)
|
115
|
+
begin
|
116
|
+
@call.transaction("foo")
|
117
|
+
fail "Should have thrown an error"
|
118
|
+
rescue BERTRPC::ReadError => e
|
119
|
+
assert_equal 0, e.code
|
120
|
+
assert_equal 'localhost', e.host
|
121
|
+
assert_equal 9941, e.port
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class EncodesTest < Test::Unit::TestCase
|
4
|
+
context "An Encodes includer" do
|
5
|
+
setup do
|
6
|
+
@enc = Enc.new
|
7
|
+
end
|
8
|
+
|
9
|
+
context "ruby request encoder" do
|
10
|
+
should "return BERT-RPC encoded request" do
|
11
|
+
bert = "\203h\004d\000\004calld\000\005mymodd\000\005myfunl\000\000\000\003a\001a\002a\003j"
|
12
|
+
assert_equal bert, @enc.encode_ruby_request(t[:call, :mymod, :myfun, [1, 2, 3]])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "bert response decoder" do
|
17
|
+
should "return response when reply" do
|
18
|
+
req = @enc.encode_ruby_request(t[:reply, [1, 2, 3]])
|
19
|
+
res = @enc.decode_bert_response(req)
|
20
|
+
assert_equal [1, 2, 3], res
|
21
|
+
end
|
22
|
+
|
23
|
+
should "return nil when noreply" do
|
24
|
+
req = @enc.encode_ruby_request(t[:noreply])
|
25
|
+
res = @enc.decode_bert_response(req)
|
26
|
+
assert_equal nil, res
|
27
|
+
end
|
28
|
+
|
29
|
+
should "raise a ProtocolError error when protocol level error is returned" do
|
30
|
+
req = @enc.encode_ruby_request(t[:error, [:protocol, 1, "class", "invalid", []]])
|
31
|
+
assert_raises(BERTRPC::ProtocolError) do
|
32
|
+
@enc.decode_bert_response(req)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
should "raise a ServerError error when server level error is returned" do
|
37
|
+
req = @enc.encode_ruby_request(t[:error, [:server, 1, "class", "invalid", []]])
|
38
|
+
assert_raises(BERTRPC::ServerError) do
|
39
|
+
@enc.decode_bert_response(req)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
should "raise a UserError error when user level error is returned" do
|
44
|
+
req = @enc.encode_ruby_request([:error, [:user, 1, "class", "invalid", []]])
|
45
|
+
assert_raises(BERTRPC::UserError) do
|
46
|
+
@enc.decode_bert_response(req)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
should "raise a ProxyError error when proxy level error is returned" do
|
51
|
+
req = @enc.encode_ruby_request([:error, [:proxy, 1, "class", "invalid", []]])
|
52
|
+
assert_raises(BERTRPC::ProxyError) do
|
53
|
+
@enc.decode_bert_response(req)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/test/error_test.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ErrorTest < Test::Unit::TestCase
|
4
|
+
context "Errors in general" do
|
5
|
+
should "be creatable with just a message string" do
|
6
|
+
begin
|
7
|
+
raise BERTRPC::BERTRPCError.new('msg')
|
8
|
+
rescue Object => e
|
9
|
+
assert_equal "msg", e.message
|
10
|
+
assert_equal 0, e.code
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
should "be creatable with a [code, message] array" do
|
15
|
+
begin
|
16
|
+
raise BERTRPC::BERTRPCError.new([7, 'msg'])
|
17
|
+
rescue Object => e
|
18
|
+
assert_equal "msg", e.message
|
19
|
+
assert_equal 7, e.code
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
should "record the original exception" do
|
24
|
+
begin
|
25
|
+
raise BERTRPC::BERTRPCError.new('msg', 'Error', ['foo', 'bar'])
|
26
|
+
rescue Object => e
|
27
|
+
assert_equal "msg", e.message
|
28
|
+
assert_equal "Error: msg", e.original_exception.message
|
29
|
+
assert_equal ['foo', 'bar'], e.original_exception.backtrace
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/test/mod_test.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ModTest < Test::Unit::TestCase
|
4
|
+
context "A Mod" do
|
5
|
+
setup do
|
6
|
+
@svc = BERTRPC::Service.new('localhost', 9941)
|
7
|
+
@req = @svc.call
|
8
|
+
end
|
9
|
+
|
10
|
+
should "be created with a Service, request and module name" do
|
11
|
+
assert BERTRPC::Mod.new(@svc, @req, :mymod).is_a?(BERTRPC::Mod)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "A Mod instance" do
|
16
|
+
setup do
|
17
|
+
@svc = BERTRPC::Service.new('localhost', 9941)
|
18
|
+
@req = @svc.call
|
19
|
+
@mod = BERTRPC::Mod.new(@svc, @req, :mymod)
|
20
|
+
end
|
21
|
+
|
22
|
+
should "call execute on the type" do
|
23
|
+
m = mock(:execute => nil)
|
24
|
+
BERTRPC::Action.expects(:new).with(@svc, @req, :mymod, :myfun, [1, 2, 3]).returns(m)
|
25
|
+
@mod.myfun(1, 2, 3)
|
26
|
+
|
27
|
+
m = mock(:execute => nil)
|
28
|
+
BERTRPC::Action.expects(:new).with(@svc, @req, :mymod, :myfun, [1]).returns(m)
|
29
|
+
@mod.myfun(1)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class RequestTest < Test::Unit::TestCase
|
4
|
+
context "A Request" do
|
5
|
+
setup do
|
6
|
+
@svc = BERTRPC::Service.new('localhost', 9941)
|
7
|
+
end
|
8
|
+
|
9
|
+
should "be created with a Service and type" do
|
10
|
+
assert BERTRPC::Request.new(@svc, :call, nil).is_a?(BERTRPC::Request)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "A Request instance" do
|
15
|
+
setup do
|
16
|
+
svc = BERTRPC::Service.new('localhost', 9941)
|
17
|
+
@req = BERTRPC::Request.new(@svc, :call, nil)
|
18
|
+
end
|
19
|
+
|
20
|
+
should "return a Mod instance" do
|
21
|
+
assert @req.myfun.is_a?(BERTRPC::Mod)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ServiceTest < Test::Unit::TestCase
|
4
|
+
context "A Service" do
|
5
|
+
should "be creatable with host and port" do
|
6
|
+
svc = BERTRPC::Service.new('localhost', 9941)
|
7
|
+
assert svc.is_a?(BERTRPC::Service)
|
8
|
+
end
|
9
|
+
|
10
|
+
should "be creatable with host, port, and timeout" do
|
11
|
+
svc = BERTRPC::Service.new('localhost', 9941, 5)
|
12
|
+
assert svc.is_a?(BERTRPC::Service)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "A Service Instance's" do
|
17
|
+
setup do
|
18
|
+
@svc = BERTRPC::Service.new('localhost', 9941)
|
19
|
+
end
|
20
|
+
|
21
|
+
context "accessors" do
|
22
|
+
should "return it's host" do
|
23
|
+
assert_equal 'localhost', @svc.host
|
24
|
+
end
|
25
|
+
|
26
|
+
should "return it's port" do
|
27
|
+
assert_equal 9941, @svc.port
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "call method" do
|
32
|
+
should "return a call type Request" do
|
33
|
+
req = @svc.call
|
34
|
+
assert req.is_a?(BERTRPC::Request)
|
35
|
+
assert_equal :call, req.kind
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "cast method" do
|
40
|
+
should "return a cast type Request" do
|
41
|
+
req = @svc.cast
|
42
|
+
assert req.is_a?(BERTRPC::Request)
|
43
|
+
assert_equal :cast, req.kind
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
require 'mocha'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
|
+
require 'bertrpc'
|
9
|
+
|
10
|
+
class Enc
|
11
|
+
include BERTRPC::Encodes
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fd-bertrpc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 3
|
8
|
+
- 0
|
9
|
+
version: 1.3.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Tom Preston-Werner
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-06-01 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: bert
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 1
|
30
|
+
- 0
|
31
|
+
version: 1.1.0
|
32
|
+
- - <
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
segments:
|
35
|
+
- 2
|
36
|
+
- 0
|
37
|
+
- 0
|
38
|
+
version: 2.0.0
|
39
|
+
type: :runtime
|
40
|
+
version_requirements: *id001
|
41
|
+
description:
|
42
|
+
email: tom@mojombo.com
|
43
|
+
executables: []
|
44
|
+
|
45
|
+
extensions: []
|
46
|
+
|
47
|
+
extra_rdoc_files:
|
48
|
+
- LICENSE
|
49
|
+
- README.md
|
50
|
+
files:
|
51
|
+
- .document
|
52
|
+
- .gitignore
|
53
|
+
- History.txt
|
54
|
+
- LICENSE
|
55
|
+
- README.md
|
56
|
+
- Rakefile
|
57
|
+
- VERSION
|
58
|
+
- bertrpc.gemspec
|
59
|
+
- lib/bertrpc.rb
|
60
|
+
- lib/bertrpc/action.rb
|
61
|
+
- lib/bertrpc/encodes.rb
|
62
|
+
- lib/bertrpc/errors.rb
|
63
|
+
- lib/bertrpc/mod.rb
|
64
|
+
- lib/bertrpc/request.rb
|
65
|
+
- lib/bertrpc/service.rb
|
66
|
+
- test/action_test.rb
|
67
|
+
- test/encodes_test.rb
|
68
|
+
- test/error_test.rb
|
69
|
+
- test/mod_test.rb
|
70
|
+
- test/request_test.rb
|
71
|
+
- test/service_test.rb
|
72
|
+
- test/test_helper.rb
|
73
|
+
has_rdoc: true
|
74
|
+
homepage: http://github.com/mojombo/bertrpc
|
75
|
+
licenses: []
|
76
|
+
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options:
|
79
|
+
- --charset=UTF-8
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
version: "0"
|
96
|
+
requirements: []
|
97
|
+
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 1.3.6
|
100
|
+
signing_key:
|
101
|
+
specification_version: 3
|
102
|
+
summary: BERTRPC is a Ruby BERT-RPC client library.
|
103
|
+
test_files:
|
104
|
+
- test/action_test.rb
|
105
|
+
- test/encodes_test.rb
|
106
|
+
- test/error_test.rb
|
107
|
+
- test/mod_test.rb
|
108
|
+
- test/request_test.rb
|
109
|
+
- test/service_test.rb
|
110
|
+
- test/test_helper.rb
|