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
data/.gemtest ADDED
File without changes
data/.gitignore CHANGED
@@ -1,15 +1,18 @@
1
- Makefile
2
- mkmf.log
3
- *.o
4
- *.bundle
1
+ Gemfile.lock
2
+
3
+ .bundle/
5
4
  doc/
6
5
  pkg/
7
- examples/log
8
- *.swp
9
- Gemfile.lock
6
+ _site
7
+
10
8
  .yardoc
11
9
  .livereload
10
+ .rvmrc
11
+
12
+ *.swp
12
13
  *.watchr
13
14
  *.rbc
14
- .rvmrc
15
- _site
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 then within the reverse proxy.
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
@@ -5,6 +5,7 @@ require 'yard'
5
5
  require 'rspec/core/rake_task'
6
6
 
7
7
  task :default => [:spec]
8
+ task :test => [:spec]
8
9
 
9
10
  desc "run spec tests"
10
11
  RSpec::Core::RakeTask.new('spec') do |t|
@@ -19,8 +19,6 @@ class User < ActiveRecord::Base
19
19
  end
20
20
 
21
21
  class Srv < Goliath::API
22
- use ::Rack::Reloader, 0 if Goliath.dev?
23
-
24
22
  use Goliath::Rack::Params
25
23
  use Goliath::Rack::DefaultMimeType
26
24
  use Goliath::Rack::Formatters::JSON
@@ -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
@@ -10,7 +10,6 @@ $:<< '../lib' << 'lib'
10
10
  require 'goliath'
11
11
 
12
12
  class ConfTest < Goliath::API
13
-
14
13
  use Goliath::Rack::Params
15
14
  use Goliath::Rack::DefaultMimeType
16
15
  use Goliath::Rack::Formatters::JSON
@@ -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
@@ -1,7 +1,9 @@
1
1
  config['forwarder'] = 'http://localhost:8080'
2
2
 
3
- config['mongo'] = EventMachine::Synchrony::ConnectionPool.new(size: 20) do
4
- # Need to deal with this just never connecting ... ?
5
- conn = EM::Mongo::Connection.new('localhost', 27017, 1, {:reconnect_in => 1})
6
- conn.db('http_log').collection('aggregators')
7
- end
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
@@ -15,7 +15,6 @@ require 'em-synchrony/em-http'
15
15
  require 'pp'
16
16
 
17
17
  class HttpLog < Goliath::API
18
- use ::Rack::Reloader, 0 if Goliath.dev?
19
18
  use Goliath::Rack::Params
20
19
 
21
20
  def on_headers(env, headers)
data/examples/valid.rb CHANGED
@@ -4,10 +4,6 @@ $:<< '../lib' << 'lib'
4
4
  require 'goliath'
5
5
 
6
6
  class Valid < Goliath::API
7
-
8
- # reload code on every request in dev environment
9
- use ::Rack::Reloader, 0 if Goliath.dev?
10
-
11
7
  use Goliath::Rack::Params
12
8
  use Goliath::Rack::ValidationError
13
9
 
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 ||= [[::Rack::ContentLength, nil, nil],
24
- [Goliath::Rack::DefaultResponseFormat, nil, nil]]
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.push([name, args, block])
48
+ @middlewares ||= []
49
+ @middlewares << [name, args, block]
41
50
  end
42
51
 
43
52
  # Returns the plugins configured for this API
@@ -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 = Kernel.const_get(camel_case(file))
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)
@@ -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[LOGGER] = logger
27
+ env[RACK_LOGGER] = logger
29
28
  env[OPTIONS] = options
30
29
  env[STATUS] = status
31
30
  env[CONFIG] = config
@@ -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)
@@ -6,7 +6,7 @@ require 'async_rack'
6
6
  module Goliath
7
7
  module_function
8
8
 
9
- @env = 'development'
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' then @env = 'development'
24
- when 'prod' then @env = 'production'
25
- when 'test' then @env = 'test'
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 == 'production'
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 == 'development'
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 == 'test'
47
+ @env == :test
48
48
  end
49
49
  end
@@ -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.parse_query(env['QUERY_STRING']))
25
- params.merge!(::Rack::Utils.parse_query(env['rack.input'].read)) unless env['rack.input'].nil?
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
@@ -47,7 +47,16 @@ module Goliath
47
47
  @env[HTTP_PREFIX + k.gsub('-','_').upcase] = v
48
48
  end
49
49
 
50
- @env[STATUS] = parser.status_code
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[LOGGER].info("Status: #{@response.status}, " +
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[LOGGER].error("#{e.message}\n#{e.backtrace.join("\n")}")
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