asap 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +3 -0
- data/LICENSE +7 -0
- data/README +82 -0
- data/Rakefile +5 -0
- data/asap.gemspec +30 -0
- data/bin/asap-em-test-server +43 -0
- data/bin/asap-mongrel-test-server +70 -0
- data/examples/followers-example-serial.rb +29 -0
- data/examples/followers-example.out +21 -0
- data/examples/followers-example.rb +22 -0
- data/java/netty-3.2.4.Final.jar +0 -0
- data/lib/asap.rb +8 -0
- data/lib/asap/fetch_context.rb +33 -0
- data/lib/asap/netty.rb +51 -0
- data/lib/asap/netty/http_response_handler.rb +21 -0
- data/lib/asap/netty/pipeline_factory.rb +20 -0
- data/lib/asap/version.rb +5 -0
- data/spec/asap/netty_spec.rb +14 -0
- data/spec/asap_spec.rb +149 -0
- data/spec/spec_helper.rb +4 -0
- metadata +114 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2011 Greg Spurrier, Avik Das
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
== ASAP - a Parallel Fetch Library =====================================
|
2
|
+
|
3
|
+
by Greg Spurrier, Avik Das
|
4
|
+
|
5
|
+
== DESCRIPTION =========================================================
|
6
|
+
|
7
|
+
ASAP is a JRuby library built on top of Netty and Java's NIO classes. It
|
8
|
+
provides an embedded domain specific language for specifying a list of
|
9
|
+
resources to fetch, all in parallel, as well specify a dependency tree
|
10
|
+
in order to use previous results in calculating subsequent ones. The
|
11
|
+
results of all the requests are then automatically collected into a
|
12
|
+
simple tree-like data structure that has a one-to-one mapping to the
|
13
|
+
tree structure specified by the code, despite the fact that results may
|
14
|
+
arrive in an unspecified order.
|
15
|
+
|
16
|
+
== EXAMPLE =============================================================
|
17
|
+
|
18
|
+
Assume that a server is running on http://0.0.0.0:1234, which maps the
|
19
|
+
paths /user/followers and /user/followers/N to a list of five numbers and
|
20
|
+
data related to the Nth follower respectively.
|
21
|
+
|
22
|
+
require 'asap'
|
23
|
+
|
24
|
+
data = Asap do
|
25
|
+
get 'http://0.0.0.0:1234/user/followers' do |followers|
|
26
|
+
followers = followers.split("\n").map(&:to_i)
|
27
|
+
|
28
|
+
# get the first 3 followers
|
29
|
+
get "http://0.0.0.0:1234/user/followers/#{followers[0]}"
|
30
|
+
get "http://0.0.0.0:1234/user/followers/#{followers[1]}"
|
31
|
+
get "http://0.0.0.0:1234/user/followers/#{followers[2]}"
|
32
|
+
|
33
|
+
# or you can use a map
|
34
|
+
followers[3,2].each do |fi|
|
35
|
+
get "http://0.0.0.0:1234/user/followers/#{fi}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
p data
|
41
|
+
|
42
|
+
# => [["93\n5\n64\n74\n11",
|
43
|
+
["Follower #93",
|
44
|
+
"Follower #5" ,
|
45
|
+
"Follower #64",
|
46
|
+
"Follower #74",
|
47
|
+
"Follower #11"]]]
|
48
|
+
|
49
|
+
== QUICK START =========================================================
|
50
|
+
|
51
|
+
gem install asap
|
52
|
+
|
53
|
+
== DEVELOPMENT QUICK START =============================================
|
54
|
+
|
55
|
+
# Check out repository
|
56
|
+
git clone git://github.com/avik-das/asap.git
|
57
|
+
cd asap
|
58
|
+
|
59
|
+
# Make sure you're using JRuby
|
60
|
+
|
61
|
+
# Install the dependencies
|
62
|
+
gem install bundler
|
63
|
+
bundle install
|
64
|
+
rake install
|
65
|
+
|
66
|
+
# start the server (run in a separate window)
|
67
|
+
asap-mongrel-test-server
|
68
|
+
|
69
|
+
# run the tests
|
70
|
+
rake spec
|
71
|
+
|
72
|
+
# compare the times required to fetch the same data if it is retrieved
|
73
|
+
# serially versus if it is retrieved with ASAP.
|
74
|
+
time examples/followers-example-serial.rb
|
75
|
+
time examples/followers-example.rb
|
76
|
+
|
77
|
+
# There is an alternate, EventMachine-based server, but it does not
|
78
|
+
# implement the /user/followers/ routes. EventMachine does not run
|
79
|
+
# well with JRuby, while the main library requires JRuby. However,
|
80
|
+
# the EventMachine-based server runs well on MRI, assuming the
|
81
|
+
# correct gems are installed (see the gemspec).
|
82
|
+
script/em_test_server.rb
|
data/Rakefile
ADDED
data/asap.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "asap/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "asap"
|
7
|
+
s.version = Asap::Gem::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Greg Spurrier", "Avik Das"]
|
10
|
+
s.email = ["gspurrier@linkedin.com", "adas@linkedin.com"]
|
11
|
+
s.homepage = "http://rubygems.org/gems/didactic_clock"
|
12
|
+
s.summary = %q{A JRuby library for parallel fetches.}
|
13
|
+
s.description = %q{A JRuby library for parallel fetches. Provides an embedded-domain specific language for declaring dependencies between resources.}
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_runtime_dependency("mongrel")
|
21
|
+
|
22
|
+
s.add_development_dependency("rspec")
|
23
|
+
s.add_development_dependency("rake")
|
24
|
+
|
25
|
+
# Event machine does not run well on JRuby, but the main library requires
|
26
|
+
# JRuby. The following gems should be installed separately on an MRI
|
27
|
+
# instance if you wish to run bin/asap-em-test-server
|
28
|
+
# s.add_runtime_dependency('eventmachine')
|
29
|
+
# s.add_runtime_dependency('eventmachine_httpserver')
|
30
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'evma_httpserver'
|
5
|
+
require 'cgi'
|
6
|
+
|
7
|
+
class MyHttpServer < EM::Connection
|
8
|
+
include EM::HttpServer
|
9
|
+
|
10
|
+
def post_init
|
11
|
+
super
|
12
|
+
no_environment_strings
|
13
|
+
end
|
14
|
+
|
15
|
+
def process_http_request
|
16
|
+
# the http request details are available via the following instance variables:
|
17
|
+
# @http_protocol
|
18
|
+
# @http_request_method
|
19
|
+
# @http_cookie
|
20
|
+
# @http_if_none_match
|
21
|
+
# @http_content_type
|
22
|
+
# @http_path_info
|
23
|
+
# @http_request_uri
|
24
|
+
# @http_query_string
|
25
|
+
# @http_post_content
|
26
|
+
# @http_headers
|
27
|
+
|
28
|
+
empty, time, message = @http_path_info.split('/')
|
29
|
+
|
30
|
+
response = EM::DelegatedHttpResponse.new(self)
|
31
|
+
response.status = 200
|
32
|
+
response.content_type 'text/html'
|
33
|
+
response.content = CGI.unescape(message)
|
34
|
+
|
35
|
+
EventMachine::Timer.new(time.to_i) do
|
36
|
+
response.send_response
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
EventMachine.run{
|
42
|
+
EventMachine.start_server '0.0.0.0', 1234, MyHttpServer
|
43
|
+
}
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'mongrel'
|
5
|
+
|
6
|
+
class TimeMessageHandler < Mongrel::HttpHandler
|
7
|
+
def process request, response
|
8
|
+
response.start 200 do |head,out|
|
9
|
+
params = get_params request
|
10
|
+
|
11
|
+
wait_time = params[:wait_time].to_i
|
12
|
+
puts "Sleeping for #{wait_time} seconds"
|
13
|
+
sleep wait_time
|
14
|
+
|
15
|
+
head["Content-Type"] = "text/plain"
|
16
|
+
out.write params[:message] # no newline
|
17
|
+
out.flush
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def get_params request
|
24
|
+
uri = request.params['REQUEST_URI']
|
25
|
+
_, wait_time, message = *uri.split("/")
|
26
|
+
message = Mongrel::HttpRequest.unescape message
|
27
|
+
{:wait_time => wait_time, :message => message}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class FollowersHandler < Mongrel::HttpHandler
|
32
|
+
def process request, response
|
33
|
+
sleep 5
|
34
|
+
|
35
|
+
id = get_follower_id(request)
|
36
|
+
message = id ? get_one_follower(id.to_i) : get_all_followers
|
37
|
+
|
38
|
+
response.start 200 do |head,out|
|
39
|
+
head["Content-Type"] = "text/plain"
|
40
|
+
out.write message # no newline
|
41
|
+
out.flush
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
MAX_FOLLOW_ID = 100
|
48
|
+
|
49
|
+
def get_follower_id request
|
50
|
+
uri = request.params['REQUEST_URI']
|
51
|
+
_, _, _, id = *uri.split("/")
|
52
|
+
id
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_all_followers
|
56
|
+
(1..5).map {|i| rand(MAX_FOLLOW_ID)}.join("\n")
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_one_follower i
|
60
|
+
"Follower ##{i}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
h = Mongrel::HttpServer.new "0.0.0.0", "1234"
|
65
|
+
h.register "/user/followers", FollowersHandler.new
|
66
|
+
h.register "/", TimeMessageHandler.new
|
67
|
+
|
68
|
+
puts "Starting server"
|
69
|
+
h.run.join
|
70
|
+
puts "Shutting down server"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'open-uri'
|
4
|
+
|
5
|
+
data = []
|
6
|
+
open('http://0.0.0.0:1234/user/followers') do |fresp|
|
7
|
+
data << fresp.read
|
8
|
+
followers = data[0].split("\n").map(&:to_i)
|
9
|
+
|
10
|
+
# get the first 3 followers
|
11
|
+
open("http://0.0.0.0:1234/user/followers/#{followers[0]}") do |resp|
|
12
|
+
data << resp.read
|
13
|
+
end
|
14
|
+
open("http://0.0.0.0:1234/user/followers/#{followers[1]}") do |resp|
|
15
|
+
data << resp.read
|
16
|
+
end
|
17
|
+
open("http://0.0.0.0:1234/user/followers/#{followers[2]}") do |resp|
|
18
|
+
data << resp.read
|
19
|
+
end
|
20
|
+
|
21
|
+
# or you can use a map
|
22
|
+
followers[3,2].each do |fi|
|
23
|
+
open("http://0.0.0.0:1234/user/followers/#{fi}") do |resp|
|
24
|
+
data << resp.read
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
p data
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
data =
|
5
|
+
[
|
6
|
+
["44\n64\n6\n21\n87", # /user/followers
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
["Follower #44", # /user/followers/44
|
11
|
+
"Follower #64", # /user/followers/64
|
12
|
+
"Follower #6" , # /user/followers/6
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
"Follower #21", "Follower #87"]]] # etc.
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'asap'
|
5
|
+
|
6
|
+
data = Asap do
|
7
|
+
get 'http://0.0.0.0:1234/user/followers' do |followers|
|
8
|
+
followers = followers.split("\n").map(&:to_i)
|
9
|
+
|
10
|
+
# get the first 3 followers
|
11
|
+
get "http://0.0.0.0:1234/user/followers/#{followers[0]}"
|
12
|
+
get "http://0.0.0.0:1234/user/followers/#{followers[1]}"
|
13
|
+
get "http://0.0.0.0:1234/user/followers/#{followers[2]}"
|
14
|
+
|
15
|
+
# or you can use a map
|
16
|
+
followers[3,2].each do |fi|
|
17
|
+
get "http://0.0.0.0:1234/user/followers/#{fi}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
p data
|
Binary file
|
data/lib/asap.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'asap/netty'
|
2
|
+
|
3
|
+
module Asap
|
4
|
+
class FetchContext
|
5
|
+
def initialize
|
6
|
+
@result = []
|
7
|
+
@semaphore = java.util.concurrent.Semaphore.new(0)
|
8
|
+
end
|
9
|
+
|
10
|
+
def get(url, &blk)
|
11
|
+
target_index = @result.size
|
12
|
+
@result << nil
|
13
|
+
Asap::Netty.get(url) do |result|
|
14
|
+
if blk
|
15
|
+
Thread.new do
|
16
|
+
nested = Asap(result, &blk)
|
17
|
+
@result[target_index] = [result, nested]
|
18
|
+
@semaphore.release
|
19
|
+
end
|
20
|
+
else
|
21
|
+
@result[target_index] = result
|
22
|
+
@semaphore.release
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def join
|
28
|
+
@semaphore.acquire(@result.size)
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :result
|
32
|
+
end
|
33
|
+
end
|
data/lib/asap/netty.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'jruby'
|
3
|
+
require 'java'
|
4
|
+
$CLASSPATH << File.expand_path('../../java/netty-3.2.4.Final.jar', File.dirname(__FILE__))
|
5
|
+
|
6
|
+
require 'asap/netty/http_response_handler'
|
7
|
+
require 'asap/netty/pipeline_factory'
|
8
|
+
|
9
|
+
module Asap
|
10
|
+
module Netty
|
11
|
+
java_import java.net.InetSocketAddress
|
12
|
+
java_import java.util.concurrent.Executors
|
13
|
+
java_import org.jboss.netty.bootstrap.ClientBootstrap
|
14
|
+
java_import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory
|
15
|
+
java_import org.jboss.netty.handler.codec.http.DefaultHttpRequest
|
16
|
+
java_import org.jboss.netty.handler.codec.http.HttpVersion
|
17
|
+
java_import org.jboss.netty.handler.codec.http.HttpMethod
|
18
|
+
java_import org.jboss.netty.handler.codec.http.HttpHeaders
|
19
|
+
|
20
|
+
|
21
|
+
def self.get(url, &callback)
|
22
|
+
uri = URI.parse(url)
|
23
|
+
|
24
|
+
bootstrap.set_pipeline_factory(PipelineFactory.new(callback))
|
25
|
+
|
26
|
+
# Open a connection
|
27
|
+
future = bootstrap.connect(InetSocketAddress.new(uri.host, uri.port))
|
28
|
+
channel = future.awaitUninterruptibly.get_channel
|
29
|
+
raise 'connection failed' unless future.is_success
|
30
|
+
|
31
|
+
# Send the request
|
32
|
+
request = DefaultHttpRequest.new(HttpVersion::HTTP_1_0, HttpMethod::GET, uri.path)
|
33
|
+
request.set_header(HttpHeaders::Names::HOST, uri.host)
|
34
|
+
channel.write(request)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def self.bootstrap
|
40
|
+
if not @bootstrap
|
41
|
+
@bootstrap = ClientBootstrap.new(
|
42
|
+
NioClientSocketChannelFactory.new(
|
43
|
+
Executors.newCachedThreadPool,
|
44
|
+
Executors.newCachedThreadPool))
|
45
|
+
|
46
|
+
at_exit { @bootstrap.release_external_resources }
|
47
|
+
end
|
48
|
+
@bootstrap
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Asap
|
2
|
+
module Netty
|
3
|
+
class HttpResponseHandler < org.jboss.netty.channel.SimpleChannelUpstreamHandler
|
4
|
+
attr_reader :callback
|
5
|
+
|
6
|
+
def initialize(callback)
|
7
|
+
super()
|
8
|
+
@callback = callback
|
9
|
+
end
|
10
|
+
|
11
|
+
def messageReceived(ctxt, e)
|
12
|
+
response = e.get_message
|
13
|
+
if response.get_status.get_code == 200
|
14
|
+
callback.call(response.get_content.to_string(org.jboss.netty.util.CharsetUtil::UTF_8))
|
15
|
+
else
|
16
|
+
raise "Request failed with #{response.get_status.get_code}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Asap
|
2
|
+
module Netty
|
3
|
+
class PipelineFactory
|
4
|
+
include org.jboss.netty.channel.ChannelPipelineFactory
|
5
|
+
|
6
|
+
attr_reader :callback
|
7
|
+
|
8
|
+
def initialize(callback)
|
9
|
+
@callback = callback
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_pipeline
|
13
|
+
org.jboss.netty.channel.Channels.pipeline.tap do |pipeline|
|
14
|
+
pipeline.add_last("codec", org.jboss.netty.handler.codec.http.HttpClientCodec.new)
|
15
|
+
pipeline.add_last("handler", Asap::Netty::HttpResponseHandler.new(callback))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/asap/version.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Asap::Netty, '.get' do
|
4
|
+
it 'fetches the specified resource and invokes the callback with it' do
|
5
|
+
semaphore = java.util.concurrent.Semaphore.new(0)
|
6
|
+
result = nil
|
7
|
+
Asap::Netty.get("http://localhost:1234/0/hello") do |data|
|
8
|
+
result = data
|
9
|
+
semaphore.release
|
10
|
+
end
|
11
|
+
semaphore.acquire
|
12
|
+
result.should == 'hello'
|
13
|
+
end
|
14
|
+
end
|
data/spec/asap_spec.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
def url path
|
4
|
+
"http://localhost:1234" + path
|
5
|
+
end
|
6
|
+
|
7
|
+
describe Asap do
|
8
|
+
it 'should be a module' do
|
9
|
+
Asap.class.should == Module
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should be callable as a function with a block' do
|
13
|
+
lambda { Asap do; end }.should_not raise_error
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should require a block' do
|
17
|
+
lambda { Asap() }.should raise_error
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'given no gets' do
|
21
|
+
it 'should have an empty result' do
|
22
|
+
Asap do
|
23
|
+
end.should == []
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'given one get' do
|
28
|
+
it 'should return one result' do
|
29
|
+
Asap do
|
30
|
+
get url("/0/hello")
|
31
|
+
end.should == ["hello"]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'given two gets' do
|
36
|
+
it 'should return two results' do
|
37
|
+
Asap do
|
38
|
+
get url("/0/hello")
|
39
|
+
get url("/0/world")
|
40
|
+
end.should == ["hello","world"]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'given three gets' do
|
45
|
+
it 'should return three results' do
|
46
|
+
Asap do
|
47
|
+
get url("/1/goodbye")
|
48
|
+
get url("/1/cruel")
|
49
|
+
get url("/1/world")
|
50
|
+
end.should == ["goodbye","cruel","world"]
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should run in parallel' do
|
54
|
+
start = Time.now
|
55
|
+
Asap do
|
56
|
+
get url("/1/goodbye")
|
57
|
+
get url("/1/cruel")
|
58
|
+
get url("/1/world")
|
59
|
+
end
|
60
|
+
(Time.now - start).should < 2
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'given one nested get' do
|
65
|
+
it 'should return one nested result' do
|
66
|
+
Asap do
|
67
|
+
get url("/0/%2F0%2Fhello") do |resp|
|
68
|
+
get url(resp)
|
69
|
+
end
|
70
|
+
end.should == [["/0/hello", ["hello"]]]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'given two single-nested gets' do
|
75
|
+
it 'should return two single-nested results' do
|
76
|
+
Asap do
|
77
|
+
get url("/0/%2F0%2Fhello") do |resp|
|
78
|
+
get url(resp)
|
79
|
+
end
|
80
|
+
get url("/0/%2F0%2Fworld") do |resp|
|
81
|
+
get url(resp)
|
82
|
+
end
|
83
|
+
end.should == [["/0/hello", ["hello"]], ["/0/world", ["world"]]]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'given a combination of nested and flat gets' do
|
88
|
+
it 'should return the same combination of nested and flat results' do
|
89
|
+
Asap do
|
90
|
+
get url("/0/hello0")
|
91
|
+
get url("/0/%2F0%2Fhello1") do |resp|
|
92
|
+
get url(resp)
|
93
|
+
end
|
94
|
+
get url("/0/world0")
|
95
|
+
get url("/0/%2F0%2Fworld1") do |resp|
|
96
|
+
get url(resp)
|
97
|
+
end
|
98
|
+
end.should == ["hello0",
|
99
|
+
["/0/hello1", ["hello1"]],
|
100
|
+
"world0",
|
101
|
+
["/0/world1", ["world1"]]]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'given deep nesting' do
|
106
|
+
it 'should return a deeply-nested result' do
|
107
|
+
Asap do
|
108
|
+
get url("/0/%2F0%2F%252F0%252F%25252F0%25252Fhello") do |r1|
|
109
|
+
get url(r1) do |r2|
|
110
|
+
get url(r2) do |r3|
|
111
|
+
get url(r3)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end.should ==
|
116
|
+
[["/0/%2F0%2F%252F0%252Fhello", [
|
117
|
+
["/0/%2F0%2Fhello", [
|
118
|
+
["/0/hello", ["hello"]]]]]]]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'given additional arguments' do
|
123
|
+
it 'should pass the arguments to its block' do
|
124
|
+
Asap("/0/hello") do |path|
|
125
|
+
get url(path)
|
126
|
+
end.should == ["hello"]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'given a map over the gets' do
|
131
|
+
it 'should behave like the gets are listed out' do
|
132
|
+
Asap do
|
133
|
+
(1..10).each do |i|
|
134
|
+
get url("/1/#{i}")
|
135
|
+
end
|
136
|
+
end.should == (1..10).to_a.map(&:to_s)
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should behave like the gets are listed out' do
|
140
|
+
start = Time.now
|
141
|
+
Asap do
|
142
|
+
(1..10).each do |i|
|
143
|
+
get url("/1/#{i}")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
(Time.now - start).should < 2
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: asap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.5
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Greg Spurrier
|
9
|
+
- Avik Das
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2011-07-18 00:00:00 -07:00
|
15
|
+
default_executable:
|
16
|
+
dependencies:
|
17
|
+
- !ruby/object:Gem::Dependency
|
18
|
+
name: mongrel
|
19
|
+
prerelease: false
|
20
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
21
|
+
none: false
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: "0"
|
26
|
+
type: :runtime
|
27
|
+
version_requirements: *id001
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rspec
|
30
|
+
prerelease: false
|
31
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
32
|
+
none: false
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: "0"
|
37
|
+
type: :development
|
38
|
+
version_requirements: *id002
|
39
|
+
- !ruby/object:Gem::Dependency
|
40
|
+
name: rake
|
41
|
+
prerelease: false
|
42
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id003
|
50
|
+
description: A JRuby library for parallel fetches. Provides an embedded-domain specific language for declaring dependencies between resources.
|
51
|
+
email:
|
52
|
+
- gspurrier@linkedin.com
|
53
|
+
- adas@linkedin.com
|
54
|
+
executables:
|
55
|
+
- asap-em-test-server
|
56
|
+
- asap-mongrel-test-server
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files: []
|
60
|
+
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE
|
65
|
+
- README
|
66
|
+
- Rakefile
|
67
|
+
- asap.gemspec
|
68
|
+
- bin/asap-em-test-server
|
69
|
+
- bin/asap-mongrel-test-server
|
70
|
+
- examples/followers-example-serial.rb
|
71
|
+
- examples/followers-example.out
|
72
|
+
- examples/followers-example.rb
|
73
|
+
- java/netty-3.2.4.Final.jar
|
74
|
+
- lib/asap.rb
|
75
|
+
- lib/asap/fetch_context.rb
|
76
|
+
- lib/asap/netty.rb
|
77
|
+
- lib/asap/netty/http_response_handler.rb
|
78
|
+
- lib/asap/netty/pipeline_factory.rb
|
79
|
+
- lib/asap/version.rb
|
80
|
+
- spec/asap/netty_spec.rb
|
81
|
+
- spec/asap_spec.rb
|
82
|
+
- spec/spec_helper.rb
|
83
|
+
has_rdoc: true
|
84
|
+
homepage: http://rubygems.org/gems/didactic_clock
|
85
|
+
licenses: []
|
86
|
+
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: "0"
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: "0"
|
104
|
+
requirements: []
|
105
|
+
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 1.5.1
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: A JRuby library for parallel fetches.
|
111
|
+
test_files:
|
112
|
+
- spec/asap/netty_spec.rb
|
113
|
+
- spec/asap_spec.rb
|
114
|
+
- spec/spec_helper.rb
|