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.

Files changed (108) hide show
  1. data/.gitignore +9 -0
  2. data/CHANGELOG +29 -107
  3. data/Gemfile +8 -0
  4. data/README.md +44 -78
  5. data/Rakefile +28 -18
  6. data/bin/thin +4 -4
  7. data/examples/async.ru +21 -0
  8. data/examples/thin.conf.rb +39 -0
  9. data/lib/thin.rb +2 -44
  10. data/lib/thin/async.rb +108 -0
  11. data/lib/thin/backends/prefork.rb +44 -0
  12. data/lib/thin/backends/single_process.rb +28 -0
  13. data/lib/thin/chunked_body.rb +28 -0
  14. data/lib/thin/configurator.rb +118 -0
  15. data/lib/thin/connection.rb +246 -172
  16. data/lib/thin/listener.rb +114 -0
  17. data/lib/thin/request.rb +94 -76
  18. data/lib/thin/response.rb +112 -45
  19. data/lib/thin/runner.rb +134 -197
  20. data/lib/thin/server.rb +203 -252
  21. data/lib/thin/system.rb +49 -0
  22. data/lib/thin/version.rb +11 -26
  23. data/man/index.txt +3 -0
  24. data/man/thin-conf.5.ronn +121 -0
  25. data/man/thin.1.ronn +105 -0
  26. data/site/.gitignore +2 -0
  27. data/site/README.md +21 -0
  28. data/site/Rakefile +20 -0
  29. data/site/config.ru +4 -0
  30. data/site/public/images/grid.png +0 -0
  31. data/site/public/javascripts/dd_belatedpng.js +13 -0
  32. data/site/public/javascripts/modernizr-1.6.min.js +30 -0
  33. data/site/public/man/thin-conf.5.html +220 -0
  34. data/site/public/man/thin.1.html +177 -0
  35. data/site/site/assets/javascripts/main.coffee +2 -0
  36. data/site/site/assets/stylesheets/_config.scss +55 -0
  37. data/site/site/assets/stylesheets/main.scss +24 -0
  38. data/site/site/helpers.rb +17 -0
  39. data/site/site/layouts/base.erb +55 -0
  40. data/site/site/layouts/default.erb +17 -0
  41. data/site/site/pages/about.md +5 -0
  42. data/site/site/pages/index.erb +10 -0
  43. data/site/site/partials/.gitkeep +0 -0
  44. data/test/fixtures/big.txt +1 -0
  45. data/test/fixtures/small.txt +1 -0
  46. data/test/fixtures/thin.conf.rb +15 -0
  47. data/test/integration/async_test.rb +35 -0
  48. data/test/integration/big_request_test.rb +30 -0
  49. data/test/integration/config.ru +57 -0
  50. data/test/integration/daemonize_test.rb +26 -0
  51. data/test/integration/env_test.rb +44 -0
  52. data/test/integration/error_test.rb +37 -0
  53. data/test/integration/file_sending_test.rb +24 -0
  54. data/test/integration/keep_alive_test.rb +35 -0
  55. data/test/integration/robustness_test.rb +37 -0
  56. data/test/integration/single_process_test.rb +15 -0
  57. data/test/integration/socket_family_test.rb +38 -0
  58. data/test/integration/worker_test.rb +22 -0
  59. data/test/test_helper.rb +195 -0
  60. data/test/unit/configurator_test.rb +43 -0
  61. data/test/unit/connection_test.rb +94 -0
  62. data/test/unit/listener_test.rb +74 -0
  63. data/test/unit/request_test.rb +74 -0
  64. data/test/unit/response_test.rb +90 -0
  65. data/test/unit/server_test.rb +29 -0
  66. data/test/unit/system_test.rb +17 -0
  67. data/thin.gemspec +26 -0
  68. data/v2.todo +21 -0
  69. metadata +138 -93
  70. checksums.yaml +0 -7
  71. data/example/adapter.rb +0 -32
  72. data/example/async_app.ru +0 -126
  73. data/example/async_chat.ru +0 -247
  74. data/example/async_tailer.ru +0 -100
  75. data/example/config.ru +0 -22
  76. data/example/monit_sockets +0 -20
  77. data/example/monit_unixsock +0 -20
  78. data/example/myapp.rb +0 -1
  79. data/example/ramaze.ru +0 -12
  80. data/example/thin.god +0 -80
  81. data/example/thin_solaris_smf.erb +0 -36
  82. data/example/thin_solaris_smf.readme.txt +0 -150
  83. data/example/vlad.rake +0 -72
  84. data/ext/thin_parser/common.rl +0 -59
  85. data/ext/thin_parser/ext_help.h +0 -14
  86. data/ext/thin_parser/extconf.rb +0 -6
  87. data/ext/thin_parser/parser.c +0 -1447
  88. data/ext/thin_parser/parser.h +0 -49
  89. data/ext/thin_parser/parser.rl +0 -152
  90. data/ext/thin_parser/thin.c +0 -435
  91. data/lib/rack/adapter/loader.rb +0 -75
  92. data/lib/rack/adapter/rails.rb +0 -178
  93. data/lib/rack/handler/thin.rb +0 -38
  94. data/lib/thin/backends/base.rb +0 -169
  95. data/lib/thin/backends/swiftiply_client.rb +0 -56
  96. data/lib/thin/backends/tcp_server.rb +0 -34
  97. data/lib/thin/backends/unix_server.rb +0 -56
  98. data/lib/thin/command.rb +0 -53
  99. data/lib/thin/controllers/cluster.rb +0 -178
  100. data/lib/thin/controllers/controller.rb +0 -189
  101. data/lib/thin/controllers/service.rb +0 -76
  102. data/lib/thin/controllers/service.sh.erb +0 -39
  103. data/lib/thin/daemonizing.rb +0 -199
  104. data/lib/thin/headers.rb +0 -40
  105. data/lib/thin/logging.rb +0 -174
  106. data/lib/thin/stats.html.erb +0 -216
  107. data/lib/thin/stats.rb +0 -52
  108. data/lib/thin/statuses.rb +0 -48
@@ -1,215 +1,289 @@
1
- require 'socket'
1
+ require "rack"
2
+ require "http/parser"
2
3
 
3
- module Thin
4
- # Connection between the server and client.
5
- # This class is instanciated by EventMachine on each new connection
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
- # Next response sent through the connection
23
- attr_accessor :response
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
- # Calling the application in a threaded allowing
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
- @request = Request.new
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
- @idle = false
38
- trace data
39
- process if @request.parse(data)
40
- rescue InvalidRequest => e
41
- log_error("Invalid request", e)
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
- def ssl_verify_peer(cert)
58
- # In order to make the cert available later we have to have made at least
59
- # a show of verifying it.
60
- true
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
- # Connection may be closed unless the App#call response was a [-1, ...]
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
- if cert = get_peer_cert
76
- @request.env['rack.peer_cert'] = cert
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 post_process(result)
96
- return unless result
97
- result = result.to_a
98
-
99
- # Status code -1 indicates that we're going to respond later (async).
100
- return if result.first == AsyncResponse.first
101
-
102
- @response.status, @response.headers, @response.body = *result
103
-
104
- log_error("Rack application returned nil body. " \
105
- "Probably you wanted it to be an empty string?") if @response.body.nil?
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
- # HEAD requests should not return a body.
108
- @response.skip_body! if @request.head?
73
+ def on_body(chunk)
74
+ @request << chunk
75
+ end
109
76
 
110
- # Make the response persistent if requested by the client
111
- @response.persistent! if @request.persistent?
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
- rescue Exception => e
120
- unexpected_error(e)
121
- # Close connection since we can't handle response gracefully
122
- close_connection
123
- ensure
124
- # If the body is being deferred, then terminate afterward.
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
- # Don't terminate the response if we're going async.
130
- terminate_request unless result && result.first == AsyncResponse.first
90
+ if response = call_app
91
+ process_response(response)
92
+ end
131
93
  end
132
94
  end
133
95
 
134
- # Logs information about an unexpected exceptional condition
135
- def unexpected_error(e)
136
- log_error("Unexpected error while processing request", e)
137
- end
138
-
139
- def close_request_response
140
- @request.async_close.succeed if @request.async_close
141
- @request.close rescue nil
142
- @response.close rescue nil
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
- # Does request and response cleanup (closes open IO streams and
146
- # deletes created temporary files).
147
- # Re-initializes response and request if client supports persistent
148
- # connection.
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
- # Called when the connection is unbinded from the socket
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
- # Allows this connection to be persistent.
172
- def can_persist!
173
- @can_persist = true
114
+ rescue Exception
115
+ handle_error
116
+ nil # Signals that the request could not be processed
174
117
  end
175
-
176
- # Return +true+ if this connection is allowed to stay open and be persistent.
177
- def can_persist?
178
- @can_persist
118
+
119
+ def prepare_response(response)
120
+ return unless response
121
+
122
+ Response.new(*response)
179
123
  end
180
124
 
181
- # Return +true+ if the connection must be left open
182
- # and ready to be reused for another request.
183
- def persistent?
184
- @can_persist && @response.persistent?
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
- # Return +true+ if the connection is open but is not
188
- # processing any user requests
189
- def idle?
190
- @idle
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
- # +true+ if <tt>app.call</tt> will be called inside a thread.
194
- # You can set all requests as threaded setting <tt>Connection#threaded=true</tt>
195
- # or on a per-request case returning +true+ in <tt>app.deferred?</tt>.
196
- def threaded?
197
- @threaded || (@app.respond_to?(:deferred?) && @app.deferred?(@request.env))
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
- # IP Address of the remote client.
201
- def remote_address
202
- socket_address
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
- log_error('Could not infer remote address', e)
205
- nil
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
- protected
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
- peer = get_peername
212
- Socket.unpack_sockaddr_in(peer)[1] if peer
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