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.
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.yardopts +2 -0
- data/Gemfile +3 -0
- data/LICENSE +66 -0
- data/README.md +86 -0
- data/Rakefile +18 -0
- data/examples/activerecord/config/srv.rb +7 -0
- data/examples/activerecord/srv.rb +37 -0
- data/examples/async_upload.rb +34 -0
- data/examples/conf_test.rb +27 -0
- data/examples/config/conf_test.rb +12 -0
- data/examples/config/echo.rb +1 -0
- data/examples/config/http_log.rb +7 -0
- data/examples/config/shared.rb +5 -0
- data/examples/custom_server.rb +57 -0
- data/examples/echo.rb +37 -0
- data/examples/gziped.rb +40 -0
- data/examples/hello_world.rb +10 -0
- data/examples/http_log.rb +85 -0
- data/examples/rack_routes.rb +44 -0
- data/examples/stream.rb +37 -0
- data/examples/valid.rb +19 -0
- data/goliath.gemspec +41 -0
- data/lib/goliath.rb +38 -0
- data/lib/goliath/api.rb +165 -0
- data/lib/goliath/application.rb +90 -0
- data/lib/goliath/connection.rb +94 -0
- data/lib/goliath/constants.rb +51 -0
- data/lib/goliath/env.rb +92 -0
- data/lib/goliath/goliath.rb +49 -0
- data/lib/goliath/headers.rb +37 -0
- data/lib/goliath/http_status_codes.rb +44 -0
- data/lib/goliath/plugins/latency.rb +33 -0
- data/lib/goliath/rack/default_mime_type.rb +30 -0
- data/lib/goliath/rack/default_response_format.rb +33 -0
- data/lib/goliath/rack/formatters/html.rb +90 -0
- data/lib/goliath/rack/formatters/json.rb +42 -0
- data/lib/goliath/rack/formatters/xml.rb +90 -0
- data/lib/goliath/rack/heartbeat.rb +23 -0
- data/lib/goliath/rack/jsonp.rb +38 -0
- data/lib/goliath/rack/params.rb +30 -0
- data/lib/goliath/rack/render.rb +66 -0
- data/lib/goliath/rack/tracer.rb +31 -0
- data/lib/goliath/rack/validation/boolean_value.rb +59 -0
- data/lib/goliath/rack/validation/default_params.rb +46 -0
- data/lib/goliath/rack/validation/numeric_range.rb +59 -0
- data/lib/goliath/rack/validation/request_method.rb +33 -0
- data/lib/goliath/rack/validation/required_param.rb +54 -0
- data/lib/goliath/rack/validation/required_value.rb +58 -0
- data/lib/goliath/rack/validation_error.rb +38 -0
- data/lib/goliath/request.rb +199 -0
- data/lib/goliath/response.rb +93 -0
- data/lib/goliath/runner.rb +236 -0
- data/lib/goliath/server.rb +149 -0
- data/lib/goliath/test_helper.rb +118 -0
- data/lib/goliath/version.rb +4 -0
- data/spec/integration/async_request_processing.rb +23 -0
- data/spec/integration/echo_spec.rb +27 -0
- data/spec/integration/keepalive_spec.rb +28 -0
- data/spec/integration/pipelining_spec.rb +43 -0
- data/spec/integration/valid_spec.rb +24 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/connection_spec.rb +59 -0
- data/spec/unit/env_spec.rb +55 -0
- data/spec/unit/headers_spec.rb +53 -0
- data/spec/unit/rack/default_mime_type_spec.rb +34 -0
- data/spec/unit/rack/formatters/json_spec.rb +54 -0
- data/spec/unit/rack/formatters/xml_spec.rb +66 -0
- data/spec/unit/rack/heartbeat_spec.rb +47 -0
- data/spec/unit/rack/params_spec.rb +94 -0
- data/spec/unit/rack/render_spec.rb +87 -0
- data/spec/unit/rack/validation/boolean_value_spec.rb +54 -0
- data/spec/unit/rack/validation/default_params_spec.rb +71 -0
- data/spec/unit/rack/validation/numeric_range_spec.rb +96 -0
- data/spec/unit/rack/validation/request_method_spec.rb +47 -0
- data/spec/unit/rack/validation/required_param_spec.rb +92 -0
- data/spec/unit/rack/validation/required_value_spec.rb +99 -0
- data/spec/unit/rack/validation_error_spec.rb +40 -0
- data/spec/unit/request_spec.rb +59 -0
- data/spec/unit/response_spec.rb +35 -0
- data/spec/unit/runner_spec.rb +129 -0
- data/spec/unit/server_spec.rb +137 -0
- metadata +409 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Rack
|
3
|
+
# Middleware to inject the tracer statistics into the response headers.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# use Goliath::Rack::Tracer
|
7
|
+
#
|
8
|
+
class Tracer
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
async_cb = env['async.callback']
|
15
|
+
|
16
|
+
env['async.callback'] = Proc.new do |status, headers, body|
|
17
|
+
async_cb.call(post_process(env, status, headers, body))
|
18
|
+
env.logger.info env.trace_stats.collect{|s| s.join(':')}.join(', ')
|
19
|
+
end
|
20
|
+
|
21
|
+
status, headers, body = @app.call(env)
|
22
|
+
post_process(env, status, headers, body)
|
23
|
+
end
|
24
|
+
|
25
|
+
def post_process(env, status, headers, body)
|
26
|
+
extra = { 'X-PostRank' => env.trace_stats.collect{|s| s.join(': ')}.join(', ')}
|
27
|
+
[status, headers.merge(extra), body]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Rack
|
3
|
+
module Validation
|
4
|
+
# A middleware to validate a given value is boolean. This will attempt to do the following
|
5
|
+
# conversions:
|
6
|
+
# true = 'true' | 't' | 1
|
7
|
+
# false = 'false' | 'f' | 0
|
8
|
+
#
|
9
|
+
# If the parameter is not provided the :default is used.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# use Goliath::Rack::Validation::BooleanValue, {:key => 'raw', :default => false}
|
13
|
+
#
|
14
|
+
class BooleanValue
|
15
|
+
# Called by the framework to create the validator
|
16
|
+
#
|
17
|
+
# @param app The app object
|
18
|
+
# @param opts [Hash] The options hash
|
19
|
+
# @option opts [String] :key The key to access in the parameters
|
20
|
+
# @option opts [Boolean] :default The default value to set
|
21
|
+
# @return [Goliath::Rack::Validation::BooleanValue] The validator
|
22
|
+
def initialize(app, opts = {})
|
23
|
+
@app = app
|
24
|
+
@key = opts[:key]
|
25
|
+
raise Exception.new("BooleanValue key required") if @key.nil?
|
26
|
+
|
27
|
+
@default = opts[:default]
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(env)
|
31
|
+
if !env['params'].has_key?(@key) || env['params'][@key].nil? || env['params'][@key] == ''
|
32
|
+
env['params'][@key] = @default
|
33
|
+
|
34
|
+
else
|
35
|
+
if env['params'][@key].instance_of?(Array)
|
36
|
+
env['params'][@key] = env['params'][@key].first
|
37
|
+
end
|
38
|
+
|
39
|
+
if env['params'][@key].downcase == 'true' ||
|
40
|
+
env['params'][@key].downcase == 't' ||
|
41
|
+
env['params'][@key].to_i == 1
|
42
|
+
env['params'][@key] = true
|
43
|
+
|
44
|
+
elsif env['params'][@key].downcase == 'false' ||
|
45
|
+
env['params'][@key].downcase == 'f' ||
|
46
|
+
env['params'][@key].to_i == 0
|
47
|
+
env['params'][@key] = false
|
48
|
+
|
49
|
+
else
|
50
|
+
env['params'][@key] = @default
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
@app.call(env)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'goliath/rack/validation_error'
|
2
|
+
|
3
|
+
module Goliath
|
4
|
+
module Rack
|
5
|
+
module Validation
|
6
|
+
# A middleware to validate that a parameter always has a value
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# use Goliath::Rack::Validation::DefaultParams, {:key => 'order', :defaults => 'pubdate'}
|
10
|
+
#
|
11
|
+
class DefaultParams
|
12
|
+
# Called by the framework to create the validator
|
13
|
+
#
|
14
|
+
# @param app The app object
|
15
|
+
# @param opts [Hash] The options hash
|
16
|
+
# @option opts [String] :key The key to access in the parameters
|
17
|
+
# @option opts :defaults The default value to assign if the key is empty or non-existant
|
18
|
+
# @return [Goliath::Rack::Validation::DefaultParams] The validator
|
19
|
+
def initialize(app, opts = {})
|
20
|
+
@app = app
|
21
|
+
@defaults = opts[:defaults]
|
22
|
+
raise Exception.new("Must provide defaults to DefaultParams") if @defaults.nil?
|
23
|
+
|
24
|
+
@key = opts[:key]
|
25
|
+
raise Exception.new("must provide key to DefaultParams") if @key.nil? || @key =~ /^\s*$/
|
26
|
+
end
|
27
|
+
|
28
|
+
def call(env)
|
29
|
+
if !env['params'].has_key?(@key) || env['params'][@key].nil?
|
30
|
+
env['params'][@key] = @defaults
|
31
|
+
|
32
|
+
elsif env['params'][@key].is_a?(Array) && env['params'][@key].empty?
|
33
|
+
env['params'][@key] = @defaults
|
34
|
+
|
35
|
+
elsif env['params'][@key].is_a?(String)
|
36
|
+
if env['params'][@key] =~ /^\s*$/
|
37
|
+
env['params'][@key] = @defaults
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
@app.call(env)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Rack
|
3
|
+
module Validation
|
4
|
+
# A middleware to validate that a parameter value is within a given range. If the value
|
5
|
+
# falls outside the range, or is not provided the default will be used, if provided.
|
6
|
+
# If no default the :min or :max values will be applied to the parameter.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# use Goliath::Rack::Validation::NumericRange, {:key => 'num', :min => 1, :max => 30, :default => 10}
|
10
|
+
# use Goliath::Rack::Validation::NumericRange, {:key => 'num', :min => 1}
|
11
|
+
# use Goliath::Rack::Validation::NumericRange, {:key => 'num', :max => 10}
|
12
|
+
#
|
13
|
+
class NumericRange
|
14
|
+
# Called by the framework to create the Goliath::Rack::Validation::NumericRange validator
|
15
|
+
#
|
16
|
+
# @param app The app object
|
17
|
+
# @param opts [Hash] The options hash
|
18
|
+
# @option opts [String] :key The key to look for in the parameters
|
19
|
+
# @option opts [Integer] :min The minimum value
|
20
|
+
# @option opts [Integer] :max The maximum value
|
21
|
+
# @option opts [Integer] :default The default to set if outside the range
|
22
|
+
# @return [Goliath::Rack::Validation::NumericRange] The validator
|
23
|
+
def initialize(app, opts = {})
|
24
|
+
@app = app
|
25
|
+
@key = opts[:key]
|
26
|
+
raise Exception.new("NumericRange key required") if @key.nil?
|
27
|
+
|
28
|
+
@min = opts[:min]
|
29
|
+
@max = opts[:max]
|
30
|
+
raise Exception.new("NumericRange requires :min or :max") if @min.nil? && @max.nil?
|
31
|
+
|
32
|
+
@default = opts[:default]
|
33
|
+
end
|
34
|
+
|
35
|
+
def call(env)
|
36
|
+
if !env['params'].has_key?(@key) || env['params'][@key].nil?
|
37
|
+
env['params'][@key] = value
|
38
|
+
|
39
|
+
else
|
40
|
+
if env['params'][@key].instance_of?(Array) then
|
41
|
+
env['params'][@key] = env['params'][@key].first
|
42
|
+
end
|
43
|
+
env['params'][@key] = env['params'][@key].to_i
|
44
|
+
|
45
|
+
if (!@min.nil? && env['params'][@key] < @min) || (!@max.nil? && env['params'][@key] > @max)
|
46
|
+
env['params'][@key] = value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
@app.call(env)
|
51
|
+
end
|
52
|
+
|
53
|
+
def value
|
54
|
+
@default || @min || @max
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'goliath/rack/validation_error'
|
2
|
+
|
3
|
+
module Goliath
|
4
|
+
module Rack
|
5
|
+
module Validation
|
6
|
+
# A middleware to validate that the request had a given HTTP method.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# use Goliath::Rack::Validation::RequestMethod, %w(GET POST)
|
10
|
+
#
|
11
|
+
class RequestMethod
|
12
|
+
attr_reader :methods
|
13
|
+
|
14
|
+
ERROR = 'Invalid request method'
|
15
|
+
|
16
|
+
# Called by the framework to create the Goliath::Rack::Validation::RequestMethod validator
|
17
|
+
#
|
18
|
+
# @param app The app object
|
19
|
+
# @param methods [Array] The accepted request methods
|
20
|
+
# @return [Goliath::Rack::Validation::RequestMethod] The validator
|
21
|
+
def initialize(app, methods = [])
|
22
|
+
@app = app
|
23
|
+
@methods = methods
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(env)
|
27
|
+
raise Goliath::Validation::Error.new(400, ERROR) unless methods.include?(env['REQUEST_METHOD'])
|
28
|
+
@app.call(env)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'goliath/rack/validation_error'
|
2
|
+
|
3
|
+
module Goliath
|
4
|
+
module Rack
|
5
|
+
module Validation
|
6
|
+
# A middleware to validate that a given parameter is provided.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# use Goliath::Rack::Validation::RequiredParam, {:key => 'mode', :type => 'Mode'}
|
10
|
+
#
|
11
|
+
class RequiredParam
|
12
|
+
attr_reader :type, :key
|
13
|
+
|
14
|
+
# Creates the Goliath::Rack::Validation::RequiredParam validator
|
15
|
+
#
|
16
|
+
# @param app The app object
|
17
|
+
# @param opts [Hash] The validator options
|
18
|
+
# @option opts [String] :key The key to look for in params (default: id)
|
19
|
+
# @option opts [String] :type The type string to put in the error message. (default: :key)
|
20
|
+
# @return [Goliath::Rack::Validation::RequiredParam] The validator
|
21
|
+
def initialize(app, opts = {})
|
22
|
+
@app = app
|
23
|
+
@key = opts[:key] || 'id'
|
24
|
+
@type = opts[:type] || @key.capitalize
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(env)
|
28
|
+
key_valid!(env['params'])
|
29
|
+
@app.call(env)
|
30
|
+
end
|
31
|
+
|
32
|
+
def key_valid!(params)
|
33
|
+
error = false
|
34
|
+
if !params.has_key?(key) || params[key].nil? ||
|
35
|
+
(params[key].is_a?(String) && params[key] =~ /^\s*$/)
|
36
|
+
error = true
|
37
|
+
end
|
38
|
+
|
39
|
+
if params[key].is_a?(Array)
|
40
|
+
unless params[key].compact.empty?
|
41
|
+
params[key].each do |k|
|
42
|
+
return unless k.is_a?(String)
|
43
|
+
return unless k =~ /^\s*$/
|
44
|
+
end
|
45
|
+
end
|
46
|
+
error = true
|
47
|
+
end
|
48
|
+
|
49
|
+
raise Goliath::Validation::Error.new(400, "#{@type} identifier missing") if error
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'goliath/rack/validation_error'
|
2
|
+
|
3
|
+
module Goliath
|
4
|
+
module Rack
|
5
|
+
module Validation
|
6
|
+
# Middleware to validate that a given parameter has a specified value.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# use Goliath::Rack::Validation::RequiredValue, {:key => 'mode', :values => %w(foo bar)}
|
10
|
+
# use Goliath::Rack::Validation::RequiredValue, {:key => 'baz', :values => 'awesome'}
|
11
|
+
#
|
12
|
+
class RequiredValue
|
13
|
+
attr_reader :key, :values
|
14
|
+
|
15
|
+
# Creates the Goliath::Rack::Validation::RequiredValue validator.
|
16
|
+
#
|
17
|
+
# @param app The app object
|
18
|
+
# @param opts [Hash] The options to create the validator with
|
19
|
+
# @option opts [String] :key The key to look for in params (default: id)
|
20
|
+
# @option opts [String | Array] :values The values to verify are in the params
|
21
|
+
# @return [Goliath::Rack::Validation::RequiredValue] The validator
|
22
|
+
def initialize(app, opts = {})
|
23
|
+
@app = app
|
24
|
+
@key = opts[:key] || 'id'
|
25
|
+
@values = [opts[:values]].flatten
|
26
|
+
end
|
27
|
+
|
28
|
+
def call(env)
|
29
|
+
value_valid!(env['params'])
|
30
|
+
@app.call(env)
|
31
|
+
end
|
32
|
+
|
33
|
+
def value_valid!(params)
|
34
|
+
error = false
|
35
|
+
if !params.has_key?(key) || params[key].nil? ||
|
36
|
+
(params[key].is_a?(String) && params[key] =~ /^\s*$/)
|
37
|
+
error = true
|
38
|
+
end
|
39
|
+
|
40
|
+
if params[key].is_a?(Array)
|
41
|
+
error = true if params[key].empty?
|
42
|
+
|
43
|
+
params[key].each do |k|
|
44
|
+
unless values.include?(k)
|
45
|
+
error = true
|
46
|
+
break
|
47
|
+
end
|
48
|
+
end
|
49
|
+
elsif !values.include?(params[key])
|
50
|
+
error = true
|
51
|
+
end
|
52
|
+
|
53
|
+
raise Goliath::Validation::Error.new(400, "Provided #{@key} is invalid") if error
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Goliath
|
2
|
+
module Validation
|
3
|
+
# A information about exceptions raised during validation.
|
4
|
+
class Error < StandardError
|
5
|
+
# The status code to return from the error handler
|
6
|
+
attr_accessor :status_code
|
7
|
+
|
8
|
+
# Create a new Goliath::Validation::Error.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# raise Goliath::Validation::Error.new(401, "Invalid credentials")
|
12
|
+
#
|
13
|
+
# @param status_code [Integer] The status code to return
|
14
|
+
# @param message [String] The error message to return
|
15
|
+
# @return [Goliath::Validation::Error] The Goliath::Validation::Error
|
16
|
+
def initialize(status_code, message)
|
17
|
+
super(message)
|
18
|
+
@status_code = status_code
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Rack
|
24
|
+
# Middleware to catch {Goliath::Validation::Error} exceptions
|
25
|
+
# and returns the [status code, no headers, :error => exception message]
|
26
|
+
class ValidationError
|
27
|
+
def initialize(app)
|
28
|
+
@app = app
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(env)
|
32
|
+
@app.call(env)
|
33
|
+
rescue Goliath::Validation::Error => e
|
34
|
+
[e.status_code, {}, {:error => e.message}]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'goliath/constants'
|
3
|
+
require 'async_rack'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
module Goliath
|
7
|
+
# Goliath::Request is responsible for processing a request and returning
|
8
|
+
# the result back to the client.
|
9
|
+
#
|
10
|
+
# @private
|
11
|
+
class Request
|
12
|
+
include EM::Deferrable
|
13
|
+
include Constants
|
14
|
+
|
15
|
+
attr_accessor :app, :conn, :env, :response, :body
|
16
|
+
|
17
|
+
def initialize(app, conn, env)
|
18
|
+
@app = app
|
19
|
+
@conn = conn
|
20
|
+
@env = env
|
21
|
+
|
22
|
+
@response = Goliath::Response.new
|
23
|
+
@body = StringIO.new(INITIAL_BODY.dup)
|
24
|
+
@env[RACK_INPUT] = body
|
25
|
+
@env[ASYNC_CALLBACK] = method(:post_process)
|
26
|
+
|
27
|
+
@env[STREAM_SEND] = proc { |data| callback { @conn.send_data(data) } }
|
28
|
+
@env[STREAM_CLOSE] = proc { callback { @conn.terminate_request(false) } }
|
29
|
+
@env[STREAM_START] = proc do
|
30
|
+
callback do
|
31
|
+
@conn.send_data(@response.head)
|
32
|
+
@conn.send_data(@response.headers_output)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@state = :processing
|
37
|
+
end
|
38
|
+
|
39
|
+
# Invoked by connection when header parsing is complete.
|
40
|
+
# This method is invoked only once per request.
|
41
|
+
#
|
42
|
+
# @param h [Hash] Request headers
|
43
|
+
# @param parser [Http::Parser] The parsed used to parse the request
|
44
|
+
# @return [Nil]
|
45
|
+
def parse_header(h, parser)
|
46
|
+
h.each do |k, v|
|
47
|
+
@env[HTTP_PREFIX + k.gsub('-','_').upcase] = v
|
48
|
+
end
|
49
|
+
|
50
|
+
@env[STATUS] = parser.status_code
|
51
|
+
@env[REQUEST_METHOD] = parser.http_method
|
52
|
+
@env[REQUEST_URI] = parser.request_url
|
53
|
+
@env[QUERY_STRING] = parser.query_string
|
54
|
+
@env[HTTP_VERSION] = parser.http_version.join('.')
|
55
|
+
@env[SCRIPT_NAME] = parser.request_path
|
56
|
+
@env[REQUEST_PATH] = parser.request_path
|
57
|
+
@env[PATH_INFO] = parser.request_path
|
58
|
+
@env[FRAGMENT] = parser.fragment
|
59
|
+
|
60
|
+
begin
|
61
|
+
@env[ASYNC_HEADERS].call(@env, h) if @env[ASYNC_HEADERS]
|
62
|
+
rescue Exception => e
|
63
|
+
server_exception(e)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Invoked by connection when new body data is
|
68
|
+
# parsed from the existing TCP stream.
|
69
|
+
#
|
70
|
+
# @note In theory, we can make this stream the
|
71
|
+
# data into the processing step for async
|
72
|
+
# uploads, etc. This would also require additional
|
73
|
+
# callbacks for headers, etc.. Maybe something to
|
74
|
+
# explore later.
|
75
|
+
#
|
76
|
+
# @param data [String] The received data
|
77
|
+
# @return [Nil]
|
78
|
+
def parse(data)
|
79
|
+
begin
|
80
|
+
if @env[ASYNC_BODY]
|
81
|
+
@env[ASYNC_BODY].call(@env, data)
|
82
|
+
else
|
83
|
+
@body << data
|
84
|
+
end
|
85
|
+
rescue Exception => e
|
86
|
+
server_exception(e)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Called to determine if the request has received all data from the client
|
91
|
+
#
|
92
|
+
# @return [Boolean] True if all data is received, false otherwise
|
93
|
+
def finished?
|
94
|
+
@state == :finished
|
95
|
+
end
|
96
|
+
|
97
|
+
# Invoked by connection when upstream client
|
98
|
+
# terminates the current TCP session.
|
99
|
+
#
|
100
|
+
# @return [Nil]
|
101
|
+
def close
|
102
|
+
@response.close rescue nil
|
103
|
+
|
104
|
+
begin
|
105
|
+
@env[ASYNC_CLOSE].call(@env) if @env[ASYNC_CLOSE]
|
106
|
+
rescue Exception => e
|
107
|
+
server_exception(e)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Invoked by connection when the parsing of the
|
112
|
+
# HTTP request and body complete. From this point
|
113
|
+
# all synchronous middleware will run until either
|
114
|
+
# an immediate response is served, or an async
|
115
|
+
# response is indicated.
|
116
|
+
#
|
117
|
+
# @return [Nil]
|
118
|
+
def process
|
119
|
+
begin
|
120
|
+
@state = :finished
|
121
|
+
post_process(@app.call(@env))
|
122
|
+
|
123
|
+
rescue Exception => e
|
124
|
+
server_exception(e)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Invoked by the app / middleware once the request
|
129
|
+
# is complete. A special async code is returned if
|
130
|
+
# the response is not ready yet.
|
131
|
+
#
|
132
|
+
# Sending of the data is deferred until the request
|
133
|
+
# is marked as ready to push data by the connection.
|
134
|
+
# Hence, two pipelined requests can come in via same
|
135
|
+
# connection, first can take take 1s to render, while
|
136
|
+
# second may take 0.5. Because HTTP spec does not
|
137
|
+
# allow for interleaved data exchange, we block the
|
138
|
+
# second request until the first one is done and the
|
139
|
+
# data is sent.
|
140
|
+
#
|
141
|
+
# However, processing on the server is done in parallel
|
142
|
+
# so the actual time to serve both requests in scenario
|
143
|
+
# above, should be ~1s + data transfer time.
|
144
|
+
#
|
145
|
+
# @param results [Array] The status, headers and body to return to the client
|
146
|
+
# @return [Nil]
|
147
|
+
def post_process(results)
|
148
|
+
begin
|
149
|
+
status, headers, body = results
|
150
|
+
return if status && status == Goliath::Connection::AsyncResponse.first
|
151
|
+
|
152
|
+
callback do
|
153
|
+
begin
|
154
|
+
@response.status, @response.headers, @response.body = status, headers, body
|
155
|
+
@response.each { |chunk| @conn.send_data(chunk) }
|
156
|
+
@env[LOGGER].info("Status: #{@response.status}, " +
|
157
|
+
"Content-Length: #{@response.headers['Content-Length']}, " +
|
158
|
+
"Response Time: #{"%.2f" % ((Time.now.to_f - @env[:start_time]) * 1000)}ms")
|
159
|
+
|
160
|
+
@conn.terminate_request(keep_alive)
|
161
|
+
rescue Exception => e
|
162
|
+
server_exception(e)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
rescue Exception => e
|
167
|
+
server_exception(e)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
# Handles logging server exceptions
|
174
|
+
#
|
175
|
+
# @param e [Exception] The exception to log
|
176
|
+
# @return [Nil]
|
177
|
+
def server_exception(e)
|
178
|
+
@env[LOGGER].error("#{e.message}\n#{e.backtrace.join("\n")}")
|
179
|
+
post_process([500, {}, 'An error happened'])
|
180
|
+
end
|
181
|
+
|
182
|
+
# Used to determine if the connection should be kept open
|
183
|
+
#
|
184
|
+
# @return [Boolean] True to keep the connection open, false otherwise
|
185
|
+
def keep_alive
|
186
|
+
case @env[HTTP_VERSION]
|
187
|
+
# HTTP 1.1: all requests are persistent requests, client
|
188
|
+
# must send a Connection:close header to indicate otherwise
|
189
|
+
when '1.1' then
|
190
|
+
(@env[HTTP_PREFIX + CONNECTION].downcase != 'close') rescue true
|
191
|
+
|
192
|
+
# HTTP 1.0: all requests are non keep-alive, client must
|
193
|
+
# send a Connection: Keep-Alive to indicate otherwise
|
194
|
+
when '1.0' then
|
195
|
+
(@env[HTTP_PREFIX + CONNECTION].downcase == 'keep-alive') rescue false
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|