thin 1.8.0 → 2.0.0.pre
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/.gitignore +9 -0
- data/CHANGELOG +29 -107
- data/Gemfile +8 -0
- data/README.md +44 -78
- data/Rakefile +28 -18
- data/bin/thin +4 -4
- data/examples/async.ru +21 -0
- data/examples/thin.conf.rb +39 -0
- data/lib/thin.rb +2 -44
- data/lib/thin/async.rb +108 -0
- data/lib/thin/backends/prefork.rb +44 -0
- data/lib/thin/backends/single_process.rb +28 -0
- data/lib/thin/chunked_body.rb +28 -0
- data/lib/thin/configurator.rb +118 -0
- data/lib/thin/connection.rb +246 -172
- data/lib/thin/listener.rb +114 -0
- data/lib/thin/request.rb +94 -76
- data/lib/thin/response.rb +112 -45
- data/lib/thin/runner.rb +134 -197
- data/lib/thin/server.rb +203 -252
- data/lib/thin/system.rb +49 -0
- data/lib/thin/version.rb +11 -26
- data/man/index.txt +3 -0
- data/man/thin-conf.5.ronn +121 -0
- data/man/thin.1.ronn +105 -0
- data/site/.gitignore +2 -0
- data/site/README.md +21 -0
- data/site/Rakefile +20 -0
- data/site/config.ru +4 -0
- data/site/public/images/grid.png +0 -0
- data/site/public/javascripts/dd_belatedpng.js +13 -0
- data/site/public/javascripts/modernizr-1.6.min.js +30 -0
- data/site/public/man/thin-conf.5.html +220 -0
- data/site/public/man/thin.1.html +177 -0
- data/site/site/assets/javascripts/main.coffee +2 -0
- data/site/site/assets/stylesheets/_config.scss +55 -0
- data/site/site/assets/stylesheets/main.scss +24 -0
- data/site/site/helpers.rb +17 -0
- data/site/site/layouts/base.erb +55 -0
- data/site/site/layouts/default.erb +17 -0
- data/site/site/pages/about.md +5 -0
- data/site/site/pages/index.erb +10 -0
- data/site/site/partials/.gitkeep +0 -0
- data/test/fixtures/big.txt +1 -0
- data/test/fixtures/small.txt +1 -0
- data/test/fixtures/thin.conf.rb +15 -0
- data/test/integration/async_test.rb +35 -0
- data/test/integration/big_request_test.rb +30 -0
- data/test/integration/config.ru +57 -0
- data/test/integration/daemonize_test.rb +26 -0
- data/test/integration/env_test.rb +44 -0
- data/test/integration/error_test.rb +37 -0
- data/test/integration/file_sending_test.rb +24 -0
- data/test/integration/keep_alive_test.rb +35 -0
- data/test/integration/robustness_test.rb +37 -0
- data/test/integration/single_process_test.rb +15 -0
- data/test/integration/socket_family_test.rb +38 -0
- data/test/integration/worker_test.rb +22 -0
- data/test/test_helper.rb +195 -0
- data/test/unit/configurator_test.rb +43 -0
- data/test/unit/connection_test.rb +94 -0
- data/test/unit/listener_test.rb +74 -0
- data/test/unit/request_test.rb +74 -0
- data/test/unit/response_test.rb +90 -0
- data/test/unit/server_test.rb +29 -0
- data/test/unit/system_test.rb +17 -0
- data/thin.gemspec +26 -0
- data/v2.todo +21 -0
- metadata +138 -93
- checksums.yaml +0 -7
- data/example/adapter.rb +0 -32
- data/example/async_app.ru +0 -126
- data/example/async_chat.ru +0 -247
- data/example/async_tailer.ru +0 -100
- data/example/config.ru +0 -22
- data/example/monit_sockets +0 -20
- data/example/monit_unixsock +0 -20
- data/example/myapp.rb +0 -1
- data/example/ramaze.ru +0 -12
- data/example/thin.god +0 -80
- data/example/thin_solaris_smf.erb +0 -36
- data/example/thin_solaris_smf.readme.txt +0 -150
- data/example/vlad.rake +0 -72
- data/ext/thin_parser/common.rl +0 -59
- data/ext/thin_parser/ext_help.h +0 -14
- data/ext/thin_parser/extconf.rb +0 -6
- data/ext/thin_parser/parser.c +0 -1447
- data/ext/thin_parser/parser.h +0 -49
- data/ext/thin_parser/parser.rl +0 -152
- data/ext/thin_parser/thin.c +0 -435
- data/lib/rack/adapter/loader.rb +0 -75
- data/lib/rack/adapter/rails.rb +0 -178
- data/lib/rack/handler/thin.rb +0 -38
- data/lib/thin/backends/base.rb +0 -169
- data/lib/thin/backends/swiftiply_client.rb +0 -56
- data/lib/thin/backends/tcp_server.rb +0 -34
- data/lib/thin/backends/unix_server.rb +0 -56
- data/lib/thin/command.rb +0 -53
- data/lib/thin/controllers/cluster.rb +0 -178
- data/lib/thin/controllers/controller.rb +0 -189
- data/lib/thin/controllers/service.rb +0 -76
- data/lib/thin/controllers/service.sh.erb +0 -39
- data/lib/thin/daemonizing.rb +0 -199
- data/lib/thin/headers.rb +0 -40
- data/lib/thin/logging.rb +0 -174
- data/lib/thin/stats.html.erb +0 -216
- data/lib/thin/stats.rb +0 -52
- data/lib/thin/statuses.rb +0 -48
@@ -0,0 +1,114 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
module Thin
|
4
|
+
# A listener holding a socket and its configuration.
|
5
|
+
class Listener
|
6
|
+
# Hostname the socket will bind to in case of IPv4/6 addresses.
|
7
|
+
attr_reader :host
|
8
|
+
|
9
|
+
# Port the socket will bind to in case of IPv4/6 addresses.
|
10
|
+
attr_reader :port
|
11
|
+
|
12
|
+
# UNIX domain socket the socket will bind to.
|
13
|
+
attr_reader :socket_file
|
14
|
+
|
15
|
+
def initialize(address, options={})
|
16
|
+
case address
|
17
|
+
when Integer
|
18
|
+
@host = ""
|
19
|
+
@port = address
|
20
|
+
when /\A(\/.*)\z/, /\Aunix:(.*)\z/ # /file.sock or unix:file.sock
|
21
|
+
@socket_file = $1
|
22
|
+
when /\A(?:\*:)?(\d+)\z/ # *:port or "port"
|
23
|
+
@host = ""
|
24
|
+
@port = $1.to_i
|
25
|
+
when /\A((?:\d{1,3}\.){3}\d{1,3}):(\d+)\z/ # 0.0.0.0:port
|
26
|
+
@host = $1
|
27
|
+
@port = $2.to_i
|
28
|
+
when /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ # IPV6 address: [::]:port
|
29
|
+
@host = $1
|
30
|
+
@port = $2.to_i
|
31
|
+
else
|
32
|
+
raise ArgumentError, "Invalid address #{address.inspect}. " +
|
33
|
+
"Accepted formats are: 3000, *:3000, 0.0.0.0:3000, [::]:3000, /file.sock or unix:file.sock"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Default values
|
37
|
+
options = {
|
38
|
+
# Same defaults as Unicorn
|
39
|
+
:tcp_no_delay => true,
|
40
|
+
:tcp_no_push => false,
|
41
|
+
:ipv6_only => false,
|
42
|
+
:backlog => 1024
|
43
|
+
}.merge(options)
|
44
|
+
|
45
|
+
@backlog = options[:backlog]
|
46
|
+
self.tcp_no_delay = options[:tcp_nodelay] || options[:tcp_no_delay]
|
47
|
+
self.tcp_no_push = options[:tcp_nopush] || options[:tcp_no_push]
|
48
|
+
self.ipv6_only = options[:ipv6_only]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Creates the socket and binds it to the address.
|
52
|
+
def socket
|
53
|
+
return @socket if @socket
|
54
|
+
|
55
|
+
@socket = Socket.new(socket_family, Socket::SOCK_STREAM, 0)
|
56
|
+
@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
|
57
|
+
|
58
|
+
@socket
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the socket family: Socket::AF_*
|
62
|
+
def socket_family
|
63
|
+
return Socket::AF_UNIX if @socket_file
|
64
|
+
return Socket::AF_INET6 if @host.include?(":")
|
65
|
+
return Socket::AF_INET
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns +true+ if the socket is a UNIX domain one.
|
69
|
+
def unix?
|
70
|
+
socket_family == Socket::AF_UNIX
|
71
|
+
end
|
72
|
+
|
73
|
+
def ipv6_only=(value)
|
74
|
+
socket.ipv6only! if value && !unix?
|
75
|
+
end
|
76
|
+
|
77
|
+
def tcp_no_delay=(value)
|
78
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, value) unless unix?
|
79
|
+
end
|
80
|
+
|
81
|
+
def tcp_no_push=(value)
|
82
|
+
# Taken from Unicorn
|
83
|
+
if defined?(TCP_CORK) # Linux
|
84
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, value)
|
85
|
+
elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is untested (FreeBSD)
|
86
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NOPUSH, value)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def listen
|
91
|
+
delete_socket_file!
|
92
|
+
|
93
|
+
socket.bind unix? ? Socket.pack_sockaddr_un(@socket_file) : # UNIX domain
|
94
|
+
Socket.pack_sockaddr_in(@port, @host) # IPv4/6
|
95
|
+
|
96
|
+
socket.listen(@backlog)
|
97
|
+
end
|
98
|
+
|
99
|
+
def close
|
100
|
+
socket.close if @socket
|
101
|
+
delete_socket_file!
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_s
|
105
|
+
unix? ? @socket_file : "#{@host}:#{@port}"
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
def delete_socket_file!
|
110
|
+
File.delete(@socket_file) if @socket_file && File.socket?(@socket_file)
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
data/lib/thin/request.rb
CHANGED
@@ -1,107 +1,94 @@
|
|
1
|
-
require
|
1
|
+
require "stringio"
|
2
|
+
require "tempfile"
|
2
3
|
|
3
4
|
module Thin
|
4
|
-
# Raised when an incoming request is not valid
|
5
|
-
# and the server can not process it.
|
6
|
-
class InvalidRequest < IOError; end
|
7
|
-
|
8
5
|
# A request sent by the client to the server.
|
9
6
|
class Request
|
10
7
|
# Maximum request body size before it is moved out of memory
|
11
8
|
# and into a tempfile for reading.
|
12
9
|
MAX_BODY = 1024 * (80 + 32)
|
13
10
|
BODY_TMPFILE = 'thin-body'.freeze
|
14
|
-
MAX_HEADER = 1024 * (80 + 32)
|
15
11
|
|
16
|
-
INITIAL_BODY
|
12
|
+
INITIAL_BODY = ''
|
17
13
|
# Force external_encoding of request's body to ASCII_8BIT
|
18
|
-
INITIAL_BODY.encode!(Encoding::ASCII_8BIT) if INITIAL_BODY.respond_to?(:encode!)
|
14
|
+
INITIAL_BODY.encode!(Encoding::ASCII_8BIT) if INITIAL_BODY.respond_to?(:encode!)
|
19
15
|
|
20
16
|
# Freeze some HTTP header names & values
|
21
17
|
SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
|
22
18
|
SERVER_NAME = 'SERVER_NAME'.freeze
|
23
|
-
|
19
|
+
SERVER_PORT = 'SERVER_PORT'.freeze
|
20
|
+
DEFAULT_PORT = '80'.freeze
|
21
|
+
HTTP_HOST = 'HTTP_HOST'.freeze
|
24
22
|
LOCALHOST = 'localhost'.freeze
|
25
23
|
HTTP_VERSION = 'HTTP_VERSION'.freeze
|
24
|
+
SERVER_PROTOCOL = 'SERVER_PROTOCOL'.freeze
|
26
25
|
HTTP_1_0 = 'HTTP/1.0'.freeze
|
27
26
|
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
27
|
+
CONTENT_TYPE = 'CONTENT_TYPE'.freeze
|
28
|
+
CONTENT_TYPE_L = 'Content-Type'.freeze
|
28
29
|
CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
|
29
|
-
|
30
|
+
CONTENT_LENGTH_L = 'Content-Length'.freeze
|
31
|
+
SCRIPT_NAME = 'SCRIPT_NAME'.freeze
|
32
|
+
QUERY_STRING = 'QUERY_STRING'.freeze
|
33
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
34
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
35
|
+
FRAGMENT = 'FRAGMENT'.freeze
|
36
|
+
HTTP = 'http'.freeze
|
37
|
+
EMPTY = ''.freeze
|
30
38
|
KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze
|
31
39
|
CLOSE_REGEXP = /\bclose\b/i.freeze
|
32
|
-
HEAD = 'HEAD'.freeze
|
33
40
|
|
34
41
|
# Freeze some Rack header names
|
35
42
|
RACK_INPUT = 'rack.input'.freeze
|
36
43
|
RACK_VERSION = 'rack.version'.freeze
|
37
44
|
RACK_ERRORS = 'rack.errors'.freeze
|
45
|
+
RACK_URL_SCHEME = 'rack.url_scheme'.freeze
|
38
46
|
RACK_MULTITHREAD = 'rack.multithread'.freeze
|
39
47
|
RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
|
40
48
|
RACK_RUN_ONCE = 'rack.run_once'.freeze
|
41
49
|
ASYNC_CALLBACK = 'async.callback'.freeze
|
42
|
-
ASYNC_CLOSE = 'async.close'.freeze
|
43
50
|
|
44
51
|
# CGI-like request environment variables
|
45
52
|
attr_reader :env
|
46
53
|
|
47
|
-
# Unparsed data of the request
|
48
|
-
attr_reader :data
|
49
|
-
|
50
54
|
# Request body
|
51
55
|
attr_reader :body
|
52
56
|
|
53
57
|
def initialize
|
54
|
-
@
|
55
|
-
@
|
56
|
-
@nparsed = 0
|
57
|
-
@body = StringIO.new(INITIAL_BODY.dup)
|
58
|
-
@env = {
|
58
|
+
@body = StringIO.new(INITIAL_BODY)
|
59
|
+
@env = {
|
59
60
|
SERVER_SOFTWARE => SERVER,
|
60
61
|
SERVER_NAME => LOCALHOST,
|
62
|
+
SCRIPT_NAME => EMPTY,
|
61
63
|
|
62
64
|
# Rack stuff
|
63
65
|
RACK_INPUT => @body,
|
66
|
+
RACK_URL_SCHEME => HTTP,
|
64
67
|
|
65
68
|
RACK_VERSION => VERSION::RACK,
|
66
|
-
RACK_ERRORS =>
|
69
|
+
RACK_ERRORS => $stderr,
|
67
70
|
|
68
|
-
RACK_MULTITHREAD => false,
|
69
|
-
RACK_MULTIPROCESS => false,
|
70
71
|
RACK_RUN_ONCE => false
|
71
72
|
}
|
73
|
+
@keep_alive = false
|
72
74
|
end
|
73
75
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
@nparsed = @parser.execute(@env, @data, @nparsed)
|
87
|
-
|
88
|
-
# Transfer to a tempfile if body is very big
|
89
|
-
move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
|
76
|
+
def headers=(headers)
|
77
|
+
# TODO benchmark & optimize
|
78
|
+
headers.each_pair do |k, v|
|
79
|
+
# Convert to Rack headers
|
80
|
+
if k == CONTENT_TYPE_L
|
81
|
+
@env[CONTENT_TYPE] = v
|
82
|
+
elsif k == CONTENT_LENGTH_L
|
83
|
+
@env[CONTENT_LENGTH] = v
|
84
|
+
else
|
85
|
+
@env["HTTP_" + k.upcase.tr("-", "_")] = v
|
86
|
+
end
|
90
87
|
end
|
91
88
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
@body.rewind
|
96
|
-
true # Request is fully parsed
|
97
|
-
else
|
98
|
-
false # Not finished, need more data
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# +true+ if headers and body are finished parsing
|
103
|
-
def finished?
|
104
|
-
@parser.finished? && @body.size >= content_length
|
89
|
+
host, port = @env[HTTP_HOST].split(":") if @env.key?(HTTP_HOST)
|
90
|
+
@env[SERVER_NAME] = host || LOCALHOST
|
91
|
+
@env[SERVER_PORT] = port || DEFAULT_PORT
|
105
92
|
end
|
106
93
|
|
107
94
|
# Expected size of the body
|
@@ -109,46 +96,77 @@ module Thin
|
|
109
96
|
@env[CONTENT_LENGTH].to_i
|
110
97
|
end
|
111
98
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
# is maintained for HTTP versions less than 1.1 unless it is explicitly
|
116
|
-
# signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
|
117
|
-
if @env[HTTP_VERSION] == HTTP_1_0
|
118
|
-
@env[CONNECTION] =~ KEEP_ALIVE_REGEXP
|
99
|
+
def remote_address=(address)
|
100
|
+
@env[REMOTE_ADDR] = address
|
101
|
+
end
|
119
102
|
|
120
|
-
|
121
|
-
|
122
|
-
# in the request
|
123
|
-
else
|
124
|
-
@env[CONNECTION].nil? || @env[CONNECTION] !~ CLOSE_REGEXP
|
125
|
-
end
|
103
|
+
def method=(method)
|
104
|
+
@env[REQUEST_METHOD] = method
|
126
105
|
end
|
127
106
|
|
128
|
-
def
|
129
|
-
@env[
|
107
|
+
def http_version=(string)
|
108
|
+
@env[HTTP_VERSION] = string
|
109
|
+
@env[SERVER_PROTOCOL] = string
|
110
|
+
end
|
111
|
+
|
112
|
+
def http_version
|
113
|
+
@env[HTTP_VERSION]
|
114
|
+
end
|
115
|
+
|
116
|
+
def path=(path)
|
117
|
+
@env[PATH_INFO] = path
|
130
118
|
end
|
131
119
|
|
132
|
-
def
|
133
|
-
@env[
|
120
|
+
def query_string=(string)
|
121
|
+
@env[QUERY_STRING] = string
|
122
|
+
end
|
123
|
+
|
124
|
+
def fragment=(string)
|
125
|
+
@env[FRAGMENT] = string
|
126
|
+
end
|
127
|
+
|
128
|
+
def keep_alive=(bool)
|
129
|
+
@keep_alive = bool
|
130
|
+
end
|
131
|
+
|
132
|
+
def multithread=(bool)
|
133
|
+
@env[RACK_MULTITHREAD] = bool
|
134
|
+
end
|
135
|
+
|
136
|
+
def multiprocess=(bool)
|
137
|
+
@env[RACK_MULTIPROCESS] = bool
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns +true+ if the client expect the connection to be kept alive.
|
141
|
+
def keep_alive?
|
142
|
+
@keep_alive
|
143
|
+
end
|
144
|
+
|
145
|
+
def <<(data)
|
146
|
+
@body << data
|
147
|
+
|
148
|
+
# Transfert to a tempfile if body is very big.
|
149
|
+
move_body_to_tempfile if content_length > MAX_BODY
|
150
|
+
|
151
|
+
@body
|
134
152
|
end
|
135
153
|
|
136
154
|
def async_callback=(callback)
|
137
155
|
@env[ASYNC_CALLBACK] = callback
|
138
|
-
@env[ASYNC_CLOSE] = EventMachine::DefaultDeferrable.new
|
139
156
|
end
|
140
|
-
|
141
|
-
def
|
142
|
-
@
|
157
|
+
|
158
|
+
def support_encoding_chunked?
|
159
|
+
@env[HTTP_VERSION] != HTTP_1_0
|
143
160
|
end
|
144
161
|
|
145
|
-
|
146
|
-
|
162
|
+
# Called when we're done processing the request.
|
163
|
+
def finish
|
164
|
+
@body.rewind
|
147
165
|
end
|
148
166
|
|
149
167
|
# Close any resource used by the request
|
150
168
|
def close
|
151
|
-
@body.
|
169
|
+
@body.delete if @body.class == Tempfile
|
152
170
|
end
|
153
171
|
|
154
172
|
private
|
data/lib/thin/response.rb
CHANGED
@@ -1,18 +1,56 @@
|
|
1
|
+
require "rack"
|
2
|
+
|
1
3
|
module Thin
|
2
4
|
# A response sent to the client.
|
3
5
|
class Response
|
6
|
+
# Template async response.
|
7
|
+
ASYNC = [-1, {}, []].freeze
|
8
|
+
|
9
|
+
# Store HTTP header name-value pairs direcly to a string
|
10
|
+
# and allow duplicated entries on some names.
|
11
|
+
class Headers
|
12
|
+
HEADER_FORMAT = "%s: %s\r\n".freeze
|
13
|
+
ALLOWED_DUPLICATES = %w(Set-Cookie Set-Cookie2 Warning WWW-Authenticate).freeze
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@sent = {}
|
17
|
+
@out = []
|
18
|
+
end
|
19
|
+
|
20
|
+
# Add <tt>key: value</tt> pair to the headers.
|
21
|
+
# Ignore if already sent and no duplicates are allowed
|
22
|
+
# for this +key+.
|
23
|
+
def []=(key, value)
|
24
|
+
if !@sent.has_key?(key) || ALLOWED_DUPLICATES.include?(key)
|
25
|
+
@sent[key] = true
|
26
|
+
value = case value
|
27
|
+
when Time
|
28
|
+
value.httpdate
|
29
|
+
when NilClass
|
30
|
+
return
|
31
|
+
else
|
32
|
+
value.to_s
|
33
|
+
end
|
34
|
+
@out << HEADER_FORMAT % [key, value]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_key?(key)
|
39
|
+
@sent[key]
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
@out.join
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
4
47
|
CONNECTION = 'Connection'.freeze
|
5
48
|
CLOSE = 'close'.freeze
|
6
49
|
KEEP_ALIVE = 'keep-alive'.freeze
|
7
50
|
SERVER = 'Server'.freeze
|
8
51
|
CONTENT_LENGTH = 'Content-Length'.freeze
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
#Error Responses
|
13
|
-
ERROR = [500, {'Content-Type' => 'text/plain'}, ['Internal server error']].freeze
|
14
|
-
PERSISTENT_ERROR = [500, {'Content-Type' => 'text/plain', 'Connection' => 'keep-alive', 'Content-Length' => "21"}, ['Internal server error']].freeze
|
15
|
-
BAD_REQUEST = [400, {'Content-Type' => 'text/plain'}, ['Bad Request']].freeze
|
52
|
+
|
53
|
+
KEEP_ALIVE_STATUSES = [100, 101].freeze
|
16
54
|
|
17
55
|
# Status code
|
18
56
|
attr_accessor :status
|
@@ -21,32 +59,21 @@ module Thin
|
|
21
59
|
attr_accessor :body
|
22
60
|
|
23
61
|
# Headers key-value hash
|
24
|
-
attr_reader
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
@
|
30
|
-
@
|
62
|
+
attr_reader :headers
|
63
|
+
|
64
|
+
attr_reader :http_version
|
65
|
+
|
66
|
+
def initialize(status=200, headers=nil, body=nil)
|
67
|
+
@headers = Headers.new
|
68
|
+
@status = status
|
69
|
+
@keep_alive = false
|
70
|
+
@body = body
|
71
|
+
@http_version = "HTTP/1.1"
|
72
|
+
|
73
|
+
self.headers = headers if headers
|
31
74
|
end
|
32
75
|
|
33
|
-
|
34
|
-
# to be sent in the response.
|
35
|
-
def headers_output
|
36
|
-
# Set default headers
|
37
|
-
@headers[CONNECTION] = persistent? ? KEEP_ALIVE : CLOSE unless @headers.has_key?(CONNECTION)
|
38
|
-
@headers[SERVER] = Thin::NAME unless @headers.has_key?(SERVER)
|
39
|
-
|
40
|
-
@headers.to_s
|
41
|
-
end
|
42
|
-
|
43
|
-
# Top header of the response,
|
44
|
-
# containing the status code and response headers.
|
45
|
-
def head
|
46
|
-
"HTTP/1.1 #{@status} #{HTTP_STATUS_CODES[@status.to_i]}\r\n#{headers_output}\r\n"
|
47
|
-
end
|
48
|
-
|
49
|
-
if Thin.ruby_18?
|
76
|
+
if System.ruby_18?
|
50
77
|
|
51
78
|
# Ruby 1.8 implementation.
|
52
79
|
# Respects Rack specs.
|
@@ -78,8 +105,22 @@ module Thin
|
|
78
105
|
|
79
106
|
end
|
80
107
|
|
108
|
+
# Finish preparing the response.
|
109
|
+
def finish
|
110
|
+
@headers[CONNECTION] = keep_alive? ? KEEP_ALIVE : CLOSE
|
111
|
+
@headers[SERVER] = Thin::SERVER
|
112
|
+
end
|
113
|
+
|
114
|
+
# Top header of the response,
|
115
|
+
# containing the status code and response headers.
|
116
|
+
def head
|
117
|
+
status_message = Rack::Utils::HTTP_STATUS_CODES[@status.to_i]
|
118
|
+
"#{@http_version} #{@status} #{status_message}\r\n#{@headers.to_s}\r\n"
|
119
|
+
end
|
120
|
+
|
81
121
|
# Close any resource used by the response
|
82
122
|
def close
|
123
|
+
@body.fail if @body.respond_to?(:fail)
|
83
124
|
@body.close if @body.respond_to?(:close)
|
84
125
|
end
|
85
126
|
|
@@ -88,30 +129,56 @@ module Thin
|
|
88
129
|
# define your own +each+ method on +body+.
|
89
130
|
def each
|
90
131
|
yield head
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
else
|
96
|
-
@body.each { |chunk| yield chunk }
|
97
|
-
end
|
132
|
+
if @body.is_a?(String)
|
133
|
+
yield @body
|
134
|
+
else
|
135
|
+
@body.each { |chunk| yield chunk }
|
98
136
|
end
|
99
137
|
end
|
100
|
-
|
138
|
+
|
101
139
|
# Tell the client the connection should stay open
|
102
|
-
def
|
103
|
-
@
|
140
|
+
def keep_alive!
|
141
|
+
@keep_alive = true
|
104
142
|
end
|
105
143
|
|
106
144
|
# Persistent connection must be requested as keep-alive
|
107
145
|
# from the server and have a Content-Length, or the response
|
108
146
|
# status must require that the connection remain open.
|
109
|
-
def
|
110
|
-
(@
|
147
|
+
def keep_alive?
|
148
|
+
(@keep_alive && @headers.has_key?(CONTENT_LENGTH)) || KEEP_ALIVE_STATUSES.include?(@status)
|
149
|
+
end
|
150
|
+
|
151
|
+
def async?
|
152
|
+
@status == ASYNC.first
|
153
|
+
end
|
154
|
+
|
155
|
+
def file?
|
156
|
+
@body.respond_to?(:to_path)
|
157
|
+
end
|
158
|
+
|
159
|
+
def filename
|
160
|
+
@body.to_path
|
161
|
+
end
|
162
|
+
|
163
|
+
def body_callback=(proc)
|
164
|
+
@body.callback(&proc) if @body.respond_to?(:callback)
|
165
|
+
@body.errback(&proc) if @body.respond_to?(:errback)
|
166
|
+
end
|
167
|
+
|
168
|
+
def chunked_encoding!
|
169
|
+
@headers['Transfer-Encoding'] = 'chunked'
|
170
|
+
end
|
171
|
+
|
172
|
+
def http_version=(string)
|
173
|
+
return unless string && string == "HTTP/1.1" || string == "HTTP/1.0"
|
174
|
+
@http_version = string
|
111
175
|
end
|
112
176
|
|
113
|
-
def
|
114
|
-
|
177
|
+
def self.error(status=500, message=Rack::Utils::HTTP_STATUS_CODES[status])
|
178
|
+
new status,
|
179
|
+
{ "Content-Type" => "text/plain",
|
180
|
+
"Content-Length" => Rack::Utils.bytesize(message).to_s },
|
181
|
+
[message]
|
115
182
|
end
|
116
183
|
end
|
117
184
|
end
|