steamcannon-thin 1.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. data/CHANGELOG +288 -0
  2. data/COPYING +18 -0
  3. data/README +69 -0
  4. data/Rakefile +44 -0
  5. data/benchmark/abc +51 -0
  6. data/benchmark/benchmarker.rb +80 -0
  7. data/benchmark/runner +82 -0
  8. data/bin/thin +6 -0
  9. data/example/adapter.rb +32 -0
  10. data/example/async_app.ru +126 -0
  11. data/example/async_chat.ru +247 -0
  12. data/example/async_tailer.ru +100 -0
  13. data/example/config.ru +22 -0
  14. data/example/monit_sockets +20 -0
  15. data/example/monit_unixsock +20 -0
  16. data/example/myapp.rb +1 -0
  17. data/example/ramaze.ru +12 -0
  18. data/example/thin.god +80 -0
  19. data/example/thin_solaris_smf.erb +36 -0
  20. data/example/thin_solaris_smf.readme.txt +150 -0
  21. data/example/vlad.rake +64 -0
  22. data/ext/thin_parser/common.rl +55 -0
  23. data/ext/thin_parser/ext_help.h +14 -0
  24. data/ext/thin_parser/extconf.rb +6 -0
  25. data/ext/thin_parser/parser.c +1249 -0
  26. data/ext/thin_parser/parser.h +49 -0
  27. data/ext/thin_parser/parser.rl +157 -0
  28. data/ext/thin_parser/thin.c +436 -0
  29. data/lib/rack/adapter/loader.rb +91 -0
  30. data/lib/rack/adapter/rails.rb +183 -0
  31. data/lib/thin.rb +56 -0
  32. data/lib/thin/backends/base.rb +149 -0
  33. data/lib/thin/backends/swiftiply_client.rb +56 -0
  34. data/lib/thin/backends/tcp_server.rb +29 -0
  35. data/lib/thin/backends/unix_server.rb +51 -0
  36. data/lib/thin/command.rb +53 -0
  37. data/lib/thin/connection.rb +224 -0
  38. data/lib/thin/controllers/cluster.rb +178 -0
  39. data/lib/thin/controllers/controller.rb +188 -0
  40. data/lib/thin/controllers/service.rb +75 -0
  41. data/lib/thin/controllers/service.sh.erb +39 -0
  42. data/lib/thin/daemonizing.rb +180 -0
  43. data/lib/thin/headers.rb +39 -0
  44. data/lib/thin/logging.rb +54 -0
  45. data/lib/thin/request.rb +156 -0
  46. data/lib/thin/response.rb +101 -0
  47. data/lib/thin/runner.rb +220 -0
  48. data/lib/thin/server.rb +253 -0
  49. data/lib/thin/stats.html.erb +216 -0
  50. data/lib/thin/stats.rb +52 -0
  51. data/lib/thin/statuses.rb +43 -0
  52. data/lib/thin/version.rb +32 -0
  53. data/lib/thin_parser.so +0 -0
  54. data/spec/backends/swiftiply_client_spec.rb +66 -0
  55. data/spec/backends/tcp_server_spec.rb +33 -0
  56. data/spec/backends/unix_server_spec.rb +37 -0
  57. data/spec/command_spec.rb +25 -0
  58. data/spec/configs/cluster.yml +9 -0
  59. data/spec/configs/single.yml +9 -0
  60. data/spec/connection_spec.rb +106 -0
  61. data/spec/controllers/cluster_spec.rb +267 -0
  62. data/spec/controllers/controller_spec.rb +129 -0
  63. data/spec/controllers/service_spec.rb +50 -0
  64. data/spec/daemonizing_spec.rb +196 -0
  65. data/spec/headers_spec.rb +40 -0
  66. data/spec/logging_spec.rb +46 -0
  67. data/spec/perf/request_perf_spec.rb +50 -0
  68. data/spec/perf/response_perf_spec.rb +19 -0
  69. data/spec/perf/server_perf_spec.rb +39 -0
  70. data/spec/rack/loader_spec.rb +42 -0
  71. data/spec/rack/rails_adapter_spec.rb +173 -0
  72. data/spec/rails_app/app/controllers/application.rb +10 -0
  73. data/spec/rails_app/app/controllers/simple_controller.rb +19 -0
  74. data/spec/rails_app/app/helpers/application_helper.rb +3 -0
  75. data/spec/rails_app/app/views/simple/index.html.erb +15 -0
  76. data/spec/rails_app/config/boot.rb +109 -0
  77. data/spec/rails_app/config/environment.rb +64 -0
  78. data/spec/rails_app/config/environments/development.rb +18 -0
  79. data/spec/rails_app/config/environments/production.rb +19 -0
  80. data/spec/rails_app/config/environments/test.rb +22 -0
  81. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  82. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  83. data/spec/rails_app/config/routes.rb +35 -0
  84. data/spec/rails_app/public/404.html +30 -0
  85. data/spec/rails_app/public/422.html +30 -0
  86. data/spec/rails_app/public/500.html +30 -0
  87. data/spec/rails_app/public/dispatch.cgi +10 -0
  88. data/spec/rails_app/public/dispatch.fcgi +24 -0
  89. data/spec/rails_app/public/dispatch.rb +10 -0
  90. data/spec/rails_app/public/favicon.ico +0 -0
  91. data/spec/rails_app/public/images/rails.png +0 -0
  92. data/spec/rails_app/public/index.html +277 -0
  93. data/spec/rails_app/public/javascripts/application.js +2 -0
  94. data/spec/rails_app/public/javascripts/controls.js +963 -0
  95. data/spec/rails_app/public/javascripts/dragdrop.js +972 -0
  96. data/spec/rails_app/public/javascripts/effects.js +1120 -0
  97. data/spec/rails_app/public/javascripts/prototype.js +4225 -0
  98. data/spec/rails_app/public/robots.txt +5 -0
  99. data/spec/rails_app/script/about +3 -0
  100. data/spec/rails_app/script/console +3 -0
  101. data/spec/rails_app/script/destroy +3 -0
  102. data/spec/rails_app/script/generate +3 -0
  103. data/spec/rails_app/script/performance/benchmarker +3 -0
  104. data/spec/rails_app/script/performance/profiler +3 -0
  105. data/spec/rails_app/script/performance/request +3 -0
  106. data/spec/rails_app/script/plugin +3 -0
  107. data/spec/rails_app/script/process/inspector +3 -0
  108. data/spec/rails_app/script/process/reaper +3 -0
  109. data/spec/rails_app/script/process/spawner +3 -0
  110. data/spec/rails_app/script/runner +3 -0
  111. data/spec/rails_app/script/server +3 -0
  112. data/spec/request/mongrel_spec.rb +39 -0
  113. data/spec/request/parser_spec.rb +254 -0
  114. data/spec/request/persistent_spec.rb +35 -0
  115. data/spec/request/processing_spec.rb +50 -0
  116. data/spec/response_spec.rb +91 -0
  117. data/spec/runner_spec.rb +168 -0
  118. data/spec/server/builder_spec.rb +44 -0
  119. data/spec/server/pipelining_spec.rb +110 -0
  120. data/spec/server/robustness_spec.rb +34 -0
  121. data/spec/server/stopping_spec.rb +55 -0
  122. data/spec/server/swiftiply.yml +6 -0
  123. data/spec/server/swiftiply_spec.rb +32 -0
  124. data/spec/server/tcp_spec.rb +57 -0
  125. data/spec/server/threaded_spec.rb +27 -0
  126. data/spec/server/unix_socket_spec.rb +26 -0
  127. data/spec/server_spec.rb +100 -0
  128. data/spec/spec_helper.rb +220 -0
  129. data/tasks/announce.rake +22 -0
  130. data/tasks/deploy.rake +13 -0
  131. data/tasks/email.erb +30 -0
  132. data/tasks/gem.rake +66 -0
  133. data/tasks/rdoc.rake +25 -0
  134. data/tasks/site.rake +15 -0
  135. data/tasks/spec.rake +43 -0
  136. data/tasks/stats.rake +28 -0
  137. metadata +251 -0
@@ -0,0 +1,253 @@
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
+ # A tag that will show in the process listing
64
+ attr_accessor :tag
65
+
66
+ # Backend handling the connections to the clients.
67
+ attr_accessor :backend
68
+
69
+ # Maximum number of seconds for incoming data to arrive before the connection
70
+ # is dropped.
71
+ def_delegators :backend, :timeout, :timeout=
72
+
73
+ # Maximum number of file or socket descriptors that the server may open.
74
+ def_delegators :backend, :maximum_connections, :maximum_connections=
75
+
76
+ # Maximum number of connection that can be persistent at the same time.
77
+ # Most browser never close the connection so most of the time they are closed
78
+ # when the timeout occur. If we don't control the number of persistent connection,
79
+ # if would be very easy to overflow the server for a DoS attack.
80
+ def_delegators :backend, :maximum_persistent_connections, :maximum_persistent_connections=
81
+
82
+ # Allow using threads in the backend.
83
+ def_delegators :backend, :threaded?, :threaded=
84
+
85
+ # Allow using SSL in the backend.
86
+ def_delegators :backend, :ssl?, :ssl=, :ssl_options=
87
+
88
+ # Address and port on which the server is listening for connections.
89
+ def_delegators :backend, :host, :port
90
+
91
+ # UNIX domain socket on which the server is listening for connections.
92
+ def_delegator :backend, :socket
93
+
94
+ # Disable the use of epoll under Linux
95
+ def_delegators :backend, :no_epoll, :no_epoll=
96
+
97
+ def initialize(*args, &block)
98
+ host, port, options = DEFAULT_HOST, DEFAULT_PORT, {}
99
+
100
+ # Guess each parameter by its type so they can be
101
+ # received in any order.
102
+ args.each do |arg|
103
+ case arg
104
+ when Fixnum, /^\d+$/ then port = arg.to_i
105
+ when String then host = arg
106
+ when Hash then options = arg
107
+ else
108
+ @app = arg if arg.respond_to?(:call)
109
+ end
110
+ end
111
+
112
+ # Set tag if needed
113
+ self.tag = options[:tag]
114
+
115
+ # Try to intelligently select which backend to use.
116
+ @backend = select_backend(host, port, options)
117
+
118
+ load_cgi_multipart_eof_fix
119
+
120
+ @backend.server = self
121
+
122
+ # Set defaults
123
+ @backend.maximum_connections = DEFAULT_MAXIMUM_CONNECTIONS
124
+ @backend.maximum_persistent_connections = DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
125
+ @backend.timeout = DEFAULT_TIMEOUT
126
+
127
+ # Allow using Rack builder as a block
128
+ @app = Rack::Builder.new(&block).to_app if block
129
+
130
+ # If in debug mode, wrap in logger adapter
131
+ @app = Rack::CommonLogger.new(@app) if Logging.debug?
132
+
133
+ setup_signals unless options[:signals].class == FalseClass
134
+ end
135
+
136
+ # Lil' shortcut to turn this:
137
+ #
138
+ # Server.new(...).start
139
+ #
140
+ # into this:
141
+ #
142
+ # Server.start(...)
143
+ #
144
+ def self.start(*args, &block)
145
+ new(*args, &block).start!
146
+ end
147
+
148
+ # Start the server and listen for connections.
149
+ def start
150
+ raise ArgumentError, 'app required' unless @app
151
+
152
+ log ">> Thin web server (v#{VERSION::STRING} codename #{VERSION::CODENAME})"
153
+ debug ">> Debugging ON"
154
+ trace ">> Tracing ON"
155
+
156
+ log ">> Maximum connections set to #{@backend.maximum_connections}"
157
+ log ">> Listening on #{@backend}, CTRL+C to stop"
158
+
159
+ @backend.start
160
+ end
161
+ alias :start! :start
162
+
163
+ # == Gracefull shutdown
164
+ # Stops the server after processing all current connections.
165
+ # As soon as this method is called, the server stops accepting
166
+ # new requests and wait for all current connections to finish.
167
+ # Calling twice is the equivalent of calling <tt>stop!</tt>.
168
+ def stop
169
+ if running?
170
+ @backend.stop
171
+ unless @backend.empty?
172
+ log ">> Waiting for #{@backend.size} connection(s) to finish, " +
173
+ "can take up to #{timeout} sec, CTRL+C to stop now"
174
+ end
175
+ else
176
+ stop!
177
+ end
178
+ end
179
+
180
+ # == Force shutdown
181
+ # Stops the server closing all current connections right away.
182
+ # This doesn't wait for connection to finish their work and send data.
183
+ # All current requests will be dropped.
184
+ def stop!
185
+ log ">> Stopping ..."
186
+
187
+ @backend.stop!
188
+ end
189
+
190
+ # == Configure the server
191
+ # The process might need to have superuser privilege to configure
192
+ # server with optimal options.
193
+ def config
194
+ @backend.config
195
+ end
196
+
197
+ # Name of the server and type of backend used.
198
+ # This is also the name of the process in which Thin is running as a daemon.
199
+ def name
200
+ "thin server (#{@backend})" + (tag ? " [#{tag}]" : "")
201
+ end
202
+ alias :to_s :name
203
+
204
+ # Return +true+ if the server is running and ready to receive requests.
205
+ # Note that the server might still be running and return +false+ when
206
+ # shuting down and waiting for active connections to complete.
207
+ def running?
208
+ @backend.running?
209
+ end
210
+
211
+ protected
212
+ # Register signals:
213
+ # * INT calls +stop+ to shutdown gracefully.
214
+ # * TERM calls <tt>stop!</tt> to force shutdown.
215
+ def setup_signals
216
+ trap('INT') { stop! }
217
+ trap('TERM') { stop! }
218
+ unless Thin.win?
219
+ trap('QUIT') { stop }
220
+ trap('HUP') { restart }
221
+ end
222
+ end
223
+
224
+ def select_backend(host, port, options)
225
+ case
226
+ when options.has_key?(:backend)
227
+ raise ArgumentError, ":backend must be a class" unless options[:backend].is_a?(Class)
228
+ options[:backend].new(host, port, options)
229
+ when options.has_key?(:swiftiply)
230
+ Backends::SwiftiplyClient.new(host, port, options)
231
+ when host.include?('/')
232
+ Backends::UnixServer.new(host)
233
+ else
234
+ Backends::TcpServer.new(host, port)
235
+ end
236
+ end
237
+
238
+ # Taken from Mongrel cgi_multipart_eof_fix
239
+ # Ruby 1.8.5 has a security bug in cgi.rb, we need to patch it.
240
+ def load_cgi_multipart_eof_fix
241
+ version = RUBY_VERSION.split('.').map { |i| i.to_i }
242
+
243
+ if version[0] <= 1 && version[1] <= 8 && version[2] <= 5 && RUBY_PLATFORM !~ /java/
244
+ begin
245
+ require 'cgi_multipart_eof_fix'
246
+ rescue LoadError
247
+ log "!! Ruby 1.8.5 is not secure please install cgi_multipart_eof_fix:"
248
+ log " gem install cgi_multipart_eof_fix"
249
+ end
250
+ end
251
+ end
252
+ end
253
+ 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>