bertclient_new 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +21 -0
- data/README.md +35 -0
- data/Rakefile +40 -0
- data/VERSION.yml +5 -0
- data/bertclient.gemspec +43 -0
- data/lib/bertclient.rb +236 -0
- metadata +70 -0
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2011
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# BERT::Client
|
2
|
+
|
3
|
+
BERT::Client is a threadsafe BERT-RPC client with support for persistent
|
4
|
+
connections and SSL. It currently exposes BERT-RPC's cast and call.
|
5
|
+
Initially designed to work with *modified* [ernie](https://github.com/mojombo/ernie) server that doesn't close connection after response.
|
6
|
+
Tested on ruby 1.9.2
|
7
|
+
|
8
|
+
# Dependancies
|
9
|
+
|
10
|
+
Requires [BERT](https://github.com/mojombo/bert) gem to be installed.
|
11
|
+
|
12
|
+
Requires Jeweler gem to be installed if you want to pack this library into it's own gem.
|
13
|
+
|
14
|
+
# Usage
|
15
|
+
|
16
|
+
require 'bertclient'
|
17
|
+
client = BERT::Client.new(:host => 'localhost', :port => 9999)
|
18
|
+
client.call(:calc, :add, 1, 2)
|
19
|
+
#=> 3
|
20
|
+
client.call(:calc, :add, 5, 5)
|
21
|
+
#=> 10
|
22
|
+
client.call(:calc, :sub, 10, 5)
|
23
|
+
#=> 5
|
24
|
+
|
25
|
+
You can also use blocks to create ephemeral connections:
|
26
|
+
|
27
|
+
BERT::Client.new(opts) do |client|
|
28
|
+
client.call(:auth, :authenticate, user, password)
|
29
|
+
client.call(:calc, :add, 1, 2)
|
30
|
+
end
|
31
|
+
|
32
|
+
# TODO
|
33
|
+
|
34
|
+
* Write some tests
|
35
|
+
* Package and publish the gem version
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = "bertclient_new"
|
9
|
+
gem.summary = %Q{BERT::Client is a threadsafe BERT-RPC client with support for persistent connections and SSL}
|
10
|
+
gem.email = "marcin.wtorkowski@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/WTK/bertclient"
|
12
|
+
gem.authors = ["Marcin Wtorkowski"]
|
13
|
+
gem.add_dependency('bert')
|
14
|
+
# gem is a Gem::Specification...
|
15
|
+
# see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => :version
|
22
|
+
|
23
|
+
require 'rake/rdoctask'
|
24
|
+
Rake::RDocTask.new do |rdoc|
|
25
|
+
if File.exist?('VERSION.yml')
|
26
|
+
config = YAML.load(File.read('VERSION.yml'))
|
27
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
28
|
+
else
|
29
|
+
version = ""
|
30
|
+
end
|
31
|
+
|
32
|
+
rdoc.rdoc_dir = 'rdoc'
|
33
|
+
rdoc.title = "bertclient #{version}"
|
34
|
+
rdoc.rdoc_files.include('README*')
|
35
|
+
rdoc.rdoc_files.include('lib/*.rb')
|
36
|
+
end
|
37
|
+
|
38
|
+
task :console do
|
39
|
+
exec('irb -I lib -r bertclient')
|
40
|
+
end
|
data/VERSION.yml
ADDED
data/bertclient.gemspec
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{bertclient}
|
8
|
+
s.version = "0.4.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Marcin Wtorkowski"]
|
12
|
+
s.date = %q{2011-04-12}
|
13
|
+
s.email = %q{marcin.wtorkowski@gmail.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE",
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
"LICENSE",
|
20
|
+
"README.md",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION.yml",
|
23
|
+
"bertclient.gemspec",
|
24
|
+
"lib/bertclient.rb"
|
25
|
+
]
|
26
|
+
s.homepage = %q{http://github.com/wtk/bertclient}
|
27
|
+
s.require_paths = ["lib"]
|
28
|
+
s.rubygems_version = %q{1.7.2}
|
29
|
+
s.summary = %q{BERT::Client is a threadsafe BERT-RPC client with support for persistent connections and SSL}
|
30
|
+
|
31
|
+
if s.respond_to? :specification_version then
|
32
|
+
s.specification_version = 3
|
33
|
+
|
34
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
35
|
+
s.add_runtime_dependency(%q<bert>, [">= 0"])
|
36
|
+
else
|
37
|
+
s.add_dependency(%q<bert>, [">= 0"])
|
38
|
+
end
|
39
|
+
else
|
40
|
+
s.add_dependency(%q<bert>, [">= 0"])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
data/lib/bertclient.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'zlib'
|
3
|
+
require 'bert'
|
4
|
+
|
5
|
+
module BERT
|
6
|
+
class Client
|
7
|
+
class RPCError < StandardError; end
|
8
|
+
class NoSuchModule < RPCError; end
|
9
|
+
class NoSuchFunction < RPCError; end
|
10
|
+
class UserError < RPCError; end
|
11
|
+
class UnknownError < RPCError; end
|
12
|
+
class InvalidResponse < RPCError; end
|
13
|
+
class BadHeader < RPCError; end
|
14
|
+
class BadData < RPCError; end
|
15
|
+
|
16
|
+
GZIP_BERP = t[:info, :encoding, [t[:gzip]]]
|
17
|
+
ACCEPT_ENCODING_BERP = t[:info, :accept_encoding, [t[:gzip]]]
|
18
|
+
|
19
|
+
@socket_pool ||= {}
|
20
|
+
|
21
|
+
def initialize(opts={}, &block)
|
22
|
+
@host = opts[:host] || 'localhost'
|
23
|
+
@port = opts[:port] || 9999
|
24
|
+
@timeout = opts[:timeout] || 15
|
25
|
+
|
26
|
+
@gzip = opts[:gzip] || false
|
27
|
+
@gzip_threshold = opts[:gzip_threshold] || 1024 # bytes
|
28
|
+
@gzip_accept_sent = false
|
29
|
+
|
30
|
+
@ssl = opts[:ssl] || false
|
31
|
+
@verify_ssl = opts.has_key?(:verify_ssl) ? opts[:verify_ssl] : true
|
32
|
+
|
33
|
+
@socket = get_socket_or_create Thread.current
|
34
|
+
|
35
|
+
execute(&block) if block_given?
|
36
|
+
end
|
37
|
+
|
38
|
+
def call(mod, fun, *args)
|
39
|
+
response = cast_or_call(:call, mod, fun, *args)
|
40
|
+
return response[1] if response[0] == :reply
|
41
|
+
|
42
|
+
handle_error(response)
|
43
|
+
end
|
44
|
+
|
45
|
+
def cast(mod, fun, *args)
|
46
|
+
response = cast_or_call(:cast, mod, fun, *args)
|
47
|
+
return nil if response[0] == :noreply
|
48
|
+
|
49
|
+
handle_error(response)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
# Wrapper for both cast and call mechanisms
|
54
|
+
def cast_or_call(cc, mod, fun, *args)
|
55
|
+
req = t[cc, mod.to_sym, fun.to_sym, args]
|
56
|
+
write_berp(req)
|
57
|
+
read_response
|
58
|
+
end
|
59
|
+
|
60
|
+
def read_response
|
61
|
+
gzip_encoded = false
|
62
|
+
response = nil
|
63
|
+
|
64
|
+
loop do
|
65
|
+
response = read_berp
|
66
|
+
break unless response[0] == :info
|
67
|
+
|
68
|
+
# For now we only know how to handle gzip encoding info packets
|
69
|
+
if response == GZIP_BERP
|
70
|
+
gzip_encoded = true
|
71
|
+
else
|
72
|
+
raise NotImplementedError, "Only gzip-encoding related info packets are supported in this version of bertclient"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if gzip_encoded and response[0] == :gzip
|
77
|
+
response = BERT.decode(Zlib::Inflate.inflate(response[1]))
|
78
|
+
end
|
79
|
+
|
80
|
+
response
|
81
|
+
end
|
82
|
+
|
83
|
+
# See bert-rpc.org for error response mechanisms
|
84
|
+
def handle_error(response)
|
85
|
+
unless response[0] == :error
|
86
|
+
raise InvalidResponse, "Expected error response, got: #{response.inspect}"
|
87
|
+
end
|
88
|
+
|
89
|
+
type, code, klass, detail, backtrace = response[1]
|
90
|
+
case type
|
91
|
+
when :server
|
92
|
+
if code == 1
|
93
|
+
raise NoSuchModule
|
94
|
+
elsif code == 2
|
95
|
+
raise NoSuchFunction
|
96
|
+
else
|
97
|
+
raise UnknownError, "Unknown server error: #{response.inspect}"
|
98
|
+
end
|
99
|
+
when :user
|
100
|
+
raise UserError.new("#{klass}: #{detail}\n#{backtrace.join()}")
|
101
|
+
when :protocol
|
102
|
+
if code == 1
|
103
|
+
raise BadHeader
|
104
|
+
elsif code == 2
|
105
|
+
raise BadData
|
106
|
+
else
|
107
|
+
raise UnknownError, "Unknown protocol error: #{response.inspect}"
|
108
|
+
end
|
109
|
+
else
|
110
|
+
raise UnknownError, "Unknown error: #{response.inspect}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def get_socket_or_create thread
|
115
|
+
sock = Client.get_socket(thread)
|
116
|
+
unless sock
|
117
|
+
sock = Client.set_socket connect(), thread
|
118
|
+
end
|
119
|
+
sock
|
120
|
+
end
|
121
|
+
|
122
|
+
# Creates a socket object which does speedy, non-blocking reads
|
123
|
+
# and can perform reliable read timeouts.
|
124
|
+
|
125
|
+
def connect_old
|
126
|
+
# Create new tcp socket
|
127
|
+
sock = Socket.tcp(@host, @port, Socket::SOCK_STREAM, 0)
|
128
|
+
#
|
129
|
+
sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
130
|
+
if @ssl
|
131
|
+
sock = OpenSSL::SSL::SSLSocket.new(sock)
|
132
|
+
sock.sync_close = true
|
133
|
+
sock.connect
|
134
|
+
sock.post_connection_check(@host) if @verify_ssl
|
135
|
+
end
|
136
|
+
|
137
|
+
if @timeout
|
138
|
+
secs = Integer(@timeout)
|
139
|
+
usecs = Integer((@timeout - secs) * 1_000_000)
|
140
|
+
optval = [secs, usecs].pack("l_2")
|
141
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
142
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
143
|
+
end
|
144
|
+
|
145
|
+
sock
|
146
|
+
end
|
147
|
+
|
148
|
+
def connect
|
149
|
+
addr = Socket.getaddrinfo(@host, nil, Socket::AF_INET)
|
150
|
+
sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
151
|
+
sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
152
|
+
|
153
|
+
if @ssl
|
154
|
+
sock = OpenSSL::SSL::SSLSocket.new(sock)
|
155
|
+
sock.sync_close = true
|
156
|
+
end
|
157
|
+
|
158
|
+
if @timeout
|
159
|
+
secs = Integer(@timeout)
|
160
|
+
usecs = Integer((@timeout - secs) * 1_000_000)
|
161
|
+
optval = [secs, usecs].pack("l_2")
|
162
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
163
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
164
|
+
end
|
165
|
+
|
166
|
+
sock.connect(Socket.pack_sockaddr_in(@port, addr[0][3]))
|
167
|
+
sock.post_connection_check(@host) if @ssl and @verify_ssl
|
168
|
+
sock
|
169
|
+
end
|
170
|
+
|
171
|
+
# Close socket and clean it up from the pool
|
172
|
+
def close
|
173
|
+
@socket.close
|
174
|
+
Client.del_socket Thread.current
|
175
|
+
true
|
176
|
+
end
|
177
|
+
|
178
|
+
# Accepts a block, yields the client, closes socket at the end of the block
|
179
|
+
def execute
|
180
|
+
ret = yield self
|
181
|
+
close
|
182
|
+
ret
|
183
|
+
end
|
184
|
+
|
185
|
+
# Reads a new berp from the socket and returns the decoded object
|
186
|
+
def read_berp
|
187
|
+
length = @socket.read(4).unpack('N').first
|
188
|
+
data = @socket.read(length)
|
189
|
+
BERT.decode(data)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Accepts a Ruby object, converts to a berp and sends through the socket
|
193
|
+
# Optionally sends gzip packet:
|
194
|
+
#
|
195
|
+
# -> {info, encoding, gzip}
|
196
|
+
# -> {gzip, GzippedBertEncodedData}
|
197
|
+
def write_berp(obj)
|
198
|
+
data = BERT.encode(obj)
|
199
|
+
data = negotiate_gzip(data) if @gzip
|
200
|
+
@socket.write(Client.create_berp(data))
|
201
|
+
end
|
202
|
+
|
203
|
+
def negotiate_gzip(data)
|
204
|
+
if not @gzip_accept_sent
|
205
|
+
@gzip_accept_sent = true
|
206
|
+
@socket.write(Client.create_berp(BERT.encode(ACCEPT_ENCODING_BERP)))
|
207
|
+
end
|
208
|
+
|
209
|
+
if data.bytesize > @gzip_threshold
|
210
|
+
@socket.write(Client.create_berp(BERT.encode(GZIP_BERP)))
|
211
|
+
data = BERT.encode(t[:gzip, Zlib::Deflate.deflate(data)])
|
212
|
+
end
|
213
|
+
data
|
214
|
+
end
|
215
|
+
|
216
|
+
class << self
|
217
|
+
# Accepts a string and returns a berp
|
218
|
+
def create_berp(data)
|
219
|
+
[[data.bytesize].pack('N'), data].join
|
220
|
+
end
|
221
|
+
|
222
|
+
def get_socket thread
|
223
|
+
@socket_pool[thread]
|
224
|
+
end
|
225
|
+
|
226
|
+
def set_socket sock, thread
|
227
|
+
@socket_pool[thread] = sock
|
228
|
+
@socket_pool[thread]
|
229
|
+
end
|
230
|
+
|
231
|
+
def del_socket thread
|
232
|
+
@socket_pool.delete thread
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bertclient_new
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.4.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Marcin Wtorkowski
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-04-12 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: bert
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
description:
|
27
|
+
email: marcin.wtorkowski@gmail.com
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files:
|
33
|
+
- LICENSE
|
34
|
+
- README.md
|
35
|
+
files:
|
36
|
+
- LICENSE
|
37
|
+
- README.md
|
38
|
+
- Rakefile
|
39
|
+
- VERSION.yml
|
40
|
+
- bertclient.gemspec
|
41
|
+
- lib/bertclient.rb
|
42
|
+
homepage: http://github.com/WTK/bertclient
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.7.2
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: BERT::Client is a threadsafe BERT-RPC client with support for persistent connections and SSL
|
69
|
+
test_files: []
|
70
|
+
|