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.

Files changed (84) hide show
  1. data/.gitignore +15 -0
  2. data/.rspec +2 -0
  3. data/.yardopts +2 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +66 -0
  6. data/README.md +86 -0
  7. data/Rakefile +18 -0
  8. data/examples/activerecord/config/srv.rb +7 -0
  9. data/examples/activerecord/srv.rb +37 -0
  10. data/examples/async_upload.rb +34 -0
  11. data/examples/conf_test.rb +27 -0
  12. data/examples/config/conf_test.rb +12 -0
  13. data/examples/config/echo.rb +1 -0
  14. data/examples/config/http_log.rb +7 -0
  15. data/examples/config/shared.rb +5 -0
  16. data/examples/custom_server.rb +57 -0
  17. data/examples/echo.rb +37 -0
  18. data/examples/gziped.rb +40 -0
  19. data/examples/hello_world.rb +10 -0
  20. data/examples/http_log.rb +85 -0
  21. data/examples/rack_routes.rb +44 -0
  22. data/examples/stream.rb +37 -0
  23. data/examples/valid.rb +19 -0
  24. data/goliath.gemspec +41 -0
  25. data/lib/goliath.rb +38 -0
  26. data/lib/goliath/api.rb +165 -0
  27. data/lib/goliath/application.rb +90 -0
  28. data/lib/goliath/connection.rb +94 -0
  29. data/lib/goliath/constants.rb +51 -0
  30. data/lib/goliath/env.rb +92 -0
  31. data/lib/goliath/goliath.rb +49 -0
  32. data/lib/goliath/headers.rb +37 -0
  33. data/lib/goliath/http_status_codes.rb +44 -0
  34. data/lib/goliath/plugins/latency.rb +33 -0
  35. data/lib/goliath/rack/default_mime_type.rb +30 -0
  36. data/lib/goliath/rack/default_response_format.rb +33 -0
  37. data/lib/goliath/rack/formatters/html.rb +90 -0
  38. data/lib/goliath/rack/formatters/json.rb +42 -0
  39. data/lib/goliath/rack/formatters/xml.rb +90 -0
  40. data/lib/goliath/rack/heartbeat.rb +23 -0
  41. data/lib/goliath/rack/jsonp.rb +38 -0
  42. data/lib/goliath/rack/params.rb +30 -0
  43. data/lib/goliath/rack/render.rb +66 -0
  44. data/lib/goliath/rack/tracer.rb +31 -0
  45. data/lib/goliath/rack/validation/boolean_value.rb +59 -0
  46. data/lib/goliath/rack/validation/default_params.rb +46 -0
  47. data/lib/goliath/rack/validation/numeric_range.rb +59 -0
  48. data/lib/goliath/rack/validation/request_method.rb +33 -0
  49. data/lib/goliath/rack/validation/required_param.rb +54 -0
  50. data/lib/goliath/rack/validation/required_value.rb +58 -0
  51. data/lib/goliath/rack/validation_error.rb +38 -0
  52. data/lib/goliath/request.rb +199 -0
  53. data/lib/goliath/response.rb +93 -0
  54. data/lib/goliath/runner.rb +236 -0
  55. data/lib/goliath/server.rb +149 -0
  56. data/lib/goliath/test_helper.rb +118 -0
  57. data/lib/goliath/version.rb +4 -0
  58. data/spec/integration/async_request_processing.rb +23 -0
  59. data/spec/integration/echo_spec.rb +27 -0
  60. data/spec/integration/keepalive_spec.rb +28 -0
  61. data/spec/integration/pipelining_spec.rb +43 -0
  62. data/spec/integration/valid_spec.rb +24 -0
  63. data/spec/spec_helper.rb +6 -0
  64. data/spec/unit/connection_spec.rb +59 -0
  65. data/spec/unit/env_spec.rb +55 -0
  66. data/spec/unit/headers_spec.rb +53 -0
  67. data/spec/unit/rack/default_mime_type_spec.rb +34 -0
  68. data/spec/unit/rack/formatters/json_spec.rb +54 -0
  69. data/spec/unit/rack/formatters/xml_spec.rb +66 -0
  70. data/spec/unit/rack/heartbeat_spec.rb +47 -0
  71. data/spec/unit/rack/params_spec.rb +94 -0
  72. data/spec/unit/rack/render_spec.rb +87 -0
  73. data/spec/unit/rack/validation/boolean_value_spec.rb +54 -0
  74. data/spec/unit/rack/validation/default_params_spec.rb +71 -0
  75. data/spec/unit/rack/validation/numeric_range_spec.rb +96 -0
  76. data/spec/unit/rack/validation/request_method_spec.rb +47 -0
  77. data/spec/unit/rack/validation/required_param_spec.rb +92 -0
  78. data/spec/unit/rack/validation/required_value_spec.rb +99 -0
  79. data/spec/unit/rack/validation_error_spec.rb +40 -0
  80. data/spec/unit/request_spec.rb +59 -0
  81. data/spec/unit/response_spec.rb +35 -0
  82. data/spec/unit/runner_spec.rb +129 -0
  83. data/spec/unit/server_spec.rb +137 -0
  84. 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