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/.gemtest
ADDED
File without changes
|
data/.gitignore
CHANGED
@@ -1,15 +1,18 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
*.bundle
|
1
|
+
Gemfile.lock
|
2
|
+
|
3
|
+
.bundle/
|
5
4
|
doc/
|
6
5
|
pkg/
|
7
|
-
|
8
|
-
|
9
|
-
Gemfile.lock
|
6
|
+
_site
|
7
|
+
|
10
8
|
.yardoc
|
11
9
|
.livereload
|
10
|
+
.rvmrc
|
11
|
+
|
12
|
+
*.swp
|
12
13
|
*.watchr
|
13
14
|
*.rbc
|
14
|
-
|
15
|
-
|
15
|
+
|
16
|
+
examples/log
|
17
|
+
examples/goliath.log*
|
18
|
+
examples/goliath.pid
|
data/README.md
CHANGED
@@ -23,9 +23,6 @@ Each HTTP request within Goliath is executed in its own Ruby fiber and all async
|
|
23
23
|
require 'goliath'
|
24
24
|
|
25
25
|
class Hello < Goliath::API
|
26
|
-
# reload code on every request in dev environment
|
27
|
-
use ::Rack::Reloader, 0 if Goliath.dev?
|
28
|
-
|
29
26
|
def response(env)
|
30
27
|
[200, {}, "Hello World"]
|
31
28
|
end
|
@@ -55,7 +52,7 @@ Goliath has been in production at PostRank for over a year, serving a sustained
|
|
55
52
|
* Mongrel is a threaded web-server, and both Passenger and Unicorn fork an entire VM to isolate each request from each other. By contrast, Goliath builds a single instance of the Rack app and runs all requests in parallel through a single VM, which leads to a much smaller memory footprint and less overhead.
|
56
53
|
|
57
54
|
* How do I deploy Goliath in production?
|
58
|
-
* We recommend deploying Goliath behind a reverse proxy such as HAProxy, Nginx or equivalent. Using one of the above, you can easily run multiple instances of the same application and load balance between
|
55
|
+
* We recommend deploying Goliath behind a reverse proxy such as HAProxy, Nginx or equivalent. Using one of the above, you can easily run multiple instances of the same application and load balance between them within the reverse proxy.
|
59
56
|
|
60
57
|
## Guides
|
61
58
|
|
@@ -74,6 +71,8 @@ Goliath has been in production at PostRank for over a year, serving a sustained
|
|
74
71
|
|
75
72
|
* [Goliath: Non-blocking, Ruby 1.9 Web Server](http://www.igvita.com/2011/03/08/goliath-non-blocking-ruby-19-web-server)
|
76
73
|
* [Stage left: Enter Goliath - HTTP Proxy + MongoDB](http://everburning.com/news/stage-left-enter-goliath/)
|
74
|
+
* [InfoQ: Meet the Goliath of Ruby Application Servers](http://www.infoq.com/articles/meet-goliath)
|
75
|
+
* [Node.jsはコールバック・スパゲティを招くか](http://el.jibun.atmarkit.co.jp/rails/2011/03/nodejs-d123.html)
|
77
76
|
|
78
77
|
## Discussion and Support
|
79
78
|
|
@@ -83,4 +82,4 @@ Goliath has been in production at PostRank for over a year, serving a sustained
|
|
83
82
|
|
84
83
|
## License & Acknowledgments
|
85
84
|
|
86
|
-
Goliath is distributed under the MIT license, for full details please see the LICENSE file.
|
85
|
+
Goliath is distributed under the MIT license, for full details please see the LICENSE file.
|
data/Rakefile
CHANGED
data/examples/async_upload.rb
CHANGED
@@ -5,10 +5,6 @@ require 'goliath'
|
|
5
5
|
require 'yajl'
|
6
6
|
|
7
7
|
class AsyncUpload < Goliath::API
|
8
|
-
|
9
|
-
# reload code on every request in dev environment
|
10
|
-
use ::Rack::Reloader, 0 if Goliath.dev?
|
11
|
-
|
12
8
|
use Goliath::Rack::Params # parse & merge query and body parameters
|
13
9
|
use Goliath::Rack::DefaultMimeType # cleanup accepted media types
|
14
10
|
use Goliath::Rack::Formatters::JSON # JSON output formatter
|
data/examples/conf_test.rb
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'amqp'
|
2
|
+
|
3
|
+
config['channel'] = EM::Channel.new
|
4
|
+
|
5
|
+
amqp_config = {
|
6
|
+
:host => 'localhost',
|
7
|
+
:user => 'test',
|
8
|
+
:pass => 'test',
|
9
|
+
:vhost => '/test'
|
10
|
+
}
|
11
|
+
|
12
|
+
conn = AMQP.connect(amqp_config)
|
13
|
+
|
14
|
+
xchange = AMQP::Channel.new(conn).fanout('stream')
|
15
|
+
q = AMQP::Channel.new(conn).queue('stream/StreamAPI')
|
16
|
+
q.bind(xchange)
|
17
|
+
|
18
|
+
# pull data off the exchange and push to streams
|
19
|
+
q.pop do |data|
|
20
|
+
if data.nil?
|
21
|
+
EM.add_timer(1) { q.pop }
|
22
|
+
else
|
23
|
+
config['channel'].push(data)
|
24
|
+
q.pop
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# push data into the stream. (Just so we have stuff going in)
|
29
|
+
count = 0
|
30
|
+
EM.add_periodic_timer(2) do
|
31
|
+
xchange.publish("Iteration #{count}\n")
|
32
|
+
count += 1
|
33
|
+
end
|
data/examples/config/http_log.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
config['forwarder'] = 'http://localhost:8080'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
environment(:development) do
|
4
|
+
config['mongo'] = EventMachine::Synchrony::ConnectionPool.new(size: 20) do
|
5
|
+
# Need to deal with this just never connecting ... ?
|
6
|
+
conn = EM::Mongo::Connection.new('localhost', 27017, 1, {:reconnect_in => 1})
|
7
|
+
conn.db('http_log').collection('aggregators')
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:<< '../lib' << 'lib'
|
3
|
+
|
4
|
+
require 'goliath'
|
5
|
+
|
6
|
+
# This example assumes you have an AMQP server up and running with the
|
7
|
+
# following config (using rabbit-mq as an example)
|
8
|
+
#
|
9
|
+
# rabbitmq-server start
|
10
|
+
# rabbitmqctl add_vhost /test
|
11
|
+
# rabbitmqctl add_user test test
|
12
|
+
# rabbitmqctl set_permissions -p /test test ".*" ".*" ".*"
|
13
|
+
|
14
|
+
class ContentStream < Goliath::API
|
15
|
+
use Goliath::Rack::Formatters::JSON
|
16
|
+
use Goliath::Rack::Params
|
17
|
+
|
18
|
+
use Goliath::Rack::Render
|
19
|
+
use Goliath::Rack::Heartbeat
|
20
|
+
use Goliath::Rack::ValidationError
|
21
|
+
use Goliath::Rack::Validation::RequestMethod, %w(GET)
|
22
|
+
|
23
|
+
def on_close(env)
|
24
|
+
# This is just to make sure if the Heartbeat fires we don't try
|
25
|
+
# to close a connection.
|
26
|
+
return unless env['subscription']
|
27
|
+
|
28
|
+
env.channel.unsubscribe(env['subscription'])
|
29
|
+
env.logger.info "Stream connection closed."
|
30
|
+
end
|
31
|
+
|
32
|
+
def response(env)
|
33
|
+
env.logger.info "Stream connection opened"
|
34
|
+
|
35
|
+
env['subscription'] = env.channel.subscribe do |msg|
|
36
|
+
env.stream_send(msg)
|
37
|
+
end
|
38
|
+
|
39
|
+
[200, {}, Goliath::Response::STREAMING]
|
40
|
+
end
|
41
|
+
end
|
data/examples/echo.rb
CHANGED
@@ -9,10 +9,6 @@ require 'goliath/plugins/latency'
|
|
9
9
|
require 'yajl'
|
10
10
|
|
11
11
|
class Echo < Goliath::API
|
12
|
-
|
13
|
-
# reload code on every request in dev environment
|
14
|
-
use ::Rack::Reloader, 0 if Goliath.dev?
|
15
|
-
|
16
12
|
use Goliath::Rack::Params # parse & merge query and body parameters
|
17
13
|
use Goliath::Rack::DefaultMimeType # cleanup accepted media types
|
18
14
|
use Goliath::Rack::Formatters::JSON # JSON output formatter
|
@@ -20,7 +16,7 @@ class Echo < Goliath::API
|
|
20
16
|
use Goliath::Rack::Heartbeat # respond to /status with 200, OK (monitoring, etc)
|
21
17
|
use Goliath::Rack::ValidationError # catch and render validation errors
|
22
18
|
|
23
|
-
use Goliath::Rack::Validation::RequestMethod, %w(GET) # allow GET requests only
|
19
|
+
use Goliath::Rack::Validation::RequestMethod, %w(GET POST) # allow GET and POST requests only
|
24
20
|
use Goliath::Rack::Validation::RequiredParam, {:key => 'echo'} # must provide ?echo= query or body param
|
25
21
|
|
26
22
|
plugin Goliath::Plugin::Latency # output reactor latency every second
|
data/examples/http_log.rb
CHANGED
data/examples/valid.rb
CHANGED
data/goliath.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.add_dependency 'http_parser.rb'
|
20
20
|
s.add_dependency 'log4r'
|
21
21
|
|
22
|
-
s.add_dependency 'rack'
|
22
|
+
s.add_dependency 'rack', '>=1.2.2'
|
23
23
|
s.add_dependency 'rack-contrib'
|
24
24
|
s.add_dependency 'rack-respond_to'
|
25
25
|
s.add_dependency 'async-rack'
|
@@ -31,6 +31,8 @@ Gem::Specification.new do |s|
|
|
31
31
|
s.add_development_dependency 'em-mongo'
|
32
32
|
s.add_development_dependency 'yajl-ruby'
|
33
33
|
s.add_development_dependency 'rack-rewrite'
|
34
|
+
s.add_development_dependency 'multipart_body'
|
35
|
+
s.add_development_dependency 'amqp', '>=0.7.1'
|
34
36
|
|
35
37
|
s.add_development_dependency 'yard'
|
36
38
|
s.add_development_dependency 'bluecloth'
|
data/lib/goliath/api.rb
CHANGED
@@ -20,8 +20,16 @@ module Goliath
|
|
20
20
|
#
|
21
21
|
# @return [Array] array contains [middleware class, args, block]
|
22
22
|
def middlewares
|
23
|
-
@middlewares ||= [
|
24
|
-
|
23
|
+
@middlewares ||= []
|
24
|
+
@middlewares.unshift([::Goliath::Rack::DefaultResponseFormat, nil, nil])
|
25
|
+
@middlewares.unshift([::Rack::ContentLength, nil, nil])
|
26
|
+
|
27
|
+
if Goliath.dev?
|
28
|
+
reloader = @middlewares.detect {|mw| mw.first == ::Rack::Reloader}
|
29
|
+
@middlewares.unshift([::Rack::Reloader, 0, nil]) if !reloader
|
30
|
+
end
|
31
|
+
|
32
|
+
@middlewares
|
25
33
|
end
|
26
34
|
|
27
35
|
# Specify a middleware to be used by the API
|
@@ -37,7 +45,8 @@ module Goliath
|
|
37
45
|
# @param args Any arguments to pass to the middeware
|
38
46
|
# @param block A block to pass to the middleware
|
39
47
|
def use(name, args = nil, &block)
|
40
|
-
middlewares
|
48
|
+
@middlewares ||= []
|
49
|
+
@middlewares << [name, args, block]
|
41
50
|
end
|
42
51
|
|
43
52
|
# Returns the plugins configured for this API
|
data/lib/goliath/application.rb
CHANGED
@@ -45,7 +45,11 @@ module Goliath
|
|
45
45
|
# @return [Nil]
|
46
46
|
def self.run!
|
47
47
|
file = File.basename(app_file, '.rb')
|
48
|
-
klass =
|
48
|
+
klass = begin
|
49
|
+
Kernel.const_get(camel_case(file))
|
50
|
+
rescue NameError
|
51
|
+
raise NameError, "Class #{camel_case(file)} not found."
|
52
|
+
end
|
49
53
|
api = klass.new
|
50
54
|
|
51
55
|
runner = Goliath::Runner.new(ARGV, api)
|
data/lib/goliath/connection.rb
CHANGED
@@ -23,9 +23,8 @@ module Goliath
|
|
23
23
|
@parser.on_headers_complete = proc do |h|
|
24
24
|
|
25
25
|
env = Goliath::Env.new
|
26
|
-
env[OPTIONS] = options
|
27
26
|
env[SERVER_PORT] = port.to_s
|
28
|
-
env[
|
27
|
+
env[RACK_LOGGER] = logger
|
29
28
|
env[OPTIONS] = options
|
30
29
|
env[STATUS] = status
|
31
30
|
env[CONFIG] = config
|
data/lib/goliath/constants.rb
CHANGED
@@ -10,7 +10,6 @@ module Goliath
|
|
10
10
|
|
11
11
|
HTTP_PREFIX = 'HTTP_'
|
12
12
|
LOCALHOST = 'localhost'
|
13
|
-
LOGGER = 'logger'
|
14
13
|
STATUS = 'status'
|
15
14
|
CONFIG = 'config'
|
16
15
|
OPTIONS = 'options'
|
@@ -22,6 +21,7 @@ module Goliath
|
|
22
21
|
RACK_MULTIPROCESS = 'rack.multiprocess'
|
23
22
|
RACK_RUN_ONCE = 'rack.run_once'
|
24
23
|
RACK_VERSION_NUM = [1, 0]
|
24
|
+
RACK_LOGGER = 'rack.logger'
|
25
25
|
|
26
26
|
ASYNC_CALLBACK = 'async.callback'
|
27
27
|
ASYNC_HEADERS = 'async.headers'
|
@@ -37,6 +37,7 @@ module Goliath
|
|
37
37
|
SCRIPT_NAME = 'SCRIPT_NAME'
|
38
38
|
REMOTE_ADDR = 'REMOTE_ADDR'
|
39
39
|
CONTENT_LENGTH = 'CONTENT_LENGTH'
|
40
|
+
CONTENT_TYPE = 'CONTENT_TYPE'
|
40
41
|
REQUEST_METHOD = 'REQUEST_METHOD'
|
41
42
|
REQUEST_URI = 'REQUEST_URI'
|
42
43
|
QUERY_STRING = 'QUERY_STRING'
|
data/lib/goliath/env.rb
CHANGED
@@ -67,6 +67,13 @@ module Goliath
|
|
67
67
|
self[STREAM_CLOSE].call
|
68
68
|
end
|
69
69
|
|
70
|
+
# Convenience method for accessing the rack.logger item in the environment.
|
71
|
+
#
|
72
|
+
# @return [Logger] The logger object
|
73
|
+
def logger
|
74
|
+
self[RACK_LOGGER]
|
75
|
+
end
|
76
|
+
|
70
77
|
# @param name [Symbol] The method to check if we respond to it.
|
71
78
|
# @return [Boolean] True if the Env responds to the method, false otherwise
|
72
79
|
def respond_to?(name)
|
data/lib/goliath/goliath.rb
CHANGED
@@ -6,7 +6,7 @@ require 'async_rack'
|
|
6
6
|
module Goliath
|
7
7
|
module_function
|
8
8
|
|
9
|
-
@env =
|
9
|
+
@env = :development
|
10
10
|
|
11
11
|
# Retrieves the current goliath environment
|
12
12
|
#
|
@@ -19,10 +19,10 @@ module Goliath
|
|
19
19
|
#
|
20
20
|
# @param [String] env the environment string of [dev|prod|test]
|
21
21
|
def env=(env)
|
22
|
-
case(env)
|
23
|
-
when 'dev'
|
24
|
-
when 'prod' then @env =
|
25
|
-
when 'test' then @env =
|
22
|
+
case(env.to_s)
|
23
|
+
when 'dev' then @env = :development
|
24
|
+
when 'prod' then @env = :production
|
25
|
+
when 'test' then @env = :test
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
@@ -30,20 +30,20 @@ module Goliath
|
|
30
30
|
#
|
31
31
|
# @return [Boolean] true if current environemnt is production, false otherwise
|
32
32
|
def prod?
|
33
|
-
@env ==
|
33
|
+
@env == :production
|
34
34
|
end
|
35
35
|
|
36
36
|
# Determines if we are in the development environment
|
37
37
|
#
|
38
38
|
# @return [Boolean] true if current environemnt is development, false otherwise
|
39
39
|
def dev?
|
40
|
-
@env ==
|
40
|
+
@env == :development
|
41
41
|
end
|
42
42
|
|
43
43
|
# Determines if we are in the test environment
|
44
44
|
#
|
45
45
|
# @return [Boolean] true if current environemnt is test, false otherwise
|
46
46
|
def test?
|
47
|
-
@env ==
|
47
|
+
@env == :test
|
48
48
|
end
|
49
49
|
end
|
data/lib/goliath/rack/params.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
require 'multi_json'
|
1
2
|
require 'rack/utils'
|
2
3
|
|
3
4
|
module Goliath
|
4
5
|
module Rack
|
6
|
+
URL_ENCODED = %r{^application/x-www-form-urlencoded}
|
7
|
+
JSON_ENCODED = %r{^application/json}
|
8
|
+
|
5
9
|
# A middle ware to parse params. This will parse both the
|
6
10
|
# query string parameters and the body and place them into
|
7
11
|
# the _params_ hash of the Goliath::Env for the request.
|
@@ -21,8 +25,27 @@ module Goliath
|
|
21
25
|
|
22
26
|
def retrieve_params(env)
|
23
27
|
params = {}
|
24
|
-
params.merge!(::Rack::Utils.
|
25
|
-
|
28
|
+
params.merge!(::Rack::Utils.parse_nested_query(env['QUERY_STRING']))
|
29
|
+
|
30
|
+
if env['rack.input']
|
31
|
+
post_params = ::Rack::Utils::Multipart.parse_multipart(env)
|
32
|
+
unless post_params
|
33
|
+
body = env['rack.input'].read
|
34
|
+
env['rack.input'].rewind
|
35
|
+
|
36
|
+
post_params = case(env['CONTENT_TYPE'])
|
37
|
+
when URL_ENCODED then
|
38
|
+
::Rack::Utils.parse_nested_query(body)
|
39
|
+
when JSON_ENCODED then
|
40
|
+
MultiJson.decode(body)
|
41
|
+
else
|
42
|
+
{}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
params.merge!(post_params)
|
47
|
+
end
|
48
|
+
|
26
49
|
params
|
27
50
|
end
|
28
51
|
end
|
data/lib/goliath/request.rb
CHANGED
@@ -47,7 +47,16 @@ module Goliath
|
|
47
47
|
@env[HTTP_PREFIX + k.gsub('-','_').upcase] = v
|
48
48
|
end
|
49
49
|
|
50
|
-
|
50
|
+
%w(CONTENT_TYPE CONTENT_LENGTH).each do |name|
|
51
|
+
@env[name] = @env.delete("HTTP_#{name}") if @env["HTTP_#{name}"]
|
52
|
+
end
|
53
|
+
|
54
|
+
if @env['HTTP_HOST']
|
55
|
+
name, port = @env['HTTP_HOST'].split(':')
|
56
|
+
@env[SERVER_NAME] = name if name
|
57
|
+
@env[SERVER_PORT] = port if port
|
58
|
+
end
|
59
|
+
|
51
60
|
@env[REQUEST_METHOD] = parser.http_method
|
52
61
|
@env[REQUEST_URI] = parser.request_url
|
53
62
|
@env[QUERY_STRING] = parser.query_string
|
@@ -118,6 +127,7 @@ module Goliath
|
|
118
127
|
def process
|
119
128
|
begin
|
120
129
|
@state = :finished
|
130
|
+
@env['rack.input'].rewind if @env['rack.input']
|
121
131
|
post_process(@app.call(@env))
|
122
132
|
|
123
133
|
rescue Exception => e
|
@@ -153,7 +163,7 @@ module Goliath
|
|
153
163
|
begin
|
154
164
|
@response.status, @response.headers, @response.body = status, headers, body
|
155
165
|
@response.each { |chunk| @conn.send_data(chunk) }
|
156
|
-
@env[
|
166
|
+
@env[RACK_LOGGER].info("Status: #{@response.status}, " +
|
157
167
|
"Content-Length: #{@response.headers['Content-Length']}, " +
|
158
168
|
"Response Time: #{"%.2f" % ((Time.now.to_f - @env[:start_time]) * 1000)}ms")
|
159
169
|
|
@@ -175,7 +185,7 @@ module Goliath
|
|
175
185
|
# @param e [Exception] The exception to log
|
176
186
|
# @return [Nil]
|
177
187
|
def server_exception(e)
|
178
|
-
@env[
|
188
|
+
@env[RACK_LOGGER].error("#{e.message}\n#{e.backtrace.join("\n")}")
|
179
189
|
post_process([500, {}, 'An error happened'])
|
180
190
|
end
|
181
191
|
|