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,34 @@
1
+ require 'spec_helper'
2
+ require 'goliath/env'
3
+ require 'goliath/rack/default_mime_type'
4
+
5
+ describe Goliath::Rack::DefaultMimeType do
6
+ let(:app) { mock('app').as_null_object }
7
+ let(:dmt) { Goliath::Rack::DefaultMimeType.new(app) }
8
+ let(:env) do
9
+ env = Goliath::Env.new
10
+ env['status'] = mock('status').as_null_object
11
+ env
12
+ end
13
+
14
+ context 'accept header cleanup' do
15
+ it 'handles a nil header' do
16
+ env['HTTP_ACCEPT'] = nil
17
+ lambda { dmt.call(env) }.should_not raise_error
18
+ end
19
+
20
+ %w(gzip deflate compressed identity).each do |type|
21
+ it "removes #{type} from the accept header" do
22
+ env['HTTP_ACCEPT'] = "text/html, #{type}, text/javascript"
23
+ dmt.call(env)
24
+ env['HTTP_ACCEPT'].should == 'text/html, text/javascript'
25
+ end
26
+ end
27
+
28
+ it 'sets to */* if all entries removed' do
29
+ env['HTTP_ACCEPT'] = 'identity'
30
+ dmt.call(env)
31
+ env['HTTP_ACCEPT'].should == '*/*'
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'goliath/rack/formatters/json'
3
+
4
+ describe Goliath::Rack::Formatters::JSON do
5
+ it 'accepts an app' do
6
+ lambda { Goliath::Rack::Formatters::JSON.new('my app') }.should_not raise_error Exception
7
+ end
8
+
9
+ describe 'with a formatter' do
10
+ before(:each) do
11
+ @app = mock('app').as_null_object
12
+ @js = Goliath::Rack::Formatters::JSON.new(@app)
13
+ end
14
+
15
+ it 'checks content type for application/json' do
16
+ @js.json_response?({'Content-Type' => 'application/json'}).should be_true
17
+ end
18
+
19
+ it 'returns false for non-applicaton/json types' do
20
+ @js.json_response?({'Content-Type' => 'application/xml'}).should be_false
21
+ end
22
+
23
+ it 'calls the app with the provided environment' do
24
+ env_mock = mock('env').as_null_object
25
+ @app.should_receive(:call).with(env_mock).and_return([200, {}, {"a" => 1}])
26
+ @js.call(env_mock)
27
+ end
28
+
29
+ it 'formats the body into json if content-type is json' do
30
+ @app.should_receive(:call).and_return([200, {'Content-Type' => 'application/json'}, {:a => 1, :b => 2}])
31
+
32
+ status, header, body = @js.call({})
33
+ lambda { Yajl::Parser.parse(body.first)['a'].should == 1 }.should_not raise_error Exception
34
+ end
35
+
36
+ it "doesn't format to json if the type is not application/json" do
37
+ @app.should_receive(:call).and_return([200, {'Content-Type' => 'application/xml'}, {:a => 1, :b => 2}])
38
+
39
+ Yajl::Parser.should_not_receive(:encode)
40
+ status, header, body = @js.call({})
41
+ body[:a].should == 1
42
+ end
43
+
44
+ it 'returns the status and headers' do
45
+ @app.should_receive(:call).and_return([200, {'Content-Type' => 'application/xml'}, {:a => 1, :b => 2}])
46
+
47
+ Yajl::Parser.should_not_receive(:encode)
48
+ status, header, body = @js.call({})
49
+ status.should == 200
50
+ header.should == {'Content-Type' => 'application/xml'}
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+ require 'goliath/rack/formatters/xml'
3
+ require 'nokogiri'
4
+
5
+ describe Goliath::Rack::Formatters::XML do
6
+ it 'accepts an app' do
7
+ lambda { Goliath::Rack::Formatters::XML.new('my app') }.should_not raise_error Exception
8
+ end
9
+
10
+ describe 'with a formatter' do
11
+ before(:each) do
12
+ @app = mock('app').as_null_object
13
+ @xml = Goliath::Rack::Formatters::XML.new(@app)
14
+ end
15
+
16
+ it 'checks content type for application/xml' do
17
+ @xml.xml_response?({'Content-Type' => 'application/xml'}).should be_true
18
+ end
19
+
20
+ it 'returns false for non-applicaton/xml types' do
21
+ @xml.xml_response?({'Content-Type' => 'application/json'}).should be_false
22
+ end
23
+
24
+ it 'calls the app with the provided environment' do
25
+ env_mock = mock('env').as_null_object
26
+ @app.should_receive(:call).with(env_mock).and_return([200, {}, {"a" => 1}])
27
+ @xml.call(env_mock)
28
+ end
29
+
30
+ it 'formats the body into xml if content-type is xml' do
31
+ @app.should_receive(:call).and_return([200, {'Content-Type' => 'application/xml'}, {:a => 1, :b => 2}])
32
+
33
+ status, header, body = @xml.call({})
34
+ lambda { Nokogiri.parse(body.first).search('a').inner_text.should == '1' }.should_not raise_error Exception
35
+ end
36
+
37
+ it 'generates arrays correctly' do
38
+ @app.should_receive(:call).and_return([200, {'Content-Type' => 'application/xml'}, [1, 2]])
39
+
40
+ status, header, body = @xml.call({})
41
+ lambda {
42
+ doc = Nokogiri.parse(body.first)
43
+ doc.search('item').first.inner_text.should == '1'
44
+ doc.search('item').last.inner_text.should == '2'
45
+ }.should_not raise_error Exception
46
+ end
47
+
48
+ it "doesn't format to xml if the type is not application/xml" do
49
+ @app.should_receive(:call).and_return([200, {'Content-Type' => 'application/json'}, {:a => 1, :b => 2}])
50
+
51
+ @xml.should_not_receive(:to_xml)
52
+ status, header, body = @xml.call({})
53
+ body[:a].should == 1
54
+ end
55
+
56
+ it 'returns the status and headers' do
57
+ @app.should_receive(:call).and_return([200, {'Content-Type' => 'application/json'}, {:a => 1, :b => 2}])
58
+
59
+ @xml.should_not_receive(:to_xml)
60
+ status, header, body = @xml.call({})
61
+ status.should == 200
62
+ header.should == {'Content-Type' => 'application/json'}
63
+ end
64
+ end
65
+ end
66
+
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+ require 'goliath/rack/heartbeat'
3
+ require 'goliath/env'
4
+
5
+ describe Goliath::Rack::Heartbeat do
6
+ it 'accepts an app' do
7
+ lambda { Goliath::Rack::Heartbeat.new('my app') }.should_not raise_error(Exception)
8
+ end
9
+
10
+ describe 'with the middleware' do
11
+ before(:each) do
12
+ @app = mock('app').as_null_object
13
+ @env = Goliath::Env.new
14
+ @env['status'] = mock('status').as_null_object
15
+ @hb = Goliath::Rack::Heartbeat.new(@app)
16
+ end
17
+
18
+ it 'allows /status as a path prefix' do
19
+ @app.should_receive(:call)
20
+ @env['PATH_INFO'] = '/status_endpoint'
21
+ @hb.call(@env)
22
+ end
23
+
24
+ it "doesn't call the app when request /status" do
25
+ @app.should_not_receive(:call)
26
+ @env['PATH_INFO'] = '/status'
27
+ @hb.call(@env)
28
+ end
29
+
30
+ it 'returns the status, headers and body from the app on non-/status' do
31
+ @env['PATH_INFO'] = '/v1'
32
+ @app.should_receive(:call).and_return([200, {'a' => 'b'}, {'c' => 'd'}])
33
+ status, headers, body = @hb.call(@env)
34
+ status.should == 200
35
+ headers.should == {'a' => 'b'}
36
+ body.should == {'c' => 'd'}
37
+ end
38
+
39
+ it 'returns the correct status, headers and body on /status' do
40
+ @env['PATH_INFO'] = '/status'
41
+ status, headers, body = @hb.call(@env)
42
+ status.should == 200
43
+ headers.should == {}
44
+ body.should == 'OK'
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+ require 'goliath/rack/params'
3
+
4
+ describe Goliath::Rack::Params do
5
+ it 'accepts an app' do
6
+ lambda { Goliath::Rack::Params.new('my app') }.should_not raise_error(Exception)
7
+ end
8
+
9
+ describe 'with middleware' do
10
+ before(:each) do
11
+ @app = mock('app').as_null_object
12
+ @env = {}
13
+ @params = Goliath::Rack::Params.new(@app)
14
+ end
15
+
16
+ it 'parses the query string' do
17
+ @env['QUERY_STRING'] = 'foo=bar&baz=bonkey'
18
+
19
+ ret = @params.retrieve_params(@env)
20
+ ret['foo'].should == 'bar'
21
+ ret['baz'].should == 'bonkey'
22
+ end
23
+
24
+ it 'parses the post body' do
25
+ @env['rack.input'] = StringIO.new
26
+ @env['rack.input'] << "foo=bar&baz=bonkey"
27
+ @env['rack.input'].rewind
28
+
29
+ ret = @params.retrieve_params(@env)
30
+ ret['foo'].should == 'bar'
31
+ ret['baz'].should == 'bonkey'
32
+ end
33
+
34
+ it 'parses arrays of data' do
35
+ @env['QUERY_STRING'] = 'foo=bar&foo=baz&foo=foos'
36
+
37
+ ret = @params.retrieve_params(@env)
38
+ ret['foo'].is_a?(Array).should be_true
39
+ ret['foo'].length.should == 3
40
+ ret['foo'].should == %w(bar baz foos)
41
+ end
42
+
43
+ it 'combines query string and post body params' do
44
+ @env['QUERY_STRING'] = "baz=bar"
45
+
46
+ @env['rack.input'] = StringIO.new
47
+ @env['rack.input'] << "foos=bonkey"
48
+ @env['rack.input'].rewind
49
+
50
+ ret = @params.retrieve_params(@env)
51
+ ret['baz'].should == 'bar'
52
+ ret['foos'].should == 'bonkey'
53
+ end
54
+
55
+ it 'handles empty query and post body' do
56
+ ret = @params.retrieve_params(@env)
57
+ ret.is_a?(Hash).should be_true
58
+ ret.should be_empty
59
+ end
60
+
61
+ it 'prefers post body over query string' do
62
+ @env['QUERY_STRING'] = "foo=bar1&baz=bar"
63
+
64
+ @env['rack.input'] = StringIO.new
65
+ @env['rack.input'] << "foo=bar2&baz=bonkey"
66
+ @env['rack.input'].rewind
67
+
68
+ ret = @params.retrieve_params(@env)
69
+ ret['foo'].should == 'bar2'
70
+ ret['baz'].should == 'bonkey'
71
+ end
72
+
73
+ it 'sets the params into the environment' do
74
+ @app.should_receive(:call).with do |app_env|
75
+ app_env.has_key?('params').should be_true
76
+ app_env['params']['a'].should == 'b'
77
+ end
78
+
79
+ @env['QUERY_STRING'] = "a=b"
80
+ @params.call(@env)
81
+ end
82
+
83
+ it 'returns status, headers and body from the app' do
84
+ app_headers = {'Content-Type' => 'hash'}
85
+ app_body = {:a => 1, :b => 2}
86
+ @app.should_receive(:call).and_return([200, app_headers, app_body])
87
+
88
+ status, headers, body = @params.call(@env)
89
+ status.should == 200
90
+ headers.should == app_headers
91
+ body.should == app_body
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+ require 'goliath/rack/render'
3
+ require 'goliath/goliath'
4
+
5
+ describe Goliath::Rack::Render do
6
+ let(:env) do
7
+ env = Goliath::Env.new
8
+ env['params'] = {}
9
+ env
10
+ end
11
+
12
+ let(:app) { mock('app').as_null_object }
13
+ let(:render) { Goliath::Rack::Render.new(app) }
14
+
15
+ it 'accepts an app' do
16
+ lambda { Goliath::Rack::Render.new('my app') }.should_not raise_error(Exception)
17
+ end
18
+
19
+ it 'returns the status, body and app headers' do
20
+ app_body = {'c' => 'd'}
21
+
22
+ app.should_receive(:call).and_return([200, {'a' => 'b'}, app_body])
23
+ status, headers, body = render.call(env)
24
+
25
+ status.should == 200
26
+ headers['a'].should == 'b'
27
+ body.should == app_body
28
+ end
29
+
30
+ describe 'Vary' do
31
+ it 'adds Accept to provided Vary header' do
32
+ app.should_receive(:call).and_return([200, {'Vary' => 'Cookie'}, {}])
33
+ status, headers, body = render.call(env)
34
+ headers['Vary'].should == 'Cookie,Accept'
35
+ end
36
+
37
+ it 'sets Accept if there is no Vary header' do
38
+ app.should_receive(:call).and_return([200, {}, {}])
39
+ status, headers, body = render.call(env)
40
+ headers['Vary'].should == 'Accept'
41
+ end
42
+ end
43
+
44
+ describe 'Content-Type' do
45
+ before(:each) do
46
+ app.should_receive(:call).and_return([200, {}, {}])
47
+ end
48
+
49
+ describe 'from header' do
50
+ %w(application/json text/html application/rss+xml application/xml).each do |type|
51
+ it "handles content type for #{type}" do
52
+ env['HTTP_ACCEPT'] = type
53
+ status, headers, body = render.call(env)
54
+ headers['Content-Type'].should =~ /^#{Regexp.escape(type)}/
55
+ end
56
+ end
57
+ end
58
+
59
+ describe 'from URL param' do
60
+ {'rss' => 'application/rss+xml',
61
+ 'xml' => 'application/xml',
62
+ 'html' => 'text/html',
63
+ 'json' => 'application/json'}.each_pair do |format, content_type|
64
+ it "converts #{format} to #{content_type}" do
65
+ env['params']['format'] = format
66
+ status, headers, body = render.call(env)
67
+ headers['Content-Type'].should =~ /^#{Regexp.escape(content_type)}/
68
+ end
69
+ end
70
+ end
71
+
72
+ it 'prefers URL format over header' do
73
+ env['HTTP_ACCEPT'] = 'application/xml'
74
+ env['params']['format'] = 'json'
75
+ status, headers, body = render.call(env)
76
+ headers['Content-Type'].should =~ %r{^application/json}
77
+ end
78
+
79
+ describe 'charset' do
80
+ it 'is set if not present' do
81
+ env['params']['format'] = 'json'
82
+ status, headers, body = render.call(env)
83
+ headers['Content-Type'].should =~ /; charset=utf-8$/
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'goliath/rack/validation/boolean_value'
3
+
4
+ describe Goliath::Rack::Validation::BooleanValue do
5
+ before(:each) do
6
+ @app = mock('app').as_null_object
7
+ @env = {'params' => {}}
8
+ end
9
+
10
+ describe 'with middleware' do
11
+ before(:each) do
12
+ @bv = Goliath::Rack::Validation::BooleanValue.new(@app, {:key => 'id', :default => true})
13
+ end
14
+
15
+ it 'uses the default if the key is not present' do
16
+ @bv.call(@env)
17
+ @env['params']['id'].should == true
18
+ end
19
+
20
+ it 'uses the default if the key is nil' do
21
+ @env['params']['id'] = nil
22
+ @bv.call(@env)
23
+ @env['params']['id'].should == true
24
+ end
25
+
26
+ it 'uses the default if the key is blank' do
27
+ @env['params']['id'] = ""
28
+ @bv.call(@env)
29
+ @env['params']['id'].should == true
30
+ end
31
+
32
+ it 'a random value is false' do
33
+ @env['params']['id'] = 'blarg'
34
+ @bv.call(@env)
35
+ @env['params']['id'].should == false
36
+ end
37
+
38
+ %w(t true TRUE T 1).each do |type|
39
+ it "considers #{type} true" do
40
+ @env['params']['id'] = type
41
+ @bv.call(@env)
42
+ @env['params']['id'].should == true
43
+ end
44
+ end
45
+
46
+ %w(f false FALSE F 0).each do |type|
47
+ it "considers #{type} false" do
48
+ @env['params']['id'] = type
49
+ @bv.call(@env)
50
+ @env['params']['id'].should == false
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+ require 'goliath/rack/validation/default_params'
3
+
4
+ describe Goliath::Rack::Validation::DefaultParams do
5
+ it 'accepts an app' do
6
+ opts = {:defaults => ['title'], :key => 'fields'}
7
+ lambda { Goliath::Rack::Validation::DefaultParams.new('my app', opts) }.should_not raise_error(Exception)
8
+ end
9
+
10
+ it 'requires defaults to be set' do
11
+ lambda { Goliath::Rack::Validation::DefaultParams.new('my app', {:key => 'test'}) }.should raise_error(Exception)
12
+ end
13
+
14
+ it 'requires key to be set' do
15
+ lambda { Goliath::Rack::Validation::DefaultParams.new('my app', {:defaults => 'test'}) }.should raise_error(Exception)
16
+ end
17
+
18
+ describe 'with middleware' do
19
+ before(:each) do
20
+ @app = mock('app').as_null_object
21
+ @env = {'params' => {}}
22
+ @rf = Goliath::Rack::Validation::DefaultParams.new(@app, {:key => 'fl', :defaults => ['title', 'link']})
23
+ end
24
+
25
+ it 'passes through provided key if set' do
26
+ @env['params']['fl'] = ['pubdate', 'content']
27
+ @rf.call(@env)
28
+ @env['params']['fl'].should == ['pubdate', 'content']
29
+ end
30
+
31
+ it 'sets defaults if no key set' do
32
+ @rf.call(@env)
33
+ @env['params']['fl'].should == ['title', 'link']
34
+ end
35
+
36
+ it 'sets defaults if no key set' do
37
+ @env['params']['fl'] = nil
38
+ @rf.call(@env)
39
+ @env['params']['fl'].should == ['title', 'link']
40
+ end
41
+
42
+ it 'sets defaults if no key is empty' do
43
+ @env['params']['fl'] = []
44
+ @rf.call(@env)
45
+ @env['params']['fl'].should == ['title', 'link']
46
+ end
47
+
48
+ it 'handles a single item' do
49
+ @env['params']['fl'] = 'title'
50
+ @rf.call(@env)
51
+ @env['params']['fl'].should == 'title'
52
+ end
53
+
54
+ it 'handles a blank string' do
55
+ @env['params']['fl'] = ''
56
+ @rf.call(@env)
57
+ @env['params']['fl'].should == ['title', 'link']
58
+ end
59
+
60
+ it 'returns the app status, headers and body' do
61
+ app_headers = {'Content-Type' => 'asdf'}
62
+ app_body = {'a' => 'b'}
63
+ @app.should_receive(:call).and_return([200, app_headers, app_body])
64
+
65
+ status, headers, body = @rf.call(@env)
66
+ status.should == 200
67
+ headers.should == app_headers
68
+ body.should == app_body
69
+ end
70
+ end
71
+ end