thin 0.6.1-x86-mswin32-60 → 0.6.2-x86-mswin32-60

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/lib/thin/headers.rb CHANGED
@@ -10,8 +10,11 @@ module Thin
10
10
  @out = ''
11
11
  end
12
12
 
13
+ # Add <tt>key: value</tt> pair to the headers.
14
+ # Ignore if already sent and no duplicates are allowed
15
+ # for this +key+.
13
16
  def []=(key, value)
14
- if !@sent[key] || ALLOWED_DUPLICATES.include?(key)
17
+ if !@sent.has_key?(key) || ALLOWED_DUPLICATES.include?(key)
15
18
  @sent[key] = true
16
19
  @out << HEADER_FORMAT % [key, value]
17
20
  end
data/lib/thin/logging.rb CHANGED
@@ -16,6 +16,7 @@ module Thin
16
16
  puts msg || yield if ($DEBUG || $TRACE) && !@silent
17
17
  end
18
18
 
19
+ # Log an error backtrace if tracing is activated
19
20
  def log_error(e)
20
21
  trace { "#{e}\n\t" + e.backtrace.join("\n\t") }
21
22
  end
data/lib/thin/request.rb CHANGED
@@ -62,21 +62,27 @@ module Thin
62
62
  def parse(data)
63
63
  @data << data
64
64
 
65
- if @parser.finished? # Header finished, can only be some more body
66
- body << data
67
- else # Parse more header using the super parser
65
+ if @parser.finished? # Header finished, can only be some more body
66
+ body << data
67
+ else # Parse more header using the super parser
68
68
  @nparsed = @parser.execute(@env, @data, @nparsed)
69
+
69
70
  # Transfert to a tempfile if body is very big
70
71
  move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
71
72
  end
72
73
 
73
- # Check if header and body are complete
74
- if @parser.finished? && @body.size >= content_length
75
- @body.rewind
76
- return true # Request is fully parsed
77
- end
78
74
 
79
- false # Not finished, need more data
75
+ if finished? # Check if header and body are complete
76
+ @body.rewind
77
+ true # Request is fully parsed
78
+ else
79
+ false # Not finished, need more data
80
+ end
81
+ end
82
+
83
+ # +true+ if headers and body are finished parsing
84
+ def finished?
85
+ @parser.finished? && @body.size >= content_length
80
86
  end
81
87
 
82
88
  # Expected size of the body
@@ -84,18 +90,19 @@ module Thin
84
90
  @env[CONTENT_LENGTH].to_i
85
91
  end
86
92
 
93
+ # Close any resource used by the response
87
94
  def close
88
- @body.close if @body === Tempfile
89
- end
90
-
95
+ @body.delete if @body.class == Tempfile
96
+ end
91
97
 
92
98
  private
93
99
  def move_body_to_tempfile
94
100
  current_body = @body
101
+ current_body.rewind
95
102
  @body = Tempfile.new(BODY_TMPFILE)
96
103
  @body.binmode
97
- @body << current_body unless current_body.size.zero?
98
- @env[RACK_INPUT] = @body
104
+ @body << current_body.read
105
+ @env[RACK_INPUT] = @body
99
106
  end
100
107
  end
101
108
  end
data/lib/thin/response.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  module Thin
2
2
  # A response sent to the client.
3
3
  class Response
4
- CONNECTION = 'Connection'.freeze
5
- SERVER = 'Server'.freeze
6
- CLOSE = 'close'.freeze
4
+ CONNECTION = 'Connection'.freeze
5
+ SERVER = 'Server'.freeze
6
+ CLOSE = 'close'.freeze
7
7
 
8
8
  # Status code
9
9
  attr_accessor :status
@@ -23,7 +23,7 @@ module Thin
23
23
  # to be sent in the response.
24
24
  def headers_output
25
25
  @headers[CONNECTION] = CLOSE
26
- @headers[SERVER] = Thin::SERVER
26
+ @headers[SERVER] = Thin::SERVER
27
27
 
28
28
  @headers.to_s
29
29
  end
@@ -34,10 +34,33 @@ module Thin
34
34
  "HTTP/1.1 #{@status} #{HTTP_STATUS_CODES[@status.to_i]}\r\n#{headers_output}\r\n"
35
35
  end
36
36
 
37
- def headers=(key_value_pairs)
38
- key_value_pairs.each do |k, vs|
39
- vs.each_line { |v| @headers[k] = v.chomp }
37
+ if Thin.ruby_18?
38
+
39
+ # Ruby 1.8 implementation.
40
+ # Respects Rack specs.
41
+ def headers=(key_value_pairs)
42
+ key_value_pairs.each do |k, vs|
43
+ vs.each { |v| @headers[k] = v.chomp }
44
+ end
40
45
  end
46
+
47
+ else
48
+
49
+ # Ruby 1.9 doesn't have a String#each anymore.
50
+ # Rack spec doesn't take care of that yet, for now we just use
51
+ # +each+ but fallback to +each_line+ on strings.
52
+ # I wish we could remove that condition.
53
+ # To be reviewed when a new Rack spec comes out.
54
+ def headers=(key_value_pairs)
55
+ key_value_pairs.each do |k, vs|
56
+ if vs.is_a?(String)
57
+ vs.each_line { |v| @headers[k] = v.chomp }
58
+ else
59
+ vs.each { |v| @headers[k] = v.chomp }
60
+ end
61
+ end
62
+ end
63
+
41
64
  end
42
65
 
43
66
  # Close any resource used by the response
data/lib/thin/server.rb CHANGED
@@ -1,7 +1,4 @@
1
1
  module Thin
2
- # Raise when we require the server to stop
3
- class StopServer < Exception; end
4
-
5
2
  # The Thin HTTP server used to served request.
6
3
  # It listen for incoming request on a given port
7
4
  # and forward all request to +app+.
@@ -42,13 +39,14 @@ module Thin
42
39
  #
43
40
  def initialize(host_or_socket, port=3000, app=nil, &block)
44
41
  if host_or_socket.include?('/')
45
- @socket = host_or_socket
42
+ @socket = host_or_socket
46
43
  else
47
- @host = host_or_socket
48
- @port = port.to_i
44
+ @host = host_or_socket
45
+ @port = port.to_i
49
46
  end
50
- @app = app
51
- @timeout = 60 # sec
47
+ @app = app
48
+ @timeout = 60 # sec
49
+ @connections = []
52
50
 
53
51
  @app = Rack::Builder.new(&block).to_app if block
54
52
  end
@@ -72,27 +70,40 @@ module Thin
72
70
  log ">> Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
73
71
  trace ">> Tracing ON"
74
72
 
75
- EventMachine.run do
76
- begin
77
- start_server
78
- rescue StopServer
79
- stop
80
- end
81
- end
73
+ EventMachine.run { @signature = start_server }
82
74
  end
83
75
  alias :start! :start
84
76
 
85
- # Stops the server by stopping the listening loop.
77
+ # Stops the server after processing all current connections.
78
+ # Calling twice is the equivalent of calling <tt>stop!</tt>.
86
79
  def stop
87
- EventMachine.stop_event_loop
88
- remove_socket_file
89
- rescue
90
- warn "Error stopping : #{$!}"
80
+ if @stopping
81
+ stop!
82
+ else
83
+ @stopping = true
84
+
85
+ # Do not accept anymore connection
86
+ EventMachine.stop_server(@signature)
87
+
88
+ unless wait_for_connections_and_stop
89
+ # Still some connections running, schedule a check later
90
+ EventMachine.add_periodic_timer(1) { wait_for_connections_and_stop }
91
+ end
92
+ end
91
93
  end
92
94
 
93
- # Stops the server by raising an error.
95
+ # Stops the server closing all current connections
94
96
  def stop!
95
- raise StopServer
97
+ log ">> Stopping ..."
98
+
99
+ @connections.each { |connection| connection.close_connection }
100
+ EventMachine.stop
101
+
102
+ remove_socket_file
103
+ end
104
+
105
+ def connection_finished(connection)
106
+ @connections.delete(connection)
96
107
  end
97
108
 
98
109
  protected
@@ -110,19 +121,34 @@ module Thin
110
121
  end
111
122
 
112
123
  def start_server_on_socket
124
+ raise PlatformNotSupported, 'UNIX sockets not available on Windows' if Thin.win?
125
+
113
126
  log ">> Listening on #{@socket}, CTRL+C to stop"
114
127
  EventMachine.start_unix_domain_server(@socket, Connection, &method(:initialize_connection))
115
128
  end
116
129
 
117
130
  def initialize_connection(connection)
131
+ connection.server = self
118
132
  connection.comm_inactivity_timeout = @timeout
119
133
  connection.app = @app
120
134
  connection.silent = @silent
121
135
  connection.unix_socket = !@socket.nil?
136
+
137
+ @connections << connection
122
138
  end
123
139
 
124
140
  def remove_socket_file
125
141
  File.delete(@socket) if @socket && File.exist?(@socket)
126
142
  end
143
+
144
+ def wait_for_connections_and_stop
145
+ if @connections.empty?
146
+ stop!
147
+ true
148
+ else
149
+ log ">> Waiting for #{@connections.size} connection(s) to finish, CTRL+C to force stop"
150
+ false
151
+ end
152
+ end
127
153
  end
128
154
  end
data/lib/thin/stats.rb CHANGED
@@ -1,10 +1,16 @@
1
+ require 'erb'
2
+
1
3
  module Thin
2
- # Rack adapter to log stats to a Rack application
3
4
  module Stats
5
+ # Rack adapter to log stats about a Rack application.
4
6
  class Adapter
7
+ include ERB::Util
8
+
5
9
  def initialize(app, path='/stats')
6
10
  @app = app
7
11
  @path = path
12
+
13
+ @template = ERB.new(TEMPLATE)
8
14
 
9
15
  @requests = 0
10
16
  @requests_finished = 0
@@ -21,34 +27,19 @@ module Thin
21
27
 
22
28
  def log(env)
23
29
  @requests += 1
24
- @server = env['SERVER_SOFTWARE']
30
+ @last_request = Rack::Request.new(env)
25
31
  request_started_at = Time.now
26
32
 
27
33
  response = yield
28
34
 
29
35
  @requests_finished += 1
30
- @last_request_path = env['PATH_INFO']
31
36
  @last_request_time = Time.now - request_started_at
32
37
 
33
38
  response
34
39
  end
35
40
 
36
41
  def serve(env)
37
- body = '<html><body>'
38
- body << '<h1>Server stats</h1>'
39
- body << '<ul>'
40
- body << "<li>#{@requests} requests</li>"
41
- body << "<li>#{@requests_finished} requests finished</li>"
42
- body << "<li>#{@requests - @requests_finished} errors</li>"
43
- body << "<li>#{Time.now - @start_time} uptime</li>"
44
- body << "<li>Running on #{@server}</li>"
45
- body << '</ul>'
46
- body << '<h2>Last request</h2>'
47
- body << '<ul>'
48
- body << "<li>#{@last_request_path}</li>"
49
- body << "<li>Took #{@last_request_time} sec</li>"
50
- body << '</ul>'
51
- body << '</body></html>'
42
+ body = @template.result(binding)
52
43
 
53
44
  [
54
45
  200,
@@ -56,9 +47,257 @@ module Thin
56
47
  'Content-Type' => 'text/html',
57
48
  'Content-Length' => body.size.to_s
58
49
  },
59
- body
50
+ [body]
60
51
  ]
61
52
  end
53
+
54
+ # Taken from Rails
55
+ def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
56
+ from_time = from_time.to_time if from_time.respond_to?(:to_time)
57
+ to_time = to_time.to_time if to_time.respond_to?(:to_time)
58
+ distance_in_minutes = (((to_time - from_time).abs)/60).round
59
+ distance_in_seconds = ((to_time - from_time).abs).round
60
+
61
+ case distance_in_minutes
62
+ when 0..1
63
+ return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
64
+ case distance_in_seconds
65
+ when 0..4 then 'less than 5 seconds'
66
+ when 5..9 then 'less than 10 seconds'
67
+ when 10..19 then 'less than 20 seconds'
68
+ when 20..39 then 'half a minute'
69
+ when 40..59 then 'less than a minute'
70
+ else '1 minute'
71
+ end
72
+
73
+ when 2..44 then "#{distance_in_minutes} minutes"
74
+ when 45..89 then 'about 1 hour'
75
+ when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
76
+ when 1440..2879 then '1 day'
77
+ when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
78
+ when 43200..86399 then 'about 1 month'
79
+ when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
80
+ when 525600..1051199 then 'about 1 year'
81
+ else "over #{(distance_in_minutes / 525600).round} years"
82
+ end
83
+ end
84
+
85
+ # Taken from Rack::ShowException
86
+ # adapted from Django <djangoproject.com>
87
+ # Copyright (c) 2005, the Lawrence Journal-World
88
+ # Used under the modified BSD license:
89
+ # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
90
+ TEMPLATE = <<'HTML'
91
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
92
+ <html lang="en">
93
+ <head>
94
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
95
+ <meta name="robots" content="NONE,NOARCHIVE" />
96
+ <title>Thin Stats</title>
97
+ <style type="text/css">
98
+ html * { padding:0; margin:0; }
99
+ body * { padding:10px 20px; }
100
+ body * * { padding:0; }
101
+ body { font:small sans-serif; }
102
+ body>div { border-bottom:1px solid #ddd; }
103
+ h1 { font-weight:normal; }
104
+ h2 { margin-bottom:.8em; }
105
+ h2 span { font-size:80%; color:#666; font-weight:normal; }
106
+ h3 { margin:1em 0 .5em 0; }
107
+ h4 { margin:0 0 .5em 0; font-weight: normal; }
108
+ table {
109
+ border:1px solid #ccc; border-collapse: collapse; background:white; }
110
+ tbody td, tbody th { vertical-align:top; padding:2px 3px; }
111
+ thead th {
112
+ padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
113
+ font-weight:normal; font-size:11px; border:1px solid #ddd; }
114
+ tbody th { text-align:right; color:#666; padding-right:.5em; }
115
+ table.vars { margin:5px 0 2px 40px; }
116
+ table.vars td, table.req td { font-family:monospace; }
117
+ table td.code { width:100%;}
118
+ table td.code div { overflow:hidden; }
119
+ table.source th { color:#666; }
120
+ table.source td {
121
+ font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
122
+ ul.traceback { list-style-type:none; }
123
+ ul.traceback li.frame { margin-bottom:1em; }
124
+ div.context { margin: 10px 0; }
125
+ div.context ol {
126
+ padding-left:30px; margin:0 10px; list-style-position: inside; }
127
+ div.context ol li {
128
+ font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
129
+ div.context ol.context-line li { color:black; background-color:#ccc; }
130
+ div.context ol.context-line li span { float: right; }
131
+ div.commands { margin-left: 40px; }
132
+ div.commands a { color:black; text-decoration:none; }
133
+ #summary { background: #ffc; }
134
+ #summary h2 { font-weight: normal; color: #666; }
135
+ #summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
136
+ #summary ul#quicklinks li { float: left; padding: 0 1em; }
137
+ #summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
138
+ #explanation { background:#eee; }
139
+ #template, #template-not-exist { background:#f6f6f6; }
140
+ #template-not-exist ul { margin: 0 0 0 20px; }
141
+ #traceback { background:#eee; }
142
+ #summary table { border:none; background:transparent; }
143
+ #requests { background:#f6f6f6; padding-left:120px; }
144
+ #requests h2, #requests h3 { position:relative; margin-left:-100px; }
145
+ #requests h3 { margin-bottom:-1em; }
146
+ .error { background: #ffc; }
147
+ .specific { color:#cc3300; font-weight:bold; }
148
+ </style>
149
+ </head>
150
+ <body>
151
+
152
+ <div id="summary">
153
+ <h1>Server stats</h1>
154
+ <h2><%= Thin::SERVER %></h2>
155
+ <table>
156
+ <tr>
157
+ <th>Uptime</th>
158
+ <td><%= distance_of_time_in_words(@start_time, Time.now) %> (<%= Time.now - @start_time %> sec)</td>
159
+ </tr>
160
+ <tr>
161
+ <th>PID</th>
162
+ <td><%=h Process.pid %></td>
163
+ </tr>
164
+ </table>
165
+
166
+ <% if @last_request %>
167
+ <h3>Jump to:</h3>
168
+ <ul id="quicklinks">
169
+ <li><a href="#get-info">GET</a></li>
170
+ <li><a href="#post-info">POST</a></li>
171
+ <li><a href="#cookie-info">Cookies</a></li>
172
+ <li><a href="#env-info">ENV</a></li>
173
+ </ul>
174
+ <% end %>
175
+ </div>
176
+
177
+ <div id="stats">
178
+ <h2>Requests</h2>
179
+ <h3>Stats</h3>
180
+ <table class="req">
181
+ <tr>
182
+ <td>Requests</td>
183
+ <td><%= @requests %></td>
184
+ </tr>
185
+ <tr>
186
+ <td>Finished</td>
187
+ <td><%= @requests_finished %></td>
188
+ </tr>
189
+ <tr>
190
+ <td>Errors</td>
191
+ <td><%= @requests - @requests_finished %></td>
192
+ </tr>
193
+ <tr>
194
+ <td>Last request</td>
195
+ <td><%= @last_request_time %> sec</td>
196
+ </tr>
197
+ </table>
198
+ </div>
199
+
200
+ <% if @last_request %>
201
+ <div id="requestinfo">
202
+ <h2>Last Request information</h2>
203
+
204
+ <h3 id="get-info">GET</h3>
205
+ <% unless @last_request.GET.empty? %>
206
+ <table class="req">
207
+ <thead>
208
+ <tr>
209
+ <th>Variable</th>
210
+ <th>Value</th>
211
+ </tr>
212
+ </thead>
213
+ <tbody>
214
+ <% @last_request.GET.sort_by { |k, v| k.to_s }.each { |key, val| %>
215
+ <tr>
216
+ <td><%=h key %></td>
217
+ <td class="code"><div><%=h val.inspect %></div></td>
218
+ </tr>
219
+ <% } %>
220
+ </tbody>
221
+ </table>
222
+ <% else %>
223
+ <p>No GET data.</p>
224
+ <% end %>
225
+
226
+ <h3 id="post-info">POST</h3>
227
+ <% unless @last_request.POST.empty? %>
228
+ <table class="req">
229
+ <thead>
230
+ <tr>
231
+ <th>Variable</th>
232
+ <th>Value</th>
233
+ </tr>
234
+ </thead>
235
+ <tbody>
236
+ <% @last_request.POST.sort_by { |k, v| k.to_s }.each { |key, val| %>
237
+ <tr>
238
+ <td><%=h key %></td>
239
+ <td class="code"><div><%=h val.inspect %></div></td>
240
+ </tr>
241
+ <% } %>
242
+ </tbody>
243
+ </table>
244
+ <% else %>
245
+ <p>No POST data.</p>
246
+ <% end %>
247
+
248
+
249
+ <h3 id="cookie-info">COOKIES</h3>
250
+ <% unless @last_request.cookies.empty? %>
251
+ <table class="req">
252
+ <thead>
253
+ <tr>
254
+ <th>Variable</th>
255
+ <th>Value</th>
256
+ </tr>
257
+ </thead>
258
+ <tbody>
259
+ <% @last_request.cookies.each { |key, val| %>
260
+ <tr>
261
+ <td><%=h key %></td>
262
+ <td class="code"><div><%=h val.inspect %></div></td>
263
+ </tr>
264
+ <% } %>
265
+ </tbody>
266
+ </table>
267
+ <% else %>
268
+ <p>No cookie data.</p>
269
+ <% end %>
270
+
271
+ <h3 id="env-info">Rack ENV</h3>
272
+ <table class="req">
273
+ <thead>
274
+ <tr>
275
+ <th>Variable</th>
276
+ <th>Value</th>
277
+ </tr>
278
+ </thead>
279
+ <tbody>
280
+ <% @last_request.env.sort_by { |k, v| k.to_s }.each { |key, val| %>
281
+ <tr>
282
+ <td><%=h key %></td>
283
+ <td class="code"><div><%=h val %></div></td>
284
+ </tr>
285
+ <% } %>
286
+ </tbody>
287
+ </table>
288
+
289
+ </div>
290
+ <% end %>
291
+
292
+ <div id="explanation">
293
+ <p>
294
+ You're seeing this error because you use <code>Thin::Stats</code>.
295
+ </p>
296
+ </div>
297
+
298
+ </body>
299
+ </html>
300
+ HTML
62
301
  end
63
302
  end
64
303
  end