elementary-rpc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/.rspec +1 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +22 -0
- data/README.md +90 -0
- data/Rakefile +13 -0
- data/elementary-rpc.gemspec +28 -0
- data/lib/elementary.rb +21 -0
- data/lib/elementary/connection.rb +40 -0
- data/lib/elementary/errors.rb +7 -0
- data/lib/elementary/executor.rb +32 -0
- data/lib/elementary/future.rb +21 -0
- data/lib/elementary/middleware.rb +4 -0
- data/lib/elementary/middleware/dummy.rb +15 -0
- data/lib/elementary/middleware/statsd.rb +30 -0
- data/lib/elementary/transport.rb +6 -0
- data/lib/elementary/transport/http.rb +64 -0
- data/lib/elementary/version.rb +4 -0
- data/spec/connection_spec.rb +82 -0
- data/spec/elementary_spec.rb +31 -0
- data/spec/executor_spec.rb +8 -0
- data/spec/future_spec.rb +38 -0
- data/spec/middleware/statsd_spec.rb +15 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/proto/simpleservice.proto +12 -0
- data/spec/support/simpleservice.pb.rb +36 -0
- data/spec/support/simpleservice.rb +19 -0
- data/spec/transport/http_spec.rb +60 -0
- metadata +166 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f92f4aa5b2489be4f30f3bd8f1c36a005f59b554
|
4
|
+
data.tar.gz: f876e99e9f8851fdb23802492f7f66428e0ab38e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5d3994a1a15f0313ab25ab9ff820a29076de9a88c852df1a7e4c1a1bb768e28359d1c6ad7bb7609581bab8b0cacb03d8f079492c33c4fc5692f6f29ef3d1e492
|
7
|
+
data.tar.gz: fe24f7fc72437bf9bbd54fd394ee4448b44582449b8483af0fe3af0a00cd8bb8c4bd02273aa2ec857ca8bd7bbd616d69755542de82b9265cb139ef4d6f51fba4
|
data/.gitignore
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
*.swp
|
24
|
+
.ruby-*
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color --order random --fail-fast
|
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in elementary-rpc.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'rack'
|
7
|
+
gem 'protobuf', :github => 'lookout/protobuf',
|
8
|
+
:ref => 'wip/http'
|
9
|
+
|
10
|
+
group :test do
|
11
|
+
gem 'rspec'
|
12
|
+
end
|
13
|
+
|
14
|
+
group :development do
|
15
|
+
gem 'pry'
|
16
|
+
gem 'debugger', :platform => :mri
|
17
|
+
gem 'debugger-pry', :platform => :mri
|
18
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 R. Tyler Croy
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# Elementary RPC
|
2
|
+
|
3
|
+
Elementary RPC is a basic
|
4
|
+
[Protobuf](https://developers.google.com/protocol-buffers/docs/overview) HTTP
|
5
|
+
RPC gem which aims to provide:
|
6
|
+
|
7
|
+
* Easy RPC usage
|
8
|
+
* Parallelism by default
|
9
|
+
* An easily extended RPC request pipeline
|
10
|
+
|
11
|
+
### Sample Usage
|
12
|
+
|
13
|
+
For the following Protobuf RPC service definition:
|
14
|
+
|
15
|
+
```
|
16
|
+
package echoserv;
|
17
|
+
|
18
|
+
message String {
|
19
|
+
required string data = 1;
|
20
|
+
optional int64 status = 2;
|
21
|
+
}
|
22
|
+
|
23
|
+
service Simple {
|
24
|
+
rpc Echo (String) returns (String);
|
25
|
+
rpc Reverse (String) returns (String);
|
26
|
+
}
|
27
|
+
```
|
28
|
+
|
29
|
+
A corresponding Ruby client might look something like this:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
require 'rubygems'
|
33
|
+
require 'elementary'
|
34
|
+
|
35
|
+
require 'echoserv/service.pb' # Include our protobuf object declarations
|
36
|
+
|
37
|
+
|
38
|
+
hosts = [{:host => 'localhost', :port => '9292', :prefix => '/rpcserv'}]
|
39
|
+
|
40
|
+
|
41
|
+
# Let's use the Statsd middleware to send RPC timing and count information to
|
42
|
+
# Graphite (this presumes we have already used `Statsd.create_instance` elsewhere
|
43
|
+
# in our code)
|
44
|
+
Elementary.use(Elementary::Middleware::Statsd, :client => Statsd.instance)
|
45
|
+
|
46
|
+
|
47
|
+
# Create our Connection object that knows about our Protobuf service
|
48
|
+
# definition
|
49
|
+
c = Elementary::Connection.new(Echoserv::Simple, :hosts => hosts)
|
50
|
+
|
51
|
+
# Create a Protobuf message to send over RPC
|
52
|
+
msg = Echoserv::String.new(:data => str)
|
53
|
+
|
54
|
+
|
55
|
+
echoed = c.rpc.echo(msg) # => Elementary::Future
|
56
|
+
reversed = c.rpc.reverse(msg) # => Elementary::Future
|
57
|
+
|
58
|
+
sleep 10 # Twiddle our thumbs doing other things
|
59
|
+
|
60
|
+
puts {
|
61
|
+
:echoed => echoed.value, # resolve the future and get our value out
|
62
|
+
:reversed => reversed.value,
|
63
|
+
}.to_json
|
64
|
+
```
|
65
|
+
|
66
|
+
## Installation
|
67
|
+
|
68
|
+
Add this line to your application's Gemfile:
|
69
|
+
|
70
|
+
gem 'elementary-rpc'
|
71
|
+
|
72
|
+
And then execute:
|
73
|
+
|
74
|
+
$ bundle
|
75
|
+
|
76
|
+
Or install it yourself as:
|
77
|
+
|
78
|
+
$ gem install elementary-rpc
|
79
|
+
|
80
|
+
## Usage
|
81
|
+
|
82
|
+
TODO: Write usage instructions here
|
83
|
+
|
84
|
+
## Contributing
|
85
|
+
|
86
|
+
1. Fork it ( https://github.com/[my-github-username]/elementary-rpc/fork )
|
87
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
88
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
89
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
90
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new
|
5
|
+
|
6
|
+
task :default => ['protobuf:spec', 'spec', 'build']
|
7
|
+
|
8
|
+
namespace :protobuf do
|
9
|
+
desc 'Generate rspec protos'
|
10
|
+
task :spec do
|
11
|
+
sh "protoc --proto_path=./spec/support/proto --ruby_out=./spec/support #{Dir['./spec/support/proto/**/*.proto'].join(' ')}"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'elementary/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "elementary-rpc"
|
8
|
+
spec.version = Elementary::VERSION
|
9
|
+
spec.authors = ["R. Tyler Croy"]
|
10
|
+
spec.email = ["tyler@monkeypox.org"]
|
11
|
+
spec.summary = "Gem supporting Protobuf RPC in a simple way"
|
12
|
+
spec.description = "BLANK"
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
|
24
|
+
spec.add_dependency 'concurrent-ruby', '~> 0.6.0'
|
25
|
+
spec.add_dependency 'faraday', '~> 0.9.0'
|
26
|
+
spec.add_dependency 'net-http-persistent', '~> 2.9.4'
|
27
|
+
spec.add_dependency 'lookout-statsd', '~> 0.9.0'
|
28
|
+
end
|
data/lib/elementary.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Elementary
|
2
|
+
def self.middleware
|
3
|
+
@middleware ||= []
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.use(klass, opts={})
|
7
|
+
if klass.nil?
|
8
|
+
raise ArgumentError, "Cannot add a nil middleware"
|
9
|
+
end
|
10
|
+
self.middleware << [klass, opts]
|
11
|
+
return true
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.flush_middleware
|
15
|
+
@middleware = []
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'elementary/version'
|
20
|
+
require 'elementary/connection'
|
21
|
+
require 'elementary/middleware'
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'protobuf'
|
3
|
+
|
4
|
+
require 'elementary/middleware'
|
5
|
+
require 'elementary/transport'
|
6
|
+
|
7
|
+
module Elementary
|
8
|
+
class Connection
|
9
|
+
attr_reader :raise_on_error, :service
|
10
|
+
|
11
|
+
DEFAULT_HOSTS = [{:host => 'localhost', :port => 8000}].freeze
|
12
|
+
|
13
|
+
# Initialize a connection to the +Protobuf::Rpc::Service+
|
14
|
+
#
|
15
|
+
# @param [Protobuf::Rpc::Service] service
|
16
|
+
# @param [Hash] opts
|
17
|
+
# @options opts [Symbol] :transport Defaults to :http, must map to a class
|
18
|
+
# in the +Elementary::Transport+ module
|
19
|
+
# @optiosn opts [Array] :hosts An array of {:host => 'localhost', :port =>
|
20
|
+
# 8080} hashes to instruct the connection
|
21
|
+
def initialize(service, opts={})
|
22
|
+
if service.nil? || service.superclass != Protobuf::Rpc::Service
|
23
|
+
raise ArgumentError,
|
24
|
+
"Cannot construct an Elementary::Connection with `#{service}` (#{service.class})"
|
25
|
+
end
|
26
|
+
|
27
|
+
@service = service
|
28
|
+
@transport = opts[:transport]
|
29
|
+
@hosts = opts[:hosts] || DEFAULT_HOSTS
|
30
|
+
end
|
31
|
+
|
32
|
+
def rpc
|
33
|
+
@rpc ||= Elementary::Executor.new(@service, select_transport)
|
34
|
+
end
|
35
|
+
|
36
|
+
def select_transport
|
37
|
+
Elementary::Transport::HTTP.new(@hosts)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
module Elementary
|
3
|
+
class Executor
|
4
|
+
attr_reader :service, :transport
|
5
|
+
|
6
|
+
def initialize(service, transport)
|
7
|
+
@service = service
|
8
|
+
@transport = transport
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(method_name, *params)
|
12
|
+
rpc_method = service.rpcs[method_name.to_sym]
|
13
|
+
# XXX: explode if rpc_method is nil
|
14
|
+
|
15
|
+
future = Elementary::Future.new do
|
16
|
+
# This is effectively a Rack middleware stack. yay.
|
17
|
+
#
|
18
|
+
# Easiest to think of it like this:
|
19
|
+
# Statsd.new(HTTP.new(nil))
|
20
|
+
stack = Elementary.middleware.inject(transport) do |accumulator, middleware|
|
21
|
+
klass = middleware.first
|
22
|
+
options = middleware.last
|
23
|
+
klass.new(accumulator, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
stack.call(service, rpc_method, *params)
|
27
|
+
end
|
28
|
+
|
29
|
+
return future.execute
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'concurrent'
|
3
|
+
|
4
|
+
module Elementary
|
5
|
+
class Future < Concurrent::Future
|
6
|
+
# Invoke undefined methods on the value of the future
|
7
|
+
#
|
8
|
+
# E.g.
|
9
|
+
# results = c.rpc.search(query)
|
10
|
+
# # sleep maxint
|
11
|
+
# results.each do |result|
|
12
|
+
# # work
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# Which would really invoke:
|
16
|
+
# results.value.each
|
17
|
+
def method_missing(method, *params)
|
18
|
+
self.value.send(method, *params)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Elementary
|
2
|
+
module Middleware
|
3
|
+
class Dummy
|
4
|
+
def initialize(app, opts={})
|
5
|
+
@app = app
|
6
|
+
@opts = opts
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(service, rpc_method, *params)
|
10
|
+
puts "#{self.class.name}#call (options: #{@opts}"
|
11
|
+
@app.call(service, rpc_method, *params)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'statsd'
|
3
|
+
|
4
|
+
module Elementary
|
5
|
+
module Middleware
|
6
|
+
class Statsd
|
7
|
+
# Create a new Statsd middleware for Elementary
|
8
|
+
#
|
9
|
+
# @param [Hash] opts Hash of optional parameters
|
10
|
+
# @option opts [::Statsd::Client] :client Set to an existing instance of
|
11
|
+
# a +Statsd::Client+
|
12
|
+
def initialize(app, opts={})
|
13
|
+
@app = app
|
14
|
+
|
15
|
+
@statsd = opts[:client] || ::Statsd::Client.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(service, rpc_method, *params)
|
19
|
+
@statsd.timing(metric_name(service.name, rpc_method.method)) do
|
20
|
+
@app.call(service, rpc_method, *params)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def metric_name(service_name, method_name)
|
25
|
+
service_name = service_name.gsub('::', '.').downcase
|
26
|
+
return "elementary.#{service_name}.#{method_name}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'cgi'
|
3
|
+
require 'faraday'
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
require 'elementary/errors'
|
7
|
+
require 'elementary/future'
|
8
|
+
|
9
|
+
module Elementary
|
10
|
+
module Transport
|
11
|
+
class HTTP
|
12
|
+
ERROR_HEADER_MSG = 'x-protobuf-error'
|
13
|
+
ERROR_HEADER_CODE = 'x-protobuf-error-reason'
|
14
|
+
|
15
|
+
def initialize(hosts)
|
16
|
+
@hosts = hosts
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(service, rpc_method, *params)
|
20
|
+
begin
|
21
|
+
response = client.post do |h|
|
22
|
+
path = "#{CGI.escape(service.name)}/#{rpc_method.method}"
|
23
|
+
h.url(path)
|
24
|
+
h.body = params[0].encode
|
25
|
+
end
|
26
|
+
|
27
|
+
error_msg = response.headers[ERROR_HEADER_MSG]
|
28
|
+
error_code = response.headers[ERROR_HEADER_CODE]
|
29
|
+
|
30
|
+
if error_msg
|
31
|
+
raise Elementary::Errors::RPCFailure, "Error #{error_code}: #{error_msg}"
|
32
|
+
end
|
33
|
+
|
34
|
+
return rpc_method[:response_type].decode(response.body)
|
35
|
+
rescue StandardError => e
|
36
|
+
#puts "EXCEPTION #{e.inspect}"
|
37
|
+
raise
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def host_url
|
44
|
+
# XXX: need to support a full collection of hosts similar to
|
45
|
+
# elasticsearch-ruby
|
46
|
+
host = @hosts.first
|
47
|
+
prefix = host[:prefix]
|
48
|
+
return "http://#{host[:host]}:#{host[:port]}/#{prefix}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def client
|
52
|
+
return @client if @client
|
53
|
+
|
54
|
+
@client = Faraday.new(:url => host_url) do |f|
|
55
|
+
f.response :logger
|
56
|
+
f.adapter :net_http_persistent
|
57
|
+
end
|
58
|
+
|
59
|
+
return @client
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'elementary/connection'
|
3
|
+
|
4
|
+
describe Elementary::Connection do
|
5
|
+
describe '#initialize' do
|
6
|
+
subject(:connection) { described_class.new(service, opts) }
|
7
|
+
let(:opts) { {} }
|
8
|
+
|
9
|
+
context 'without a Protobuf::Rpc::Service' do
|
10
|
+
let(:service) { nil }
|
11
|
+
|
12
|
+
it 'should raise ArgumentError' do
|
13
|
+
expect { connection }.to raise_error(ArgumentError)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with a Protobuf::Rpc::Service' do
|
18
|
+
let(:service) { Elementary::Rspec::Simple }
|
19
|
+
|
20
|
+
it { should be_instance_of described_class }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:connection) do
|
25
|
+
described_class.new(Elementary::Rspec::Simple)
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#select_transport' do
|
29
|
+
subject(:transport) { connection.select_transport }
|
30
|
+
|
31
|
+
context 'by default' do
|
32
|
+
it { should be_instance_of Elementary::Transport::HTTP }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
describe 'an error request', :type => :integration do
|
38
|
+
describe 'rpc' do
|
39
|
+
describe '#error' do
|
40
|
+
subject(:response) { connection.rpc.error(request) }
|
41
|
+
let(:request) { Elementary::Rspec::String.new(:data => 'rspec') }
|
42
|
+
|
43
|
+
before :each do
|
44
|
+
response.value
|
45
|
+
end
|
46
|
+
|
47
|
+
it { should be_rejected }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'an echo request', :type => :integration do
|
53
|
+
after :each do
|
54
|
+
Elementary.flush_middleware
|
55
|
+
end
|
56
|
+
|
57
|
+
describe 'rpc' do
|
58
|
+
describe '#echo' do
|
59
|
+
let(:request) { Elementary::Rspec::String.new(:data => 'rspec') }
|
60
|
+
subject(:response) { connection.rpc.echo(request) }
|
61
|
+
|
62
|
+
before :each do
|
63
|
+
Elementary.use Elementary::Middleware::Dummy, :rspec => true
|
64
|
+
expect_any_instance_of(Elementary::Middleware::Dummy).to \
|
65
|
+
receive(:call).and_call_original
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should have a value containing the echoed string' do
|
69
|
+
puts "Sending req #{Time.now.to_f}"
|
70
|
+
expect(response).to be_instance_of Elementary::Future
|
71
|
+
|
72
|
+
puts "Waiting for future #{Time.now.to_f}"
|
73
|
+
value = response.value # Wait on the future
|
74
|
+
puts "Future responded: #{Time.now.to_f}"
|
75
|
+
|
76
|
+
expect(response).not_to be_rejected
|
77
|
+
expect(value.data).to eql('rspec')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'elementary'
|
3
|
+
|
4
|
+
describe Elementary do
|
5
|
+
after :each do
|
6
|
+
described_class.flush_middleware
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '.middleware' do
|
10
|
+
subject { described_class.middleware }
|
11
|
+
|
12
|
+
it { should be_instance_of Array }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '.use' do
|
16
|
+
let(:mw) { nil }
|
17
|
+
subject { described_class.use(mw) }
|
18
|
+
|
19
|
+
context 'with nil' do
|
20
|
+
it 'should raise' do
|
21
|
+
expect { subject }.to raise_error ArgumentError
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'with a class' do
|
26
|
+
let(:mw) { Elementary::Middleware::Statsd }
|
27
|
+
|
28
|
+
it { should be true }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/spec/future_spec.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'elementary/future'
|
3
|
+
|
4
|
+
describe Elementary::Future do
|
5
|
+
let(:future) { described_class.new do 1 end }
|
6
|
+
|
7
|
+
describe '#method_missing' do
|
8
|
+
context 'methods defined on Concurrent::Future' do
|
9
|
+
it 'should invoke the methods directly on the future' do
|
10
|
+
expect(future).to receive(:value)
|
11
|
+
future.value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'methods not defined on Concurrent::Future' do
|
16
|
+
let(:actual) do
|
17
|
+
double('Elementary::Future#value mock')
|
18
|
+
end
|
19
|
+
|
20
|
+
before :each do
|
21
|
+
expect(future).to receive(:value).and_return(actual)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should invoke the method on the value of the future' do
|
25
|
+
value = 1337
|
26
|
+
expect(actual).to receive(:number).and_return(value)
|
27
|
+
|
28
|
+
expect(future.number).to eql(value)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should invoke the method with params on the value of the future' do
|
32
|
+
expect(actual).to receive(:add).with(2, 2).and_return(5)
|
33
|
+
|
34
|
+
expect(future.add(2, 2)).to eql(5)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'elementary/middleware/statsd'
|
3
|
+
|
4
|
+
describe Elementary::Middleware::Statsd do
|
5
|
+
let(:statsd) { described_class.new(nil) }
|
6
|
+
|
7
|
+
describe '#metric_name' do
|
8
|
+
subject(:metric) { statsd.metric_name(service_name, method_name) }
|
9
|
+
|
10
|
+
let(:service_name) { 'My::Rpc::Service' }
|
11
|
+
let(:method_name) { 'echo' }
|
12
|
+
|
13
|
+
it { should eql 'elementary.my.rpc.service.echo' }
|
14
|
+
end
|
15
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/../'))
|
2
|
+
require 'elementary'
|
3
|
+
|
4
|
+
unless RUBY_PLATFORM == 'java'
|
5
|
+
require 'debugger'
|
6
|
+
require 'debugger/pry'
|
7
|
+
end
|
8
|
+
|
9
|
+
# Reequire all our generate test protos
|
10
|
+
Dir[File.expand_path(File.dirname(__FILE__) + '/support/**/*.pb.rb')].each do |f|
|
11
|
+
require f
|
12
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
##
|
2
|
+
# This file is auto-generated. DO NOT EDIT!
|
3
|
+
#
|
4
|
+
require 'protobuf/message'
|
5
|
+
require 'protobuf/rpc/service'
|
6
|
+
|
7
|
+
module Elementary
|
8
|
+
module Rspec
|
9
|
+
|
10
|
+
##
|
11
|
+
# Message Classes
|
12
|
+
#
|
13
|
+
class String < ::Protobuf::Message; end
|
14
|
+
|
15
|
+
|
16
|
+
##
|
17
|
+
# Message Fields
|
18
|
+
#
|
19
|
+
class String
|
20
|
+
required :string, :data, 1
|
21
|
+
optional :int64, :status, 2
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
##
|
26
|
+
# Service Classes
|
27
|
+
#
|
28
|
+
class Simple < ::Protobuf::Rpc::Service
|
29
|
+
rpc :echo, ::Elementary::Rspec::String, ::Elementary::Rspec::String
|
30
|
+
rpc :error, ::Elementary::Rspec::String, ::Elementary::Rspec::String
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require './spec/support/simpleservice.pb'
|
2
|
+
|
3
|
+
module Elementary
|
4
|
+
module Rspec
|
5
|
+
class Simple
|
6
|
+
def echo
|
7
|
+
puts "ECHOING #{request.inspect}"
|
8
|
+
sleep 1
|
9
|
+
|
10
|
+
respond_with(String.new(:data => request.data))
|
11
|
+
end
|
12
|
+
|
13
|
+
def error
|
14
|
+
rpc_failed 'sample failure'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'elementary/transport/http'
|
3
|
+
|
4
|
+
describe Elementary::Transport::HTTP do
|
5
|
+
let(:hosts) { [] }
|
6
|
+
let(:http) { described_class.new(hosts) }
|
7
|
+
|
8
|
+
describe '#host_url' do
|
9
|
+
subject(:host_url) { http.send(:host_url) }
|
10
|
+
|
11
|
+
context 'without a prefix' do
|
12
|
+
let(:hosts) do
|
13
|
+
[
|
14
|
+
{
|
15
|
+
:host => 'localhost',
|
16
|
+
:port => 8080,
|
17
|
+
}
|
18
|
+
]
|
19
|
+
end
|
20
|
+
|
21
|
+
it { should eql 'http://localhost:8080/' }
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with a prefix' do
|
25
|
+
let(:hosts) do
|
26
|
+
[
|
27
|
+
{
|
28
|
+
:host => 'localhost',
|
29
|
+
:port => 8080,
|
30
|
+
:prefix => 'rspec'
|
31
|
+
}
|
32
|
+
]
|
33
|
+
end
|
34
|
+
|
35
|
+
it { should eql 'http://localhost:8080/rspec' }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#client' do
|
40
|
+
let(:hosts) do
|
41
|
+
[
|
42
|
+
{
|
43
|
+
:host => 'localhost',
|
44
|
+
:port => 8080,
|
45
|
+
}
|
46
|
+
]
|
47
|
+
end
|
48
|
+
subject(:client) { http.send(:client) }
|
49
|
+
|
50
|
+
it { should be_instance_of Faraday::Connection }
|
51
|
+
|
52
|
+
it 'should cache connections' do
|
53
|
+
first = http.send(:client)
|
54
|
+
second = http.send(:client)
|
55
|
+
|
56
|
+
# Object identity!
|
57
|
+
expect(first).to be second
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: elementary-rpc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- R. Tyler Croy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: concurrent-ruby
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.6.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.6.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: faraday
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.9.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.9.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: net-http-persistent
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.9.4
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.9.4
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: lookout-statsd
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.9.0
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.9.0
|
97
|
+
description: BLANK
|
98
|
+
email:
|
99
|
+
- tyler@monkeypox.org
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec"
|
106
|
+
- Gemfile
|
107
|
+
- LICENSE.txt
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- elementary-rpc.gemspec
|
111
|
+
- lib/elementary.rb
|
112
|
+
- lib/elementary/connection.rb
|
113
|
+
- lib/elementary/errors.rb
|
114
|
+
- lib/elementary/executor.rb
|
115
|
+
- lib/elementary/future.rb
|
116
|
+
- lib/elementary/middleware.rb
|
117
|
+
- lib/elementary/middleware/dummy.rb
|
118
|
+
- lib/elementary/middleware/statsd.rb
|
119
|
+
- lib/elementary/transport.rb
|
120
|
+
- lib/elementary/transport/http.rb
|
121
|
+
- lib/elementary/version.rb
|
122
|
+
- spec/connection_spec.rb
|
123
|
+
- spec/elementary_spec.rb
|
124
|
+
- spec/executor_spec.rb
|
125
|
+
- spec/future_spec.rb
|
126
|
+
- spec/middleware/statsd_spec.rb
|
127
|
+
- spec/spec_helper.rb
|
128
|
+
- spec/support/proto/simpleservice.proto
|
129
|
+
- spec/support/simpleservice.pb.rb
|
130
|
+
- spec/support/simpleservice.rb
|
131
|
+
- spec/transport/http_spec.rb
|
132
|
+
homepage: ''
|
133
|
+
licenses:
|
134
|
+
- MIT
|
135
|
+
metadata: {}
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options: []
|
138
|
+
require_paths:
|
139
|
+
- lib
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
requirements: []
|
151
|
+
rubyforge_project:
|
152
|
+
rubygems_version: 2.2.2
|
153
|
+
signing_key:
|
154
|
+
specification_version: 4
|
155
|
+
summary: Gem supporting Protobuf RPC in a simple way
|
156
|
+
test_files:
|
157
|
+
- spec/connection_spec.rb
|
158
|
+
- spec/elementary_spec.rb
|
159
|
+
- spec/executor_spec.rb
|
160
|
+
- spec/future_spec.rb
|
161
|
+
- spec/middleware/statsd_spec.rb
|
162
|
+
- spec/spec_helper.rb
|
163
|
+
- spec/support/proto/simpleservice.proto
|
164
|
+
- spec/support/simpleservice.pb.rb
|
165
|
+
- spec/support/simpleservice.rb
|
166
|
+
- spec/transport/http_spec.rb
|