bertclient_new 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,5 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 4
4
+ :patch: 1
5
+ :build:
@@ -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
+