goliath 0.9.1 → 0.9.2

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 (94) hide show
  1. data/.gitignore +1 -0
  2. data/HISTORY +50 -0
  3. data/README.md +2 -0
  4. data/examples/activerecord/srv.rb +1 -3
  5. data/examples/async_aroundware_demo.rb +81 -0
  6. data/examples/async_upload.rb +1 -2
  7. data/examples/auth_and_rate_limit.rb +143 -0
  8. data/examples/chunked_streaming.rb +37 -0
  9. data/examples/conf_test.rb +1 -3
  10. data/examples/config/auth_and_rate_limit.rb +30 -0
  11. data/examples/config/template.rb +8 -0
  12. data/examples/content_stream.rb +1 -3
  13. data/examples/echo.rb +8 -6
  14. data/examples/env_use_statements.rb +17 -0
  15. data/examples/gziped.rb +1 -3
  16. data/examples/public/stylesheets/style.css +296 -0
  17. data/examples/rack_routes.rb +65 -3
  18. data/examples/rasterize/rasterize.js +15 -0
  19. data/examples/rasterize/rasterize.rb +36 -0
  20. data/examples/rasterize/rasterize_and_shorten.rb +37 -0
  21. data/examples/stream.rb +2 -2
  22. data/examples/template.rb +48 -0
  23. data/examples/test_rig.rb +125 -0
  24. data/examples/valid.rb +4 -2
  25. data/examples/views/debug.haml +4 -0
  26. data/examples/views/joke.markdown +13 -0
  27. data/examples/views/layout.erb +12 -0
  28. data/examples/views/layout.haml +39 -0
  29. data/examples/views/root.haml +28 -0
  30. data/goliath.gemspec +10 -3
  31. data/lib/goliath.rb +0 -36
  32. data/lib/goliath/api.rb +137 -26
  33. data/lib/goliath/application.rb +71 -21
  34. data/lib/goliath/connection.rb +4 -2
  35. data/lib/goliath/constants.rb +1 -0
  36. data/lib/goliath/env.rb +40 -1
  37. data/lib/goliath/goliath.rb +30 -15
  38. data/lib/goliath/headers.rb +2 -2
  39. data/lib/goliath/plugins/latency.rb +8 -2
  40. data/lib/goliath/rack.rb +18 -0
  41. data/lib/goliath/rack/async_aroundware.rb +56 -0
  42. data/lib/goliath/rack/async_middleware.rb +93 -0
  43. data/lib/goliath/rack/builder.rb +42 -0
  44. data/lib/goliath/rack/default_response_format.rb +3 -15
  45. data/lib/goliath/rack/formatters.rb +11 -0
  46. data/lib/goliath/rack/formatters/html.rb +2 -18
  47. data/lib/goliath/rack/formatters/json.rb +2 -17
  48. data/lib/goliath/rack/formatters/plist.rb +32 -0
  49. data/lib/goliath/rack/formatters/xml.rb +23 -31
  50. data/lib/goliath/rack/formatters/yaml.rb +27 -0
  51. data/lib/goliath/rack/jsonp.rb +1 -13
  52. data/lib/goliath/rack/params.rb +55 -27
  53. data/lib/goliath/rack/render.rb +13 -22
  54. data/lib/goliath/rack/templates.rb +357 -0
  55. data/lib/goliath/rack/tracer.rb +11 -12
  56. data/lib/goliath/rack/validation.rb +12 -0
  57. data/lib/goliath/rack/validation/default_params.rb +0 -2
  58. data/lib/goliath/rack/validation/numeric_range.rb +11 -2
  59. data/lib/goliath/rack/validation/request_method.rb +3 -2
  60. data/lib/goliath/rack/validation/required_param.rb +13 -11
  61. data/lib/goliath/rack/validation/required_value.rb +11 -15
  62. data/lib/goliath/rack/validator.rb +51 -0
  63. data/lib/goliath/request.rb +34 -20
  64. data/lib/goliath/response.rb +3 -2
  65. data/lib/goliath/runner.rb +5 -11
  66. data/lib/goliath/server.rb +2 -1
  67. data/lib/goliath/synchrony/mongo_receiver.rb +64 -0
  68. data/lib/goliath/synchrony/response_receiver.rb +64 -0
  69. data/lib/goliath/test_helper.rb +39 -21
  70. data/lib/goliath/validation.rb +2 -0
  71. data/lib/goliath/{rack/validation_error.rb → validation/error.rb} +0 -16
  72. data/lib/goliath/validation/standard_http_errors.rb +31 -0
  73. data/lib/goliath/version.rb +1 -1
  74. data/spec/integration/http_log_spec.rb +16 -16
  75. data/spec/integration/rack_routes_spec.rb +144 -0
  76. data/spec/integration/reloader_spec.rb +4 -4
  77. data/spec/integration/template_spec.rb +54 -0
  78. data/spec/integration/trace_spec.rb +23 -0
  79. data/spec/integration/valid_spec.rb +21 -0
  80. data/spec/spec_helper.rb +3 -1
  81. data/spec/unit/api_spec.rb +30 -0
  82. data/spec/unit/rack/builder_spec.rb +40 -0
  83. data/spec/unit/rack/formatters/plist_spec.rb +51 -0
  84. data/spec/unit/rack/formatters/yaml_spec.rb +53 -0
  85. data/spec/unit/rack/params_spec.rb +22 -0
  86. data/spec/unit/rack/render_spec.rb +10 -5
  87. data/spec/unit/rack/validation/numeric_range_spec.rb +8 -1
  88. data/spec/unit/rack/validation/request_method_spec.rb +8 -8
  89. data/spec/unit/rack/validation/required_param_spec.rb +19 -15
  90. data/spec/unit/rack/validation/required_value_spec.rb +10 -13
  91. data/spec/unit/server_spec.rb +4 -4
  92. data/spec/unit/validation/standard_http_errors_spec.rb +21 -0
  93. metadata +177 -35
  94. data/spec/unit/rack/validation_error_spec.rb +0 -40
@@ -2,6 +2,7 @@ require 'em-synchrony'
2
2
  require 'em-synchrony/em-http'
3
3
 
4
4
  require 'goliath/server'
5
+ require 'goliath/rack/builder'
5
6
  require 'rack'
6
7
 
7
8
  module Goliath
@@ -24,20 +25,7 @@ module Goliath
24
25
  #
25
26
  module TestHelper
26
27
  def self.included(mod)
27
- Goliath.env = 'test'
28
- end
29
-
30
- # Builds the rack middleware chain for the given API
31
- #
32
- # @param klass [Class] The API class to build the middlewares for
33
- # @return [Object] The Rack middleware chain
34
- def build_app(klass)
35
- ::Rack::Builder.new do
36
- klass.middlewares.each do |mw|
37
- use(*(mw[0..1].compact), &mw[2])
38
- end
39
- run klass.new
40
- end
28
+ Goliath.env = :test
41
29
  end
42
30
 
43
31
  # Launches an instance of a given API server. The server
@@ -47,17 +35,17 @@ module Goliath
47
35
  # @param port [Integer] The port to run the server on
48
36
  # @param options [Hash] The options hash to provide to the server
49
37
  # @return [Goliath::Server] The executed server
50
- def server(api, port = 9000, options = {})
38
+ def server(api, port = 9000, options = {}, &blk)
51
39
  op = OptionParser.new
52
40
 
53
41
  s = Goliath::Server.new
54
42
  s.logger = mock('log').as_null_object
55
43
  s.api = api.new
56
- s.app = build_app(api)
44
+ s.app = Goliath::Rack::Builder.build(api, s.api)
57
45
  s.api.options_parser(op, options)
58
46
  s.options = options
59
47
  s.port = port
60
- s.start
48
+ s.start(&blk)
61
49
  s
62
50
  end
63
51
 
@@ -76,10 +64,7 @@ module Goliath
76
64
  # @param blk [Proc] The code to execute after the server is launched.
77
65
  # @note This will not return until stop is called.
78
66
  def with_api(api, options = {}, &blk)
79
- EM.synchrony do
80
- @api_server = server(api, 9000, options)
81
- blk.call
82
- end
67
+ server(api, 9000, options, &blk)
83
68
  end
84
69
 
85
70
  # Helper method to setup common callbacks for various request methods.
@@ -99,6 +84,17 @@ module Goliath
99
84
  req.errback { stop }
100
85
  end
101
86
 
87
+ # Make a HEAD request the currently launched API.
88
+ #
89
+ # @param request_data [Hash] Any data to pass to the HEAD request.
90
+ # @param errback [Proc] An error handler to attach
91
+ # @param blk [Proc] The callback block to execute
92
+ def head_request(request_data = {}, errback = nil, &blk)
93
+ path = request_data.delete(:path) || ''
94
+ req = EM::HttpRequest.new("http://localhost:9000#{path}").head(request_data)
95
+ hookup_request_callbacks(req, errback, &blk)
96
+ end
97
+
102
98
  # Make a GET request the currently launched API.
103
99
  #
104
100
  # @param request_data [Hash] Any data to pass to the GET request.
@@ -120,5 +116,27 @@ module Goliath
120
116
  req = EM::HttpRequest.new("http://localhost:9000#{path}").post(request_data)
121
117
  hookup_request_callbacks(req, errback, &blk)
122
118
  end
119
+
120
+ # Make a PUT request the currently launched API.
121
+ #
122
+ # @param request_data [Hash] Any data to pass to the PUT request.
123
+ # @param errback [Proc] An error handler to attach
124
+ # @param blk [Proc] The callback block to execute
125
+ def put_request(request_data = {}, errback = nil, &blk)
126
+ path = request_data.delete(:path) || ''
127
+ req = EM::HttpRequest.new("http://localhost:9000#{path}").put(request_data)
128
+ hookup_request_callbacks(req, errback, &blk)
129
+ end
130
+
131
+ # Make a DELETE request the currently launched API.
132
+ #
133
+ # @param request_data [Hash] Any data to pass to the DELETE request.
134
+ # @param errback [Proc] An error handler to attach
135
+ # @param blk [Proc] The callback block to execute
136
+ def delete_request(request_data = {}, errback = nil, &blk)
137
+ path = request_data.delete(:path) || ''
138
+ req = EM::HttpRequest.new("http://localhost:9000#{path}").delete(request_data)
139
+ hookup_request_callbacks(req, errback, &blk)
140
+ end
123
141
  end
124
142
  end
@@ -0,0 +1,2 @@
1
+ require 'goliath/validation/error'
2
+ require 'goliath/validation/standard_http_errors'
@@ -19,20 +19,4 @@ module Goliath
19
19
  end
20
20
  end
21
21
  end
22
-
23
- module Rack
24
- # Middleware to catch {Goliath::Validation::Error} exceptions
25
- # and returns the [status code, no headers, :error => exception message]
26
- class ValidationError
27
- def initialize(app)
28
- @app = app
29
- end
30
-
31
- def call(env)
32
- @app.call(env)
33
- rescue Goliath::Validation::Error => e
34
- [e.status_code, {}, {:error => e.message}]
35
- end
36
- end
37
- end
38
22
  end
@@ -0,0 +1,31 @@
1
+ require 'goliath/validation/error'
2
+ require 'goliath/http_status_codes'
3
+
4
+ module Goliath
5
+ #
6
+ # Make a subclass of Goliath::Validation::Error for each standard HTTP error
7
+ # code (4xx and 5xx). Error will have a default status_code and message
8
+ # correct for that response:
9
+ #
10
+ # err = Goliath::Validation::NotFoundError.new
11
+ # p [err.status_code, err.to_s]
12
+ # # => [400, "Not Found"]
13
+ #
14
+ # Each class is named for the standard HTTP message, so 504 'Gateway Time-out'
15
+ # becomes a Goliath::Validation::GatewayTimeoutError (except 'Internal Server
16
+ # Error', which becomes InternalServerError not InternalServerErrorError). All non-alphanumeric
17
+ # characters are smushed together, with no upcasing or
18
+ # downcasing.
19
+ HTTP_ERROR_CODES = HTTP_STATUS_CODES.select { |code,msg| code >= 400 && code <= 599 }
20
+
21
+ HTTP_ERROR_CODES.each do |code, msg|
22
+ klass_name = "#{msg.gsub(/\W+/, '')}Error".gsub(/ErrorError$/, "Error")
23
+ klass = Class.new(Goliath::Validation::Error)
24
+ klass.class_eval(%Q{
25
+ def initialize(message='#{msg}')
26
+ super('#{code}', message)
27
+ end }, __FILE__, __LINE__)
28
+
29
+ Goliath::Validation.const_set(klass_name, klass)
30
+ end
31
+ end
@@ -1,4 +1,4 @@
1
1
  module Goliath
2
2
  # The current version of Goliath
3
- VERSION = '0.9.1'
3
+ VERSION = '0.9.2'
4
4
  end
@@ -32,14 +32,14 @@ describe HttpLog do
32
32
 
33
33
  let(:api_options) { { :config => config_file } }
34
34
 
35
- def mock_mongo
36
- @api_server.config['mongo'] = mock('mongo').as_null_object
35
+ def mock_mongo(api)
36
+ api.config['mongo'] = mock('mongo').as_null_object
37
37
  end
38
38
 
39
39
  it 'responds to requests' do
40
- with_api(HttpLog, api_options) do
40
+ with_api(HttpLog, api_options) do |api|
41
41
  server(Responder, 8080)
42
- mock_mongo
42
+ mock_mongo(api)
43
43
 
44
44
  get_request({}, err) do |c|
45
45
  c.response_header.status.should == 200
@@ -48,9 +48,9 @@ describe HttpLog do
48
48
  end
49
49
 
50
50
  it 'forwards to our API server' do
51
- with_api(HttpLog, api_options) do
51
+ with_api(HttpLog, api_options) do |api|
52
52
  server(Responder, 8080)
53
- mock_mongo
53
+ mock_mongo(api)
54
54
 
55
55
  get_request({}, err) do |c|
56
56
  c.response_header.status.should == 200
@@ -70,9 +70,9 @@ describe HttpLog do
70
70
 
71
71
  context 'query parameters' do
72
72
  it 'forwards the query parameters' do
73
- with_api(HttpLog, api_options) do
73
+ with_api(HttpLog, api_options) do |api|
74
74
  server(Responder, 8080)
75
- mock_mongo
75
+ mock_mongo(api)
76
76
 
77
77
  get_request({:query => {:first => :foo, :second => :bar, :third => :baz}}, err) do |c|
78
78
  c.response_header.status.should == 200
@@ -84,9 +84,9 @@ describe HttpLog do
84
84
 
85
85
  context 'request path' do
86
86
  it 'forwards the request path' do
87
- with_api(HttpLog, api_options) do
87
+ with_api(HttpLog, api_options) do |api|
88
88
  server(Responder, 8080)
89
- mock_mongo
89
+ mock_mongo(api)
90
90
 
91
91
  get_request({:path => '/my/request/path'}, err) do |c|
92
92
  c.response_header.status.should == 200
@@ -98,9 +98,9 @@ describe HttpLog do
98
98
 
99
99
  context 'headers' do
100
100
  it 'forwards the headers' do
101
- with_api(HttpLog, api_options) do
101
+ with_api(HttpLog, api_options) do |api|
102
102
  server(Responder, 8080)
103
- mock_mongo
103
+ mock_mongo(api)
104
104
 
105
105
  get_request({:head => {:first => :foo, :second => :bar}}, err) do |c|
106
106
  c.response_header.status.should == 200
@@ -112,9 +112,9 @@ describe HttpLog do
112
112
 
113
113
  context 'request method' do
114
114
  it 'forwards GET requests' do
115
- with_api(HttpLog, api_options) do
115
+ with_api(HttpLog, api_options) do |api|
116
116
  server(Responder, 8080)
117
- mock_mongo
117
+ mock_mongo(api)
118
118
 
119
119
  get_request({}, err) do |c|
120
120
  c.response_header.status.should == 200
@@ -124,9 +124,9 @@ describe HttpLog do
124
124
  end
125
125
 
126
126
  it 'forwards POST requests' do
127
- with_api(HttpLog, api_options) do
127
+ with_api(HttpLog, api_options) do |api|
128
128
  server(Responder, 8080)
129
- mock_mongo
129
+ mock_mongo(api)
130
130
 
131
131
  post_request({}, err) do |c|
132
132
  c.response_header.status.should == 200
@@ -0,0 +1,144 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require File.join(File.dirname(__FILE__), '../../', 'examples/rack_routes')
4
+
5
+ describe RackRoutes do
6
+ let(:err) { Proc.new { fail "API request failed" } }
7
+
8
+ context "when using maps" do
9
+
10
+ it "ignores #response" do
11
+ expect {
12
+ with_api(RackRoutes) do
13
+ get_request({:path => '/'}, err) {}
14
+ end
15
+ }.to_not raise_error
16
+ end
17
+
18
+ it 'fallback not found to missing' do
19
+ with_api(RackRoutes) do
20
+ get_request({:path => '/donkey'}, err) do |cb|
21
+ cb.response_header.status.should == 404
22
+ cb.response.should == 'Try /version /hello_world, /bonjour, or /hola'
23
+ end
24
+ end
25
+ with_api(RackRoutes) do
26
+ get_request({:path => '/'}, err) do |cb|
27
+ cb.response_header.status.should == 404
28
+ cb.response.should == 'Try /version /hello_world, /bonjour, or /hola'
29
+ end
30
+ end
31
+ end
32
+
33
+ it 'fallback not found to /' do
34
+ with_api(RackRoutes) do
35
+ get_request({:path => '/donkey'}, err) do |cb|
36
+ cb.response_header.status.should == 404
37
+ cb.response.should == 'Try /version /hello_world, /bonjour, or /hola'
38
+ end
39
+ end
40
+ end
41
+
42
+ it 'routes to the correct API' do
43
+ with_api(RackRoutes) do
44
+ get_request({:path => '/bonjour'}, err) do |c|
45
+ c.response_header.status.should == 200
46
+ c.response.should == 'bonjour!'
47
+ end
48
+ end
49
+ end
50
+
51
+ it 'routes to the correct API using regex filters' do
52
+ with_api(RackRoutes) do
53
+ get_request({:path => '/98'}, err) do |c|
54
+ c.response_header.status.should == 200
55
+ c.response.should == 'number 98!'
56
+ end
57
+ end
58
+ end
59
+
60
+ it 'routes to the correct API referencing params in the body of the buidler' do
61
+ with_api(RackRoutes) do
62
+ get_request({:path => '/123123'}, err) do |c|
63
+ c.response_header.status.should == 200
64
+ c.response.should == 'big number 123123!'
65
+ end
66
+ end
67
+ end
68
+
69
+ context 'sinatra style route definition' do
70
+ it 'should honor the request method' do
71
+ with_api(RackRoutes) do
72
+ post_request({:path => '/hello_world'}, err) do |c|
73
+ c.response_header.status.should == 200
74
+ c.response.should == 'hello post world!'
75
+ end
76
+ end
77
+ end
78
+ it 'should reject other request methods' do
79
+ with_api(RackRoutes) do
80
+ put_request({:path => '/hello_world'}, err) do |c|
81
+ c.response_header.status.should == 405
82
+ c.response_header['ALLOW'].split(/, /).should == %w(GET HEAD POST)
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ context 'routes defined with get' do
89
+ it 'should allow get' do
90
+ with_api(RackRoutes) do
91
+ get_request({:path => '/hello_world'}, err) do |c|
92
+ c.response_header.status.should == 200
93
+ c.response.should == 'hello world!'
94
+ end
95
+ end
96
+ end
97
+ it 'should allow head' do
98
+ with_api(RackRoutes) do
99
+ head_request({:path => '/hello_world'}, err) do |c|
100
+ c.response_header.status.should == 200
101
+ c.response.should == 'hello world!'
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ context "defined in blocks" do
108
+ it 'uses middleware defined in the block' do
109
+ with_api(RackRoutes) do
110
+ post_request({:path => '/hola'}, err) do |c|
111
+ # the /hola route only supports GET requests
112
+ c.response_header.status.should == 405
113
+ c.response.should == '[:error, "Invalid request method"]'
114
+ c.response_header['ALLOW'].should == 'GET'
115
+ end
116
+ end
117
+ end
118
+
119
+ it "doesn't use middleware defined in the API" do
120
+ with_api(RackRoutes) do
121
+ get_request({:path => '/hola'}, err) do |cb|
122
+ # it doesn't raise required param error
123
+ cb.response_header.status.should == 200
124
+ cb.response.should == "hola!"
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ context "defined in classes" do
131
+ it 'uses API middleware' do
132
+ with_api(RackRoutes) do
133
+ post_request({:path => '/aloha'}, err) do |c|
134
+ # the /hola route only supports GET requests
135
+ c.response_header.status.should == 405
136
+ c.response.should == '[:error, "Invalid request method"]'
137
+ c.response_header['ALLOW'].should == 'GET'
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ end
144
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- Goliath.env = 'dev'
3
+ Goliath.env = :development
4
4
 
5
5
  class ReloaderDev < Goliath::API
6
6
  use Goliath::Rack::Params
@@ -17,8 +17,8 @@ end
17
17
  describe "Reloader" do
18
18
  let(:err) { Proc.new { fail "API request failed" } }
19
19
 
20
- before(:each) { Goliath.env = "dev" }
21
- after(:each) { Goliath.env = "test" }
20
+ before(:each) { Goliath.env = :development }
21
+ after(:each) { Goliath.env = :test }
22
22
 
23
23
  def count(klass)
24
24
  cnt = 0
@@ -37,7 +37,7 @@ describe "Reloader" do
37
37
  end
38
38
 
39
39
  it "doesn't add in prod mode" do
40
- Goliath.env = "prod"
40
+ Goliath.env = :production
41
41
  count(ReloaderProd).should == 0
42
42
  end
43
43
  end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require File.join(File.dirname(__FILE__), '../../', 'examples/template')
3
+
4
+ describe Template do
5
+
6
+ def config_file
7
+ File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'examples', 'config', 'template.rb'))
8
+ end
9
+
10
+ let(:api_options) { { :config => config_file } }
11
+
12
+ it 'renders haml template with default haml layout' do
13
+ with_api(Template, api_options) do
14
+ get_request do |c|
15
+ c.response.should =~ %r{<li><a href="/joke">Tell me a joke</a></li>}
16
+ end
17
+ end
18
+ end
19
+
20
+ it 'renders a markdown template with default haml layout' do
21
+ with_api(Template, api_options) do
22
+ get_request(:path => '/joke') do |c|
23
+ c.response.should =~ %r{<code>"Arr, I dunno matey -- but it's drivin' me nuts!"\s*</code>}m
24
+ end
25
+ end
26
+ end
27
+
28
+ it 'lets me specify an alternate layout engine' do
29
+ with_api(Template, api_options) do
30
+ get_request(:path => '/erb_me') do |c|
31
+ c.response.should =~ %r{I AM ERB</h1>}m
32
+ end
33
+ end
34
+ end
35
+
36
+ it 'accepts local variables' do
37
+ with_api(Template, api_options) do
38
+ get_request(:path => '/erb_me') do |c|
39
+ c.response.should =~ %r{<title>HERE IS A JOKE</title>}m
40
+ end
41
+ end
42
+ end
43
+
44
+ describe 'On a missing template' do
45
+ it 'raises an explanatory 500 error' do
46
+ with_api(Template, api_options) do
47
+ get_request(:path => '/oops') do |c|
48
+ c.response.should =~ %r{^\[:error, "Template no_such_template not found in .*examples/views for haml"\]$}
49
+ c.response_header.status.should == 500
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end