goliath 0.9.0 → 0.9.1

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/.gemtest +0 -0
  2. data/.gitignore +12 -9
  3. data/README.md +4 -5
  4. data/Rakefile +1 -0
  5. data/examples/activerecord/srv.rb +0 -2
  6. data/examples/async_upload.rb +0 -4
  7. data/examples/conf_test.rb +0 -1
  8. data/examples/config/content_stream.rb +33 -0
  9. data/examples/config/http_log.rb +7 -5
  10. data/examples/content_stream.rb +41 -0
  11. data/examples/echo.rb +1 -5
  12. data/examples/http_log.rb +0 -1
  13. data/examples/valid.rb +0 -4
  14. data/goliath.gemspec +3 -1
  15. data/lib/goliath/api.rb +12 -3
  16. data/lib/goliath/application.rb +5 -1
  17. data/lib/goliath/connection.rb +1 -2
  18. data/lib/goliath/constants.rb +2 -1
  19. data/lib/goliath/env.rb +7 -0
  20. data/lib/goliath/goliath.rb +8 -8
  21. data/lib/goliath/rack/params.rb +25 -2
  22. data/lib/goliath/request.rb +13 -3
  23. data/lib/goliath/runner.rb +7 -1
  24. data/lib/goliath/server.rb +6 -7
  25. data/lib/goliath/test_helper.rb +10 -4
  26. data/lib/goliath/version.rb +1 -1
  27. data/spec/integration/async_request_processing.rb +0 -2
  28. data/spec/integration/echo_spec.rb +37 -2
  29. data/spec/integration/empty_body_spec.rb +20 -0
  30. data/spec/integration/http_log_spec.rb +138 -0
  31. data/spec/integration/keepalive_spec.rb +0 -2
  32. data/spec/integration/pipelining_spec.rb +0 -2
  33. data/spec/integration/reloader_spec.rb +43 -0
  34. data/spec/integration/valid_spec.rb +0 -2
  35. data/spec/spec_helper.rb +6 -0
  36. data/spec/unit/env_spec.rb +2 -2
  37. data/spec/unit/rack/formatters/json_spec.rb +2 -2
  38. data/spec/unit/rack/formatters/xml_spec.rb +3 -3
  39. data/spec/unit/rack/heartbeat_spec.rb +1 -1
  40. data/spec/unit/rack/params_spec.rb +66 -3
  41. data/spec/unit/rack/render_spec.rb +1 -1
  42. data/spec/unit/rack/validation/default_params_spec.rb +3 -3
  43. data/spec/unit/rack/validation/numeric_range_spec.rb +3 -3
  44. data/spec/unit/rack/validation/request_method_spec.rb +1 -1
  45. data/spec/unit/rack/validation/required_param_spec.rb +2 -2
  46. data/spec/unit/rack/validation/required_value_spec.rb +2 -2
  47. data/spec/unit/rack/validation_error_spec.rb +1 -1
  48. data/spec/unit/request_spec.rb +23 -4
  49. metadata +39 -65
@@ -102,9 +102,11 @@ module Goliath
102
102
  opts.on('-a', '--address HOST', "Bind to HOST address (default: #{@options[:address]})") { |addr| @options[:address] = addr }
103
103
  opts.on('-p', '--port PORT', "Use PORT (default: #{@options[:port]})") { |port| @options[:port] = port.to_i }
104
104
 
105
+ opts.on('-u', '--user USER', "Run as specified user") {|v| @options[:user] = v }
105
106
  opts.on('-l', '--log FILE', "Log to file (default: off)") { |file| @options[:log_file] = file }
106
107
  opts.on('-s', '--stdout', "Log to stdout (default: #{@options[:log_stdout]})") { |v| @options[:log_stdout] = v }
107
108
 
109
+ opts.on('-c', '--config FILE', "Config file (default: ./config/<server>.rb)") { |v| @options[:config] = v }
108
110
  opts.on('-P', '--pid FILE', "Pid file (default: off)") { |file| @options[:pid_file] = file }
109
111
  opts.on('-d', '--daemonize', "Run daemonized in the background (default: #{@options[:daemonize]})") { |v| @options[:daemonize] = v }
110
112
  opts.on('-v', '--verbose', "Enable verbose logging (default: #{@options[:verbose]})") { |v| @options[:verbose] = v }
@@ -134,6 +136,11 @@ module Goliath
134
136
  #
135
137
  # @return [Nil]
136
138
  def run
139
+ unless Goliath.test?
140
+ $LOADED_FEATURES.unshift(File.basename($0))
141
+ Dir.chdir(File.expand_path(File.dirname($0)))
142
+ end
143
+
137
144
  if @daemonize
138
145
  Process.fork do
139
146
  Process.setsid
@@ -143,7 +150,6 @@ module Goliath
143
150
  @log_file ||= File.expand_path('goliath.log')
144
151
  store_pid(Process.pid)
145
152
 
146
- Dir.chdir(File.dirname(__FILE__))
147
153
  File.umask(0000)
148
154
 
149
155
  stdout_log_file = "#{File.dirname(@log_file)}/#{File.basename(@log_file)}_stdout.log"
@@ -58,6 +58,7 @@ module Goliath
58
58
  @address = address
59
59
  @port = port
60
60
 
61
+ @options = {}
61
62
  @status = {}
62
63
  @config = {}
63
64
  @plugins = []
@@ -74,9 +75,11 @@ module Goliath
74
75
 
75
76
  EM.epoll
76
77
 
77
- load_config
78
+ load_config(options[:config])
78
79
  load_plugins
79
80
 
81
+ EM.set_effective_user(options[:user]) if options[:user]
82
+
80
83
  EM.start_server(address, port, Goliath::Connection) do |conn|
81
84
  conn.port = port
82
85
  conn.app = app
@@ -87,7 +90,6 @@ module Goliath
87
90
  conn.options = options
88
91
  end
89
92
 
90
- EM.set_effective_user("nobody") if Goliath.prod?
91
93
  end
92
94
  end
93
95
 
@@ -107,11 +109,8 @@ module Goliath
107
109
  #
108
110
  # @return [String] THe full path to the config directory
109
111
  def config_dir
110
- if Goliath.test?
111
- "#{File.expand_path(ENV['PWD'])}/config"
112
- else
113
- "#{File.expand_path(File.dirname($0))}/config"
114
- end
112
+ dir = options[:config] ? File.dirname(options[:config]) : './config'
113
+ File.expand_path(dir)
115
114
  end
116
115
 
117
116
  # Import callback for configuration files
@@ -45,12 +45,17 @@ module Goliath
45
45
  #
46
46
  # @param api [Class] The API class to launch
47
47
  # @param port [Integer] The port to run the server on
48
- # @return [Nil]
49
- def server(api, port = 9000)
48
+ # @param options [Hash] The options hash to provide to the server
49
+ # @return [Goliath::Server] The executed server
50
+ def server(api, port = 9000, options = {})
51
+ op = OptionParser.new
52
+
50
53
  s = Goliath::Server.new
51
54
  s.logger = mock('log').as_null_object
52
55
  s.api = api.new
53
56
  s.app = build_app(api)
57
+ s.api.options_parser(op, options)
58
+ s.options = options
54
59
  s.port = port
55
60
  s.start
56
61
  s
@@ -67,11 +72,12 @@ module Goliath
67
72
  # will start the EventMachine reactor running.
68
73
  #
69
74
  # @param api [Class] The API class to launch
75
+ # @param options [Hash] The options to pass to the server
70
76
  # @param blk [Proc] The code to execute after the server is launched.
71
77
  # @note This will not return until stop is called.
72
- def with_api(api, &blk)
78
+ def with_api(api, options = {}, &blk)
73
79
  EM.synchrony do
74
- @api_server = server(api)
80
+ @api_server = server(api, 9000, options)
75
81
  blk.call
76
82
  end
77
83
  end
@@ -1,4 +1,4 @@
1
1
  module Goliath
2
2
  # The current version of Goliath
3
- VERSION = '0.9.0'
3
+ VERSION = '0.9.1'
4
4
  end
@@ -2,8 +2,6 @@ require 'spec_helper'
2
2
  require File.join(File.dirname(__FILE__), '../../', 'examples/async_upload')
3
3
 
4
4
  describe 'Async Request processing' do
5
- include Goliath::TestHelper
6
-
7
5
  it 'asynchronously processes the incoming request' do
8
6
  with_api(AsyncUpload) do
9
7
  request_data = {
@@ -1,9 +1,9 @@
1
1
  require 'spec_helper'
2
2
  require File.join(File.dirname(__FILE__), '../../', 'examples/echo')
3
+ require 'multipart_body'
4
+ require 'yajl'
3
5
 
4
6
  describe Echo do
5
- include Goliath::TestHelper
6
-
7
7
  let(:err) { Proc.new { fail "API request failed" } }
8
8
 
9
9
  it 'returns the echo param' do
@@ -24,4 +24,39 @@ describe Echo do
24
24
  end
25
25
  end
26
26
  end
27
+
28
+ it 'echos POST data' do
29
+ with_api(Echo) do
30
+ post_request({:body => {'echo' => 'test'}}, err) do |c|
31
+ b = Yajl::Parser.parse(c.response)
32
+ b['response'].should == 'test'
33
+ end
34
+ end
35
+ end
36
+
37
+ it 'echos POST data that is multipart encoded' do
38
+ with_api(Echo) do
39
+ body = MultipartBody.new(:echo => 'test')
40
+ head = {'content-type' => "multipart/form-data; boundary=#{body.boundary}"}
41
+
42
+ post_request({:body => body.to_s,
43
+ :head => head}, err) do |c|
44
+ b = Yajl::Parser.parse(c.response)
45
+ b['response'].should == 'test'
46
+ end
47
+ end
48
+ end
49
+
50
+ it 'echos application/json POST body data' do
51
+ with_api(Echo) do
52
+ body = Yajl::Encoder.encode({'echo' => 'My Echo'})
53
+ head = {'content-type' => 'application/json'}
54
+
55
+ post_request({:body => body.to_s,
56
+ :head => head}, err) do |c|
57
+ b = Yajl::Parser.parse(c.response)
58
+ b['response'].should == 'My Echo'
59
+ end
60
+ end
61
+ end
27
62
  end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ class Empty < Goliath::API
4
+ def response(env)
5
+ [201, {}, []]
6
+ end
7
+ end
8
+
9
+ describe 'Empty body API' do
10
+ let(:err) { Proc.new { fail "API request failed" } }
11
+
12
+ it 'serves a 201 with no body' do
13
+ with_api(Empty) do
14
+ get_request({}, err) do |c|
15
+ c.response_header.status.should == 201
16
+ c.response_header['CONTENT_LENGTH'].should == '0'
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,138 @@
1
+ require 'spec_helper'
2
+ require File.join(File.dirname(__FILE__), '../../', 'examples/http_log')
3
+
4
+ class Responder < Goliath::API
5
+ use Goliath::Rack::Params
6
+
7
+ def on_headers(env, headers)
8
+ env['client-headers'] = headers
9
+ end
10
+
11
+ def response(env)
12
+ query_params = env.params.collect { |param| param.join(": ") }
13
+ query_headers = env['client-headers'].collect { |param| param.join(": ") }
14
+
15
+ headers = {"Special" => "Header",
16
+ "Params" => query_params.join("|"),
17
+ "Path" => env[Goliath::Request::REQUEST_PATH],
18
+ "Headers" => query_headers.join("|"),
19
+ "Method" => env[Goliath::Request::REQUEST_METHOD]}
20
+ [200, headers, "Hello from Responder"]
21
+ end
22
+ end
23
+
24
+ describe HttpLog do
25
+ include Goliath::TestHelper
26
+
27
+ let(:err) { Proc.new { |c| fail "HTTP Request failed #{c.response}" } }
28
+
29
+ def config_file
30
+ File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'examples', 'config', 'http_log.rb'))
31
+ end
32
+
33
+ let(:api_options) { { :config => config_file } }
34
+
35
+ def mock_mongo
36
+ @api_server.config['mongo'] = mock('mongo').as_null_object
37
+ end
38
+
39
+ it 'responds to requests' do
40
+ with_api(HttpLog, api_options) do
41
+ server(Responder, 8080)
42
+ mock_mongo
43
+
44
+ get_request({}, err) do |c|
45
+ c.response_header.status.should == 200
46
+ end
47
+ end
48
+ end
49
+
50
+ it 'forwards to our API server' do
51
+ with_api(HttpLog, api_options) do
52
+ server(Responder, 8080)
53
+ mock_mongo
54
+
55
+ get_request({}, err) do |c|
56
+ c.response_header.status.should == 200
57
+ c.response_header['SPECIAL'].should == 'Header'
58
+ c.response.should == 'Hello from Responder'
59
+ end
60
+ end
61
+ end
62
+
63
+ context 'HTTP header handling' do
64
+ it 'transforms back properly' do
65
+ hl = HttpLog.new
66
+ hl.to_http_header("SPECIAL").should == 'Special'
67
+ hl.to_http_header("CONTENT_TYPE").should == 'Content-Type'
68
+ end
69
+ end
70
+
71
+ context 'query parameters' do
72
+ it 'forwards the query parameters' do
73
+ with_api(HttpLog, api_options) do
74
+ server(Responder, 8080)
75
+ mock_mongo
76
+
77
+ get_request({:query => {:first => :foo, :second => :bar, :third => :baz}}, err) do |c|
78
+ c.response_header.status.should == 200
79
+ c.response_header["PARAMS"].should == "first: foo|second: bar|third: baz"
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ context 'request path' do
86
+ it 'forwards the request path' do
87
+ with_api(HttpLog, api_options) do
88
+ server(Responder, 8080)
89
+ mock_mongo
90
+
91
+ get_request({:path => '/my/request/path'}, err) do |c|
92
+ c.response_header.status.should == 200
93
+ c.response_header['PATH'].should == '/my/request/path'
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ context 'headers' do
100
+ it 'forwards the headers' do
101
+ with_api(HttpLog, api_options) do
102
+ server(Responder, 8080)
103
+ mock_mongo
104
+
105
+ get_request({:head => {:first => :foo, :second => :bar}}, err) do |c|
106
+ c.response_header.status.should == 200
107
+ c.response_header["HEADERS"].should =~ /First: foo\|Second: bar/
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ context 'request method' do
114
+ it 'forwards GET requests' do
115
+ with_api(HttpLog, api_options) do
116
+ server(Responder, 8080)
117
+ mock_mongo
118
+
119
+ get_request({}, err) do |c|
120
+ c.response_header.status.should == 200
121
+ c.response_header["METHOD"].should == "GET"
122
+ end
123
+ end
124
+ end
125
+
126
+ it 'forwards POST requests' do
127
+ with_api(HttpLog, api_options) do
128
+ server(Responder, 8080)
129
+ mock_mongo
130
+
131
+ post_request({}, err) do |c|
132
+ c.response_header.status.should == 200
133
+ c.response_header["METHOD"].should == "POST"
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -2,8 +2,6 @@ require 'spec_helper'
2
2
  require File.join(File.dirname(__FILE__), '../../', 'examples/echo')
3
3
 
4
4
  describe 'HTTP Keep-Alive support' do
5
- include Goliath::TestHelper
6
-
7
5
  it 'serves multiple requests via single connection' do
8
6
  with_api(Echo) do
9
7
  conn = EM::HttpRequest.new('http://localhost:9000')
@@ -12,8 +12,6 @@ class Interleaving < Goliath::API
12
12
  end
13
13
 
14
14
  describe 'HTTP Pipelining support' do
15
- include Goliath::TestHelper
16
-
17
15
  it 'serves multiple requests via single connection' do
18
16
  with_api(Interleaving) do
19
17
  start = Time.now.to_f
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ Goliath.env = 'dev'
4
+
5
+ class ReloaderDev < Goliath::API
6
+ use Goliath::Rack::Params
7
+ end
8
+
9
+ class ReloaderAlreadyLoaded < Goliath::API
10
+ use ::Rack::Reloader, 0
11
+ use Goliath::Rack::Params
12
+ end
13
+
14
+ class ReloaderProd < Goliath::API
15
+ end
16
+
17
+ describe "Reloader" do
18
+ let(:err) { Proc.new { fail "API request failed" } }
19
+
20
+ before(:each) { Goliath.env = "dev" }
21
+ after(:each) { Goliath.env = "test" }
22
+
23
+ def count(klass)
24
+ cnt = 0
25
+ klass.middlewares.each do |mw|
26
+ cnt += 1 if mw.first == ::Rack::Reloader
27
+ end
28
+ cnt
29
+ end
30
+
31
+ it 'adds reloader in dev mode' do
32
+ count(ReloaderDev).should == 1
33
+ end
34
+
35
+ it "doesn't add if it's already there in dev mode" do
36
+ count(ReloaderAlreadyLoaded).should == 1
37
+ end
38
+
39
+ it "doesn't add in prod mode" do
40
+ Goliath.env = "prod"
41
+ count(ReloaderProd).should == 0
42
+ end
43
+ end
@@ -2,8 +2,6 @@ require 'spec_helper'
2
2
  require File.join(File.dirname(__FILE__), '../../', 'examples/valid')
3
3
 
4
4
  describe Valid do
5
- include Goliath::TestHelper
6
-
7
5
  let(:err) { Proc.new { fail "API request failed" } }
8
6
 
9
7
  it 'returns OK with param' do
data/spec/spec_helper.rb CHANGED
@@ -4,3 +4,9 @@ Bundler.setup
4
4
  Bundler.require
5
5
 
6
6
  require 'goliath/test_helper'
7
+
8
+ RSpec.configure do |c|
9
+ c.include Goliath::TestHelper, :example_group => {
10
+ :file_path => /spec\/integration/
11
+ }
12
+ end
@@ -7,12 +7,12 @@ describe Goliath::Env do
7
7
  end
8
8
 
9
9
  it 'responds to []=' do
10
- lambda { @env['test'] = 'blah' }.should_not raise_error(Exception)
10
+ lambda { @env['test'] = 'blah' }.should_not raise_error
11
11
  end
12
12
 
13
13
  it 'responds to []' do
14
14
  @env['test'] = 'blah'
15
- lambda { @env['test'].should == 'blah' }.should_not raise_error(Exception)
15
+ lambda { @env['test'].should == 'blah' }.should_not raise_error
16
16
  end
17
17
 
18
18
  context '#method_missing' do
@@ -3,7 +3,7 @@ require 'goliath/rack/formatters/json'
3
3
 
4
4
  describe Goliath::Rack::Formatters::JSON do
5
5
  it 'accepts an app' do
6
- lambda { Goliath::Rack::Formatters::JSON.new('my app') }.should_not raise_error Exception
6
+ lambda { Goliath::Rack::Formatters::JSON.new('my app') }.should_not raise_error
7
7
  end
8
8
 
9
9
  describe 'with a formatter' do
@@ -30,7 +30,7 @@ describe Goliath::Rack::Formatters::JSON do
30
30
  @app.should_receive(:call).and_return([200, {'Content-Type' => 'application/json'}, {:a => 1, :b => 2}])
31
31
 
32
32
  status, header, body = @js.call({})
33
- lambda { Yajl::Parser.parse(body.first)['a'].should == 1 }.should_not raise_error Exception
33
+ lambda { Yajl::Parser.parse(body.first)['a'].should == 1 }.should_not raise_error
34
34
  end
35
35
 
36
36
  it "doesn't format to json if the type is not application/json" do