tom 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +23 -0
- data/Gemfile.lock +128 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +112 -0
- data/Rakefile +68 -0
- data/VERSION +1 -0
- data/lib/adapter.rb +96 -0
- data/lib/dispatcher.rb +160 -0
- data/lib/http.rb +35 -0
- data/lib/init.rb +8 -0
- data/lib/merger.rb +34 -0
- data/lib/tom.rb +62 -0
- data/spec/lib/adapter_spec.rb +35 -0
- data/spec/lib/dispatcher_spec.rb +48 -0
- data/spec/spec_helper.rb +40 -0
- data/tom.gemspec +96 -0
- metadata +224 -0
data/.document
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm --create use ruby-1.9.2-p290@leonard
|
data/Gemfile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
group :default do
|
4
|
+
gem "goliath"
|
5
|
+
gem "em-synchrony"
|
6
|
+
gem "em-http-request"
|
7
|
+
gem "mp-deployment", "0.0.21"
|
8
|
+
gem "json"
|
9
|
+
gem "rake"
|
10
|
+
end
|
11
|
+
|
12
|
+
# Add dependencies to develop your gem here.
|
13
|
+
# Include everything needed to run rake, tests, features, etc.
|
14
|
+
group :development do
|
15
|
+
gem "bundler", "~> 1.0.0"
|
16
|
+
gem "jeweler", "~> 1.6.4"
|
17
|
+
gem "shoulda", ">= 0"
|
18
|
+
gem "rcov", ">= 0"
|
19
|
+
gem "ruby-debug19"
|
20
|
+
gem "rspec"
|
21
|
+
gem "webmock"
|
22
|
+
gem "yard"
|
23
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.2.6)
|
5
|
+
archive-tar-minitar (0.5.2)
|
6
|
+
async-rack (0.5.1)
|
7
|
+
rack (~> 1.1)
|
8
|
+
capistrano (2.5.21)
|
9
|
+
highline
|
10
|
+
net-scp (>= 1.0.0)
|
11
|
+
net-sftp (>= 2.0.0)
|
12
|
+
net-ssh (>= 2.0.14)
|
13
|
+
net-ssh-gateway (>= 1.0.0)
|
14
|
+
capistrano-ext (1.2.1)
|
15
|
+
capistrano (>= 1.0.0)
|
16
|
+
columnize (0.3.5)
|
17
|
+
crack (0.3.1)
|
18
|
+
diff-lcs (1.1.3)
|
19
|
+
em-http-request (1.0.0)
|
20
|
+
addressable (>= 2.2.3)
|
21
|
+
em-socksify
|
22
|
+
eventmachine (>= 1.0.0.beta.3)
|
23
|
+
http_parser.rb (>= 0.5.2)
|
24
|
+
em-socksify (0.1.0)
|
25
|
+
eventmachine
|
26
|
+
em-synchrony (1.0.0)
|
27
|
+
eventmachine (>= 1.0.0.beta.1)
|
28
|
+
eventmachine (1.0.0.beta.4)
|
29
|
+
facter (1.6.3)
|
30
|
+
git (1.2.5)
|
31
|
+
goliath (0.9.4)
|
32
|
+
async-rack
|
33
|
+
em-synchrony (>= 1.0.0)
|
34
|
+
eventmachine (>= 1.0.0.beta.3)
|
35
|
+
http_parser.rb
|
36
|
+
http_router (~> 0.9.0)
|
37
|
+
log4r
|
38
|
+
multi_json
|
39
|
+
rack (>= 1.2.2)
|
40
|
+
rack-contrib
|
41
|
+
rack-respond_to
|
42
|
+
hashie (1.0.0)
|
43
|
+
highline (1.6.8)
|
44
|
+
http_parser.rb (0.5.3)
|
45
|
+
http_router (0.9.7)
|
46
|
+
rack (>= 1.0.0)
|
47
|
+
url_mount (~> 0.2.1)
|
48
|
+
jeweler (1.6.4)
|
49
|
+
bundler (~> 1.0)
|
50
|
+
git (>= 1.2.5)
|
51
|
+
rake
|
52
|
+
json (1.6.2)
|
53
|
+
linecache19 (0.5.12)
|
54
|
+
ruby_core_source (>= 0.1.4)
|
55
|
+
log4r (1.1.9)
|
56
|
+
mp-deployment (0.0.21)
|
57
|
+
capistrano (~> 2.5.20)
|
58
|
+
capistrano-ext (~> 1.2.1)
|
59
|
+
facter (~> 1.6.0)
|
60
|
+
hashie (~> 1.0.0)
|
61
|
+
newrelic_rpm (~> 3.3.0)
|
62
|
+
rpm_contrib (~> 2.1.3)
|
63
|
+
rvm (~> 1.6.20)
|
64
|
+
multi_json (1.0.3)
|
65
|
+
net-scp (1.0.4)
|
66
|
+
net-ssh (>= 1.99.1)
|
67
|
+
net-sftp (2.0.5)
|
68
|
+
net-ssh (>= 2.0.9)
|
69
|
+
net-ssh (2.2.1)
|
70
|
+
net-ssh-gateway (1.1.0)
|
71
|
+
net-ssh (>= 1.99.1)
|
72
|
+
newrelic_rpm (3.3.0)
|
73
|
+
rack (1.3.5)
|
74
|
+
rack-accept-media-types (0.9)
|
75
|
+
rack-contrib (1.1.0)
|
76
|
+
rack (>= 0.9.1)
|
77
|
+
rack-respond_to (0.9.8)
|
78
|
+
rack-accept-media-types (>= 0.6)
|
79
|
+
rake (0.9.2.2)
|
80
|
+
rcov (0.9.11)
|
81
|
+
rpm_contrib (2.1.6)
|
82
|
+
newrelic_rpm (>= 3.1.1)
|
83
|
+
newrelic_rpm (>= 3.1.1)
|
84
|
+
rspec (2.7.0)
|
85
|
+
rspec-core (~> 2.7.0)
|
86
|
+
rspec-expectations (~> 2.7.0)
|
87
|
+
rspec-mocks (~> 2.7.0)
|
88
|
+
rspec-core (2.7.1)
|
89
|
+
rspec-expectations (2.7.0)
|
90
|
+
diff-lcs (~> 1.1.2)
|
91
|
+
rspec-mocks (2.7.0)
|
92
|
+
ruby-debug-base19 (0.11.25)
|
93
|
+
columnize (>= 0.3.1)
|
94
|
+
linecache19 (>= 0.5.11)
|
95
|
+
ruby_core_source (>= 0.1.4)
|
96
|
+
ruby-debug19 (0.11.6)
|
97
|
+
columnize (>= 0.3.1)
|
98
|
+
linecache19 (>= 0.5.11)
|
99
|
+
ruby-debug-base19 (>= 0.11.19)
|
100
|
+
ruby_core_source (0.1.5)
|
101
|
+
archive-tar-minitar (>= 0.5.2)
|
102
|
+
rvm (1.6.32)
|
103
|
+
shoulda (2.11.3)
|
104
|
+
url_mount (0.2.1)
|
105
|
+
rack
|
106
|
+
webmock (1.7.8)
|
107
|
+
addressable (~> 2.2, > 2.2.5)
|
108
|
+
crack (>= 0.1.7)
|
109
|
+
yard (0.7.3)
|
110
|
+
|
111
|
+
PLATFORMS
|
112
|
+
ruby
|
113
|
+
|
114
|
+
DEPENDENCIES
|
115
|
+
bundler (~> 1.0.0)
|
116
|
+
em-http-request
|
117
|
+
em-synchrony
|
118
|
+
goliath
|
119
|
+
jeweler (~> 1.6.4)
|
120
|
+
json
|
121
|
+
mp-deployment (= 0.0.21)
|
122
|
+
rake
|
123
|
+
rcov
|
124
|
+
rspec
|
125
|
+
ruby-debug19
|
126
|
+
shoulda
|
127
|
+
webmock
|
128
|
+
yard
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Jannis Hermanns
|
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.markdown
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
![Tom Smykowski](http://dl.dropbox.com/u/1953503/tom%20smykowsky.jpg)
|
2
|
+
# Tom Smykowski - I have people skills!
|
3
|
+
|
4
|
+
Tom Smykowski, Tom for short, is a gem that takes the specifications from the customers and brings them down to the engineers. He has people skills. He is good at dealing with people. If this does not make sense to you, please refer to [this introductory video](http://www.youtube.com/watch?v=mGS2tKQhdhY) and, of course, [the movie](http://www.imdb.com/video/screenplay/vi3215851801/).
|
5
|
+
|
6
|
+
To go into technical detail: Tom uses [Goliath](http://goliath.io) to dispatch HTTP requests to multiple other APIs (via `Adapter`s) in parallel. In a next step, a `Merger` merges the result and responds to the clients request.
|
7
|
+
|
8
|
+
All you have to do is define some `Adapter`s that get activated for certain routes and some `Merger`s for certain routes.
|
9
|
+
|
10
|
+
# TL;DR
|
11
|
+
|
12
|
+
The general flow goes like this:
|
13
|
+
|
14
|
+
TIME Request
|
15
|
+
| |
|
16
|
+
| Dispatcher
|
17
|
+
| .__________|___________.
|
18
|
+
| | | |
|
19
|
+
| Adapter1 Adapter3 AdapterN (in parallel)
|
20
|
+
| |________. | ._________|
|
21
|
+
| | | |
|
22
|
+
| Merger
|
23
|
+
| |
|
24
|
+
V Response
|
25
|
+
|
26
|
+
So per request there can be many `Adapter`s that talk to different APIs, and one `Merger` that combines the responses of all APIs to one response.
|
27
|
+
|
28
|
+
# How to use
|
29
|
+
## Tom::Dispatcher
|
30
|
+
|
31
|
+
This class basically does what is pictured in the flow above:
|
32
|
+
|
33
|
+
1. Take the request
|
34
|
+
2. Find all `Adapter`s that registered for a matching route
|
35
|
+
3. Dispatch the requests to them
|
36
|
+
4. Collect results
|
37
|
+
5. Merge results into one and respond
|
38
|
+
|
39
|
+
To add APIs or change the behavior of Tom, you don't have to touch this class, though. `Adapter` and `Merger` is what you're looking for.
|
40
|
+
|
41
|
+
## Tom::Adapter
|
42
|
+
|
43
|
+
The `Adapter` class comes with the class methods:
|
44
|
+
|
45
|
+
- `host=` (set the api to talk to)
|
46
|
+
- `rewrite_request` (overwrite if you don't want to pass on URIs 1:1)
|
47
|
+
- `forward_request` (takes env, calls the `rewrite_request` method and returns the result)
|
48
|
+
|
49
|
+
To hook Tom up to an API, you would first create an `Adapter` that inherits from the `Tom::Adapter` class, register some routes (they become regular expressions) and finally implement the logic by overwriting the `handle` method:
|
50
|
+
|
51
|
+
class Sheldon < Tom::Adapter
|
52
|
+
register_route "/nodes/*"
|
53
|
+
|
54
|
+
def self.handle(env)
|
55
|
+
# Insert biz logic here
|
56
|
+
forward_request(env)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
The handle method takes a rack env, does some stuff like removing things that are not needed in Sheldon and then forwards the request on to the API.
|
61
|
+
|
62
|
+
In your initializer you can configure `Adapter`s like this:
|
63
|
+
|
64
|
+
Sheldon.host = 'http://localhost:9292'
|
65
|
+
|
66
|
+
This causes the aforementioned `rewrite_request` method to use that host name. The `forward_request` method uses an instance variable called `@request` to know what to do. `rewrite_request`, for example, takes a rack env and initializes `@request` with `:host`, `:method` and `:uri`.
|
67
|
+
|
68
|
+
If the method is `POST` or `PUT`, then you should also set the appropriate `@request[:body]` if you want to use the automatic `forward_request` method.
|
69
|
+
|
70
|
+
## Tom::Merger
|
71
|
+
|
72
|
+
`Mergers` work very similar to `Adapter`s. But all you have to do here is subclass it and implement the `merge` method. For example we could create a `Merger` that always ignores everything and just returns the response from Sheldon:
|
73
|
+
|
74
|
+
class OnlySheldon < Tom::Merger
|
75
|
+
|
76
|
+
register_route "^/nodes/[0-9]+$"
|
77
|
+
|
78
|
+
def self.merge(env, responses)
|
79
|
+
responses[Sheldon]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
You can use the rack `env` to decide what to do, and you get a hash of `responses` that has the Adapter class as keys and their respective rack `responses``:
|
84
|
+
|
85
|
+
{ MyAdapter => [[200,{},"first response" ],
|
86
|
+
[200,{},"second response"]],
|
87
|
+
MyOtherAdapter => […]
|
88
|
+
}
|
89
|
+
|
90
|
+
## On registering routes
|
91
|
+
|
92
|
+
If you call
|
93
|
+
|
94
|
+
class Foo < Tom::Adapter
|
95
|
+
register_route "^/nodes/[0-9]+$"
|
96
|
+
…
|
97
|
+
end
|
98
|
+
|
99
|
+
the adapter will be registered for all methods (namely `head`, `get`, `put`, `post`, `delete`). If you just want to register for some methods, you can do that with
|
100
|
+
|
101
|
+
register_route "^/nodes/[0-9]_$", :get, :put
|
102
|
+
|
103
|
+
Same goes for mergers.
|
104
|
+
|
105
|
+
# Todo
|
106
|
+
|
107
|
+
- handle adapter errors/states in mergers
|
108
|
+
- register routes with concurrency on and off
|
109
|
+
- use Goliath::Rack::Params
|
110
|
+
- use Goliath::Rack::Heartbeat
|
111
|
+
- think about consensus protocols
|
112
|
+
- ...
|
data/Rakefile
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
|
6
|
+
begin
|
7
|
+
Bundler.setup(:default, :development)
|
8
|
+
rescue Bundler::BundlerError => e
|
9
|
+
$stderr.puts e.message
|
10
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
11
|
+
exit e.status_code
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'rake'
|
15
|
+
require 'yard'
|
16
|
+
require 'rspec/core/rake_task'
|
17
|
+
require 'jeweler'
|
18
|
+
|
19
|
+
|
20
|
+
#
|
21
|
+
# Jeweler Stuff
|
22
|
+
#
|
23
|
+
Jeweler::Tasks.new do |gem|
|
24
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
25
|
+
gem.name = "tom"
|
26
|
+
gem.homepage = "http://github.com/moviepilot/tom"
|
27
|
+
gem.license = "MIT"
|
28
|
+
gem.summary = %Q{Parallel request dispatcher and merger for goliath.io}
|
29
|
+
gem.description = %Q{ Tom uses Goliath to dispatch HTTP requests to multiple other APIs (via Adapters) in parallel. In a next step, a Merger merges the result and responds to the clients request.}
|
30
|
+
gem.email = "jannis@gmail.com"
|
31
|
+
gem.authors = ["Jannis Hermanns"]
|
32
|
+
# dependencies defined in Gemfile
|
33
|
+
end
|
34
|
+
Jeweler::RubygemsDotOrgTasks.new
|
35
|
+
|
36
|
+
|
37
|
+
#
|
38
|
+
# Rcov
|
39
|
+
#
|
40
|
+
require 'rcov/rcovtask'
|
41
|
+
Rcov::RcovTask.new do |test|
|
42
|
+
test.libs << 'test'
|
43
|
+
test.pattern = 'test/**/test_*.rb'
|
44
|
+
test.verbose = true
|
45
|
+
test.rcov_opts << '--exclude "gems/*"'
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
#
|
50
|
+
# RSpec
|
51
|
+
#
|
52
|
+
task :default => [:spec]
|
53
|
+
task :test => [:spec]
|
54
|
+
desc "run spec tests"
|
55
|
+
RSpec::Core::RakeTask.new('spec') do |t|
|
56
|
+
t.pattern = 'spec/**/*_spec.rb'
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
#
|
61
|
+
# Yard
|
62
|
+
#
|
63
|
+
desc 'Generate documentation'
|
64
|
+
YARD::Rake::YardocTask.new do |t|
|
65
|
+
t.files = ['lib/**/*.rb', '-', 'LICENSE']
|
66
|
+
t.options = ['--main', 'README.md', '--no-private']
|
67
|
+
end
|
68
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/adapter.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'em-synchrony/em-http'
|
2
|
+
require_relative 'http'
|
3
|
+
|
4
|
+
module Tom
|
5
|
+
class Adapter
|
6
|
+
class << self
|
7
|
+
attr_accessor :host
|
8
|
+
end
|
9
|
+
|
10
|
+
#
|
11
|
+
# Registers a route with the request dispatcher
|
12
|
+
# so that this classes subclass gets called when
|
13
|
+
# a request is made. One that matches the route.
|
14
|
+
#
|
15
|
+
# The route can be a string, but it becomes a
|
16
|
+
# regular expression in here, followed by methods.
|
17
|
+
#
|
18
|
+
def self.register_route(*args)
|
19
|
+
route = args[0]
|
20
|
+
methods = args[1..-1]
|
21
|
+
Dispatcher.register(route: /#{route}/, adapter: self, methods: methods)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@request = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Takes a request from rack and issues the same
|
30
|
+
# request again, just to a different host. This
|
31
|
+
# method is to be used by subclasses.
|
32
|
+
#
|
33
|
+
def forward_request(env)
|
34
|
+
rewrite_request(env)
|
35
|
+
options = http_request_options(env)
|
36
|
+
url = @request[:host] + @request[:uri]
|
37
|
+
|
38
|
+
result = Tom::Http.make_request(@request[:method], url, options)
|
39
|
+
|
40
|
+
headers = {"Downstream-Url" => url}.merge result.response_header
|
41
|
+
[result.response_header.status, headers, result.response]
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Takes a request and generates the options for calling
|
46
|
+
# HttpRequest.put (or whatever the the requested
|
47
|
+
# REQUEST_METHOD is).
|
48
|
+
#
|
49
|
+
# It's content depends on the request method, for PUTs
|
50
|
+
# and POSTs this will add the request body
|
51
|
+
#
|
52
|
+
def http_request_options(env)
|
53
|
+
opts = {}
|
54
|
+
if [:put, :post].include? @request[:method]
|
55
|
+
opts[:body] = @request[:body] || extract_request_body(env)
|
56
|
+
end
|
57
|
+
opts
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Extracts the given POT/PUST (hehe) body
|
62
|
+
#
|
63
|
+
def extract_request_body(env)
|
64
|
+
Rack::Request.new(env).POST.keys.first rescue "{}"
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Takes a request from rack and extracts the request
|
69
|
+
# method, uri and returns the host this adapter talks
|
70
|
+
# to. Can be overwritten if you want to change stuff
|
71
|
+
# before forwarding it.
|
72
|
+
#
|
73
|
+
def rewrite_request(env)
|
74
|
+
rewritten = rewrite_host(env)
|
75
|
+
@request = rewritten.merge(@request)
|
76
|
+
end
|
77
|
+
|
78
|
+
def handle(env)
|
79
|
+
raise "Subclass, implement #handle(env)!"
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
#
|
85
|
+
# Returns a hash that can be used as the @request variable,
|
86
|
+
# which is exactly like the given env except for a changed
|
87
|
+
# hostname.
|
88
|
+
#
|
89
|
+
def rewrite_host(env)
|
90
|
+
{ host: self.class.host,
|
91
|
+
uri: env["REQUEST_URI"],
|
92
|
+
method: env["REQUEST_METHOD"].downcase.to_sym
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/dispatcher.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
require "em-synchrony/fiber_iterator"
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Tom
|
5
|
+
|
6
|
+
LOG = ::Logger.new(STDOUT)
|
7
|
+
LOG.level = ::Logger::ERROR
|
8
|
+
LOG.datetime_format = "%H:%M:%S:"
|
9
|
+
Logger::Formatter.module_eval(
|
10
|
+
%q{ def call(severity, time, progname, msg)} +
|
11
|
+
%q{ "#{format_datetime(time)} #{msg2str(msg)}\n" end}
|
12
|
+
)
|
13
|
+
|
14
|
+
class Dispatcher
|
15
|
+
|
16
|
+
#
|
17
|
+
# Dispatches this request to all adapters that registered
|
18
|
+
# for the route and then calls the merger for this route
|
19
|
+
# to compose a response
|
20
|
+
#
|
21
|
+
def self.dispatch(env)
|
22
|
+
adapters = adapters_for_route(env)
|
23
|
+
return [404, {}, '{reason: "No adapters for this route"}'] if adapters.empty?
|
24
|
+
|
25
|
+
# Hit APIs. All at the same time. Oh, mygodd!
|
26
|
+
responses = {}
|
27
|
+
Tom::LOG.info "#{env['REQUEST_METHOD'].upcase} #{env['REQUEST_URI']}"
|
28
|
+
Tom::LOG.info "Dispatching to:"
|
29
|
+
EM::Synchrony::FiberIterator.new(adapters, adapters.count).map do |clazz|
|
30
|
+
Tom::LOG.info " -> #{clazz}"
|
31
|
+
(responses[clazz] ||= []) << clazz.new.handle(env)
|
32
|
+
end
|
33
|
+
|
34
|
+
merged = merge(env, responses)
|
35
|
+
Tom::LOG.info "-------------------------------------------------------n"
|
36
|
+
merged
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Takes a request (rack env) and a couple of responses
|
41
|
+
# generated by api adapters and composes a response for the
|
42
|
+
# client.
|
43
|
+
#
|
44
|
+
# The merger used depends on the route.
|
45
|
+
#
|
46
|
+
def self.merge(env, responses)
|
47
|
+
merger = merger_for_route(env)
|
48
|
+
Tom::LOG.info "Merging with:"
|
49
|
+
Tom::LOG.info " -> #{merger}"
|
50
|
+
merger.new.merge env, responses
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Registers a opts[:adapter] or opts[:merger] for the
|
55
|
+
# given opts[:route].
|
56
|
+
#
|
57
|
+
# This method should not be called directly, use register_route
|
58
|
+
# in Tom::Adapter or Tom::Merger instead.
|
59
|
+
#
|
60
|
+
def self.register(opts)
|
61
|
+
return register_adapter(opts) if opts[:adapter]
|
62
|
+
return register_merger(opts) if opts[:merger]
|
63
|
+
raise "You need to supply opts[:adapter] or opts[:merger]"
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
#
|
69
|
+
# Registers an adapter for a given route and request method
|
70
|
+
#
|
71
|
+
def self.register_adapter(opts)
|
72
|
+
validate_type(opts[:adapter], Adapter)
|
73
|
+
methods = get_methods(opts)
|
74
|
+
@adapters ||= default_methods_hash
|
75
|
+
methods.each do |method|
|
76
|
+
@adapters[method][opts[:route]] ||= []
|
77
|
+
@adapters[method][opts[:route]] << opts[:adapter]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Registers merger for a given route and request method
|
83
|
+
#
|
84
|
+
def self.register_merger(opts)
|
85
|
+
validate_type(opts[:merger], Merger)
|
86
|
+
methods = get_methods(opts)
|
87
|
+
@mergers ||= default_methods_hash
|
88
|
+
methods.each do |method|
|
89
|
+
@mergers[method][opts[:route]] ||= []
|
90
|
+
@mergers[method][opts[:route]] << opts[:merger]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Fetches the methods from the options hash, defaults
|
96
|
+
# to all methods.
|
97
|
+
#
|
98
|
+
def self.get_methods(opts)
|
99
|
+
return opts[:methods] unless opts[:methods].empty?
|
100
|
+
[:head, :get, :put, :post, :delete]
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# Just some defaults to initialize thing
|
105
|
+
#
|
106
|
+
def self.default_methods_hash
|
107
|
+
{ head: {},
|
108
|
+
get: {},
|
109
|
+
put: {},
|
110
|
+
post: {},
|
111
|
+
delete: {}
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# Find the right adapter for a route
|
117
|
+
#
|
118
|
+
def self.adapters_for_route(env)
|
119
|
+
@adapters ||= default_methods_hash
|
120
|
+
route, method = route_and_method(env)
|
121
|
+
matches = []
|
122
|
+
@adapters[method].map do |reg_route, adapters|
|
123
|
+
next unless reg_route.match(route)
|
124
|
+
matches += adapters
|
125
|
+
end
|
126
|
+
matches.uniq
|
127
|
+
end
|
128
|
+
|
129
|
+
#
|
130
|
+
# Find the right merger for a route
|
131
|
+
#
|
132
|
+
def self.merger_for_route(env)
|
133
|
+
@mergers ||= default_methods_hash
|
134
|
+
route, method = route_and_method(env)
|
135
|
+
@mergers[method].each do |reg_route, mergers|
|
136
|
+
next unless reg_route.match(route)
|
137
|
+
return mergers.first
|
138
|
+
end
|
139
|
+
raise "Found no merger for route #{route}"
|
140
|
+
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# Extract the route/request uri and the method from a
|
144
|
+
# rack env
|
145
|
+
#
|
146
|
+
def self.route_and_method(env)
|
147
|
+
[env["REQUEST_PATH"],
|
148
|
+
env["REQUEST_METHOD"].downcase.to_sym]
|
149
|
+
end
|
150
|
+
|
151
|
+
#
|
152
|
+
# Make sure one class is a subclass of another class
|
153
|
+
#
|
154
|
+
def self.validate_type(c, expected)
|
155
|
+
return if c < expected
|
156
|
+
raise "Invalid type. Expected #{expected} got #{c}"
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
data/lib/http.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Tom
|
2
|
+
module Http
|
3
|
+
|
4
|
+
#
|
5
|
+
# Makes a http request of the given method to the given url.
|
6
|
+
# Passes the options on to EM::HttpRequest.put (or whatever
|
7
|
+
# method has to be called) and does some error handling and
|
8
|
+
# works around some EM:HttpRequest oddities (see handle_errors).
|
9
|
+
#
|
10
|
+
def self.make_request(method, url, options = {})
|
11
|
+
Tom::LOG.info " curl -X#{method.upcase} -d '#{options[:body]}' #{url}"
|
12
|
+
|
13
|
+
conn = EM::HttpRequest.new(url, connection_options)
|
14
|
+
result = conn.send(method, options)
|
15
|
+
handle_errors(method, url, result)
|
16
|
+
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def self.connection_options
|
23
|
+
{ connect_timeout: Tom.config[:timeouts][:connect_timeout],
|
24
|
+
inactivity_timeout: Tom.config[:timeouts][:inactivity_timeout]}
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.handle_errors(method, url, result)
|
28
|
+
result.errback do
|
29
|
+
raise "Tom::Adapter.forward_request error #{method} #{url}"
|
30
|
+
end
|
31
|
+
return unless result.response_header.status == 0
|
32
|
+
raise "EM::HttpRequest returned response code 0 for #{url} - timeout?"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/init.rb
ADDED
data/lib/merger.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Tom
|
2
|
+
class Merger
|
3
|
+
|
4
|
+
#
|
5
|
+
# Registers a route with the request dispatcher
|
6
|
+
# so that this classes subclass gets called when
|
7
|
+
# a request is made. One that matches the route.
|
8
|
+
#
|
9
|
+
# The route can be a string, but it becomes a
|
10
|
+
# regular expression in here. When matching in
|
11
|
+
# order to find a merger for a request, the first
|
12
|
+
# one matching wins.
|
13
|
+
#
|
14
|
+
def self.register_route(*args)
|
15
|
+
route = args[0]
|
16
|
+
methods = args[1..-1]
|
17
|
+
Dispatcher.register(route: /#{route}/, merger: self, methods: methods)
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# When the request dispatcher made all the requests,
|
22
|
+
# it will call the merge method of the subclass with
|
23
|
+
# the responses as a hash in the form
|
24
|
+
#
|
25
|
+
# {MyAdapter: rack_env, MyOtherAdapter: other_env}
|
26
|
+
#
|
27
|
+
# Has to return a rack response (for example, something
|
28
|
+
# like [200, {}, "body"])
|
29
|
+
#
|
30
|
+
def merge(env, responses)
|
31
|
+
raise "Subclass, implement #merge(env, responses)!"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/tom.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require_relative 'init'
|
2
|
+
|
3
|
+
# Init Goliath env unless it was done already
|
4
|
+
Goliath.env rescue Goliath.env = (ENV['RACK_ENV'] || 'development').to_sym
|
5
|
+
|
6
|
+
# In dev mode, we do some logging (defaults to Logger::ERROR in other
|
7
|
+
# envs)
|
8
|
+
if Goliath.env == :development
|
9
|
+
Tom::LOG.level = Logger::INFO
|
10
|
+
end
|
11
|
+
Tom::LOG.info "Started goliath in #{Goliath.env} environment (change with ruby your_app.rb -e development or by setting $RACK_ENV)"
|
12
|
+
|
13
|
+
module Tom
|
14
|
+
|
15
|
+
def self.config
|
16
|
+
@config || default_config
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.config=(config)
|
20
|
+
@config = config
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# WE HAZ ALL TEH GOLIATH REQUESTS AND FORWARDETH
|
25
|
+
# THEM TO DEH DISPATCHERETH.
|
26
|
+
#
|
27
|
+
# We have to see if this is the right way to do
|
28
|
+
# it when it comes to parallel stuff and so on...
|
29
|
+
#
|
30
|
+
class GoliathAPI < Goliath::API
|
31
|
+
use Goliath::Rack::JSONP
|
32
|
+
use Goliath::Rack::Params
|
33
|
+
use Goliath::Rack::Formatters::JSON
|
34
|
+
use Goliath::Rack::Render
|
35
|
+
|
36
|
+
def response(env)
|
37
|
+
begin
|
38
|
+
Tom::Dispatcher.dispatch(env)
|
39
|
+
rescue => e
|
40
|
+
handle_exception e, env
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def handle_exception(e, env)
|
45
|
+
trace = e.backtrace.join "\n"
|
46
|
+
Tom::LOG.info e
|
47
|
+
Tom::LOG.info trace
|
48
|
+
[500, {}, {error: e,
|
49
|
+
stacktrace: trace,
|
50
|
+
url: env["REQUEST_URI"]
|
51
|
+
}.to_json]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def self.default_config
|
58
|
+
{ timeouts:
|
59
|
+
{ connect_timeout: 5,
|
60
|
+
inactivity_timeout: 10 } }
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
class ForwardingAdapter1 < Tom::Adapter
|
4
|
+
register_route "^/manatees/[0-9]+$"
|
5
|
+
def handle(env)
|
6
|
+
forward_request(env)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
ForwardingAdapter1.host = 'http://webmocked-host-1.com'
|
10
|
+
|
11
|
+
describe Tom::Adapter do
|
12
|
+
|
13
|
+
let(:env){ {'REQUEST_URI' => '/manatees/15?foo',
|
14
|
+
'REQUEST_METHOD' => 'GET'} }
|
15
|
+
|
16
|
+
it "forward_request uses rewrite_request to change the host" do
|
17
|
+
rewritten = {host: "http://webmocked-host-1.com", uri: "/o", method: "get" }
|
18
|
+
ForwardingAdapter1.any_instance.should_receive(:rewrite_host).and_return(rewritten)
|
19
|
+
with_api(Tom::GoliathAPI) do
|
20
|
+
request = get_request(:path => '/manatees/15?foo')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "rewrite_request changes the host" do
|
25
|
+
rewritten = ForwardingAdapter1.new.rewrite_request(env)
|
26
|
+
|
27
|
+
rewritten[:host].should == "http://webmocked-host-1.com"
|
28
|
+
rewritten[:uri].should == "/manatees/15?foo"
|
29
|
+
rewritten[:method].should == :get
|
30
|
+
end
|
31
|
+
|
32
|
+
it "rubs the lotion on its skin" do
|
33
|
+
true
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
class APIAdapter1 < Tom::Adapter
|
4
|
+
register_route "^/manatees/[0-9]+$"
|
5
|
+
def handle(env);end
|
6
|
+
end
|
7
|
+
APIAdapter1.host = 'http://api_host_1.com'
|
8
|
+
|
9
|
+
class APIAdapter2 < Tom::Adapter
|
10
|
+
register_route "^/manatees/[0-9]+$"
|
11
|
+
def handle(env);end
|
12
|
+
end
|
13
|
+
APIAdapter2.host = 'http://api_host_2.com'
|
14
|
+
|
15
|
+
class Merger < Tom::Merger
|
16
|
+
register_route ".*"
|
17
|
+
end
|
18
|
+
|
19
|
+
describe Tom do
|
20
|
+
|
21
|
+
before(:each) do
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
it "emits a 404 when there are no adapters for the route" do
|
26
|
+
with_api(Tom::GoliathAPI) do
|
27
|
+
request = get_request(:path => '/walruses/5')
|
28
|
+
request.response_header.status.should == 404
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "makes requests to all registered adapters" do
|
33
|
+
expected_rack_env = hash_including("REQUEST_URI" => "/manatees/15",
|
34
|
+
"REQUEST_METHOD" => "GET")
|
35
|
+
|
36
|
+
APIAdapter1.any_instance.should_receive(:handle).with(expected_rack_env).and_return true
|
37
|
+
APIAdapter2.any_instance.should_receive(:handle).with(expected_rack_env).and_return true
|
38
|
+
|
39
|
+
with_api(Tom::GoliathAPI) do
|
40
|
+
request = get_request(:path => '/manatees/15')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Until we figure out how to properly mock EM:HttpRequest's with em-synchrony
|
45
|
+
# and Webmock, testing doesn't make too much sense here. So we'll discard it
|
46
|
+
# until then.
|
47
|
+
it "merges the result"
|
48
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
|
3
|
+
Bundler.setup
|
4
|
+
Bundler.require :default, :test
|
5
|
+
|
6
|
+
require 'goliath/test_helper'
|
7
|
+
require 'ruby-debug'
|
8
|
+
require 'webmock/rspec'
|
9
|
+
|
10
|
+
Goliath.env = :test
|
11
|
+
RSpec.configure do |c|
|
12
|
+
c.include Goliath::TestHelper, :example_group => {
|
13
|
+
:file_path => /spec/
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
# Until we figured out the Webmock/em-synchrony/em-http-request woes
|
19
|
+
config.before(:suite) do
|
20
|
+
WebMock.allow_net_connect!
|
21
|
+
end
|
22
|
+
|
23
|
+
config.before(:each) do
|
24
|
+
# I don't always make http requests. But when I do,
|
25
|
+
# they are successful.
|
26
|
+
stub_request(:any, /.*webmocked-host.*/)
|
27
|
+
end
|
28
|
+
|
29
|
+
config.after(:each) do
|
30
|
+
EM.stop rescue nil
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
require_relative '../lib/tom'
|
36
|
+
|
37
|
+
class Merger < Tom::Merger
|
38
|
+
register_route ".*"
|
39
|
+
def merge(a,b);[200, {}, ""]; end
|
40
|
+
end
|
data/tom.gemspec
ADDED
@@ -0,0 +1,96 @@
|
|
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 = "tom"
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Jannis Hermanns"]
|
12
|
+
s.date = "2011-12-02"
|
13
|
+
s.description = " Tom uses Goliath to dispatch HTTP requests to multiple other APIs (via Adapters) in parallel. In a next step, a Merger merges the result and responds to the clients request."
|
14
|
+
s.email = "jannis@gmail.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rvmrc",
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
24
|
+
"LICENSE.txt",
|
25
|
+
"README.markdown",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"lib/adapter.rb",
|
29
|
+
"lib/dispatcher.rb",
|
30
|
+
"lib/http.rb",
|
31
|
+
"lib/init.rb",
|
32
|
+
"lib/merger.rb",
|
33
|
+
"lib/tom.rb",
|
34
|
+
"spec/lib/adapter_spec.rb",
|
35
|
+
"spec/lib/dispatcher_spec.rb",
|
36
|
+
"spec/spec_helper.rb",
|
37
|
+
"tom.gemspec"
|
38
|
+
]
|
39
|
+
s.homepage = "http://github.com/moviepilot/tom"
|
40
|
+
s.licenses = ["MIT"]
|
41
|
+
s.require_paths = ["lib"]
|
42
|
+
s.rubygems_version = "1.8.10"
|
43
|
+
s.summary = "Parallel request dispatcher and merger for goliath.io"
|
44
|
+
|
45
|
+
if s.respond_to? :specification_version then
|
46
|
+
s.specification_version = 3
|
47
|
+
|
48
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
49
|
+
s.add_runtime_dependency(%q<goliath>, [">= 0"])
|
50
|
+
s.add_runtime_dependency(%q<em-synchrony>, [">= 0"])
|
51
|
+
s.add_runtime_dependency(%q<em-http-request>, [">= 0"])
|
52
|
+
s.add_runtime_dependency(%q<mp-deployment>, ["= 0.0.21"])
|
53
|
+
s.add_runtime_dependency(%q<json>, [">= 0"])
|
54
|
+
s.add_runtime_dependency(%q<rake>, [">= 0"])
|
55
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
56
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
|
57
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
58
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
59
|
+
s.add_development_dependency(%q<ruby-debug19>, [">= 0"])
|
60
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
61
|
+
s.add_development_dependency(%q<webmock>, [">= 0"])
|
62
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<goliath>, [">= 0"])
|
65
|
+
s.add_dependency(%q<em-synchrony>, [">= 0"])
|
66
|
+
s.add_dependency(%q<em-http-request>, [">= 0"])
|
67
|
+
s.add_dependency(%q<mp-deployment>, ["= 0.0.21"])
|
68
|
+
s.add_dependency(%q<json>, [">= 0"])
|
69
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
70
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
71
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
72
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
73
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
74
|
+
s.add_dependency(%q<ruby-debug19>, [">= 0"])
|
75
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
76
|
+
s.add_dependency(%q<webmock>, [">= 0"])
|
77
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
78
|
+
end
|
79
|
+
else
|
80
|
+
s.add_dependency(%q<goliath>, [">= 0"])
|
81
|
+
s.add_dependency(%q<em-synchrony>, [">= 0"])
|
82
|
+
s.add_dependency(%q<em-http-request>, [">= 0"])
|
83
|
+
s.add_dependency(%q<mp-deployment>, ["= 0.0.21"])
|
84
|
+
s.add_dependency(%q<json>, [">= 0"])
|
85
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
86
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
87
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
88
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
89
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
90
|
+
s.add_dependency(%q<ruby-debug19>, [">= 0"])
|
91
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
92
|
+
s.add_dependency(%q<webmock>, [">= 0"])
|
93
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
metadata
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tom
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jannis Hermanns
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-02 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: goliath
|
16
|
+
requirement: &70256849153920 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70256849153920
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: em-synchrony
|
27
|
+
requirement: &70256849153340 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70256849153340
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: em-http-request
|
38
|
+
requirement: &70256849152840 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70256849152840
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: mp-deployment
|
49
|
+
requirement: &70256849152280 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - =
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.0.21
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70256849152280
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: json
|
60
|
+
requirement: &70256849151680 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70256849151680
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: &70256849151080 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70256849151080
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: bundler
|
82
|
+
requirement: &70256849150560 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ~>
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: 1.0.0
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70256849150560
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: jeweler
|
93
|
+
requirement: &70256849150000 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ~>
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 1.6.4
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *70256849150000
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: shoulda
|
104
|
+
requirement: &70256849149420 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: *70256849149420
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: rcov
|
115
|
+
requirement: &70256849148740 !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ! '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
type: :development
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: *70256849148740
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: ruby-debug19
|
126
|
+
requirement: &70256849148120 !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ! '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: *70256849148120
|
135
|
+
- !ruby/object:Gem::Dependency
|
136
|
+
name: rspec
|
137
|
+
requirement: &70256849147600 !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
type: :development
|
144
|
+
prerelease: false
|
145
|
+
version_requirements: *70256849147600
|
146
|
+
- !ruby/object:Gem::Dependency
|
147
|
+
name: webmock
|
148
|
+
requirement: &70256849147020 !ruby/object:Gem::Requirement
|
149
|
+
none: false
|
150
|
+
requirements:
|
151
|
+
- - ! '>='
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
type: :development
|
155
|
+
prerelease: false
|
156
|
+
version_requirements: *70256849147020
|
157
|
+
- !ruby/object:Gem::Dependency
|
158
|
+
name: yard
|
159
|
+
requirement: &70256849146400 !ruby/object:Gem::Requirement
|
160
|
+
none: false
|
161
|
+
requirements:
|
162
|
+
- - ! '>='
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '0'
|
165
|
+
type: :development
|
166
|
+
prerelease: false
|
167
|
+
version_requirements: *70256849146400
|
168
|
+
description: ! ' Tom uses Goliath to dispatch HTTP requests to multiple other APIs
|
169
|
+
(via Adapters) in parallel. In a next step, a Merger merges the result and responds
|
170
|
+
to the clients request.'
|
171
|
+
email: jannis@gmail.com
|
172
|
+
executables: []
|
173
|
+
extensions: []
|
174
|
+
extra_rdoc_files:
|
175
|
+
- LICENSE.txt
|
176
|
+
- README.markdown
|
177
|
+
files:
|
178
|
+
- .document
|
179
|
+
- .rvmrc
|
180
|
+
- Gemfile
|
181
|
+
- Gemfile.lock
|
182
|
+
- LICENSE.txt
|
183
|
+
- README.markdown
|
184
|
+
- Rakefile
|
185
|
+
- VERSION
|
186
|
+
- lib/adapter.rb
|
187
|
+
- lib/dispatcher.rb
|
188
|
+
- lib/http.rb
|
189
|
+
- lib/init.rb
|
190
|
+
- lib/merger.rb
|
191
|
+
- lib/tom.rb
|
192
|
+
- spec/lib/adapter_spec.rb
|
193
|
+
- spec/lib/dispatcher_spec.rb
|
194
|
+
- spec/spec_helper.rb
|
195
|
+
- tom.gemspec
|
196
|
+
homepage: http://github.com/moviepilot/tom
|
197
|
+
licenses:
|
198
|
+
- MIT
|
199
|
+
post_install_message:
|
200
|
+
rdoc_options: []
|
201
|
+
require_paths:
|
202
|
+
- lib
|
203
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
204
|
+
none: false
|
205
|
+
requirements:
|
206
|
+
- - ! '>='
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
segments:
|
210
|
+
- 0
|
211
|
+
hash: -4016356889838983520
|
212
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
213
|
+
none: false
|
214
|
+
requirements:
|
215
|
+
- - ! '>='
|
216
|
+
- !ruby/object:Gem::Version
|
217
|
+
version: '0'
|
218
|
+
requirements: []
|
219
|
+
rubyforge_project:
|
220
|
+
rubygems_version: 1.8.10
|
221
|
+
signing_key:
|
222
|
+
specification_version: 3
|
223
|
+
summary: Parallel request dispatcher and merger for goliath.io
|
224
|
+
test_files: []
|