goliath 0.9.2 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of goliath might be problematic. Click here for more details.

Files changed (49) hide show
  1. data/Gemfile +1 -1
  2. data/{HISTORY → HISTORY.md} +26 -12
  3. data/README.md +17 -10
  4. data/examples/api_proxy.rb +28 -0
  5. data/examples/async_aroundware_demo.rb +14 -10
  6. data/examples/auth_and_rate_limit.rb +160 -38
  7. data/examples/config/auth_and_rate_limit.rb +8 -5
  8. data/examples/config/content_stream.rb +5 -9
  9. data/examples/early_abort.rb +37 -0
  10. data/examples/env_use_statements.rb +3 -0
  11. data/examples/favicon.rb +40 -0
  12. data/examples/http_log.rb +2 -1
  13. data/examples/public/favicon.ico +0 -0
  14. data/examples/rack_routes.rb +19 -0
  15. data/examples/rasterize/rasterize.rb +2 -1
  16. data/examples/rasterize/rasterize_and_shorten.rb +10 -5
  17. data/goliath.gemspec +7 -9
  18. data/lib/goliath/api.rb +16 -4
  19. data/lib/goliath/connection.rb +8 -7
  20. data/lib/goliath/deprecated/async_aroundware.rb +133 -0
  21. data/lib/goliath/{synchrony → deprecated}/mongo_receiver.rb +28 -8
  22. data/lib/goliath/deprecated/response_receiver.rb +97 -0
  23. data/lib/goliath/env.rb +5 -0
  24. data/lib/goliath/rack.rb +6 -1
  25. data/lib/goliath/rack/async_middleware.rb +34 -12
  26. data/lib/goliath/rack/barrier_aroundware.rb +228 -0
  27. data/lib/goliath/rack/barrier_aroundware_factory.rb +60 -0
  28. data/lib/goliath/rack/builder.rb +22 -6
  29. data/lib/goliath/rack/heartbeat.rb +8 -5
  30. data/lib/goliath/rack/simple_aroundware.rb +114 -0
  31. data/lib/goliath/rack/simple_aroundware_factory.rb +121 -0
  32. data/lib/goliath/rack/validation/required_param.rb +9 -2
  33. data/lib/goliath/request.rb +7 -0
  34. data/lib/goliath/runner.rb +17 -5
  35. data/lib/goliath/server.rb +11 -3
  36. data/lib/goliath/test_helper.rb +14 -14
  37. data/lib/goliath/version.rb +1 -1
  38. data/spec/integration/early_abort_spec.rb +50 -0
  39. data/spec/integration/keepalive_spec.rb +2 -2
  40. data/spec/integration/pipelining_spec.rb +2 -2
  41. data/spec/integration/rack_routes_spec.rb +25 -0
  42. data/spec/integration/template_spec.rb +2 -0
  43. data/spec/unit/rack/heartbeat_spec.rb +11 -1
  44. data/spec/unit/rack/validation/required_param_spec.rb +10 -0
  45. data/spec/unit/runner_spec.rb +13 -0
  46. data/spec/unit/server_spec.rb +4 -0
  47. metadata +218 -265
  48. data/lib/goliath/rack/async_aroundware.rb +0 -56
  49. data/lib/goliath/synchrony/response_receiver.rb +0 -64
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gemspec
3
+ gemspec
@@ -1,33 +1,48 @@
1
- HISTORY
2
- =======
1
+ # HISTORY
2
+
3
+ ## v0.9.3 (Oct 16, 2011)
4
+
5
+ - new router DSL - much improved, see examples
6
+ - refactored async_aroundware
7
+ - make jruby friendlier (removed 1.9 req in gemspec)
8
+ - enable epoll
9
+ - SSL support
10
+ - unix socket support
11
+ - reload config on HUP
12
+ - and a number of small bugfixes + other improvements..
13
+ - See full list @ https://github.com/postrank-labs/goliath/compare/v0.9.2...v0.9.3
14
+
15
+ ## v0.9.2 (July 21, 2011)
16
+
17
+ - See full list @ https://github.com/postrank-labs/goliath/compare/v0.9.1...v0.9.2
18
+
19
+ ## v0.9.1 (Apr 12, 2011)
3
20
 
4
- v0.9.1 (Apr 12, 2011)
5
- ---------------------
6
21
  - Added extra messaging around the class not matching the file name (Carlos Brando)
7
-
22
+
8
23
  - Fix issue with POST parameters not being parsed by Goliath::Rack::Params
9
24
  - Added support for multipart encoded POST bodies
10
25
  - Added support for parsing nested query string parameters (Nolan Evans)
11
26
  - Added support for parsing application/json POST bodies
12
27
  - Content-Types outside of multipart, urlencoded and application/json will not be parsed automatically.
13
-
28
+
14
29
  - added 'run as user' option
15
30
  - SERVER_NAME and SERVER_PORT are set to values in HOST header
16
-
31
+
17
32
  - Cleaned up spec examples (Justin Ko)
18
-
33
+
19
34
  - moved logger into 'rack.logger' key to be more Rack compliant (Env#logger added to
20
35
  keep original API consistent)
21
36
  - add command line option for specifying config file
22
37
  - HTTP_CONTENT_LENGTH and HTTP_CONTENT_TYPE were changed to CONTENT_TYPE and CONTENT_LENGTH
23
38
  to be more Rack compliant
24
39
  - fix issue with loading config file in development mode
25
-
40
+
26
41
  - Rack::Reloader will be loaded automatically by the framework in development mode.
27
42
 
28
43
 
29
- v0.9.0 (Mar 9, 2011)
30
- --------------------
44
+ ## v0.9.0 (Mar 9, 2011)
45
+
31
46
  (Initial Public Release)
32
47
 
33
48
  Goliath is an open source version of the non-blocking (asynchronous) Ruby web server framework
@@ -47,4 +62,3 @@ internal and external applications. Many of the Goliath processes have been runn
47
62
  a time (read: no memory leaks) and have served hundreds of gigabytes of data without restarts. To
48
63
  scale up and provide failover and redundancy, our individual Goliath servers at PostRank are usually
49
64
  deployed behind a reverse proxy (such as HAProxy).
50
-
data/README.md CHANGED
@@ -20,18 +20,23 @@ Each HTTP request within Goliath is executed in its own Ruby fiber and all async
20
20
 
21
21
  ## Getting Started: Hello World
22
22
 
23
- require 'goliath'
23
+ ```ruby
24
+ require 'goliath'
24
25
 
25
- class Hello < Goliath::API
26
- def response(env)
27
- [200, {}, "Hello World"]
28
- end
29
- end
26
+ class Hello < Goliath::API
27
+ def response(env)
28
+ [200, {}, "Hello World"]
29
+ end
30
+ end
30
31
 
31
- > ruby hello.rb -sv
32
- > [97570:INFO] 2011-02-15 00:33:51 :: Starting server on 0.0.0.0:9000 in development mode. Watch out for stones.
32
+ > ruby hello.rb -sv
33
+ > [97570:INFO] 2011-02-15 00:33:51 :: Starting server on 0.0.0.0:9000 in development mode. Watch out for stones.
34
+ ```
33
35
 
34
- See examples directory for more, hands-on examples of building Goliath powered web-services.
36
+ See examples directory for more, hands-on examples of building Goliath powered web-services. Are you new to EventMachine, or want a detailed walk-through of building a Goliath powered API? You're in luck, we have two super-awesome peepcode screencasts which will teach you all you need to know:
37
+
38
+ * [Meet EventMachine: Part 1](http://peepcode.com/products/eventmachine) - introduction to EM, Fibers, etc.
39
+ * [Meet EventMachine: Part 2](http://peepcode.com/products/eventmachine-ii) - building an API with Goliath
35
40
 
36
41
  ## Performance: MRI, JRuby, Rubinius
37
42
 
@@ -52,7 +57,7 @@ Goliath has been in production at PostRank for over a year, serving a sustained
52
57
  * Mongrel is a threaded web-server, and both Passenger and Unicorn fork an entire VM to isolate each request from each other. By contrast, Goliath builds a single instance of the Rack app and runs all requests in parallel through a single VM, which leads to a much smaller memory footprint and less overhead.
53
58
 
54
59
  * How do I deploy Goliath in production?
55
- * We recommend deploying Goliath behind a reverse proxy such as HAProxy, Nginx or equivalent. Using one of the above, you can easily run multiple instances of the same application and load balance between them within the reverse proxy.
60
+ * We recommend deploying Goliath behind a reverse proxy such as HAProxy ([sample config](https://github.com/postrank-labs/goliath/wiki/HAProxy)), Nginx or equivalent. Using one of the above, you can easily run multiple instances of the same application and load balance between them within the reverse proxy.
56
61
 
57
62
  ## Guides
58
63
 
@@ -63,6 +68,7 @@ Goliath has been in production at PostRank for over a year, serving a sustained
63
68
 
64
69
  ### Hands-on applications:
65
70
 
71
+ * [Peepcode](http://peepcode.com/products/eventmachine) [screencasts](http://peepcode.com/products/eventmachine-ii)
66
72
  * [Asynchronous HTTP, MySQL, etc](https://github.com/postrank-labs/goliath/wiki/Asynchronous-Processing)
67
73
  * [Response streaming with Goliath](https://github.com/postrank-labs/goliath/wiki/Streaming)
68
74
  * [Examples](https://github.com/postrank-labs/goliath/tree/master/examples)
@@ -85,3 +91,4 @@ Goliath has been in production at PostRank for over a year, serving a sustained
85
91
  ## License & Acknowledgments
86
92
 
87
93
  Goliath is distributed under the MIT license, for full details please see the LICENSE file.
94
+ Rock favicon CC-BY from [Douglas Feer](http://www.favicon.cc/?action=icon&file_id=375421)
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Rewrites and proxies requests to a third-party API, with HTTP basic authentication.
4
+
5
+ require 'goliath'
6
+ require 'em-synchrony/em-http'
7
+
8
+ class TwilioResponse < Goliath::API
9
+ use Goliath::Rack::Params
10
+ use Goliath::Rack::JSONP
11
+
12
+ HEADERS = { authorization: ENV.values_at("TWILIO_SID","TWILIO_AUTH_TOKEN") }
13
+ BASE_URL = "https://api.twilio.com/2010-04-01/Accounts/#{ENV['TWILIO_SID']}/AvailablePhoneNumbers/US"
14
+
15
+ def response(env)
16
+ url = "#{BASE_URL}#{env['REQUEST_PATH']}?#{env['QUERY_STRING']}"
17
+ logger.debug "Proxying #{url}"
18
+
19
+ http = EM::HttpRequest.new(url).get head: HEADERS
20
+ logger.debug "Received #{http.response_header.status} from Twilio"
21
+
22
+ [200, {'X-Goliath' => 'Proxy','Content-Type' => 'application/javascript'}, http.response]
23
+ end
24
+ end
25
+
26
+ class Twilio < Goliath::API
27
+ get %r{^/(Local|TollFree)}, TwilioResponse
28
+ end
@@ -12,11 +12,11 @@ require 'yajl/json_gem'
12
12
  #
13
13
  # To run this, start the 'test_rig.rb' server on port 9002:
14
14
  #
15
- # ./examples/test_rig.rb -sv -p 9002
15
+ # bundle exec ./examples/test_rig.rb -sv -p 9002
16
16
  #
17
17
  # And then start this server on port 9000:
18
18
  #
19
- # ./async_aroundware_demo.rb -sv -p 9000
19
+ # bundle exec ./examples/barrier_aroundware_demo.rb -sv -p 9000
20
20
  #
21
21
  # Now curl the async_aroundware_demo_multi:
22
22
  #
@@ -42,27 +42,31 @@ require 'yajl/json_gem'
42
42
 
43
43
  BASE_URL = 'http://localhost:9002/'
44
44
 
45
- class MyResponseReceiver < Goliath::Synchrony::MultiReceiver
45
+ class RemoteRequestBarrier
46
+ include Goliath::Rack::BarrierAroundware
47
+ attr_accessor :sleep_1
48
+
46
49
  def pre_process
47
50
  # Request with delay_1 and drop_1 -- note: 'aget', because we want execution to continue
48
51
  req = EM::HttpRequest.new(BASE_URL).aget(:query => { :delay => env.params['delay_1'], :drop => env.params['drop_1'] })
49
- add :sleep_1, req
52
+ enqueue :sleep_1, req
53
+ return Goliath::Connection::AsyncResponse
50
54
  end
51
55
 
52
56
  def post_process
53
57
  # unify the results with the results of the API call
54
- responses[:callback].each{|name, resp| body[:results][name] = JSON.parse(resp.response) }
55
- responses[:errback ].each{|name, err| body[:errors][name] = err.error }
56
- [status, headers, JSON.generate(body)]
58
+ if successes.include?(:sleep_1) then body[:results][:sleep_1] = JSON.parse(sleep_1.response)
59
+ else body[:errors][:sleep_1] = sleep_1.error ; end
60
+ [status, headers, JSON.pretty_generate(body)]
57
61
  end
58
62
  end
59
63
 
60
- class AsyncAroundwareDemo < Goliath::API
64
+ class BarrierAroundwareDemo < Goliath::API
61
65
  use Goliath::Rack::Params
62
66
  use Goliath::Rack::Validation::NumericRange, {:key => 'delay_1', :default => 1.0, :max => 5.0, :min => 0.0, :as => Float}
63
67
  use Goliath::Rack::Validation::NumericRange, {:key => 'delay_2', :default => 0.5, :max => 5.0, :min => 0.0, :as => Float}
64
68
  #
65
- use Goliath::Rack::AsyncAroundware, MyResponseReceiver
69
+ use Goliath::Rack::BarrierAroundwareFactory, RemoteRequestBarrier
66
70
 
67
71
  def response(env)
68
72
  # Request with delay_2 and drop_2 -- note: 'get', because we want execution to proceed linearly
@@ -71,7 +75,7 @@ class AsyncAroundwareDemo < Goliath::API
71
75
  body = { :results => {}, :errors => {} }
72
76
 
73
77
  if resp.response_header.status.to_i != 0
74
- body[:results][:sleep_2] = JSON.parse(resp.response)
78
+ body[:results][:sleep_2] = JSON.parse(resp.response) rescue 'parsing failed'
75
79
  else
76
80
  body[:errors ][:sleep_2] = resp.error
77
81
  end
@@ -4,46 +4,115 @@ require 'goliath'
4
4
  require 'em-mongo'
5
5
  require 'em-http'
6
6
  require 'em-synchrony/em-http'
7
+ require 'em-synchrony/em-mongo'
7
8
  require 'yajl/json_gem'
8
9
 
9
- require 'goliath/synchrony/mongo_receiver' # has the aroundware logic for talking to mongodb
10
10
  require File.join(File.dirname(__FILE__), 'http_log') # Use the HttpLog as our actual endpoint, but include this in the middleware
11
11
 
12
+ #
12
13
  # Usage:
13
14
  #
14
- # First launch a dummy responder, like hello_world.rb or test_rig.rb:
15
- # ruby ./examples/hello_world.rb -sv -p 8080 -e prod &
15
+ # First launch the test rig:
16
+ # bundle exec ./examples/test_rig.rb -sv -p 8080 -e prod &
16
17
  #
17
18
  # Then launch this script
18
- # ruby ./examples/auth_and_rate_limit.rb -sv -p 9000 --config $PWD/examples/config/auth_and_rate_limit.rb
19
+ # bundle exec ./examples/auth_and_rate_limit.rb -sv -p 9000 --config $PWD/examples/config/auth_and_rate_limit.rb
20
+ #
21
+ # The auth info is returned in the headers:
22
+ #
23
+ # curl -vv 'http://127.0.0.1:9000/?_apikey=i_am_busy&drop=false' ; echo
24
+ # ...snip...
25
+ # < X-RateLimit-MaxRequests: 1000
26
+ # < X-RateLimit-Requests: 999
27
+ # < X-RateLimit-Reset: 1312059600
28
+ #
29
+ # This user will hit the rate limit after 10 requests:
30
+ #
31
+ # for foo in 1 2 3 4 5 6 7 8 9 10 11 12 ; do echo -ne $foo "\t" ; curl 'http://127.0.0.1:9000/?_apikey=i_am_limited' ; echo ; done
32
+ # 1 {"Special":"Header","Params":"_apikey: i_am_awesome|drop: false","Path":"/","Headers":"User-Agent: ...
33
+ # ...
34
+ # 11 [:error, "Your request rate (11) is over your limit (10)"]
35
+ #
36
+ # You can test the barrier (both delays are in fractional seconds):
37
+ # * drop=true will drop the request at the remote host
38
+ # * auth_db_delay will fake a slow response from the mongo
39
+ # * delay will cause a slow response from the remote host
40
+ #
41
+ # time curl -vv 'http://127.0.0.1:9000/?_apikey=i_am_awesome&drop=false&delay=0.4&auth_db_delay=0.3'
42
+ # ...
43
+ # X-Tracer: ... received_usage_info: 0.06, received_sleepy: 299.52, received_downstream_resp: 101.67, ..., total: 406.09
44
+ # ...
45
+ # real 0m0.416s user 0m0.002s sys 0m0.003s pct 1.24
46
+ #
47
+ # This shows the mongodb response returning quickly, the fake DB delay returning
48
+ # after 300ms, and the downstream response returning after an additional 101 ms.
49
+ # The total request took 416ms of wall-clock time
50
+ #
51
+ # This will hold up even in the face of many concurrent connections. Relaunch in
52
+ # production (you may have to edit the config/auth_and_rate_limit scripts):
53
+ #
54
+ # bundle exec ./examples/auth_and_rate_limit.rb -sv -p 9000 -e prod --config $PWD/examples/config/auth_and_rate_limit.rb
55
+ #
56
+ # On my laptop, with 20 concurrent requests (each firing two db gets, a 400 ms
57
+ # http get, and two db writes), the median/90%ile times were 431ms / 457ms:
58
+ #
59
+ # time ab -c20 -n20 'http://127.0.0.1:9000/?_apikey=i_am_awesome&drop=false&delay=0.4&auth_db_delay=0.3'
60
+ # ...
61
+ # Percentage of the requests served within a certain time (ms)
62
+ # 50% 431
63
+ # 90% 457
64
+ # real 0m0.460s user 0m0.001s sys 0m0.003s pct 0.85
65
+ #
66
+ # With 100 concurrent requests, the request latency starts to drop but the
67
+ # throughput and variance stand up:
68
+ #
69
+ # time ab -c100 -n100 'http://127.0.0.1:9000/?_apikey=i_am_awesome&drop=false&delay=0.4&auth_db_delay=0.3'
70
+ # ...
71
+ # Percentage of the requests served within a certain time (ms)
72
+ # 50% 640
73
+ # 90% 673
74
+ # real 0m0.679s user 0m0.002s sys 0m0.007s pct 1.33
19
75
  #
20
76
 
21
77
  # Tracks and enforces account and rate limit policies.
22
78
  #
23
- # Before the request:
79
+ # This is like a bouncer who lets townies order a drink while he checks their
80
+ # ID, but who's a jerk to college kids.
24
81
  #
25
- # * validates the apikey exists
26
- # * launches requests for the account and current usage (hourly rate limit, etc)
82
+ # On GET or HEAD requests, it proxies the request and gets account/usage info
83
+ # concurrently; authorizing the account doesn't delay the response.
27
84
  #
28
- # It then passes the request down the middleware chain; execution resumes only
29
- # when both the remote request and the auth info have returned.
85
+ # On a POST or other non-idempotent request, it checks the account/usage info
86
+ # *before* allowing the request to fire. This takes longer, but is necessary and
87
+ # tolerable.
30
88
  #
31
- # After remote request and auth info return:
89
+ # The magic of BarrierAroundware:
32
90
  #
33
- # * Check the account exists and is valid
34
- # * Check the rate limit is OK
91
+ # 1) In pre_process (before the request):
92
+ # * validate an apikey was given; if not, raise (returning directly)
93
+ # * launch requests for the account and rate limit usage
35
94
  #
36
- # If it passes all those checks, the request goes through; otherwise we raise an
37
- # error that Goliath::Rack::Validator turns into a 4xx response
95
+ # 2) On a POST or other non-GET non-HEAD, we issue `perform`, which barriers
96
+ # (allowing other requests to proceed) until the two pending requests
97
+ # complete. It then checks the account exists and is valid, and that the rate
98
+ # limit is OK
38
99
  #
39
- # WARNING: Since this passes ALL requests through to the responder, it's only
40
- # suitable for idempotent requests (GET, typically). You may need to handle
41
- # POST/PUT/DELETE requests differently.
100
+ # 3) If the auth check fails, we raise an error (later caught by a safely{}
101
+ # block and turned into the right 4xx HTTP response.
42
102
  #
103
+ # 4) If the auth check succeeds, or the request is a GET or HEAD, we return
104
+ # Goliath::Connection::AsyncResponse, and BarrierAroundwareFactory passes the
105
+ # request down the middleware chain
43
106
  #
44
- class AuthReceiver < Goliath::Synchrony::MongoReceiver
107
+ # 5) post_process resumes only when both proxied request & auth info are complete
108
+ # (it already has of course in the non-lazy scenario)
109
+ #
110
+ # 6) If we were lazy, the post_process method now checks authorization
111
+ #
112
+ class AuthBarrier
113
+ include Goliath::Rack::BarrierAroundware
45
114
  include Goliath::Validation
46
- include Goliath::Rack::Validator
115
+ attr_reader :db
47
116
  attr_accessor :account_info, :usage_info
48
117
 
49
118
  # time period to aggregate stats over, in seconds
@@ -53,36 +122,84 @@ class AuthReceiver < Goliath::Synchrony::MongoReceiver
53
122
  class RateLimitExceededError < ForbiddenError ; end
54
123
  class InvalidApikeyError < UnauthorizedError ; end
55
124
 
125
+ def initialize(env, db_name)
126
+ @db = env.config[db_name]
127
+ super(env)
128
+ end
129
+
56
130
  def pre_process
131
+ env.trace('pre_process_beg')
57
132
  validate_apikey!
58
- first('AccountInfo', { :_id => apikey }){|res| self.account_info = res }
59
- first('UsageInfo', { :_id => usage_id }){|res| self.usage_info = res }
133
+
134
+ # the results of the afirst deferrable will be set right into account_info (and the request into successes)
135
+ enqueue_mongo_request(:account_info, { :_id => apikey })
136
+ enqueue_mongo_request(:usage_info, { :_id => usage_id })
137
+ maybe_fake_delay!
138
+
139
+ # On non-GET non-HEAD requests, we have to check auth now.
140
+ unless lazy_authorization?
141
+ perform # yield execution until user_info has arrived
142
+ charge_usage
143
+ check_authorization!
144
+ end
145
+
60
146
  env.trace('pre_process_end')
147
+ return Goliath::Connection::AsyncResponse
61
148
  end
62
149
 
63
150
  def post_process
64
151
  env.trace('post_process_beg')
65
- env.logger.info [account_info, usage_info].inspect
66
- self.account_info ||= {}
67
- self.usage_info ||= {}
152
+ # [:account_info, :usage_info, :status, :headers, :body].each{|attr| env.logger.info(("%23s\t%s" % [attr, self.send(attr).inspect[0..200]])) }
68
153
 
69
154
  inject_headers
70
155
 
71
- EM.next_tick do
72
- safely(env){ charge_usage }
156
+ # We have to check auth now, we skipped it before
157
+ if lazy_authorization?
158
+ charge_usage
159
+ check_authorization!
73
160
  end
74
161
 
75
- safely(env, headers) do
76
- check_apikey!
77
- check_rate_limit!
162
+ env.trace('post_process_end')
163
+ [status, headers, body]
164
+ end
165
+
166
+ def lazy_authorization?
167
+ (env['REQUEST_METHOD'] == 'GET') || (env['REQUEST_METHOD'] == 'HEAD')
168
+ end
169
+
170
+ if defined?(EM::Mongo::Cursor)
171
+ # em-mongo > 0.3.6 gives us a deferrable back. nice and clean.
172
+ def enqueue_mongo_request(handle, query)
173
+ enqueue handle, db.collection(handle).afirst(query)
174
+ end
175
+ else
176
+ # em-mongo <= 0.3.6 makes us fake a deferrable response.
177
+ def enqueue_mongo_request(handle, query)
178
+ enqueue_acceptor(handle) do |acc|
179
+ db.collection(handle).afind(query){|resp| acc.succeed(resp.first) }
180
+ end
181
+ end
182
+ end
78
183
 
79
- env.trace('post_process_end')
80
- [status, headers, body]
184
+ # Fake out a delay in the database response if auth_db_delay is given
185
+ def maybe_fake_delay!
186
+ if (auth_db_delay = env.params['auth_db_delay'].to_f) > 0
187
+ enqueue_acceptor(:sleepy){|acc| EM.add_timer(auth_db_delay){ acc.succeed } }
81
188
  end
82
189
  end
83
190
 
191
+ def accept_response(handle, *args)
192
+ env.trace("received_#{handle}")
193
+ super(handle, *args)
194
+ end
195
+
84
196
  # ===========================================================================
85
197
 
198
+ def check_authorization!
199
+ check_apikey!
200
+ check_rate_limit!
201
+ end
202
+
86
203
  def validate_apikey!
87
204
  if apikey.to_s.empty?
88
205
  raise MissingApikeyError
@@ -90,25 +207,30 @@ class AuthReceiver < Goliath::Synchrony::MongoReceiver
90
207
  end
91
208
 
92
209
  def check_apikey!
93
- unless account_info['valid'] == true
210
+ unless account_info && (account_info['valid'] == true)
94
211
  raise InvalidApikeyError
95
212
  end
96
213
  end
97
214
 
98
215
  def check_rate_limit!
99
- return true if usage_info['calls'].to_f <= account_info['max_call_rate'].to_f
100
- raise RateLimitExceededError
216
+ self.usage_info ||= {}
217
+ rate = usage_info['calls'].to_i + 1
218
+ limit = account_info['max_call_rate'].to_i
219
+ return true if rate <= limit
220
+ raise RateLimitExceededError, "Your request rate (#{rate}) is over your limit (#{limit})"
101
221
  end
102
222
 
103
223
  def charge_usage
104
- update('UsageInfo', { :_id => usage_id },
105
- { '$inc' => { :calls => 1 } }, :upsert => true)
224
+ EM.next_tick do
225
+ safely(env){ db.collection(:usage_info).update({ :_id => usage_id },
226
+ { '$inc' => { :calls => 1 } }, :upsert => true) }
227
+ end
106
228
  end
107
229
 
108
230
  def inject_headers
109
231
  headers.merge!({
110
232
  'X-RateLimit-MaxRequests' => account_info['max_call_rate'].to_s,
111
- 'X-RateLimit-Requests' => usage_info['calls'].to_s,
233
+ 'X-RateLimit-Requests' => usage_info['calls'].to_i.to_s,
112
234
  'X-RateLimit-Reset' => timebin_end.to_s,
113
235
  })
114
236
  end
@@ -139,5 +261,5 @@ end
139
261
  class AuthAndRateLimit < HttpLog
140
262
  use Goliath::Rack::Tracer, 'X-Tracer'
141
263
  use Goliath::Rack::Params # parse & merge query and body parameters
142
- use Goliath::Rack::AsyncAroundware, AuthReceiver, 'api_auth_db'
264
+ use Goliath::Rack::BarrierAroundwareFactory, AuthBarrier, 'api_auth_db'
143
265
  end