thin 1.2.3-x86-mswin32

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 (137) hide show
  1. data/CHANGELOG +263 -0
  2. data/COPYING +18 -0
  3. data/README +69 -0
  4. data/Rakefile +36 -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 +452 -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 +433 -0
  29. data/lib/rack/adapter/loader.rb +79 -0
  30. data/lib/rack/adapter/rails.rb +181 -0
  31. data/lib/thin.rb +46 -0
  32. data/lib/thin/backends/base.rb +141 -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 +222 -0
  38. data/lib/thin/controllers/cluster.rb +127 -0
  39. data/lib/thin/controllers/controller.rb +183 -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 +174 -0
  43. data/lib/thin/headers.rb +39 -0
  44. data/lib/thin/logging.rb +54 -0
  45. data/lib/thin/request.rb +153 -0
  46. data/lib/thin/response.rb +101 -0
  47. data/lib/thin/runner.rb +209 -0
  48. data/lib/thin/server.rb +247 -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 +235 -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 +192 -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 +29 -0
  71. data/spec/rack/rails_adapter_spec.rb +106 -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 +215 -0
  114. data/spec/request/persistent_spec.rb +35 -0
  115. data/spec/request/processing_spec.rb +45 -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 +96 -0
  128. data/spec/spec_helper.rb +219 -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 +74 -0
  133. data/tasks/rdoc.rake +25 -0
  134. data/tasks/site.rake +15 -0
  135. data/tasks/spec.rake +49 -0
  136. data/tasks/stats.rake +28 -0
  137. metadata +246 -0
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+ # Simple benchmark to compare Thin performance against
3
+ # other webservers supported by Rack.
4
+ #
5
+ # Run with:
6
+ #
7
+ # ruby simple.rb [num of request] [print|graph] [concurrency levels]
8
+ #
9
+
10
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
11
+ require 'thin'
12
+
13
+ require File.dirname(__FILE__) + '/benchmarker'
14
+ require 'optparse'
15
+
16
+ options = {
17
+ :requests => 1000,
18
+ :concurrencies => [1, 10, 100],
19
+ :keep_alive => false,
20
+ :output => :table
21
+ }
22
+
23
+ OptionParser.new do |opts|
24
+ opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
25
+
26
+ opts.on("-n", "--requests NUM", "Number of requests") { |num| options[:requests] = num.to_i }
27
+ opts.on("-c", "--concurrencies EXP", "Concurrency levels") { |exp| options[:concurrencies] = eval(exp).to_a }
28
+ opts.on("-k", "--keep-alive", "Use persistent connections") { options[:keep_alive] = true }
29
+ opts.on("-t", "--table", "Output as text table") { options[:output] = :table }
30
+ opts.on("-g", "--graph", "Output as graph") { options[:output] = :graph }
31
+
32
+ opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
33
+ end.parse!(ARGV)
34
+
35
+ # benchmark output_type, %w(WEBrick Mongrel EMongrel Thin), request, levels
36
+ b = Benchmarker.new
37
+ b.requests = options[:requests]
38
+ b.concurrencies = options[:concurrencies]
39
+ b.keep_alive = options[:keep_alive]
40
+
41
+ case options[:output]
42
+ when :table
43
+ puts 'server request concurrency req/s failures'
44
+ puts '=' * 52
45
+
46
+ b.writer do |server, requests, concurrency, req_sec, failed|
47
+ puts "#{server.ljust(8)} #{requests} #{concurrency.to_s.ljust(4)} #{req_sec.to_s.ljust(8)} #{failed}"
48
+ end
49
+
50
+ b.run!
51
+
52
+ when :graph
53
+ require '/usr/local/lib/ruby/gems/1.8/gems/gruff-0.2.9/lib/gruff'
54
+ g = Gruff::Area.new
55
+ g.title = "#{options[:requests]} requests"
56
+ g.title << ' w/ Keep-Alive' if options[:keep_alive]
57
+
58
+ g.x_axis_label = 'Concurrency'
59
+ g.y_axis_label = 'Requests / sec'
60
+ g.maximum_value = 0
61
+ g.minimum_value = 0
62
+ g.labels = {}
63
+ b.concurrencies.each_with_index { |c, i| g.labels[i] = c.to_s }
64
+
65
+ results = {}
66
+
67
+ b.writer do |server, requests, concurrency, req_sec, failed|
68
+ print '.'
69
+ results[server] ||= []
70
+ results[server] << req_sec
71
+ end
72
+
73
+ b.run!
74
+ puts
75
+
76
+ results.each do |server, concurrencies|
77
+ g.data(server, concurrencies)
78
+ end
79
+
80
+ g.write('bench.png')
81
+ `open bench.png`
82
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # Thin command line interface script.
3
+ # Run <tt>thin -h</tt> to get more usage.
4
+
5
+ require 'thin'
6
+ Thin::Runner.new(ARGV).run!
@@ -0,0 +1,32 @@
1
+ # Run with: ruby adapter.rb
2
+ # Then browse to http://localhost:3000/test
3
+ # and http://localhost:3000/files/adapter.rb
4
+ require 'thin'
5
+
6
+ class SimpleAdapter
7
+ def call(env)
8
+ body = ["hello!"]
9
+ [
10
+ 200,
11
+ { 'Content-Type' => 'text/plain' },
12
+ body
13
+ ]
14
+ end
15
+ end
16
+
17
+ Thin::Server.start('0.0.0.0', 3000) do
18
+ use Rack::CommonLogger
19
+ map '/test' do
20
+ run SimpleAdapter.new
21
+ end
22
+ map '/files' do
23
+ run Rack::File.new('.')
24
+ end
25
+ end
26
+
27
+ # You could also start the server like this:
28
+ #
29
+ # app = Rack::URLMap.new('/test' => SimpleAdapter.new,
30
+ # '/files' => Rack::File.new('.'))
31
+ # Thin::Server.start('0.0.0.0', 3000, app)
32
+ #
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env rackup -s thin
2
+ #
3
+ # async_app.ru
4
+ # raggi/thin
5
+ #
6
+ # A second demo app for async rack + thin app processing!
7
+ # Now using http status code 100 instead.
8
+ #
9
+ # Created by James Tucker on 2008-06-17.
10
+ # Copyright 2008 James Tucker <raggi@rubyforge.org>.
11
+ #
12
+ #--
13
+ # Benchmark Results:
14
+ #
15
+ # raggi@mbk:~$ ab -c 100 -n 500 http://127.0.0.1:3000/
16
+ # This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
17
+ # Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
18
+ # Copyright 2006 The Apache Software Foundation, http://www.apache.org/
19
+ #
20
+ # Benchmarking 127.0.0.1 (be patient)
21
+ # Completed 100 requests
22
+ # Completed 200 requests
23
+ # Completed 300 requests
24
+ # Completed 400 requests
25
+ # Finished 500 requests
26
+ #
27
+ #
28
+ # Server Software: thin
29
+ # Server Hostname: 127.0.0.1
30
+ # Server Port: 3000
31
+ #
32
+ # Document Path: /
33
+ # Document Length: 12 bytes
34
+ #
35
+ # Concurrency Level: 100
36
+ # Time taken for tests: 5.263089 seconds
37
+ # Complete requests: 500
38
+ # Failed requests: 0
39
+ # Write errors: 0
40
+ # Total transferred: 47000 bytes
41
+ # HTML transferred: 6000 bytes
42
+ # Requests per second: 95.00 [#/sec] (mean)
43
+ # Time per request: 1052.618 [ms] (mean)
44
+ # Time per request: 10.526 [ms] (mean, across all concurrent requests)
45
+ # Transfer rate: 8.55 [Kbytes/sec] received
46
+ #
47
+ # Connection Times (ms)
48
+ # min mean[+/-sd] median max
49
+ # Connect: 0 3 2.2 3 8
50
+ # Processing: 1042 1046 3.1 1046 1053
51
+ # Waiting: 1037 1042 3.6 1041 1050
52
+ # Total: 1045 1049 3.1 1049 1057
53
+ #
54
+ # Percentage of the requests served within a certain time (ms)
55
+ # 50% 1049
56
+ # 66% 1051
57
+ # 75% 1053
58
+ # 80% 1053
59
+ # 90% 1054
60
+ # 95% 1054
61
+ # 98% 1056
62
+ # 99% 1057
63
+ # 100% 1057 (longest request)
64
+
65
+ class DeferrableBody
66
+ include EventMachine::Deferrable
67
+
68
+ def call(body)
69
+ body.each do |chunk|
70
+ @body_callback.call(chunk)
71
+ end
72
+ end
73
+
74
+ def each &blk
75
+ @body_callback = blk
76
+ end
77
+
78
+ end
79
+
80
+ class AsyncApp
81
+
82
+ # This is a template async response. N.B. Can't use string for body on 1.9
83
+ AsyncResponse = [-1, {}, []].freeze
84
+
85
+ def call(env)
86
+
87
+ body = DeferrableBody.new
88
+
89
+ # Get the headers out there asap, let the client know we're alive...
90
+ EventMachine::next_tick { env['async.callback'].call [200, {'Content-Type' => 'text/plain'}, body] }
91
+
92
+ # Semi-emulate a long db request, instead of a timer, in reality we'd be
93
+ # waiting for the response data. Whilst this happens, other connections
94
+ # can be serviced.
95
+ # This could be any callback based thing though, a deferrable waiting on
96
+ # IO data, a db request, an http request, an smtp send, whatever.
97
+ EventMachine::add_timer(1) {
98
+ body.call ["Woah, async!\n"]
99
+
100
+ EventMachine::next_tick {
101
+ # This could actually happen any time, you could spawn off to new
102
+ # threads, pause as a good looking lady walks by, whatever.
103
+ # Just shows off how we can defer chunks of data in the body, you can
104
+ # even call this many times.
105
+ body.call ["Cheers then!"]
106
+ body.succeed
107
+ }
108
+ }
109
+
110
+ # throw :async # Still works for supporting non-async frameworks...
111
+
112
+ AsyncResponse # May end up in Rack :-)
113
+ end
114
+
115
+ end
116
+
117
+ # The additions to env for async.connection and async.callback absolutely
118
+ # destroy the speed of the request if Lint is doing it's checks on env.
119
+ # It is also important to note that an async response will not pass through
120
+ # any further middleware, as the async response notification has been passed
121
+ # right up to the webserver, and the callback goes directly there too.
122
+ # Middleware could possibly catch :async, and also provide a different
123
+ # async.connection and async.callback.
124
+
125
+ # use Rack::Lint
126
+ run AsyncApp.new
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env rackup -s thin
2
+ #
3
+ # async_chat.ru
4
+ # raggi/thin
5
+ #
6
+ # Created by James Tucker on 2008-06-19.
7
+ # Copyright 2008 James Tucker <raggi@rubyforge.org>.
8
+
9
+ # Uncomment if appropriate for you..
10
+ EM.epoll
11
+ # EM.kqueue # bug on OS X in 0.12?
12
+
13
+ class DeferrableBody
14
+ include EventMachine::Deferrable
15
+
16
+ def initialize
17
+ @queue = []
18
+ end
19
+
20
+ def schedule_dequeue
21
+ return unless @body_callback
22
+ EventMachine::next_tick do
23
+ next unless body = @queue.shift
24
+ body.each do |chunk|
25
+ @body_callback.call(chunk)
26
+ end
27
+ schedule_dequeue unless @queue.empty?
28
+ end
29
+ end
30
+
31
+ def call(body)
32
+ @queue << body
33
+ schedule_dequeue
34
+ end
35
+
36
+ def each &blk
37
+ @body_callback = blk
38
+ schedule_dequeue
39
+ end
40
+
41
+ end
42
+
43
+ class Chat
44
+
45
+ module UserBody
46
+ attr_accessor :username
47
+ end
48
+
49
+ def initialize
50
+ @users = {}
51
+ end
52
+
53
+ def render_page
54
+ [] << <<-EOPAGE
55
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
56
+ <html>
57
+ <head>
58
+ <style>
59
+ body {
60
+ font-family: sans-serif;
61
+ margin: 0;
62
+ padding: 0;
63
+ margin-top: 4em;
64
+ margin-bottom: 1em;
65
+ }
66
+ #header {
67
+ background: silver;
68
+ height: 4em;
69
+ width: 100%;
70
+ position: fixed;
71
+ top: 0px;
72
+ border-bottom: 1px solid black;
73
+ padding-left: 0.5em;
74
+ }
75
+ #messages {
76
+ width: 100%;
77
+ height: 100%;
78
+ }
79
+ .message {
80
+ margin-left: 1em;
81
+ }
82
+ #send_form {
83
+ position: fixed;
84
+ bottom: 0px;
85
+ height: 1em;
86
+ width: 100%;
87
+ }
88
+ #message_box {
89
+ background: silver;
90
+ width: 100%;
91
+ border: 0px;
92
+ border-top: 1px solid black;
93
+ }
94
+ .gray {
95
+ color: gray;
96
+ }
97
+ </style>
98
+ <script type="text/javascript" src="http://ra66i.org/tmp/jquery-1.2.6.min.js"></script>
99
+ <script type="text/javascript">
100
+ XHR = function() {
101
+ var request = false;
102
+ try { request = new ActiveXObject('Msxml2.XMLHTTP'); } catch(e) {
103
+ try { request = new ActiveXObject('Microsoft.XMLHTTP'); } catch(e1) {
104
+ try { request = new XMLHttpRequest(); } catch(e2) {
105
+ return false;
106
+ }
107
+ }
108
+ }
109
+ return request;
110
+ }
111
+ scroll = function() {
112
+ window.scrollBy(0,50);
113
+ setTimeout('scroll()',100);
114
+ }
115
+ focus = function() {
116
+ $('#message_box').focus();
117
+ }
118
+ send_message = function(message_box) {
119
+ xhr = XHR();
120
+ xhr.open("POST", "/", true);
121
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
122
+ xhr.setRequestHeader("X_REQUESTED_WITH", "XMLHttpRequest");
123
+ xhr.send("message="+escape(message_box.value));
124
+ scroll();
125
+ message_box.value = '';
126
+ focus();
127
+ return false;
128
+ }
129
+ new_message = function(username, message) {
130
+ // TODO html escape message
131
+ formatted_message = "<div class='message'>" + username + ": " + message + "</div>";
132
+ messages_div = $('#messages');
133
+ $(formatted_message).appendTo(messages_div);
134
+ scroll();
135
+ return true;
136
+ }
137
+ </script>
138
+ <title>Async Chat</title>
139
+ </head>
140
+ <body>
141
+ <div id="header">
142
+ <h1>Async Chat</h1>
143
+ </div>
144
+ <div id="messages" onclick="focus();">
145
+ <span class="gray">Your first message will become your nickname!</span>
146
+ <span>Users: #{@users.map{|k,u|u.username}.join(', ')}</span>
147
+ </div>
148
+ <form id="send_form" onSubmit="return send_message(this.message)">
149
+ <input type="text" id="message_box" name="message"></input>
150
+ </form>
151
+ <script type="text/javascript">focus();</script>
152
+ </body>
153
+ </html>
154
+ EOPAGE
155
+ end
156
+
157
+ def register_user(user_id, renderer)
158
+ body = create_user(user_id)
159
+ body.call render_page
160
+ body.errback { delete_user user_id }
161
+ body.callback { delete_user user_id }
162
+
163
+ EventMachine::next_tick do
164
+ renderer.call [200, {'Content-Type' => 'text/html'}, body]
165
+ end
166
+ end
167
+
168
+ def new_message(user_id, message)
169
+ return unless @users[user_id]
170
+ if @users[user_id].username == :anonymous
171
+ username = unique_username(message)
172
+ log "User: #{user_id} is #{username}"
173
+ @users[user_id].username = message
174
+ message = "<span class='gray'>-> #{username} signed on.</span>"
175
+ end
176
+ username ||= @users[user_id].username
177
+ log "User: #{username} sent: #{message}"
178
+ @users.each do |id, body|
179
+ EventMachine::next_tick { body.call [js_message(username, message)] }
180
+ end
181
+ end
182
+
183
+ private
184
+ def unique_username(name)
185
+ name.concat('_') while @users.any? { |id,u| name == u.username }
186
+ name
187
+ end
188
+
189
+ def log(str)
190
+ print str, "\n"
191
+ end
192
+
193
+ def add_user(id, body)
194
+ @users[id] = body
195
+ end
196
+
197
+ def delete_user(id)
198
+ message = "User: #{id} - #{@users[id].username if @users[id]} disconnected."
199
+ log message
200
+ new_message(id, message)
201
+ @users.delete id
202
+ end
203
+
204
+ def js_message(username, message)
205
+ %(<script type="text/javascript">new_message("#{username}","#{message}");</script>)
206
+ end
207
+
208
+ def create_user(id)
209
+ message = "User: #{id} connected."
210
+ log message
211
+ new_message(id, message)
212
+ body = DeferrableBody.new
213
+ body.extend UserBody
214
+ body.username = :anonymous
215
+ add_user(id, body)
216
+ body
217
+ end
218
+
219
+ end
220
+
221
+ class AsyncChat
222
+
223
+ AsyncResponse = [-1, {}, []].freeze
224
+ AjaxResponse = [200, {}, []].freeze
225
+
226
+ def initialize
227
+ @chat = Chat.new
228
+ end
229
+
230
+ def call(env)
231
+ request = Rack::Request.new(env)
232
+ # TODO - cookie me, baby
233
+ user_id = request.env['REMOTE_ADDR']
234
+ if request.xhr?
235
+ message = request['message']
236
+ @chat.new_message(user_id, Rack::Utils.escape_html(message))
237
+ AjaxResponse
238
+ else
239
+ renderer = request.env['async.callback']
240
+ @chat.register_user(user_id, renderer)
241
+ AsyncResponse
242
+ end
243
+ end
244
+
245
+ end
246
+
247
+ run AsyncChat.new