goliath 0.9.0

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 (84) hide show
  1. data/.gitignore +15 -0
  2. data/.rspec +2 -0
  3. data/.yardopts +2 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +66 -0
  6. data/README.md +86 -0
  7. data/Rakefile +18 -0
  8. data/examples/activerecord/config/srv.rb +7 -0
  9. data/examples/activerecord/srv.rb +37 -0
  10. data/examples/async_upload.rb +34 -0
  11. data/examples/conf_test.rb +27 -0
  12. data/examples/config/conf_test.rb +12 -0
  13. data/examples/config/echo.rb +1 -0
  14. data/examples/config/http_log.rb +7 -0
  15. data/examples/config/shared.rb +5 -0
  16. data/examples/custom_server.rb +57 -0
  17. data/examples/echo.rb +37 -0
  18. data/examples/gziped.rb +40 -0
  19. data/examples/hello_world.rb +10 -0
  20. data/examples/http_log.rb +85 -0
  21. data/examples/rack_routes.rb +44 -0
  22. data/examples/stream.rb +37 -0
  23. data/examples/valid.rb +19 -0
  24. data/goliath.gemspec +41 -0
  25. data/lib/goliath.rb +38 -0
  26. data/lib/goliath/api.rb +165 -0
  27. data/lib/goliath/application.rb +90 -0
  28. data/lib/goliath/connection.rb +94 -0
  29. data/lib/goliath/constants.rb +51 -0
  30. data/lib/goliath/env.rb +92 -0
  31. data/lib/goliath/goliath.rb +49 -0
  32. data/lib/goliath/headers.rb +37 -0
  33. data/lib/goliath/http_status_codes.rb +44 -0
  34. data/lib/goliath/plugins/latency.rb +33 -0
  35. data/lib/goliath/rack/default_mime_type.rb +30 -0
  36. data/lib/goliath/rack/default_response_format.rb +33 -0
  37. data/lib/goliath/rack/formatters/html.rb +90 -0
  38. data/lib/goliath/rack/formatters/json.rb +42 -0
  39. data/lib/goliath/rack/formatters/xml.rb +90 -0
  40. data/lib/goliath/rack/heartbeat.rb +23 -0
  41. data/lib/goliath/rack/jsonp.rb +38 -0
  42. data/lib/goliath/rack/params.rb +30 -0
  43. data/lib/goliath/rack/render.rb +66 -0
  44. data/lib/goliath/rack/tracer.rb +31 -0
  45. data/lib/goliath/rack/validation/boolean_value.rb +59 -0
  46. data/lib/goliath/rack/validation/default_params.rb +46 -0
  47. data/lib/goliath/rack/validation/numeric_range.rb +59 -0
  48. data/lib/goliath/rack/validation/request_method.rb +33 -0
  49. data/lib/goliath/rack/validation/required_param.rb +54 -0
  50. data/lib/goliath/rack/validation/required_value.rb +58 -0
  51. data/lib/goliath/rack/validation_error.rb +38 -0
  52. data/lib/goliath/request.rb +199 -0
  53. data/lib/goliath/response.rb +93 -0
  54. data/lib/goliath/runner.rb +236 -0
  55. data/lib/goliath/server.rb +149 -0
  56. data/lib/goliath/test_helper.rb +118 -0
  57. data/lib/goliath/version.rb +4 -0
  58. data/spec/integration/async_request_processing.rb +23 -0
  59. data/spec/integration/echo_spec.rb +27 -0
  60. data/spec/integration/keepalive_spec.rb +28 -0
  61. data/spec/integration/pipelining_spec.rb +43 -0
  62. data/spec/integration/valid_spec.rb +24 -0
  63. data/spec/spec_helper.rb +6 -0
  64. data/spec/unit/connection_spec.rb +59 -0
  65. data/spec/unit/env_spec.rb +55 -0
  66. data/spec/unit/headers_spec.rb +53 -0
  67. data/spec/unit/rack/default_mime_type_spec.rb +34 -0
  68. data/spec/unit/rack/formatters/json_spec.rb +54 -0
  69. data/spec/unit/rack/formatters/xml_spec.rb +66 -0
  70. data/spec/unit/rack/heartbeat_spec.rb +47 -0
  71. data/spec/unit/rack/params_spec.rb +94 -0
  72. data/spec/unit/rack/render_spec.rb +87 -0
  73. data/spec/unit/rack/validation/boolean_value_spec.rb +54 -0
  74. data/spec/unit/rack/validation/default_params_spec.rb +71 -0
  75. data/spec/unit/rack/validation/numeric_range_spec.rb +96 -0
  76. data/spec/unit/rack/validation/request_method_spec.rb +47 -0
  77. data/spec/unit/rack/validation/required_param_spec.rb +92 -0
  78. data/spec/unit/rack/validation/required_value_spec.rb +99 -0
  79. data/spec/unit/rack/validation_error_spec.rb +40 -0
  80. data/spec/unit/request_spec.rb +59 -0
  81. data/spec/unit/response_spec.rb +35 -0
  82. data/spec/unit/runner_spec.rb +129 -0
  83. data/spec/unit/server_spec.rb +137 -0
  84. metadata +409 -0
@@ -0,0 +1,118 @@
1
+ require 'em-synchrony'
2
+ require 'em-synchrony/em-http'
3
+
4
+ require 'goliath/server'
5
+ require 'rack'
6
+
7
+ module Goliath
8
+ # Methods to help with testing Goliath APIs
9
+ #
10
+ # @example
11
+ # describe Echo do
12
+ # include Goliath::TestHelper
13
+ #
14
+ # let(:err) { Proc.new { fail "API request failed" } }
15
+ # it 'returns the echo param' do
16
+ # with_api(Echo) do
17
+ # get_request({:query => {:echo => 'test'}}, err) do |c|
18
+ # b = Yajl::Parser.parse(c.response)
19
+ # b['response'].should == 'test'
20
+ # end
21
+ # end
22
+ # end
23
+ # end
24
+ #
25
+ module TestHelper
26
+ 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
41
+ end
42
+
43
+ # Launches an instance of a given API server. The server
44
+ # will launch on the default settings of localhost port 9000.
45
+ #
46
+ # @param api [Class] The API class to launch
47
+ # @param port [Integer] The port to run the server on
48
+ # @return [Nil]
49
+ def server(api, port = 9000)
50
+ s = Goliath::Server.new
51
+ s.logger = mock('log').as_null_object
52
+ s.api = api.new
53
+ s.app = build_app(api)
54
+ s.port = port
55
+ s.start
56
+ s
57
+ end
58
+
59
+ # Stops the launched API
60
+ #
61
+ # @return [Nil]
62
+ def stop
63
+ EM.stop
64
+ end
65
+
66
+ # Wrapper for launching API and executing given code block. This
67
+ # will start the EventMachine reactor running.
68
+ #
69
+ # @param api [Class] The API class to launch
70
+ # @param blk [Proc] The code to execute after the server is launched.
71
+ # @note This will not return until stop is called.
72
+ def with_api(api, &blk)
73
+ EM.synchrony do
74
+ @api_server = server(api)
75
+ blk.call
76
+ end
77
+ end
78
+
79
+ # Helper method to setup common callbacks for various request methods.
80
+ # The given err and callback handlers will be attached and a callback
81
+ # to stop the reactor will be added.
82
+ #
83
+ # @param req [EM::HttpRequest] The HTTP request to augment
84
+ # @param errback [Proc] An error handler to attach
85
+ # @param blk [Proc] The callback handler to attach
86
+ # @return [Nil]
87
+ # @api private
88
+ def hookup_request_callbacks(req, errback, &blk)
89
+ req.callback &blk
90
+ req.callback { stop }
91
+
92
+ req.errback &errback if errback
93
+ req.errback { stop }
94
+ end
95
+
96
+ # Make a GET request the currently launched API.
97
+ #
98
+ # @param request_data [Hash] Any data to pass to the GET request.
99
+ # @param errback [Proc] An error handler to attach
100
+ # @param blk [Proc] The callback block to execute
101
+ def get_request(request_data = {}, errback = nil, &blk)
102
+ path = request_data.delete(:path) || ''
103
+ req = EM::HttpRequest.new("http://localhost:9000#{path}").get(request_data)
104
+ hookup_request_callbacks(req, errback, &blk)
105
+ end
106
+
107
+ # Make a POST request the currently launched API.
108
+ #
109
+ # @param request_data [Hash] Any data to pass to the POST request.
110
+ # @param errback [Proc] An error handler to attach
111
+ # @param blk [Proc] The callback block to execute
112
+ def post_request(request_data = {}, errback = nil, &blk)
113
+ path = request_data.delete(:path) || ''
114
+ req = EM::HttpRequest.new("http://localhost:9000#{path}").post(request_data)
115
+ hookup_request_callbacks(req, errback, &blk)
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,4 @@
1
+ module Goliath
2
+ # The current version of Goliath
3
+ VERSION = '0.9.0'
4
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ require File.join(File.dirname(__FILE__), '../../', 'examples/async_upload')
3
+
4
+ describe 'Async Request processing' do
5
+ include Goliath::TestHelper
6
+
7
+ it 'asynchronously processes the incoming request' do
8
+ with_api(AsyncUpload) do
9
+ request_data = {
10
+ :body => {:some => :data},
11
+ :head => {'X-Upload' => 'custom'}
12
+ }
13
+
14
+ err = Proc.new { fail "API request failed" }
15
+
16
+ post_request(request_data, err) do |c|
17
+ resp = Yajl::Parser.parse(c.response)
18
+ resp['body'].should match('some=data')
19
+ resp['head'].should include('X-Upload')
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ require File.join(File.dirname(__FILE__), '../../', 'examples/echo')
3
+
4
+ describe Echo do
5
+ include Goliath::TestHelper
6
+
7
+ let(:err) { Proc.new { fail "API request failed" } }
8
+
9
+ it 'returns the echo param' do
10
+ with_api(Echo) do
11
+ get_request({:query => {:echo => 'test'}}, err) do |c|
12
+ b = Yajl::Parser.parse(c.response)
13
+ b['response'].should == 'test'
14
+ end
15
+ end
16
+ end
17
+
18
+ it 'returns error without echo' do
19
+ with_api(Echo) do
20
+ get_request({}, err) do |c|
21
+ b = Yajl::Parser.parse(c.response)
22
+ b['error'].should_not be_nil
23
+ b['error'].should == 'Echo identifier missing'
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+ require File.join(File.dirname(__FILE__), '../../', 'examples/echo')
3
+
4
+ describe 'HTTP Keep-Alive support' do
5
+ include Goliath::TestHelper
6
+
7
+ it 'serves multiple requests via single connection' do
8
+ with_api(Echo) do
9
+ conn = EM::HttpRequest.new('http://localhost:9000')
10
+ r1 = conn.get(:query => {:echo => 'test'}, :keepalive => true)
11
+
12
+ r1.errback { fail }
13
+ r1.callback do |c|
14
+ b = Yajl::Parser.parse(c.response)
15
+ b['response'].should == 'test'
16
+
17
+ r2 = conn.get(:query => {:echo => 'test2'})
18
+ r2.errback { fail }
19
+ r2.callback do |c|
20
+ b = Yajl::Parser.parse(c.response)
21
+ b['response'].should == 'test2'
22
+
23
+ stop
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ class Interleaving < Goliath::API
4
+ use Goliath::Rack::Params
5
+
6
+ def response(env)
7
+ delay = env.params['delay']
8
+ EM::Synchrony.sleep(delay.to_f)
9
+
10
+ [200, {}, delay]
11
+ end
12
+ end
13
+
14
+ describe 'HTTP Pipelining support' do
15
+ include Goliath::TestHelper
16
+
17
+ it 'serves multiple requests via single connection' do
18
+ with_api(Interleaving) do
19
+ start = Time.now.to_f
20
+ res = []
21
+
22
+ conn = EM::HttpRequest.new('http://localhost:9000')
23
+ r1 = conn.aget :query => {:delay => 0.3}, :keepalive => true
24
+ r2 = conn.aget :query => {:delay => 0.2}
25
+
26
+ r1.errback { fail }
27
+ r1.callback do |c|
28
+ res << c.response
29
+ c.response.should match('0.3')
30
+ end
31
+
32
+ r2.errback { fail }
33
+ r2.callback do |c|
34
+ res << c.response
35
+
36
+ res.should == ['0.3', '0.2']
37
+ (Time.now.to_f - start).should be_within(0.1).of(0.3)
38
+
39
+ stop
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require File.join(File.dirname(__FILE__), '../../', 'examples/valid')
3
+
4
+ describe Valid do
5
+ include Goliath::TestHelper
6
+
7
+ let(:err) { Proc.new { fail "API request failed" } }
8
+
9
+ it 'returns OK with param' do
10
+ with_api(Valid) do
11
+ get_request({:query => {:test => 'test'}}, err) do |c|
12
+ c.response.should == 'OK'
13
+ end
14
+ end
15
+ end
16
+
17
+ it 'returns error without param' do
18
+ with_api(Valid) do
19
+ get_request({}, err) do |c|
20
+ c.response.should == '[:error, "Test identifier missing"]'
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ require 'bundler'
2
+
3
+ Bundler.setup
4
+ Bundler.require
5
+
6
+ require 'goliath/test_helper'
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Goliath::Connection do
4
+ before(:each) do
5
+ @c = Goliath::Connection.new('blah')
6
+ end
7
+
8
+ describe 'configuration' do
9
+ it 'accepts an app' do
10
+ app = mock('app')
11
+ @c.app = app
12
+ @c.app.should == app
13
+ end
14
+
15
+ it 'accepts a logger' do
16
+ logger = mock('logger')
17
+ @c.logger = logger
18
+ @c.logger.should == logger
19
+ end
20
+
21
+ it 'accepts a status object' do
22
+ status = mock('status')
23
+ @c.status = status
24
+ @c.status.should == status
25
+ end
26
+
27
+ it 'accepts config' do
28
+ config = mock('config')
29
+ @c.config = config
30
+ @c.config.should == config
31
+ end
32
+ end
33
+
34
+ describe 'post_init' do
35
+ it 'sets up the parser' do
36
+ @c.post_init
37
+ @c.instance_variable_get("@parser").should_not be_nil
38
+ end
39
+ end
40
+
41
+ describe 'receive_data' do
42
+ it 'passes data to the http parser' do
43
+ request_mock = mock("parser").as_null_object
44
+ request_mock.should_receive(:<<)
45
+
46
+ @c.instance_variable_set("@parser", request_mock)
47
+ @c.receive_data('more_data')
48
+ end
49
+
50
+ it "closes the connection when a parse error is received" do
51
+ current_mock = mock("current").as_null_object
52
+ current_mock.should_receive(:close)
53
+
54
+ @c.instance_variable_set("@current", current_mock)
55
+ lambda { @c.receive_data("bad data") }.should_not raise_error
56
+ end
57
+ end
58
+
59
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+ require 'goliath/env'
3
+
4
+ describe Goliath::Env do
5
+ before(:each) do
6
+ @env = Goliath::Env.new
7
+ end
8
+
9
+ it 'responds to []=' do
10
+ lambda { @env['test'] = 'blah' }.should_not raise_error(Exception)
11
+ end
12
+
13
+ it 'responds to []' do
14
+ @env['test'] = 'blah'
15
+ lambda { @env['test'].should == 'blah' }.should_not raise_error(Exception)
16
+ end
17
+
18
+ context '#method_missing' do
19
+ it 'allows access to items as methods' do
20
+ @env['db'] = 'test'
21
+ @env.db.should == 'test'
22
+ end
23
+
24
+ it 'allows access to config items as methods' do
25
+ @env['config'] = {}
26
+ @env['config']['db'] = 'test'
27
+ @env.db.should == 'test'
28
+ end
29
+ end
30
+
31
+ context '#respond_to?' do
32
+ it 'returns true for items in the hash' do
33
+ @env['test'] = 'true'
34
+ @env.respond_to?(:test).should be_true
35
+ end
36
+
37
+ it 'returns false for items not in hash' do
38
+ @env.respond_to?(:test).should be_false
39
+ end
40
+
41
+ it 'returns true for items in the config hash' do
42
+ @env['config'] = {'test' => true}
43
+ @env.respond_to?(:test).should be_true
44
+ end
45
+
46
+ it 'returns false for items not in the config hash' do
47
+ @env['config'] = {}
48
+ @env.respond_to?(:test).should be_false
49
+ end
50
+
51
+ it 'delegates if not found' do
52
+ @env.respond_to?(:[]).should be_true
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+ require 'goliath/headers'
3
+
4
+ describe Goliath::Headers do
5
+ before(:each) do
6
+ @h = Goliath::Headers.new
7
+ end
8
+
9
+ it 'outputs in the correct format' do
10
+ @h['my_header'] = 'my_value'
11
+ @h.to_s.should == "my_header: my_value\r\n"
12
+ end
13
+
14
+ it 'suppresses duplicate keys' do
15
+ @h['my_header'] = 'my_value1'
16
+ @h['my_header'] = 'my_value2'
17
+ @h.to_s.should == "my_header: my_value1\r\n"
18
+ end
19
+
20
+ it 'returns true if a key has been set' do
21
+ @h['my_header'] = 'my_value'
22
+ @h.has_key?('my_header').should be_true
23
+ end
24
+
25
+ it 'returns false if the key has not been set' do
26
+ @h.has_key?('my_header').should be_false
27
+ end
28
+
29
+ it 'ignores nil values' do
30
+ @h['my_header'] = nil
31
+ @h.to_s.should == ''
32
+ end
33
+
34
+ it 'allows a value after setting nil' do
35
+ @h['my_header'] = nil
36
+ @h['my_header'] = 'my_value'
37
+ @h.to_s.should == "my_header: my_value\r\n"
38
+ end
39
+
40
+ it 'formats time as an http time' do
41
+ time = Time.now
42
+ @h['my_time'] = time
43
+ @h.to_s.should == "my_time: #{time.httpdate}\r\n"
44
+ end
45
+
46
+ %w(Set-Cookie Set-Cookie2 Warning WWW-Authenticate).each do |key|
47
+ it "allows #{key} as to be duplicate" do
48
+ @h[key] = 'value1'
49
+ @h[key] = 'value2'
50
+ @h.to_s.should == "#{key}: value1\r\n#{key}: value2\r\n"
51
+ end
52
+ end
53
+ end