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.
- data/.gemtest +0 -0
- data/.gitignore +12 -9
- data/README.md +4 -5
- data/Rakefile +1 -0
- data/examples/activerecord/srv.rb +0 -2
- data/examples/async_upload.rb +0 -4
- data/examples/conf_test.rb +0 -1
- data/examples/config/content_stream.rb +33 -0
- data/examples/config/http_log.rb +7 -5
- data/examples/content_stream.rb +41 -0
- data/examples/echo.rb +1 -5
- data/examples/http_log.rb +0 -1
- data/examples/valid.rb +0 -4
- data/goliath.gemspec +3 -1
- data/lib/goliath/api.rb +12 -3
- data/lib/goliath/application.rb +5 -1
- data/lib/goliath/connection.rb +1 -2
- data/lib/goliath/constants.rb +2 -1
- data/lib/goliath/env.rb +7 -0
- data/lib/goliath/goliath.rb +8 -8
- data/lib/goliath/rack/params.rb +25 -2
- data/lib/goliath/request.rb +13 -3
- data/lib/goliath/runner.rb +7 -1
- data/lib/goliath/server.rb +6 -7
- data/lib/goliath/test_helper.rb +10 -4
- data/lib/goliath/version.rb +1 -1
- data/spec/integration/async_request_processing.rb +0 -2
- data/spec/integration/echo_spec.rb +37 -2
- data/spec/integration/empty_body_spec.rb +20 -0
- data/spec/integration/http_log_spec.rb +138 -0
- data/spec/integration/keepalive_spec.rb +0 -2
- data/spec/integration/pipelining_spec.rb +0 -2
- data/spec/integration/reloader_spec.rb +43 -0
- data/spec/integration/valid_spec.rb +0 -2
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/env_spec.rb +2 -2
- data/spec/unit/rack/formatters/json_spec.rb +2 -2
- data/spec/unit/rack/formatters/xml_spec.rb +3 -3
- data/spec/unit/rack/heartbeat_spec.rb +1 -1
- data/spec/unit/rack/params_spec.rb +66 -3
- data/spec/unit/rack/render_spec.rb +1 -1
- data/spec/unit/rack/validation/default_params_spec.rb +3 -3
- data/spec/unit/rack/validation/numeric_range_spec.rb +3 -3
- data/spec/unit/rack/validation/request_method_spec.rb +1 -1
- data/spec/unit/rack/validation/required_param_spec.rb +2 -2
- data/spec/unit/rack/validation/required_value_spec.rb +2 -2
- data/spec/unit/rack/validation_error_spec.rb +1 -1
- data/spec/unit/request_spec.rb +23 -4
- metadata +39 -65
data/lib/goliath/runner.rb
CHANGED
@@ -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"
|
data/lib/goliath/server.rb
CHANGED
@@ -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
|
-
|
111
|
-
|
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
|
data/lib/goliath/test_helper.rb
CHANGED
@@ -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
|
-
# @
|
49
|
-
|
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
|
data/lib/goliath/version.rb
CHANGED
@@ -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')
|
@@ -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
|
data/spec/spec_helper.rb
CHANGED
data/spec/unit/env_spec.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
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
|