son_of_a_batch 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,22 @@
1
+ source "http://rubygems.org"
2
+
3
+ # gem 'goliath', :git => 'https://github.com/postrank-labs/goliath.git'
4
+ gem 'em-synchrony', :git => 'https://github.com/igrigorik/em-synchrony.git'
5
+ gem 'em-http-request', :git => 'https://github.com/igrigorik/em-http-request.git'
6
+ gem 'yajl-ruby', "~> 0.8.2"
7
+ gem 'gorillib', "~> 0.0.4"
8
+
9
+ gem 'rack-respond_to'
10
+ gem 'rack-abstract-format'
11
+
12
+ group :development, :test do
13
+ gem 'bundler', "~> 1.0.12"
14
+ gem 'yard', "~> 0.6.7"
15
+ gem 'jeweler', "~> 1.5.2"
16
+ gem 'rspec', "~> 2.5.0"
17
+ gem 'rcov', ">= 0"
18
+ end
19
+
20
+ group :test do
21
+ gem 'spork', '~> 0.9.0.rc5'
22
+ end
@@ -0,0 +1,66 @@
1
+ GIT
2
+ remote: https://github.com/igrigorik/em-http-request.git
3
+ revision: b9c6b4b03f0b4d78f0826314dc1f39f297debc1d
4
+ specs:
5
+ em-http-request (1.0.0.beta.3)
6
+ addressable (>= 2.2.3)
7
+ em-socksify
8
+ eventmachine (>= 1.0.0.beta.3)
9
+ http_parser.rb (>= 0.5.1)
10
+
11
+ GIT
12
+ remote: https://github.com/igrigorik/em-synchrony.git
13
+ revision: 60887014aa65bc8fb85bcd058c2b5bf938d72ad0
14
+ specs:
15
+ em-synchrony (0.3.0.beta.1)
16
+ eventmachine (>= 1.0.0.beta.1)
17
+
18
+ GEM
19
+ remote: http://rubygems.org/
20
+ specs:
21
+ addressable (2.2.5)
22
+ diff-lcs (1.1.2)
23
+ em-socksify (0.1.0)
24
+ eventmachine
25
+ eventmachine (1.0.0.beta.3)
26
+ git (1.2.5)
27
+ gorillib (0.0.4)
28
+ http_parser.rb (0.5.1)
29
+ jeweler (1.5.2)
30
+ bundler (~> 1.0.0)
31
+ git (>= 1.2.5)
32
+ rake
33
+ rack-abstract-format (0.9.9)
34
+ rack-accept-media-types (0.9)
35
+ rack-respond_to (0.9.8)
36
+ rack-accept-media-types (>= 0.6)
37
+ rake (0.8.7)
38
+ rcov (0.9.9)
39
+ rspec (2.5.0)
40
+ rspec-core (~> 2.5.0)
41
+ rspec-expectations (~> 2.5.0)
42
+ rspec-mocks (~> 2.5.0)
43
+ rspec-core (2.5.1)
44
+ rspec-expectations (2.5.0)
45
+ diff-lcs (~> 1.1.2)
46
+ rspec-mocks (2.5.0)
47
+ spork (0.9.0.rc5)
48
+ yajl-ruby (0.8.2)
49
+ yard (0.6.8)
50
+
51
+ PLATFORMS
52
+ ruby
53
+
54
+ DEPENDENCIES
55
+ bundler (~> 1.0.12)
56
+ em-http-request!
57
+ em-synchrony!
58
+ gorillib (~> 0.0.4)
59
+ jeweler (~> 1.5.2)
60
+ rack-abstract-format
61
+ rack-respond_to
62
+ rcov
63
+ rspec (~> 2.5.0)
64
+ spork (~> 0.9.0.rc5)
65
+ yajl-ruby (~> 0.8.2)
66
+ yard (~> 0.6.7)
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Philip (flip) Kromer for Infochimps
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.
@@ -0,0 +1,65 @@
1
+ h1. son_of_a_batch
2
+
3
+ Smelt from a plentiferous gallimaufry of requests an agglomerated bale of responses. With, y'know, concurrency and all that.
4
+
5
+ Install this as a shim to allow batch requests against a remote API.
6
+
7
+ h2.
8
+
9
+
10
+ h4. Results vs Errors
11
+
12
+ Note that a request may do any of the following:
13
+
14
+ * The request might *complete* and be *successful* (eg @200 OK@). Each of these will be in the "results" block, with its status code set appropriately.
15
+ * The request might *complete* but be *unsuccessful* (eg @404 Not Found@). These are no different to son_of_a_batch, so each of them will also be in the "results" block with its status code set appropriately.
16
+ * The request might *not complete*. These will be in the "errors" block.
17
+
18
+ h2. Usage
19
+
20
+ Start a test server:
21
+
22
+ <pre>
23
+ $ ruby app/endpoints/sleepy.rb -sv -p 9002 # an example endpoint that sleeps a given amount of time before responding.
24
+ [54941:INFO] 2011-04-23 17:22:37 :: Starting server on 0.0.0.0:9002 in development mode. Watch out for stones.
25
+
26
+ $ curl 'http://localhost:9002?delay=2.2'
27
+ {"start":1303597564.663358,"delay":2.2,"actual":2.2001771926879883}
28
+ </pre>
29
+
30
+ Start the son_of_a_batch server:
31
+
32
+ <pre>
33
+ $ ruby app/endpoints/sleepy.rb -sv -p 9002 # an example endpoint that sleeps a given amount of time before responding.
34
+ [54941:INFO] 2011-04-23 17:22:37 :: Starting server on 0.0.0.0:9002 in development mode. Watch out for stones.
35
+
36
+ $ curl 'http://localhost:9002?delay=2.2'
37
+ {"start":1303597564.663358,"delay":2.2,"actual":2.2001771926879883}
38
+ </pre>
39
+
40
+ h3. Colophon
41
+
42
+ Uses
43
+
44
+ * Goliath -- fast asynchronous API library
45
+ * Gorillib -- only the lightest of weight helpers plucked from active_support and friends, with nothing obtrusive required by default.
46
+
47
+ h4. Credits
48
+
49
+ Built using goliath, gorillib, and the infochimps API
50
+
51
+ h4. Contributing to son_of_a_batch
52
+
53
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
54
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
55
+ * Fork the project
56
+ * Start a feature/bugfix branch
57
+ * Commit and push until you are happy with your contribution
58
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
59
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
60
+
61
+ h4. Copyright
62
+
63
+ Copyright (c) 2011 Philip (flip) Kromer for Infochimps. See LICENSE.txt for
64
+ further details.
65
+
@@ -0,0 +1,43 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "son_of_a_batch"
16
+ gem.homepage = "http://infochimps.com/labs"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Smelt from a plentiferous gallimaufry of requests an agglomerated bale of responses. With, y'know, concurrency and all that.}
19
+ gem.description = %Q{Smelt from a plentiferous gallimaufry of requests an agglomerated bale of responses. With, y'know, concurrency and all that.}
20
+ gem.email = "coders@infochimps.com"
21
+ gem.authors = ["Philip (flip) Kromer for Infochimps"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rspec/core'
30
+ require 'rspec/core/rake_task'
31
+ RSpec::Core::RakeTask.new(:spec) do |spec|
32
+ spec.pattern = FileList['spec/**/*_spec.rb']
33
+ end
34
+
35
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ require 'yard'
43
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.2
@@ -0,0 +1,80 @@
1
+ $: << File.dirname(__FILE__)+'/../../lib'
2
+ require 'boot'
3
+ require 'goliath'
4
+ require 'rack/abstract_format'
5
+
6
+ #
7
+ # Wait the amount of time given by the 'delay' parameter before responding
8
+ # Handles multiple parallel requests -- its EM::Synchrony call allows the
9
+ # reactor to keep spinning.
10
+ #
11
+ class Sleepy < Goliath::API
12
+ use Goliath::Rack::Params # parse query & body params
13
+ use Goliath::Rack::Formatters::JSON # JSON output formatter
14
+ use Goliath::Rack::Render # auto-negotiate response format
15
+ use Goliath::Rack::ValidationError # catch and render validation errors
16
+ use Goliath::Rack::Validation::NumericRange, {:key => 'delay', :max => 15.0, :default => 1.5, :as => Float}
17
+ use Rack::AbstractFormat, 'application/json'
18
+
19
+ def response(env)
20
+ start = Time.now.utc.to_f
21
+ delay = env.params['delay']
22
+ env.logger.debug "timer #{start} [#{delay}]: start of response"
23
+
24
+ # EM::Synchrony call allows the reactor to keep spinning: HOORAY CONCURRENCY
25
+ EM::Synchrony.sleep(delay)
26
+ body = { :start => start, :delay => delay, :actual => (Time.now.utc.to_f - start) }
27
+
28
+ env.logger.debug "timer #{start} [#{delay}]: after sleep: #{body.inspect}"
29
+ [200, {'X-Responder' => self.class.to_s, 'X-Sleepy-Delay' => delay.to_s, }, body]
30
+ end
31
+ end
32
+
33
+ #
34
+ # Proof of concurrency!
35
+ #
36
+ # $ ruby app/rack/sleepy.rb -sv -p 9000
37
+ # $ ab -c 10 -n 50 'http://127.0.0.1:9000/?delay=2.0'
38
+ # This is ApacheBench, Version 2.3 <$Revision: 655654 $>
39
+ # Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
40
+ # Licensed to The Apache Software Foundation, http://www.apache.org/
41
+ #
42
+ # Benchmarking 127.0.0.1 (be patient).....done
43
+ #
44
+ #
45
+ # Server Software: Goliath
46
+ # Server Hostname: 127.0.0.1
47
+ # Server Port: 9000
48
+ #
49
+ # Document Path: /?delay=2.0
50
+ # Document Length: 88 bytes
51
+ #
52
+ # Concurrency Level: 10
53
+ # Time taken for tests: 10.117 seconds
54
+ # Complete requests: 50
55
+ # Failed requests: 0
56
+ # Write errors: 0
57
+ # Total transferred: 11550 bytes
58
+ # HTML transferred: 4400 bytes
59
+ # Requests per second: 4.94 [#/sec] (mean)
60
+ # Time per request: 2023.306 [ms] (mean)
61
+ # Time per request: 202.331 [ms] (mean, across all concurrent requests)
62
+ # Transfer rate: 1.11 [Kbytes/sec] received
63
+ #
64
+ # Connection Times (ms)
65
+ # min mean[+/-sd] median max
66
+ # Connect: 0 0 0.1 0 1
67
+ # Processing: 2004 2018 11.0 2017 2044
68
+ # Waiting: 2004 2018 11.0 2017 2044
69
+ # Total: 2004 2018 11.0 2018 2044
70
+ #
71
+ # Percentage of the requests served within a certain time (ms)
72
+ # 50% 2018
73
+ # 66% 2025
74
+ # 75% 2028
75
+ # 80% 2029
76
+ # 90% 2032
77
+ # 95% 2036
78
+ # 98% 2044
79
+ # 99% 2044
80
+ # 100% 2044 (longest request)
@@ -0,0 +1,4 @@
1
+ %h1 Debug
2
+
3
+ %pre
4
+ = JSON.pretty_generate(env)
@@ -0,0 +1,39 @@
1
+ - title ||= 'Goliath'
2
+ !!! 5
3
+ %html
4
+ %head
5
+ %meta{ :charset => "utf-8" }/
6
+ %meta{ :content => "IE=edge,chrome=1", "http-equiv" => "X-UA-Compatible" }/
7
+
8
+ %title= title
9
+ <link href="data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQEAYAAABPYyMiAAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAAF0lEQVRIx2NgGAWjYBSMglEwCkbBSAcACBAAAeaR9cIAAAAASUVORK5CYII=" rel="icon" type="image/x-icon" />
10
+ %link{ :href => "/stylesheets/style.css", :rel => "stylesheet", :type => "text/css", :media => "all" }
11
+
12
+ %body{ :lang => 'en' }
13
+ #header-container
14
+ %header.wrapper
15
+ %h1#title
16
+ == <a href="/">#{title}</a>
17
+
18
+ %nav
19
+ %ul.menu
20
+ %li.item
21
+ <a href="/">Home</a>
22
+ %li.item
23
+ <a href="/debug">Debug</a>
24
+ %li &nbsp;
25
+
26
+ #main.wrapper{ :role => 'main' }
27
+ != yield
28
+
29
+ #footer-container
30
+ %footer.wrapper
31
+ .copyright
32
+ %p Copyright &copy; #{Time.now.strftime("%Y")}
33
+
34
+ %script{ :src => "http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js", :type => "text/javascript" }/
35
+ :javascript
36
+ !window.jQuery && document.write(unescape('%3Cscript src="/javascripts/jquery.min.js"%3E%3C/script%3E'))
37
+
38
+ /
39
+ haml layout
@@ -0,0 +1,28 @@
1
+ %h2 Routes
2
+
3
+ %ul
4
+ %li <a href="/joke">Tell me a joke</a>
5
+ %li <a href="/erb_me">Tell me a joke, but with a boring .erb layout</a>
6
+ %li <a href="/debug">debug info</a>
7
+ %li <a href="/oops">Template missing</a>
8
+
9
+ %h2 Dashboard
10
+
11
+ %table.vertical
12
+ %tr
13
+ %th Script
14
+ %td= $0
15
+ %tr
16
+ %th Server Port
17
+ %td= env['SERVER_PORT']
18
+ %tr
19
+ %th Latency
20
+ %td= recent_latency
21
+ %tr
22
+ %th Request took
23
+ %td= (Time.now.to_f - env[:start_time]).round(3)
24
+ %tr
25
+ %th Config
26
+ %td
27
+ %pre
28
+ = "\n"+JSON.pretty_generate(env.config)
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env ruby
2
+ #/ Usage: config/bootstrap.rb [<options>]
3
+ #/ Bootstraps the gem environment.
4
+ #/
5
+ #/ Options are passed through to the bundle-install command. In most cases you
6
+ #/ won't need these. They're used primarily in production environments.
7
+ #/ --local use gems in vendor/cache instead of rubygems.org
8
+ #/ --without=<groups> do not install gems in the groups specified
9
+ #
10
+ # =============================================================================
11
+ # Uses bundler to install all gems specified in the Gemfile under vendor/gems,
12
+ # records the load path in config/loadpath, and generates bundler-free binstubs
13
+ # under bin.
14
+ #
15
+ # The basic idea is to use bundler to install necessary gems but not
16
+ # rely on it to manage the load path at runtime because it's slow. Requiring
17
+ # 'bundler/setup' takes ~500ms user CPU time in production and ~1500ms in
18
+ # development/test. This makes it unusable in scenarios that require a fast
19
+ # boot (e.g., script/gerve, proxymachine daemons, ernie/smoke, etc.). It's also
20
+ # a problem in development where it slows tools like rake command line
21
+ # completion to a crawl and adds at least a second to single-file test runs.
22
+ #
23
+ # There's very little reason to use bundler at runtime since everything
24
+ # is known at install time. We simply save off the result of the work done by
25
+ # bundle/setup and use it until bundle-install is run again.
26
+
27
+ # show usage message with --help
28
+ if ARGV.include?('--help')
29
+ system "grep '^#/' <'#{__FILE__}' |cut -c4-"
30
+ exit 2
31
+ end
32
+
33
+ # go into the project root because it makes everything easier
34
+ root = File.expand_path('../..', __FILE__)
35
+ Dir.chdir(root)
36
+
37
+ # point bundler to the right stuff
38
+ ENV['BUNDLE_GEMFILE'] = "#{root}/Gemfile"
39
+ ENV['BUNDLE_PATH'] = "#{root}/vendor/gems"
40
+
41
+ # bring in rubygems and make sure bundler is installed.
42
+ require 'rubygems'
43
+ begin
44
+ require 'bundler'
45
+ rescue LoadError => boom
46
+ warn "Bundler not found. Install it with `gem install bundler' and try again."
47
+ exit 0
48
+ end
49
+
50
+ # record the Gemfile checksum so we can tell if the Gemfile has changed
51
+ # since our loadpath was last generated. this is used in config/boot.rb
52
+ # to verify the environment is bootstrapped and up-to-date.
53
+ checksum = `cksum Gemfile`.to_i
54
+ installed = File.read('.bundle/checksum').to_i rescue nil
55
+
56
+ # run a quick check to see if everything's installed and up-to-date so we can
57
+ # skip the install and loadpath generation step if possible.
58
+ if checksum == installed && system('bundle check 1>/dev/null 2>&1')
59
+ puts "Gem environment up-to-date."
60
+ else
61
+ # run bundle-install to install any missing gems
62
+ argv = ['--no-color', 'install', '--path', 'vendor/gems'] + ARGV
63
+ system("bundle", *argv) || begin
64
+ warn "bundle executable not found. Ensure bundler is installed (`gem " +
65
+ "install bundler`) and that the gem bin path is in your PATH"
66
+ exit($?.exitstatus)
67
+ end
68
+
69
+ # load the Gemfile
70
+ bundle = Bundler.setup
71
+
72
+ # extract load paths for each gem and write to the config/loadpath file.
73
+ load_paths = []
74
+ bundle.gems.each do |gem|
75
+ next if gem.name == 'bundler'
76
+ gem.load_paths.each do |path|
77
+ if path[0, root.size] == root
78
+ path = path[(root.size + 1), path.size]
79
+ load_paths << path
80
+ else
81
+ warn "external load path directory detected: #{path}"
82
+ end
83
+ end
84
+ end
85
+
86
+ # move the loadpath and checksum files into place if everything was installed
87
+ # okay and the load path file was written successfully.
88
+ File.open('.bundle/loadpath+', 'wb') { |fd| fd.write(load_paths.join("\n")) }
89
+ File.rename('.bundle/loadpath+', '.bundle/loadpath')
90
+ File.open('.bundle/checksum', 'wb') { |fd| fd.puts(checksum) }
91
+
92
+ # write binstubs for all executables. we can't use bundler's --binstubs option
93
+ # because the generated executables require 'bundler/setup'. the binstubs
94
+ # generated here require only config/boot.rb, which sets up the loadpath
95
+ # manually using the .bundle/loadpath file.
96
+ Dir.mkdir "vendor/bin" unless File.directory?("vendor/bin")
97
+ template = DATA.read
98
+ lineno = File.read(__FILE__).count("\n") - template.count("\n")
99
+ bundle.gems.each do |spec|
100
+ spec.executables.each do |executable|
101
+ script = eval('%Q{' + template + '}', binding, __FILE__, lineno)
102
+ File.open("vendor/bin/#{executable}+", 'wb') { |fd| fd.write(script) }
103
+ File.chmod 0755, "vendor/bin/#{executable}+"
104
+ File.rename("vendor/bin/#{executable}+", "vendor/bin/#{executable}")
105
+ end
106
+ end
107
+ end
108
+
109
+ __END__
110
+ #!/usr/bin/env #{RbConfig::CONFIG['ruby_install_name']}
111
+ #
112
+ # This file was generated by config/bootstrap.
113
+ require '#{root}/lib/boot'
114
+ load File.join('#{spec.full_gem_path}', 'vendor', '#{spec.bindir}', '#{executable}')