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.
- data/Gemfile +1 -1
- data/{HISTORY → HISTORY.md} +26 -12
- data/README.md +17 -10
- data/examples/api_proxy.rb +28 -0
- data/examples/async_aroundware_demo.rb +14 -10
- data/examples/auth_and_rate_limit.rb +160 -38
- data/examples/config/auth_and_rate_limit.rb +8 -5
- data/examples/config/content_stream.rb +5 -9
- data/examples/early_abort.rb +37 -0
- data/examples/env_use_statements.rb +3 -0
- data/examples/favicon.rb +40 -0
- data/examples/http_log.rb +2 -1
- data/examples/public/favicon.ico +0 -0
- data/examples/rack_routes.rb +19 -0
- data/examples/rasterize/rasterize.rb +2 -1
- data/examples/rasterize/rasterize_and_shorten.rb +10 -5
- data/goliath.gemspec +7 -9
- data/lib/goliath/api.rb +16 -4
- data/lib/goliath/connection.rb +8 -7
- data/lib/goliath/deprecated/async_aroundware.rb +133 -0
- data/lib/goliath/{synchrony → deprecated}/mongo_receiver.rb +28 -8
- data/lib/goliath/deprecated/response_receiver.rb +97 -0
- data/lib/goliath/env.rb +5 -0
- data/lib/goliath/rack.rb +6 -1
- data/lib/goliath/rack/async_middleware.rb +34 -12
- data/lib/goliath/rack/barrier_aroundware.rb +228 -0
- data/lib/goliath/rack/barrier_aroundware_factory.rb +60 -0
- data/lib/goliath/rack/builder.rb +22 -6
- data/lib/goliath/rack/heartbeat.rb +8 -5
- data/lib/goliath/rack/simple_aroundware.rb +114 -0
- data/lib/goliath/rack/simple_aroundware_factory.rb +121 -0
- data/lib/goliath/rack/validation/required_param.rb +9 -2
- data/lib/goliath/request.rb +7 -0
- data/lib/goliath/runner.rb +17 -5
- data/lib/goliath/server.rb +11 -3
- data/lib/goliath/test_helper.rb +14 -14
- data/lib/goliath/version.rb +1 -1
- data/spec/integration/early_abort_spec.rb +50 -0
- data/spec/integration/keepalive_spec.rb +2 -2
- data/spec/integration/pipelining_spec.rb +2 -2
- data/spec/integration/rack_routes_spec.rb +25 -0
- data/spec/integration/template_spec.rb +2 -0
- data/spec/unit/rack/heartbeat_spec.rb +11 -1
- data/spec/unit/rack/validation/required_param_spec.rb +10 -0
- data/spec/unit/runner_spec.rb +13 -0
- data/spec/unit/server_spec.rb +4 -0
- metadata +218 -265
- data/lib/goliath/rack/async_aroundware.rb +0 -56
- data/lib/goliath/synchrony/response_receiver.rb +0 -64
data/Gemfile
CHANGED
data/{HISTORY → HISTORY.md}
RENAMED
@@ -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
|
-
|
23
|
+
```ruby
|
24
|
+
require 'goliath'
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
class Hello < Goliath::API
|
27
|
+
def response(env)
|
28
|
+
[200, {}, "Hello World"]
|
29
|
+
end
|
30
|
+
end
|
30
31
|
|
31
|
-
|
32
|
-
|
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
|
-
# ./
|
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
|
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
|
-
|
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
|
-
|
55
|
-
|
56
|
-
[status, headers, JSON.
|
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
|
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::
|
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
|
15
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
26
|
-
#
|
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
|
-
#
|
29
|
-
#
|
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
|
-
#
|
89
|
+
# The magic of BarrierAroundware:
|
32
90
|
#
|
33
|
-
#
|
34
|
-
#
|
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
|
-
#
|
37
|
-
#
|
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
|
-
#
|
40
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
|
59
|
-
|
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 [
|
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
|
-
|
72
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
80
|
-
|
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
|
-
|
100
|
-
|
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
|
-
|
105
|
-
{
|
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::
|
264
|
+
use Goliath::Rack::BarrierAroundwareFactory, AuthBarrier, 'api_auth_db'
|
143
265
|
end
|