thin 0.6.4 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of thin might be problematic. Click here for more details.
- data/CHANGELOG +20 -0
- data/README +11 -12
- data/benchmark/abc +51 -0
- data/benchmark/benchmarker.rb +80 -0
- data/benchmark/runner +79 -0
- data/example/adapter.rb +3 -3
- data/example/thin.god +11 -7
- data/lib/thin.rb +17 -16
- data/lib/thin/command.rb +10 -4
- data/lib/thin/connection.rb +46 -13
- data/lib/thin/connectors/connector.rb +22 -10
- data/lib/thin/connectors/swiftiply_client.rb +55 -0
- data/lib/thin/controllers/cluster.rb +28 -22
- data/lib/thin/controllers/controller.rb +74 -14
- data/lib/thin/controllers/service.rb +1 -1
- data/lib/thin/daemonizing.rb +6 -4
- data/lib/thin/headers.rb +4 -0
- data/lib/thin/logging.rb +34 -9
- data/lib/thin/request.rb +31 -2
- data/lib/thin/response.rb +22 -7
- data/lib/thin/runner.rb +27 -14
- data/lib/thin/server.rb +55 -7
- data/lib/thin/version.rb +3 -3
- data/spec/command_spec.rb +2 -3
- data/spec/connection_spec.rb +15 -1
- data/spec/connectors/swiftiply_client_spec.rb +66 -0
- data/spec/controllers/cluster_spec.rb +43 -12
- data/spec/controllers/controller_spec.rb +16 -4
- data/spec/controllers/service_spec.rb +0 -1
- data/spec/logging_spec.rb +42 -0
- data/spec/request/persistent_spec.rb +35 -0
- data/spec/response_spec.rb +18 -0
- data/spec/server/pipelining_spec.rb +108 -0
- data/spec/server/swiftiply.yml +6 -0
- data/spec/server/swiftiply_spec.rb +27 -0
- data/spec/server/tcp_spec.rb +3 -3
- data/spec/server_spec.rb +22 -0
- data/spec/spec_helper.rb +3 -3
- data/tasks/gem.rake +1 -1
- data/tasks/spec.rake +9 -0
- metadata +13 -6
- data/benchmark/previous.rb +0 -14
- data/benchmark/simple.rb +0 -15
- data/benchmark/utils.rb +0 -75
- data/lib/thin_parser.bundle +0 -0
data/CHANGELOG
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
== 0.7.0 Spherical Cow release
|
2
|
+
* Add --max-persistent-conns option to sets the maximum number of persistent connections.
|
3
|
+
Set to 0 to disable Keep-Alive.
|
4
|
+
* INT signal now force stop and QUIT signal gracefully stops.
|
5
|
+
* Warn when descriptors table size can't be set as high as expected.
|
6
|
+
* Eval Rackup config file using top level bindings.
|
7
|
+
* Remove daemons gem dependency on Windows plateform, fixes #45.
|
8
|
+
* Change default timeout from 60 to 30 seconds.
|
9
|
+
* Add --max-conns option to sets the maximum number of file or socket descriptors that
|
10
|
+
your process may open, defaults to 1024.
|
11
|
+
* Tail logfile when stopping and restarting a demonized server, fixes #26.
|
12
|
+
* Wrap application in a Rack::CommonLogger adapter in debug mode.
|
13
|
+
* --debug (-D) option no longer set $DEBUG so logging will be less verbose
|
14
|
+
and Ruby won't be too strict, fixes #36.
|
15
|
+
* Deprecate Server#silent in favour of Logging.silent.
|
16
|
+
* Persistent connection (keep-alive) & HTTP pipelining support.
|
17
|
+
* Fix -s option not being included in generated config file, fixes #37.
|
18
|
+
* Add Swiftiply support. Use w/ the --swiftiply (-y) option in the thin script,
|
19
|
+
closes #28 [Alex MacCaw]
|
20
|
+
|
1
21
|
== 0.6.4 Sexy Lobster release
|
2
22
|
* Fix error when stopping server on UNIX domain socket, fixes #42
|
3
23
|
* Rescue errors in Connection#get_peername more gracefully, setting REMOTE_ADDR to nil, fixes #43
|
data/README
CHANGED
@@ -2,9 +2,10 @@
|
|
2
2
|
Tiny, fast & funny HTTP server
|
3
3
|
|
4
4
|
Thin is a Ruby web server that glues together 3 of the best Ruby libraries in web history:
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
* the Mongrel parser: the root of Mongrel speed and security
|
6
|
+
* Event Machine: a network I/O library with extremely high scalability, performance and stability
|
7
|
+
* Rack: a minimal interface between webservers and Ruby frameworks
|
8
|
+
|
8
9
|
Which makes it, with all humility, the most secure, stable, fast and extensible Ruby web server
|
9
10
|
bundled in an easy to use gem for your own pleasure.
|
10
11
|
|
@@ -24,17 +25,15 @@ Or from source:
|
|
24
25
|
rake install
|
25
26
|
|
26
27
|
=== Usage
|
27
|
-
A +thin+ script
|
28
|
+
A +thin+ script offers an easy way to start your Rails application:
|
28
29
|
|
29
30
|
cd to/your/rails/app
|
30
31
|
thin start
|
31
32
|
|
32
|
-
But Thin is also usable
|
33
|
-
You need to setup a config.ru file and
|
33
|
+
But Thin is also usable with a Rack config file.
|
34
|
+
You need to setup a config.ru file and pass it to the thin script:
|
34
35
|
|
35
|
-
cat
|
36
|
-
require 'thin'
|
37
|
-
|
36
|
+
cat config.ru
|
38
37
|
app = proc do |env|
|
39
38
|
[
|
40
39
|
200,
|
@@ -47,10 +46,10 @@ You need to setup a config.ru file and require thin in it:
|
|
47
46
|
end
|
48
47
|
|
49
48
|
run app
|
50
|
-
|
51
|
-
|
49
|
+
|
50
|
+
thin start -r config.ru
|
52
51
|
|
53
|
-
See example
|
52
|
+
See example directory for more samples and run 'thin -h' for usage.
|
54
53
|
|
55
54
|
=== License
|
56
55
|
Ruby License, http://www.ruby-lang.org/en/LICENSE.txt.
|
data/benchmark/abc
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Automate benchmarking with ab with various concurrency levels.
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
options = {
|
6
|
+
:address => '0.0.0.0',
|
7
|
+
:port => 3000,
|
8
|
+
:requests => 1000,
|
9
|
+
:start => 1,
|
10
|
+
:end => 100,
|
11
|
+
:step => 10
|
12
|
+
}
|
13
|
+
|
14
|
+
OptionParser.new do |opts|
|
15
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
16
|
+
|
17
|
+
opts.on("-n", "--requests NUM", "Number of requests") { |num| options[:requests] = num }
|
18
|
+
opts.on("-a", "--address HOST", "Address (default: 0.0.0.0)") { |host| options[:address] = host }
|
19
|
+
opts.on("-p", "--port PORT", "use PORT (default: 3000)") { |port| options[:port] = port.to_i }
|
20
|
+
opts.on("-s", "--start N", "First concurrency level") { |n| options[:start] = n.to_i }
|
21
|
+
opts.on("-e", "--end N", "Last concurrency level") { |n| options[:end] = n.to_i }
|
22
|
+
opts.on("-S", "--step N", "Concurrency level step") { |n| options[:step] = n.to_i }
|
23
|
+
opts.on("-u", "--uri PATH", "Path to send to") { |u| options[:uri] = u }
|
24
|
+
opts.on("-k", "--keep-alive", "Use Keep-Alive") { options[:keep_alive] = true }
|
25
|
+
|
26
|
+
opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
|
27
|
+
end.parse!(ARGV)
|
28
|
+
|
29
|
+
puts 'request concurrency req/s failures'
|
30
|
+
puts '=' * 42
|
31
|
+
|
32
|
+
c = options[:start]
|
33
|
+
until c >= options[:end]
|
34
|
+
sleep 0.5
|
35
|
+
out = `nice -n20 ab #{'-k' if options[:keep_alive]} -c #{c} -n #{options[:requests]} #{options[:address]}:#{options[:port]}/#{options[:uri]} 2> /dev/null`
|
36
|
+
|
37
|
+
r = if requests = out.match(/^Requests.+?(\d+\.\d+)/)
|
38
|
+
requests[1].to_i
|
39
|
+
else
|
40
|
+
0
|
41
|
+
end
|
42
|
+
f = if requests = out.match(/^Failed requests.+?(\d+)/)
|
43
|
+
requests[1].to_i
|
44
|
+
else
|
45
|
+
0
|
46
|
+
end
|
47
|
+
|
48
|
+
puts "#{options[:requests].to_s.ljust(9)} #{c.to_s.ljust(13)} #{r.to_s.ljust(8)} #{f}"
|
49
|
+
|
50
|
+
c += options[:step]
|
51
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rack/lobster'
|
2
|
+
|
3
|
+
class Benchmarker
|
4
|
+
PORT = 7000
|
5
|
+
ADDRESS = '0.0.0.0'
|
6
|
+
|
7
|
+
attr_accessor :requests, :concurrencies, :servers, :keep_alive
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@servers = %w(Mongrel EMongrel Thin)
|
11
|
+
@requests = 1000
|
12
|
+
@concurrencies = [1, 10, 100]
|
13
|
+
end
|
14
|
+
|
15
|
+
def writer(&block)
|
16
|
+
@writer = block
|
17
|
+
end
|
18
|
+
|
19
|
+
def run!
|
20
|
+
@concurrencies.each do |concurrency|
|
21
|
+
@servers.each do |server|
|
22
|
+
req_sec, failed = run_one(server, concurrency)
|
23
|
+
@writer.call(server, @requests, concurrency, req_sec, failed)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def start_server(handler_name)
|
30
|
+
@server = fork do
|
31
|
+
[STDOUT, STDERR].each { |o| o.reopen "/dev/null" }
|
32
|
+
|
33
|
+
case handler_name
|
34
|
+
when 'EMongrel'
|
35
|
+
require 'swiftcore/evented_mongrel'
|
36
|
+
handler_name = 'Mongrel'
|
37
|
+
end
|
38
|
+
|
39
|
+
app = proc do |env|
|
40
|
+
[200, {'Content-Type' => 'text/html', 'Content-Length' => '11'}, ['hello world']]
|
41
|
+
end
|
42
|
+
|
43
|
+
handler = Rack::Handler.const_get(handler_name)
|
44
|
+
handler.run app, :Host => ADDRESS, :Port => PORT
|
45
|
+
end
|
46
|
+
|
47
|
+
sleep 2
|
48
|
+
end
|
49
|
+
|
50
|
+
def stop_server
|
51
|
+
Process.kill('SIGKILL', @server)
|
52
|
+
Process.wait
|
53
|
+
end
|
54
|
+
|
55
|
+
def run_ab(concurrency)
|
56
|
+
`nice -n20 ab -c #{concurrency} -n #{@requests} #{@keep_alive ? '-k' : ''} #{ADDRESS}:#{PORT}/ 2> /dev/null`
|
57
|
+
end
|
58
|
+
|
59
|
+
def run_one(handler_name, concurrency)
|
60
|
+
start_server(handler_name)
|
61
|
+
|
62
|
+
out = run_ab(concurrency)
|
63
|
+
|
64
|
+
stop_server
|
65
|
+
|
66
|
+
req_sec = if matches = out.match(/^Requests.+?(\d+\.\d+)/)
|
67
|
+
matches[1].to_i
|
68
|
+
else
|
69
|
+
0
|
70
|
+
end
|
71
|
+
|
72
|
+
failed = if matches = out.match(/^Failed requests.+?(\d+)/)
|
73
|
+
matches[1].to_i
|
74
|
+
else
|
75
|
+
0
|
76
|
+
end
|
77
|
+
|
78
|
+
[req_sec, failed]
|
79
|
+
end
|
80
|
+
end
|
data/benchmark/runner
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Simple benchmark to compare Thin performance against
|
3
|
+
# other webservers supported by Rack.
|
4
|
+
#
|
5
|
+
# Run with:
|
6
|
+
#
|
7
|
+
# ruby simple.rb [num of request] [print|graph] [concurrency levels]
|
8
|
+
#
|
9
|
+
require File.dirname(__FILE__) + '/../lib/thin'
|
10
|
+
require File.dirname(__FILE__) + '/benchmarker'
|
11
|
+
require 'optparse'
|
12
|
+
|
13
|
+
options = {
|
14
|
+
:requests => 1000,
|
15
|
+
:concurrencies => [1, 10, 100],
|
16
|
+
:keep_alive => false,
|
17
|
+
:output => :table
|
18
|
+
}
|
19
|
+
|
20
|
+
OptionParser.new do |opts|
|
21
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
22
|
+
|
23
|
+
opts.on("-n", "--requests NUM", "Number of requests") { |num| options[:requests] = num.to_i }
|
24
|
+
opts.on("-c", "--concurrencies EXP", "Concurrency levels") { |exp| options[:concurrencies] = eval(exp).to_a }
|
25
|
+
opts.on("-k", "--keep-alive", "Use persistent connections") { options[:keep_alive] = true }
|
26
|
+
opts.on("-t", "--table", "Output as text table") { options[:output] = :table }
|
27
|
+
opts.on("-g", "--graph", "Output as graph") { options[:output] = :graph }
|
28
|
+
|
29
|
+
opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
|
30
|
+
end.parse!(ARGV)
|
31
|
+
|
32
|
+
# benchmark output_type, %w(WEBrick Mongrel EMongrel Thin), request, levels
|
33
|
+
b = Benchmarker.new
|
34
|
+
b.requests = options[:requests]
|
35
|
+
b.concurrencies = options[:concurrencies]
|
36
|
+
b.keep_alive = options[:keep_alive]
|
37
|
+
|
38
|
+
case options[:output]
|
39
|
+
when :table
|
40
|
+
puts 'server request concurrency req/s failures'
|
41
|
+
puts '=' * 52
|
42
|
+
|
43
|
+
b.writer do |server, requests, concurrency, req_sec, failed|
|
44
|
+
puts "#{server.ljust(8)} #{requests} #{concurrency.to_s.ljust(4)} #{req_sec.to_s.ljust(8)} #{failed}"
|
45
|
+
end
|
46
|
+
|
47
|
+
b.run!
|
48
|
+
|
49
|
+
when :graph
|
50
|
+
require '/usr/local/lib/ruby/gems/1.8/gems/gruff-0.2.9/lib/gruff'
|
51
|
+
g = Gruff::Area.new
|
52
|
+
g.title = "#{options[:requests]} requests"
|
53
|
+
g.title << ' w/ Keep-Alive' if options[:keep_alive]
|
54
|
+
|
55
|
+
g.x_axis_label = 'Concurrency'
|
56
|
+
g.y_axis_label = 'Requests / sec'
|
57
|
+
g.maximum_value = 0
|
58
|
+
g.minimum_value = 0
|
59
|
+
g.labels = {}
|
60
|
+
b.concurrencies.each_with_index { |c, i| g.labels[i] = c.to_s }
|
61
|
+
|
62
|
+
results = {}
|
63
|
+
|
64
|
+
b.writer do |server, requests, concurrency, req_sec, failed|
|
65
|
+
print '.'
|
66
|
+
results[server] ||= []
|
67
|
+
results[server] << req_sec
|
68
|
+
end
|
69
|
+
|
70
|
+
b.run!
|
71
|
+
puts
|
72
|
+
|
73
|
+
results.each do |server, concurrencies|
|
74
|
+
g.data(server, concurrencies)
|
75
|
+
end
|
76
|
+
|
77
|
+
g.write('bench.png')
|
78
|
+
`open bench.png`
|
79
|
+
end
|
data/example/adapter.rb
CHANGED
@@ -9,8 +9,8 @@ class SimpleAdapter
|
|
9
9
|
[
|
10
10
|
200,
|
11
11
|
{
|
12
|
-
'Content-Type'
|
13
|
-
'Content-
|
12
|
+
'Content-Type' => 'text/plain',
|
13
|
+
'Content-Length' => body.join.size.to_s,
|
14
14
|
},
|
15
15
|
body
|
16
16
|
]
|
@@ -32,4 +32,4 @@ end
|
|
32
32
|
# app = Rack::URLMap.new('/test' => SimpleAdapter.new,
|
33
33
|
# '/files' => Rack::File.new('.'))
|
34
34
|
# Thin::Server.new('0.0.0.0', 3000, app).start
|
35
|
-
#
|
35
|
+
#
|
data/example/thin.god
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# == God config file
|
2
2
|
# http://god.rubyforge.org/
|
3
|
-
#
|
3
|
+
# Authors: Gump and michael@glauche.de
|
4
4
|
#
|
5
5
|
# Config file for god that configures watches for each instance of a thin server for
|
6
6
|
# each thin configuration file found in /etc/thin.
|
@@ -16,28 +16,32 @@ Dir[config_path + "/*.yml"].each do |file|
|
|
16
16
|
config = YAML.load_file(file)
|
17
17
|
num_servers = config["servers"] ||= 1
|
18
18
|
|
19
|
-
|
19
|
+
(0...num_servers).each do |i|
|
20
|
+
# UNIX socket cluster use number 0 to 2 (for 3 servers)
|
21
|
+
# and tcp cluster use port number 3000 to 3002.
|
22
|
+
number = config['socket'] ? i : (config['port'] + i)
|
23
|
+
|
20
24
|
God.watch do |w|
|
21
25
|
w.group = "thin-" + File.basename(file, ".yml")
|
22
|
-
w.name = w.group + "-#{
|
26
|
+
w.name = w.group + "-#{number}"
|
23
27
|
|
24
28
|
w.interval = 30.seconds
|
25
29
|
|
26
30
|
w.uid = config["user"]
|
27
31
|
w.gid = config["group"]
|
28
32
|
|
29
|
-
w.start = "thin start -C #{file} -o #{
|
33
|
+
w.start = "thin start -C #{file} -o #{number}"
|
30
34
|
w.start_grace = 10.seconds
|
31
35
|
|
32
|
-
w.stop = "thin stop -C #{file} -o #{
|
36
|
+
w.stop = "thin stop -C #{file} -o #{number}"
|
33
37
|
w.stop_grace = 10.seconds
|
34
38
|
|
35
|
-
w.restart = "thin restart -C #{file} -o #{
|
39
|
+
w.restart = "thin restart -C #{file} -o #{number}"
|
36
40
|
|
37
41
|
pid_path = config["chdir"] + "/" + config["pid"]
|
38
42
|
ext = File.extname(pid_path)
|
39
43
|
|
40
|
-
w.pid_file = pid_path.gsub(/#{ext}$/, ".#{
|
44
|
+
w.pid_file = pid_path.gsub(/#{ext}$/, ".#{number}#{ext}")
|
41
45
|
|
42
46
|
w.behavior(:clean_pid_file)
|
43
47
|
|
data/lib/thin.rb
CHANGED
@@ -11,27 +11,28 @@ require 'thin/version'
|
|
11
11
|
require 'thin/statuses'
|
12
12
|
|
13
13
|
module Thin
|
14
|
-
autoload :Command,
|
15
|
-
autoload :Connection,
|
16
|
-
autoload :Daemonizable,
|
17
|
-
autoload :Logging,
|
18
|
-
autoload :Headers,
|
19
|
-
autoload :Request,
|
20
|
-
autoload :Response,
|
21
|
-
autoload :Runner,
|
22
|
-
autoload :Server,
|
23
|
-
autoload :Stats,
|
14
|
+
autoload :Command, 'thin/command'
|
15
|
+
autoload :Connection, 'thin/connection'
|
16
|
+
autoload :Daemonizable, 'thin/daemonizing'
|
17
|
+
autoload :Logging, 'thin/logging'
|
18
|
+
autoload :Headers, 'thin/headers'
|
19
|
+
autoload :Request, 'thin/request'
|
20
|
+
autoload :Response, 'thin/response'
|
21
|
+
autoload :Runner, 'thin/runner'
|
22
|
+
autoload :Server, 'thin/server'
|
23
|
+
autoload :Stats, 'thin/stats'
|
24
24
|
|
25
25
|
module Connectors
|
26
|
-
autoload :Connector,
|
27
|
-
autoload :
|
28
|
-
autoload :
|
26
|
+
autoload :Connector, 'thin/connectors/connector'
|
27
|
+
autoload :SwiftiplyClient, 'thin/connectors/swiftiply_client'
|
28
|
+
autoload :TcpServer, 'thin/connectors/tcp_server'
|
29
|
+
autoload :UnixServer, 'thin/connectors/unix_server'
|
29
30
|
end
|
30
31
|
|
31
32
|
module Controllers
|
32
|
-
autoload :Cluster,
|
33
|
-
autoload :Controller,
|
34
|
-
autoload :Service,
|
33
|
+
autoload :Cluster, 'thin/controllers/cluster'
|
34
|
+
autoload :Controller, 'thin/controllers/controller'
|
35
|
+
autoload :Service, 'thin/controllers/service'
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
data/lib/thin/command.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
1
3
|
module Thin
|
2
4
|
# Run a command though the +thin+ command-line script.
|
3
5
|
class Command
|
@@ -22,16 +24,20 @@ module Thin
|
|
22
24
|
def run
|
23
25
|
shell_cmd = shellify
|
24
26
|
trace shell_cmd
|
25
|
-
|
26
|
-
|
27
|
+
trap('INT') {} # Ignore INT signal to pass CTRL+C to subprocess
|
28
|
+
Open3.popen3(shell_cmd) do |stdin, stdout, stderr|
|
29
|
+
log stdout.gets until stdout.eof?
|
30
|
+
log stderr.gets until stderr.eof?
|
31
|
+
end
|
27
32
|
end
|
28
33
|
|
29
34
|
# Turn into a runnable shell command
|
30
35
|
def shellify
|
31
36
|
shellified_options = @options.inject([]) do |args, (name, value)|
|
32
37
|
args << case value
|
33
|
-
when NilClass
|
34
|
-
|
38
|
+
when NilClass,
|
39
|
+
TrueClass then "--#{name}"
|
40
|
+
when FalseClass
|
35
41
|
else "--#{name.to_s.tr('_', '-')}=#{value.inspect}"
|
36
42
|
end
|
37
43
|
end
|
data/lib/thin/connection.rb
CHANGED
@@ -2,6 +2,8 @@ require 'socket'
|
|
2
2
|
|
3
3
|
module Thin
|
4
4
|
# Connection between the server and client.
|
5
|
+
# This class is instanciated by EventMachine on each new connection
|
6
|
+
# that is opened.
|
5
7
|
class Connection < EventMachine::Connection
|
6
8
|
include Logging
|
7
9
|
|
@@ -11,62 +13,93 @@ module Thin
|
|
11
13
|
# Connector to the server
|
12
14
|
attr_accessor :connector
|
13
15
|
|
14
|
-
|
16
|
+
# Current request served by the connection
|
17
|
+
attr_accessor :request
|
15
18
|
|
19
|
+
# Next response sent through connection
|
20
|
+
attr_accessor :response
|
21
|
+
|
22
|
+
# Get the connection ready to process a request.
|
16
23
|
def post_init
|
17
24
|
@request = Request.new
|
18
25
|
@response = Response.new
|
19
26
|
end
|
20
27
|
|
28
|
+
# Called when data is received from the client.
|
21
29
|
def receive_data(data)
|
22
30
|
trace { data }
|
23
31
|
process if @request.parse(data)
|
24
32
|
rescue InvalidRequest => e
|
25
|
-
log "Invalid request"
|
33
|
+
log "!! Invalid request"
|
26
34
|
log_error e
|
27
35
|
close_connection
|
28
36
|
end
|
29
37
|
|
38
|
+
# Called when all data was received and the request
|
39
|
+
# is ready to being processed.
|
30
40
|
def process
|
31
41
|
# Add client info to the request env
|
32
|
-
@request.
|
42
|
+
@request.remote_address = remote_address
|
33
43
|
|
34
44
|
# Process the request
|
35
45
|
@response.status, @response.headers, @response.body = @app.call(@request.env)
|
36
46
|
|
47
|
+
# Make the response persistent if requested by the client
|
48
|
+
@response.persistent! if @request.persistent?
|
49
|
+
|
37
50
|
# Send the response
|
38
51
|
@response.each do |chunk|
|
39
52
|
trace { chunk }
|
40
53
|
send_data chunk
|
41
54
|
end
|
42
55
|
|
43
|
-
|
56
|
+
# If no more request on that same connection, we close it.
|
57
|
+
close_connection_after_writing unless persistent?
|
44
58
|
|
45
|
-
rescue
|
46
|
-
log "Unexpected error while processing request: #{
|
47
|
-
log_error
|
59
|
+
rescue
|
60
|
+
log "!! Unexpected error while processing request: #{$!.message}"
|
61
|
+
log_error
|
48
62
|
close_connection rescue nil
|
49
63
|
ensure
|
50
64
|
@request.close rescue nil
|
51
65
|
@response.close rescue nil
|
66
|
+
|
67
|
+
# Prepare the connection for another request if the client
|
68
|
+
# supports HTTP pipelining (persistent connection).
|
69
|
+
post_init if persistent?
|
52
70
|
end
|
53
71
|
|
72
|
+
# Called when the connection is unbinded from the socket
|
73
|
+
# and can no longer be used to process requests.
|
54
74
|
def unbind
|
55
75
|
@connector.connection_finished(self)
|
56
76
|
end
|
57
77
|
|
78
|
+
# Allows this connection to be persistent.
|
79
|
+
def can_persist!
|
80
|
+
@can_persist = true
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return +true+ if this connection is allowed to stay open and be persistent.
|
84
|
+
def can_persist?
|
85
|
+
@can_persist
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return +true+ if the connection must be left open
|
89
|
+
# and ready to be reused for another request.
|
90
|
+
def persistent?
|
91
|
+
@can_persist && @response.persistent?
|
92
|
+
end
|
93
|
+
|
94
|
+
# IP Address of the remote client.
|
58
95
|
def remote_address
|
59
|
-
@request.
|
96
|
+
@request.forwarded_for || socket_address
|
60
97
|
rescue
|
61
|
-
log_error
|
98
|
+
log_error
|
62
99
|
nil
|
63
100
|
end
|
64
101
|
|
65
102
|
protected
|
66
|
-
def has_peername?
|
67
|
-
!get_peername.nil? && !get_peername.empty?
|
68
|
-
end
|
69
|
-
|
70
103
|
def socket_address
|
71
104
|
Socket.unpack_sockaddr_in(get_peername)[1]
|
72
105
|
end
|