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
data/lib/thin/connection.rb
CHANGED
@@ -1,215 +1,289 @@
|
|
1
|
-
require
|
1
|
+
require "rack"
|
2
|
+
require "http/parser"
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
# that is opened.
|
7
|
-
class Connection < EventMachine::Connection
|
8
|
-
include Logging
|
9
|
-
|
10
|
-
# This is a template async response. N.B. Can't use string for body on 1.9
|
11
|
-
AsyncResponse = [-1, {}, []].freeze
|
12
|
-
|
13
|
-
# Rack application (adapter) served by this connection.
|
14
|
-
attr_accessor :app
|
15
|
-
|
16
|
-
# Backend to the server
|
17
|
-
attr_accessor :backend
|
18
|
-
|
19
|
-
# Current request served by the connection
|
20
|
-
attr_accessor :request
|
4
|
+
require "thin/request"
|
5
|
+
require "thin/response"
|
6
|
+
require "thin/chunked_body"
|
21
7
|
|
22
|
-
|
23
|
-
|
8
|
+
module Thin
|
9
|
+
# EventMachine connection.
|
10
|
+
# Supports:
|
11
|
+
# * Rack specifications v1.1: http://rack.rubyforge.org/doc/SPEC.html
|
12
|
+
# * Asynchronous responses with chunked encoding, via the <tt>env['async.callback']</tt> or <tt>throw :async</tt>.
|
13
|
+
# * Keep-alive.
|
14
|
+
# * File streaming.
|
15
|
+
# * Calling the Rack app from pooled threads.
|
16
|
+
class Connection < EM::Connection
|
17
|
+
attr_accessor :server
|
18
|
+
attr_accessor :listener
|
19
|
+
attr_accessor :can_keep_alive
|
20
|
+
|
21
|
+
# For tests
|
22
|
+
attr_reader :request, :response
|
23
|
+
|
24
|
+
|
25
|
+
def on_close(&block)
|
26
|
+
@on_close = block
|
27
|
+
end
|
28
|
+
|
24
29
|
|
25
|
-
#
|
26
|
-
# concurrent processing of requests.
|
27
|
-
attr_writer :threaded
|
30
|
+
# == EM callback methods
|
28
31
|
|
29
32
|
# Get the connection ready to process a request.
|
30
33
|
def post_init
|
31
|
-
@
|
32
|
-
@response = Response.new
|
34
|
+
@parser = HTTP::Parser.new(self)
|
33
35
|
end
|
34
36
|
|
35
37
|
# Called when data is received from the client.
|
36
38
|
def receive_data(data)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
post_process Response::BAD_REQUEST
|
43
|
-
end
|
44
|
-
|
45
|
-
# Called when all data was received and the request
|
46
|
-
# is ready to be processed.
|
47
|
-
def process
|
48
|
-
if threaded?
|
49
|
-
@request.threaded = true
|
50
|
-
EventMachine.defer { post_process(pre_process) }
|
51
|
-
else
|
52
|
-
@request.threaded = false
|
53
|
-
post_process(pre_process)
|
54
|
-
end
|
39
|
+
puts data if $DEBUG
|
40
|
+
@parser << data
|
41
|
+
rescue HTTP::Parser::Error => e
|
42
|
+
$stderr.puts "Parse error: #{e}"
|
43
|
+
send_response_and_reset Response.error(400) # Bad Request
|
55
44
|
end
|
56
45
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
46
|
+
# Called when the connection is unbinded from the socket
|
47
|
+
# and can no longer be used to process requests.
|
48
|
+
def unbind
|
49
|
+
close_request_and_response
|
50
|
+
@on_close.call if @on_close
|
61
51
|
end
|
62
52
|
|
63
|
-
def pre_process
|
64
|
-
# Add client info to the request env
|
65
|
-
@request.remote_address = remote_address
|
66
53
|
|
67
|
-
|
68
|
-
# It should be noted that connection objects will linger until this
|
69
|
-
# callback is no longer referenced, so be tidy!
|
70
|
-
@request.async_callback = method(:post_process)
|
71
|
-
|
72
|
-
if @backend.ssl?
|
73
|
-
@request.env["rack.url_scheme"] = "https"
|
54
|
+
# == Parser callback methods
|
74
55
|
|
75
|
-
|
76
|
-
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# When we're under a non-async framework like rails, we can still spawn
|
81
|
-
# off async responses using the callback info, so there's little point
|
82
|
-
# in removing this.
|
83
|
-
response = AsyncResponse
|
84
|
-
catch(:async) do
|
85
|
-
# Process the request calling the Rack adapter
|
86
|
-
response = @app.call(@request.env)
|
87
|
-
end
|
88
|
-
response
|
89
|
-
rescue Exception => e
|
90
|
-
unexpected_error(e)
|
91
|
-
# Pass through error response
|
92
|
-
can_persist? && @request.persistent? ? Response::PERSISTENT_ERROR : Response::ERROR
|
56
|
+
def on_message_begin
|
57
|
+
@request = Request.new
|
93
58
|
end
|
94
59
|
|
95
|
-
def
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
@
|
103
|
-
|
104
|
-
|
105
|
-
|
60
|
+
def on_headers_complete(headers)
|
61
|
+
@request.multithread = server.threaded?
|
62
|
+
@request.multiprocess = server.prefork?
|
63
|
+
@request.remote_address = socket_address
|
64
|
+
@request.http_version = "HTTP/%d.%d" % @parser.http_version
|
65
|
+
@request.method = @parser.http_method
|
66
|
+
@request.path = @parser.request_path
|
67
|
+
@request.fragment = @parser.fragment
|
68
|
+
@request.query_string = @parser.query_string
|
69
|
+
@request.keep_alive = @parser.keep_alive?
|
70
|
+
@request.headers = headers
|
71
|
+
end
|
106
72
|
|
107
|
-
|
108
|
-
@
|
73
|
+
def on_body(chunk)
|
74
|
+
@request << chunk
|
75
|
+
end
|
109
76
|
|
110
|
-
|
111
|
-
@
|
77
|
+
def on_message_complete
|
78
|
+
@request.finish
|
79
|
+
process
|
80
|
+
end
|
112
81
|
|
113
|
-
# Send the response
|
114
|
-
@response.each do |chunk|
|
115
|
-
trace chunk
|
116
|
-
send_data chunk
|
117
|
-
end
|
118
82
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
if @response.body.respond_to?(:callback) && @response.body.respond_to?(:errback)
|
126
|
-
@response.body.callback { terminate_request }
|
127
|
-
@response.body.errback { terminate_request }
|
83
|
+
# == Request processing methods
|
84
|
+
|
85
|
+
# Starts the processing of the current request in <tt>@request</tt>.
|
86
|
+
def process
|
87
|
+
if server.threaded?
|
88
|
+
EM.defer(method(:call_app), method(:process_response))
|
128
89
|
else
|
129
|
-
|
130
|
-
|
90
|
+
if response = call_app
|
91
|
+
process_response(response)
|
92
|
+
end
|
131
93
|
end
|
132
94
|
end
|
133
95
|
|
134
|
-
#
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
@
|
143
|
-
end
|
96
|
+
# Calls the Rack app in <tt>server.app</tt>.
|
97
|
+
# Returns a Rack response: <tt>[status, {headers}, [body]]</tt>
|
98
|
+
# or +nil+ if there was an error.
|
99
|
+
# The app can return [-1, ...] or throw :async to short-circuit request processing.
|
100
|
+
def call_app
|
101
|
+
# Connection may be closed unless the App#call response was a [-1, ...]
|
102
|
+
# It should be noted that connection objects will linger until this
|
103
|
+
# callback is no longer referenced, so be tidy!
|
104
|
+
@request.async_callback = method(:process_async_response)
|
144
105
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
def terminate_request
|
150
|
-
unless persistent?
|
151
|
-
close_connection_after_writing rescue nil
|
152
|
-
close_request_response
|
153
|
-
else
|
154
|
-
close_request_response
|
155
|
-
# Connection become idle but it's still open
|
156
|
-
@idle = true
|
157
|
-
# Prepare the connection for another request if the client
|
158
|
-
# supports HTTP pipelining (persistent connection).
|
159
|
-
post_init
|
106
|
+
# Call the Rack application
|
107
|
+
response = Response::ASYNC # `throw :async` will result in this response
|
108
|
+
catch(:async) do
|
109
|
+
response = @server.app.call(@request.env)
|
160
110
|
end
|
161
|
-
end
|
162
111
|
|
163
|
-
|
164
|
-
# and can no longer be used to process requests.
|
165
|
-
def unbind
|
166
|
-
@request.async_close.succeed if @request.async_close
|
167
|
-
@response.body.fail if @response.body.respond_to?(:fail)
|
168
|
-
@backend.connection_finished(self)
|
169
|
-
end
|
112
|
+
response
|
170
113
|
|
171
|
-
|
172
|
-
|
173
|
-
|
114
|
+
rescue Exception
|
115
|
+
handle_error
|
116
|
+
nil # Signals that the request could not be processed
|
174
117
|
end
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
118
|
+
|
119
|
+
def prepare_response(response)
|
120
|
+
return unless response
|
121
|
+
|
122
|
+
Response.new(*response)
|
179
123
|
end
|
180
124
|
|
181
|
-
#
|
182
|
-
|
183
|
-
|
184
|
-
|
125
|
+
# Process the response returns by +call_app+.
|
126
|
+
def process_response(response)
|
127
|
+
@response = prepare_response(response)
|
128
|
+
|
129
|
+
# We're going to respond later (async).
|
130
|
+
return if @response.async?
|
131
|
+
|
132
|
+
# Close the resources used by the request as soon as possible.
|
133
|
+
@request.close
|
134
|
+
|
135
|
+
# Send the response.
|
136
|
+
send_response_and_reset
|
137
|
+
|
138
|
+
rescue Exception
|
139
|
+
handle_error
|
185
140
|
end
|
186
|
-
|
187
|
-
#
|
188
|
-
#
|
189
|
-
|
190
|
-
|
141
|
+
|
142
|
+
# Process the response sent asynchronously via <tt>body.call</tt>.
|
143
|
+
# The response will automatically be send using chunked encoding under
|
144
|
+
# HTTP 1.1 protocol.
|
145
|
+
def process_async_response(response)
|
146
|
+
@response = prepare_response(response)
|
147
|
+
|
148
|
+
# Terminate the connection on callback from the response's body.
|
149
|
+
@response.body_callback = method(:terminate_async_response)
|
150
|
+
|
151
|
+
# Use chunked encoding if available.
|
152
|
+
if @request.support_encoding_chunked?
|
153
|
+
@response.chunked_encoding!
|
154
|
+
@response.body = ChunkedBody.new(@response.body)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Send the response.
|
158
|
+
send_response
|
159
|
+
|
160
|
+
rescue Exception
|
161
|
+
handle_error
|
191
162
|
end
|
192
|
-
|
193
|
-
#
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
163
|
+
|
164
|
+
# Called after an asynchronous response is done sending the body.
|
165
|
+
def terminate_async_response
|
166
|
+
if @request.support_encoding_chunked?
|
167
|
+
# Send tail chunk. 0 length signals we're done w/ HTTP chunked encoding.
|
168
|
+
send_chunk ChunkedBody::TAIL
|
169
|
+
end
|
170
|
+
|
171
|
+
reset
|
172
|
+
|
173
|
+
rescue Exception
|
174
|
+
handle_error
|
198
175
|
end
|
199
|
-
|
200
|
-
#
|
201
|
-
|
202
|
-
|
176
|
+
|
177
|
+
# Reset the connection and prepare for another request if keep-alive is
|
178
|
+
# requested.
|
179
|
+
# Else, closes the connection.
|
180
|
+
def reset
|
181
|
+
if @response && @response.keep_alive?
|
182
|
+
# Prepare the connection for another request if the client
|
183
|
+
# requested a persistent connection (keep-alive).
|
184
|
+
post_init
|
185
|
+
else
|
186
|
+
close_connection_after_writing
|
187
|
+
end
|
188
|
+
|
189
|
+
close_request_and_response
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
# == Response sending methods
|
194
|
+
|
195
|
+
# Send the HTTP response back to the client.
|
196
|
+
def send_response(response=@response)
|
197
|
+
@response = response
|
198
|
+
|
199
|
+
if @request
|
200
|
+
# Keep connection alive if requested by the client.
|
201
|
+
@response.keep_alive! if @can_keep_alive && @request.keep_alive?
|
202
|
+
@response.http_version = @request.http_version
|
203
|
+
end
|
204
|
+
|
205
|
+
# Prepare the response for sending.
|
206
|
+
@response.finish
|
207
|
+
|
208
|
+
if @response.file?
|
209
|
+
send_file
|
210
|
+
return
|
211
|
+
end
|
212
|
+
|
213
|
+
@response.each(&method(:send_chunk))
|
214
|
+
puts if $DEBUG
|
215
|
+
|
203
216
|
rescue Exception => e
|
204
|
-
|
205
|
-
|
217
|
+
# In case there's an error sending the response, we give up and just
|
218
|
+
# close the connection to prevent recursion and consuming too much
|
219
|
+
# resources.
|
220
|
+
$stderr.puts "Error sending response: #{e}"
|
221
|
+
close_connection
|
206
222
|
end
|
207
|
-
|
208
|
-
|
223
|
+
|
224
|
+
def send_response_and_reset(response=@response)
|
225
|
+
send_response(response)
|
226
|
+
reset
|
227
|
+
end
|
228
|
+
|
229
|
+
# Sending a file using EM streaming and HTTP 1.1 style chunked-encoding if
|
230
|
+
# supported by the client.
|
231
|
+
def send_file
|
232
|
+
# Use HTTP 1.1 style chunked-encoding to send the file if supported
|
233
|
+
if @request.support_encoding_chunked?
|
234
|
+
@response.chunked_encoding!
|
235
|
+
send_chunk @response.head
|
236
|
+
deferrable = stream_file_data @response.filename, :http_chunks => true
|
237
|
+
else
|
238
|
+
send_chunk @response.head
|
239
|
+
deferrable = stream_file_data @response.filename
|
240
|
+
end
|
241
|
+
|
242
|
+
if $DEBUG
|
243
|
+
puts "<Serving file #{@response.filename} with streaming ...>"
|
244
|
+
puts
|
245
|
+
end
|
246
|
+
|
247
|
+
deferrable.callback(&method(:reset))
|
248
|
+
deferrable.errback(&method(:reset))
|
249
|
+
end
|
250
|
+
|
251
|
+
def send_chunk(data)
|
252
|
+
print data if $DEBUG
|
253
|
+
send_data data
|
254
|
+
end
|
255
|
+
|
256
|
+
private
|
257
|
+
# == Support methods
|
258
|
+
|
259
|
+
def close_request_and_response
|
260
|
+
if @request
|
261
|
+
@request.close
|
262
|
+
@request = nil
|
263
|
+
end
|
264
|
+
if @response
|
265
|
+
@response.close
|
266
|
+
@response = nil
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
209
270
|
# Returns IP address of peer as a string.
|
210
271
|
def socket_address
|
211
|
-
|
212
|
-
|
272
|
+
if listener.unix?
|
273
|
+
""
|
274
|
+
else
|
275
|
+
Socket.unpack_sockaddr_in(get_peername)[1]
|
276
|
+
end
|
277
|
+
rescue Exception => e
|
278
|
+
$stderr.puts "Can't get socket address: #{e}"
|
279
|
+
""
|
280
|
+
end
|
281
|
+
|
282
|
+
# Output the error to stderr and sends back a 500 error.
|
283
|
+
def handle_error(e=$!)
|
284
|
+
$stderr.puts "[ERROR] #{e}"
|
285
|
+
$stderr.puts "\t" + e.backtrace.join("\n\t") if $DEBUG
|
286
|
+
send_response_and_reset Response.error(500) # Internal Server Error
|
213
287
|
end
|
214
288
|
end
|
215
289
|
end
|