grockit-thin 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. data/CHANGELOG +220 -0
  2. data/COMMITTERS +3 -0
  3. data/COPYING +18 -0
  4. data/README +77 -0
  5. data/Rakefile +13 -0
  6. data/benchmark/abc +51 -0
  7. data/benchmark/benchmarker.rb +80 -0
  8. data/benchmark/runner +79 -0
  9. data/bin/thin +6 -0
  10. data/example/adapter.rb +32 -0
  11. data/example/config.ru +23 -0
  12. data/example/monit_sockets +20 -0
  13. data/example/monit_unixsock +20 -0
  14. data/example/myapp.rb +1 -0
  15. data/example/ramaze.ru +12 -0
  16. data/example/thin.god +80 -0
  17. data/example/thin_solaris_smf.erb +36 -0
  18. data/example/thin_solaris_smf.readme.txt +150 -0
  19. data/example/vlad.rake +61 -0
  20. data/ext/thin_parser/common.rl +55 -0
  21. data/ext/thin_parser/ext_help.h +14 -0
  22. data/ext/thin_parser/extconf.rb +6 -0
  23. data/ext/thin_parser/parser.c +1218 -0
  24. data/ext/thin_parser/parser.h +49 -0
  25. data/ext/thin_parser/parser.rl +159 -0
  26. data/ext/thin_parser/thin.c +433 -0
  27. data/lib/rack/adapter/loader.rb +78 -0
  28. data/lib/rack/adapter/rails.rb +167 -0
  29. data/lib/rack/handler/thin.rb +18 -0
  30. data/lib/thin.rb +49 -0
  31. data/lib/thin/backends/base.rb +135 -0
  32. data/lib/thin/backends/swiftiply_client.rb +56 -0
  33. data/lib/thin/backends/tcp_server.rb +29 -0
  34. data/lib/thin/backends/unix_server.rb +51 -0
  35. data/lib/thin/command.rb +52 -0
  36. data/lib/thin/connection.rb +178 -0
  37. data/lib/thin/controllers/cluster.rb +121 -0
  38. data/lib/thin/controllers/controller.rb +182 -0
  39. data/lib/thin/controllers/service.rb +75 -0
  40. data/lib/thin/controllers/service.sh.erb +39 -0
  41. data/lib/thin/daemonizing.rb +163 -0
  42. data/lib/thin/headers.rb +31 -0
  43. data/lib/thin/logging.rb +54 -0
  44. data/lib/thin/request.rb +144 -0
  45. data/lib/thin/response.rb +96 -0
  46. data/lib/thin/runner.rb +208 -0
  47. data/lib/thin/server.rb +241 -0
  48. data/lib/thin/stats.html.erb +216 -0
  49. data/lib/thin/stats.rb +52 -0
  50. data/lib/thin/statuses.rb +43 -0
  51. data/lib/thin/version.rb +32 -0
  52. data/spec/backends/swiftiply_client_spec.rb +66 -0
  53. data/spec/backends/tcp_server_spec.rb +33 -0
  54. data/spec/backends/unix_server_spec.rb +37 -0
  55. data/spec/command_spec.rb +20 -0
  56. data/spec/configs/cluster.yml +9 -0
  57. data/spec/configs/single.yml +9 -0
  58. data/spec/connection_spec.rb +105 -0
  59. data/spec/controllers/cluster_spec.rb +179 -0
  60. data/spec/controllers/controller_spec.rb +121 -0
  61. data/spec/controllers/service_spec.rb +50 -0
  62. data/spec/daemonizing_spec.rb +192 -0
  63. data/spec/headers_spec.rb +29 -0
  64. data/spec/logging_spec.rb +46 -0
  65. data/spec/perf/request_perf_spec.rb +50 -0
  66. data/spec/perf/response_perf_spec.rb +19 -0
  67. data/spec/perf/server_perf_spec.rb +39 -0
  68. data/spec/rack/loader_spec.rb +29 -0
  69. data/spec/rack/rails_adapter_spec.rb +106 -0
  70. data/spec/rails_app/app/controllers/application.rb +10 -0
  71. data/spec/rails_app/app/controllers/simple_controller.rb +19 -0
  72. data/spec/rails_app/app/helpers/application_helper.rb +3 -0
  73. data/spec/rails_app/app/views/simple/index.html.erb +15 -0
  74. data/spec/rails_app/config/boot.rb +109 -0
  75. data/spec/rails_app/config/environment.rb +64 -0
  76. data/spec/rails_app/config/environments/development.rb +18 -0
  77. data/spec/rails_app/config/environments/production.rb +19 -0
  78. data/spec/rails_app/config/environments/test.rb +22 -0
  79. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  80. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  81. data/spec/rails_app/config/routes.rb +35 -0
  82. data/spec/rails_app/public/404.html +30 -0
  83. data/spec/rails_app/public/422.html +30 -0
  84. data/spec/rails_app/public/500.html +30 -0
  85. data/spec/rails_app/public/dispatch.cgi +10 -0
  86. data/spec/rails_app/public/dispatch.fcgi +24 -0
  87. data/spec/rails_app/public/dispatch.rb +10 -0
  88. data/spec/rails_app/public/favicon.ico +0 -0
  89. data/spec/rails_app/public/images/rails.png +0 -0
  90. data/spec/rails_app/public/index.html +277 -0
  91. data/spec/rails_app/public/javascripts/application.js +2 -0
  92. data/spec/rails_app/public/javascripts/controls.js +963 -0
  93. data/spec/rails_app/public/javascripts/dragdrop.js +972 -0
  94. data/spec/rails_app/public/javascripts/effects.js +1120 -0
  95. data/spec/rails_app/public/javascripts/prototype.js +4225 -0
  96. data/spec/rails_app/public/robots.txt +5 -0
  97. data/spec/rails_app/script/about +3 -0
  98. data/spec/rails_app/script/console +3 -0
  99. data/spec/rails_app/script/destroy +3 -0
  100. data/spec/rails_app/script/generate +3 -0
  101. data/spec/rails_app/script/performance/benchmarker +3 -0
  102. data/spec/rails_app/script/performance/profiler +3 -0
  103. data/spec/rails_app/script/performance/request +3 -0
  104. data/spec/rails_app/script/plugin +3 -0
  105. data/spec/rails_app/script/process/inspector +3 -0
  106. data/spec/rails_app/script/process/reaper +3 -0
  107. data/spec/rails_app/script/process/spawner +3 -0
  108. data/spec/rails_app/script/runner +3 -0
  109. data/spec/rails_app/script/server +3 -0
  110. data/spec/request/mongrel_spec.rb +39 -0
  111. data/spec/request/parser_spec.rb +191 -0
  112. data/spec/request/persistent_spec.rb +35 -0
  113. data/spec/request/processing_spec.rb +45 -0
  114. data/spec/response_spec.rb +76 -0
  115. data/spec/runner_spec.rb +167 -0
  116. data/spec/server/builder_spec.rb +44 -0
  117. data/spec/server/pipelining_spec.rb +109 -0
  118. data/spec/server/robustness_spec.rb +34 -0
  119. data/spec/server/stopping_spec.rb +45 -0
  120. data/spec/server/swiftiply.yml +6 -0
  121. data/spec/server/swiftiply_spec.rb +32 -0
  122. data/spec/server/tcp_spec.rb +57 -0
  123. data/spec/server/threaded_spec.rb +27 -0
  124. data/spec/server/unix_socket_spec.rb +26 -0
  125. data/spec/server_spec.rb +96 -0
  126. data/spec/spec_helper.rb +219 -0
  127. data/tasks/announce.rake +22 -0
  128. data/tasks/deploy.rake +16 -0
  129. data/tasks/email.erb +30 -0
  130. data/tasks/ext.rake +42 -0
  131. data/tasks/gem.rake +102 -0
  132. data/tasks/rdoc.rake +25 -0
  133. data/tasks/site.rake +15 -0
  134. data/tasks/spec.rake +48 -0
  135. data/tasks/stats.rake +28 -0
  136. metadata +240 -0
@@ -0,0 +1,241 @@
1
+ module Thin
2
+ # The uterly famous Thin HTTP server.
3
+ # It listen for incoming request through a given +backend+
4
+ # and forward all request to +app+.
5
+ #
6
+ # == TCP server
7
+ # Create a new TCP server on bound to <tt>host:port</tt> by specifiying +host+
8
+ # and +port+ as the first 2 arguments.
9
+ #
10
+ # Thin::Server.start('0.0.0.0', 3000, app)
11
+ #
12
+ # == UNIX domain server
13
+ # Create a new UNIX domain socket bound to +socket+ file by specifiying a filename
14
+ # as the first argument. Eg.: /tmp/thin.sock. If the first argument contains a <tt>/</tt>
15
+ # it will be assumed to be a UNIX socket.
16
+ #
17
+ # Thin::Server.start('/tmp/thin.sock', app)
18
+ #
19
+ # == Using a custom backend
20
+ # You can implement your own way to connect the server to its client by creating your
21
+ # own Backend class and pass it as the :backend option.
22
+ #
23
+ # Thin::Server.start('galaxy://faraway', 1345, app, :backend => Thin::Backends::MyFancyBackend)
24
+ #
25
+ # == Rack application (+app+)
26
+ # All requests will be processed through +app+ that must be a valid Rack adapter.
27
+ # A valid Rack adapter (application) must respond to <tt>call(env#Hash)</tt> and
28
+ # return an array of <tt>[status, headers, body]</tt>.
29
+ #
30
+ # == Building an app in place
31
+ # If a block is passed, a <tt>Rack::Builder</tt> instance
32
+ # will be passed to build the +app+. So you can do cool stuff like this:
33
+ #
34
+ # Thin::Server.start('0.0.0.0', 3000) do
35
+ # use Rack::CommonLogger
36
+ # use Rack::ShowExceptions
37
+ # map "/lobster" do
38
+ # use Rack::Lint
39
+ # run Rack::Lobster.new
40
+ # end
41
+ # end
42
+ #
43
+ # == Controlling with signals
44
+ # * QUIT: Gracefull shutdown (see Server#stop)
45
+ # * INT and TERM: Force shutdown (see Server#stop!)
46
+ # Disable signals by passing <tt>:signals => false</tt>
47
+ #
48
+ class Server
49
+ include Logging
50
+ include Daemonizable
51
+ extend Forwardable
52
+
53
+ # Default values
54
+ DEFAULT_TIMEOUT = 30 #sec
55
+ DEFAULT_HOST = '0.0.0.0'
56
+ DEFAULT_PORT = 3000
57
+ DEFAULT_MAXIMUM_CONNECTIONS = 1024
58
+ DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS = 512
59
+
60
+ # Application (Rack adapter) called with the request that produces the response.
61
+ attr_accessor :app
62
+
63
+ # Backend handling the connections to the clients.
64
+ attr_accessor :backend
65
+
66
+ # Maximum number of seconds for incoming data to arrive before the connection
67
+ # is dropped.
68
+ def_delegators :backend, :timeout, :timeout=
69
+
70
+ # Maximum number of file or socket descriptors that the server may open.
71
+ def_delegators :backend, :maximum_connections, :maximum_connections=
72
+
73
+ # Maximum number of connection that can be persistent at the same time.
74
+ # Most browser never close the connection so most of the time they are closed
75
+ # when the timeout occur. If we don't control the number of persistent connection,
76
+ # if would be very easy to overflow the server for a DoS attack.
77
+ def_delegators :backend, :maximum_persistent_connections, :maximum_persistent_connections=
78
+
79
+ # Allow using threads in the backend.
80
+ def_delegators :backend, :threaded?, :threaded=
81
+
82
+ # Address and port on which the server is listening for connections.
83
+ def_delegators :backend, :host, :port
84
+
85
+ # UNIX domain socket on which the server is listening for connections.
86
+ def_delegator :backend, :socket
87
+
88
+ # Disable the use of epoll under Linux
89
+ def_delegators :backend, :no_epoll, :no_epoll=
90
+
91
+ def initialize(*args, &block)
92
+ host, port, options = DEFAULT_HOST, DEFAULT_PORT, {}
93
+
94
+ # Guess each parameter by its type so they can be
95
+ # received in any order.
96
+ args.each do |arg|
97
+ case arg
98
+ when Fixnum, /^\d+$/ then port = arg.to_i
99
+ when String then host = arg
100
+ when Hash then options = arg
101
+ else
102
+ @app = arg if arg.respond_to?(:call)
103
+ end
104
+ end
105
+
106
+ # Try to intelligently select which backend to use.
107
+ @backend = select_backend(host, port, options)
108
+
109
+ load_cgi_multipart_eof_fix
110
+
111
+ @backend.server = self
112
+
113
+ # Set defaults
114
+ @backend.maximum_connections = DEFAULT_MAXIMUM_CONNECTIONS
115
+ @backend.maximum_persistent_connections = DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
116
+ @backend.timeout = DEFAULT_TIMEOUT
117
+
118
+ # Allow using Rack builder as a block
119
+ @app = Rack::Builder.new(&block).to_app if block
120
+
121
+ # If in debug mode, wrap in logger adapter
122
+ @app = Rack::CommonLogger.new(@app) if Logging.debug?
123
+
124
+ setup_signals unless options[:signals].class == FalseClass
125
+ end
126
+
127
+ # Lil' shortcut to turn this:
128
+ #
129
+ # Server.new(...).start
130
+ #
131
+ # into this:
132
+ #
133
+ # Server.start(...)
134
+ #
135
+ def self.start(*args, &block)
136
+ new(*args, &block).start!
137
+ end
138
+
139
+ # Start the server and listen for connections.
140
+ def start
141
+ raise ArgumentError, 'app required' unless @app
142
+
143
+ log ">> Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
144
+ debug ">> Debugging ON"
145
+ trace ">> Tracing ON"
146
+
147
+ log ">> Maximum connections set to #{@backend.maximum_connections}"
148
+ log ">> Listening on #{@backend}, CTRL+C to stop"
149
+
150
+ @backend.start
151
+ end
152
+ alias :start! :start
153
+
154
+ # == Gracefull shutdown
155
+ # Stops the server after processing all current connections.
156
+ # As soon as this method is called, the server stops accepting
157
+ # new requests and wait for all current connections to finish.
158
+ # Calling twice is the equivalent of calling <tt>stop!</tt>.
159
+ def stop
160
+ if running?
161
+ @backend.stop
162
+ unless @backend.empty?
163
+ log ">> Waiting for #{@backend.size} connection(s) to finish, " +
164
+ "can take up to #{timeout} sec, CTRL+C to stop now"
165
+ end
166
+ else
167
+ stop!
168
+ end
169
+ end
170
+
171
+ # == Force shutdown
172
+ # Stops the server closing all current connections right away.
173
+ # This doesn't wait for connection to finish their work and send data.
174
+ # All current requests will be dropped.
175
+ def stop!
176
+ log ">> Stopping ..."
177
+
178
+ @backend.stop!
179
+ end
180
+
181
+ # == Configure the server
182
+ # The process might need to have superuser privilege to configure
183
+ # server with optimal options.
184
+ def config
185
+ @backend.config
186
+ end
187
+
188
+ # Name of the server and type of backend used.
189
+ # This is also the name of the process in which Thin is running as a daemon.
190
+ def name
191
+ "thin server (#{@backend})"
192
+ end
193
+ alias :to_s :name
194
+
195
+ # Return +true+ if the server is running and ready to receive requests.
196
+ # Note that the server might still be running and return +false+ when
197
+ # shuting down and waiting for active connections to complete.
198
+ def running?
199
+ @backend.running?
200
+ end
201
+
202
+ protected
203
+ # Register signals:
204
+ # * INT calls +stop+ to shutdown gracefully.
205
+ # * TERM calls <tt>stop!</tt> to force shutdown.
206
+ def setup_signals
207
+ trap('QUIT') { stop } unless Thin.win?
208
+ trap('INT') { stop! }
209
+ trap('TERM') { stop! }
210
+ end
211
+
212
+ def select_backend(host, port, options)
213
+ case
214
+ when options.has_key?(:backend)
215
+ raise ArgumentError, ":backend must be a class" unless options[:backend].is_a?(Class)
216
+ options[:backend].new(host, port, options)
217
+ when options.has_key?(:swiftiply)
218
+ Backends::SwiftiplyClient.new(host, port, options)
219
+ when host.include?('/')
220
+ Backends::UnixServer.new(host)
221
+ else
222
+ Backends::TcpServer.new(host, port)
223
+ end
224
+ end
225
+
226
+ # Taken from Mongrel cgi_multipart_eof_fix
227
+ # Ruby 1.8.5 has a security bug in cgi.rb, we need to patch it.
228
+ def load_cgi_multipart_eof_fix
229
+ version = RUBY_VERSION.split('.').map { |i| i.to_i }
230
+
231
+ if version[0] <= 1 && version[1] <= 8 && version[2] <= 5 && RUBY_PLATFORM !~ /java/
232
+ begin
233
+ require 'cgi_multipart_eof_fix'
234
+ rescue LoadError
235
+ log "!! Ruby 1.8.5 is not secure please install cgi_multipart_eof_fix:"
236
+ log " gem install cgi_multipart_eof_fix"
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,216 @@
1
+ <%#
2
+ # Taken from Rack::ShowException
3
+ # adapted from Django <djangoproject.com>
4
+ # Copyright (c) 2005, the Lawrence Journal-World
5
+ # Used under the modified BSD license:
6
+ # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
7
+ %>
8
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
9
+ <html lang="en">
10
+ <head>
11
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
12
+ <meta name="robots" content="NONE,NOARCHIVE" />
13
+ <title>Thin Stats</title>
14
+ <style type="text/css">
15
+ html * { padding:0; margin:0; }
16
+ body * { padding:10px 20px; }
17
+ body * * { padding:0; }
18
+ body { font:small sans-serif; }
19
+ body>div { border-bottom:1px solid #ddd; }
20
+ h1 { font-weight:normal; }
21
+ h2 { margin-bottom:.8em; }
22
+ h2 span { font-size:80%; color:#666; font-weight:normal; }
23
+ h3 { margin:1em 0 .5em 0; }
24
+ h4 { margin:0 0 .5em 0; font-weight: normal; }
25
+ table {
26
+ border:1px solid #ccc; border-collapse: collapse; background:white; }
27
+ tbody td, tbody th { vertical-align:top; padding:2px 3px; }
28
+ thead th {
29
+ padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
30
+ font-weight:normal; font-size:11px; border:1px solid #ddd; }
31
+ tbody th { text-align:right; color:#666; padding-right:.5em; }
32
+ table.vars { margin:5px 0 2px 40px; }
33
+ table.vars td, table.req td { font-family:monospace; }
34
+ table td.code { width:100%;}
35
+ table td.code div { overflow:hidden; }
36
+ table.source th { color:#666; }
37
+ table.source td {
38
+ font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
39
+ ul.traceback { list-style-type:none; }
40
+ ul.traceback li.frame { margin-bottom:1em; }
41
+ div.context { margin: 10px 0; }
42
+ div.context ol {
43
+ padding-left:30px; margin:0 10px; list-style-position: inside; }
44
+ div.context ol li {
45
+ font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
46
+ div.context ol.context-line li { color:black; background-color:#ccc; }
47
+ div.context ol.context-line li span { float: right; }
48
+ div.commands { margin-left: 40px; }
49
+ div.commands a { color:black; text-decoration:none; }
50
+ #summary { background: #ffc; }
51
+ #summary h2 { font-weight: normal; color: #666; }
52
+ #summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
53
+ #summary ul#quicklinks li { float: left; padding: 0 1em; }
54
+ #summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
55
+ #explanation { background:#eee; }
56
+ #template, #template-not-exist { background:#f6f6f6; }
57
+ #template-not-exist ul { margin: 0 0 0 20px; }
58
+ #traceback { background:#eee; }
59
+ #summary table { border:none; background:transparent; }
60
+ #requests { background:#f6f6f6; padding-left:120px; }
61
+ #requests h2, #requests h3 { position:relative; margin-left:-100px; }
62
+ #requests h3 { margin-bottom:-1em; }
63
+ .error { background: #ffc; }
64
+ .specific { color:#cc3300; font-weight:bold; }
65
+ </style>
66
+ </head>
67
+ <body>
68
+
69
+ <div id="summary">
70
+ <h1>Server stats</h1>
71
+ <h2><%= Thin::SERVER %></h2>
72
+ <table>
73
+ <tr>
74
+ <th>Uptime</th>
75
+ <td><%= Time.now - @start_time %> sec</td>
76
+ </tr>
77
+ <tr>
78
+ <th>PID</th>
79
+ <td><%=h Process.pid %></td>
80
+ </tr>
81
+ </table>
82
+
83
+ <% if @last_request %>
84
+ <h3>Jump to:</h3>
85
+ <ul id="quicklinks">
86
+ <li><a href="#get-info">GET</a></li>
87
+ <li><a href="#post-info">POST</a></li>
88
+ <li><a href="#cookie-info">Cookies</a></li>
89
+ <li><a href="#env-info">ENV</a></li>
90
+ </ul>
91
+ <% end %>
92
+ </div>
93
+
94
+ <div id="stats">
95
+ <h2>Requests</h2>
96
+ <h3>Stats</h3>
97
+ <table class="req">
98
+ <tr>
99
+ <td>Requests</td>
100
+ <td><%= @requests %></td>
101
+ </tr>
102
+ <tr>
103
+ <td>Finished</td>
104
+ <td><%= @requests_finished %></td>
105
+ </tr>
106
+ <tr>
107
+ <td>Errors</td>
108
+ <td><%= @requests - @requests_finished %></td>
109
+ </tr>
110
+ <tr>
111
+ <td>Last request</td>
112
+ <td><%= @last_request_time %> sec</td>
113
+ </tr>
114
+ </table>
115
+ </div>
116
+
117
+ <% if @last_request %>
118
+ <div id="requestinfo">
119
+ <h2>Last Request information</h2>
120
+
121
+ <h3 id="get-info">GET</h3>
122
+ <% unless @last_request.GET.empty? %>
123
+ <table class="req">
124
+ <thead>
125
+ <tr>
126
+ <th>Variable</th>
127
+ <th>Value</th>
128
+ </tr>
129
+ </thead>
130
+ <tbody>
131
+ <% @last_request.GET.sort_by { |k, v| k.to_s }.each { |key, val| %>
132
+ <tr>
133
+ <td><%=h key %></td>
134
+ <td class="code"><div><%=h val.inspect %></div></td>
135
+ </tr>
136
+ <% } %>
137
+ </tbody>
138
+ </table>
139
+ <% else %>
140
+ <p>No GET data.</p>
141
+ <% end %>
142
+
143
+ <h3 id="post-info">POST</h3>
144
+ <% unless @last_request.POST.empty? %>
145
+ <table class="req">
146
+ <thead>
147
+ <tr>
148
+ <th>Variable</th>
149
+ <th>Value</th>
150
+ </tr>
151
+ </thead>
152
+ <tbody>
153
+ <% @last_request.POST.sort_by { |k, v| k.to_s }.each { |key, val| %>
154
+ <tr>
155
+ <td><%=h key %></td>
156
+ <td class="code"><div><%=h val.inspect %></div></td>
157
+ </tr>
158
+ <% } %>
159
+ </tbody>
160
+ </table>
161
+ <% else %>
162
+ <p>No POST data.</p>
163
+ <% end %>
164
+
165
+
166
+ <h3 id="cookie-info">COOKIES</h3>
167
+ <% unless @last_request.cookies.empty? %>
168
+ <table class="req">
169
+ <thead>
170
+ <tr>
171
+ <th>Variable</th>
172
+ <th>Value</th>
173
+ </tr>
174
+ </thead>
175
+ <tbody>
176
+ <% @last_request.cookies.each { |key, val| %>
177
+ <tr>
178
+ <td><%=h key %></td>
179
+ <td class="code"><div><%=h val.inspect %></div></td>
180
+ </tr>
181
+ <% } %>
182
+ </tbody>
183
+ </table>
184
+ <% else %>
185
+ <p>No cookie data.</p>
186
+ <% end %>
187
+
188
+ <h3 id="env-info">Rack ENV</h3>
189
+ <table class="req">
190
+ <thead>
191
+ <tr>
192
+ <th>Variable</th>
193
+ <th>Value</th>
194
+ </tr>
195
+ </thead>
196
+ <tbody>
197
+ <% @last_request.env.sort_by { |k, v| k.to_s }.each { |key, val| %>
198
+ <tr>
199
+ <td><%=h key %></td>
200
+ <td class="code"><div><%=h val %></div></td>
201
+ </tr>
202
+ <% } %>
203
+ </tbody>
204
+ </table>
205
+
206
+ </div>
207
+ <% end %>
208
+
209
+ <div id="explanation">
210
+ <p>
211
+ You're seeing this page because you use <code>Thin::Stats</code>.
212
+ </p>
213
+ </div>
214
+
215
+ </body>
216
+ </html>