thin 1.2.6-x86-mingw32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +273 -0
 - data/COPYING +18 -0
 - data/README +69 -0
 - data/Rakefile +39 -0
 - data/benchmark/abc +51 -0
 - data/benchmark/benchmarker.rb +80 -0
 - data/benchmark/runner +82 -0
 - data/bin/thin +6 -0
 - data/example/adapter.rb +32 -0
 - data/example/async_app.ru +126 -0
 - data/example/async_chat.ru +247 -0
 - data/example/async_tailer.ru +100 -0
 - data/example/config.ru +22 -0
 - data/example/monit_sockets +20 -0
 - data/example/monit_unixsock +20 -0
 - data/example/myapp.rb +1 -0
 - data/example/ramaze.ru +12 -0
 - data/example/thin.god +80 -0
 - data/example/thin_solaris_smf.erb +36 -0
 - data/example/thin_solaris_smf.readme.txt +150 -0
 - data/example/vlad.rake +64 -0
 - data/ext/thin_parser/common.rl +55 -0
 - data/ext/thin_parser/ext_help.h +14 -0
 - data/ext/thin_parser/extconf.rb +6 -0
 - data/ext/thin_parser/parser.c +1185 -0
 - data/ext/thin_parser/parser.h +49 -0
 - data/ext/thin_parser/parser.rl +157 -0
 - data/ext/thin_parser/thin.c +436 -0
 - data/lib/rack/adapter/loader.rb +91 -0
 - data/lib/rack/adapter/rails.rb +180 -0
 - data/lib/thin.rb +46 -0
 - data/lib/thin/backends/base.rb +141 -0
 - data/lib/thin/backends/swiftiply_client.rb +56 -0
 - data/lib/thin/backends/tcp_server.rb +29 -0
 - data/lib/thin/backends/unix_server.rb +51 -0
 - data/lib/thin/command.rb +53 -0
 - data/lib/thin/connection.rb +222 -0
 - data/lib/thin/controllers/cluster.rb +178 -0
 - data/lib/thin/controllers/controller.rb +182 -0
 - data/lib/thin/controllers/service.rb +75 -0
 - data/lib/thin/controllers/service.sh.erb +39 -0
 - data/lib/thin/daemonizing.rb +176 -0
 - data/lib/thin/headers.rb +39 -0
 - data/lib/thin/logging.rb +54 -0
 - data/lib/thin/request.rb +157 -0
 - data/lib/thin/response.rb +101 -0
 - data/lib/thin/runner.rb +212 -0
 - data/lib/thin/server.rb +248 -0
 - data/lib/thin/stats.html.erb +216 -0
 - data/lib/thin/stats.rb +52 -0
 - data/lib/thin/statuses.rb +43 -0
 - data/lib/thin/version.rb +32 -0
 - data/lib/thin_parser.so +0 -0
 - data/spec/backends/swiftiply_client_spec.rb +66 -0
 - data/spec/backends/tcp_server_spec.rb +33 -0
 - data/spec/backends/unix_server_spec.rb +37 -0
 - data/spec/command_spec.rb +25 -0
 - data/spec/configs/cluster.yml +9 -0
 - data/spec/configs/single.yml +9 -0
 - data/spec/connection_spec.rb +106 -0
 - data/spec/controllers/cluster_spec.rb +267 -0
 - data/spec/controllers/controller_spec.rb +129 -0
 - data/spec/controllers/service_spec.rb +50 -0
 - data/spec/daemonizing_spec.rb +192 -0
 - data/spec/headers_spec.rb +40 -0
 - data/spec/logging_spec.rb +46 -0
 - data/spec/perf/request_perf_spec.rb +50 -0
 - data/spec/perf/response_perf_spec.rb +19 -0
 - data/spec/perf/server_perf_spec.rb +39 -0
 - data/spec/rack/loader_spec.rb +42 -0
 - data/spec/rack/rails_adapter_spec.rb +106 -0
 - data/spec/rails_app/app/controllers/application.rb +10 -0
 - data/spec/rails_app/app/controllers/simple_controller.rb +19 -0
 - data/spec/rails_app/app/helpers/application_helper.rb +3 -0
 - data/spec/rails_app/app/views/simple/index.html.erb +15 -0
 - data/spec/rails_app/config/boot.rb +109 -0
 - data/spec/rails_app/config/environment.rb +64 -0
 - data/spec/rails_app/config/environments/development.rb +18 -0
 - data/spec/rails_app/config/environments/production.rb +19 -0
 - data/spec/rails_app/config/environments/test.rb +22 -0
 - data/spec/rails_app/config/initializers/inflections.rb +10 -0
 - data/spec/rails_app/config/initializers/mime_types.rb +5 -0
 - data/spec/rails_app/config/routes.rb +35 -0
 - data/spec/rails_app/public/404.html +30 -0
 - data/spec/rails_app/public/422.html +30 -0
 - data/spec/rails_app/public/500.html +30 -0
 - data/spec/rails_app/public/dispatch.cgi +10 -0
 - data/spec/rails_app/public/dispatch.fcgi +24 -0
 - data/spec/rails_app/public/dispatch.rb +10 -0
 - data/spec/rails_app/public/favicon.ico +0 -0
 - data/spec/rails_app/public/images/rails.png +0 -0
 - data/spec/rails_app/public/index.html +277 -0
 - data/spec/rails_app/public/javascripts/application.js +2 -0
 - data/spec/rails_app/public/javascripts/controls.js +963 -0
 - data/spec/rails_app/public/javascripts/dragdrop.js +972 -0
 - data/spec/rails_app/public/javascripts/effects.js +1120 -0
 - data/spec/rails_app/public/javascripts/prototype.js +4225 -0
 - data/spec/rails_app/public/robots.txt +5 -0
 - data/spec/rails_app/script/about +3 -0
 - data/spec/rails_app/script/console +3 -0
 - data/spec/rails_app/script/destroy +3 -0
 - data/spec/rails_app/script/generate +3 -0
 - data/spec/rails_app/script/performance/benchmarker +3 -0
 - data/spec/rails_app/script/performance/profiler +3 -0
 - data/spec/rails_app/script/performance/request +3 -0
 - data/spec/rails_app/script/plugin +3 -0
 - data/spec/rails_app/script/process/inspector +3 -0
 - data/spec/rails_app/script/process/reaper +3 -0
 - data/spec/rails_app/script/process/spawner +3 -0
 - data/spec/rails_app/script/runner +3 -0
 - data/spec/rails_app/script/server +3 -0
 - data/spec/request/mongrel_spec.rb +39 -0
 - data/spec/request/parser_spec.rb +243 -0
 - data/spec/request/persistent_spec.rb +35 -0
 - data/spec/request/processing_spec.rb +50 -0
 - data/spec/response_spec.rb +91 -0
 - data/spec/runner_spec.rb +168 -0
 - data/spec/server/builder_spec.rb +44 -0
 - data/spec/server/pipelining_spec.rb +110 -0
 - data/spec/server/robustness_spec.rb +34 -0
 - data/spec/server/stopping_spec.rb +55 -0
 - data/spec/server/swiftiply.yml +6 -0
 - data/spec/server/swiftiply_spec.rb +32 -0
 - data/spec/server/tcp_spec.rb +57 -0
 - data/spec/server/threaded_spec.rb +27 -0
 - data/spec/server/unix_socket_spec.rb +26 -0
 - data/spec/server_spec.rb +100 -0
 - data/spec/spec_helper.rb +219 -0
 - data/tasks/announce.rake +22 -0
 - data/tasks/deploy.rake +13 -0
 - data/tasks/email.erb +30 -0
 - data/tasks/gem.rake +66 -0
 - data/tasks/rdoc.rake +25 -0
 - data/tasks/site.rake +15 -0
 - data/tasks/spec.rake +43 -0
 - data/tasks/stats.rake +28 -0
 - metadata +219 -0
 
| 
         @@ -0,0 +1,80 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rack/lobster'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class Benchmarker
         
     | 
| 
      
 4 
     | 
    
         
            +
              PORT    = 7000
         
     | 
| 
      
 5 
     | 
    
         
            +
              ADDRESS = '0.0.0.0'
         
     | 
| 
      
 6 
     | 
    
         
            +
              
         
     | 
| 
      
 7 
     | 
    
         
            +
              attr_accessor :requests, :concurrencies, :servers, :keep_alive
         
     | 
| 
      
 8 
     | 
    
         
            +
              
         
     | 
| 
      
 9 
     | 
    
         
            +
              def initialize
         
     | 
| 
      
 10 
     | 
    
         
            +
                @servers = %w(Mongrel EMongrel Thin)
         
     | 
| 
      
 11 
     | 
    
         
            +
                @requests = 1000
         
     | 
| 
      
 12 
     | 
    
         
            +
                @concurrencies = [1, 10, 100]
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
              
         
     | 
| 
      
 15 
     | 
    
         
            +
              def writer(&block)
         
     | 
| 
      
 16 
     | 
    
         
            +
                @writer = block
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
              
         
     | 
| 
      
 19 
     | 
    
         
            +
              def run!
         
     | 
| 
      
 20 
     | 
    
         
            +
                @concurrencies.each do |concurrency|
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @servers.each do |server|
         
     | 
| 
      
 22 
     | 
    
         
            +
                    req_sec, failed = run_one(server, concurrency)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    @writer.call(server, @requests, concurrency, req_sec, failed)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
              
         
     | 
| 
      
 28 
     | 
    
         
            +
              private
         
     | 
| 
      
 29 
     | 
    
         
            +
                def start_server(handler_name)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @server = fork do
         
     | 
| 
      
 31 
     | 
    
         
            +
                    [STDOUT, STDERR].each { |o| o.reopen "/dev/null" }
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                    case handler_name
         
     | 
| 
      
 34 
     | 
    
         
            +
                    when 'EMongrel'
         
     | 
| 
      
 35 
     | 
    
         
            +
                      require 'swiftcore/evented_mongrel'
         
     | 
| 
      
 36 
     | 
    
         
            +
                      handler_name = 'Mongrel'
         
     | 
| 
      
 37 
     | 
    
         
            +
                    end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    app = proc do |env|
         
     | 
| 
      
 40 
     | 
    
         
            +
                      [200, {'Content-Type' => 'text/html', 'Content-Length' => '11'}, ['hello world']]
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    handler = Rack::Handler.const_get(handler_name)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    handler.run app, :Host => ADDRESS, :Port => PORT
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
                
         
     | 
| 
      
 47 
     | 
    
         
            +
                  sleep 2
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
              
         
     | 
| 
      
 50 
     | 
    
         
            +
                def stop_server
         
     | 
| 
      
 51 
     | 
    
         
            +
                  Process.kill('SIGKILL', @server)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  Process.wait
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
              
         
     | 
| 
      
 55 
     | 
    
         
            +
                def run_ab(concurrency)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  `nice -n20 ab -c #{concurrency} -n #{@requests} #{@keep_alive ? '-k' : ''} #{ADDRESS}:#{PORT}/ 2> /dev/null`
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
              
         
     | 
| 
      
 59 
     | 
    
         
            +
                def run_one(handler_name, concurrency)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  start_server(handler_name)
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                  out = run_ab(concurrency)
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  stop_server
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                  req_sec = if matches = out.match(/^Requests.+?(\d+\.\d+)/)
         
     | 
| 
      
 67 
     | 
    
         
            +
                    matches[1].to_i
         
     | 
| 
      
 68 
     | 
    
         
            +
                  else
         
     | 
| 
      
 69 
     | 
    
         
            +
                    0
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
                
         
     | 
| 
      
 72 
     | 
    
         
            +
                  failed = if matches = out.match(/^Failed requests.+?(\d+)/)
         
     | 
| 
      
 73 
     | 
    
         
            +
                    matches[1].to_i
         
     | 
| 
      
 74 
     | 
    
         
            +
                  else
         
     | 
| 
      
 75 
     | 
    
         
            +
                    0
         
     | 
| 
      
 76 
     | 
    
         
            +
                  end
         
     | 
| 
      
 77 
     | 
    
         
            +
                
         
     | 
| 
      
 78 
     | 
    
         
            +
                  [req_sec, failed]
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
            end
         
     | 
    
        data/benchmark/runner
    ADDED
    
    | 
         @@ -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
         
     | 
    
        data/bin/thin
    ADDED
    
    
    
        data/example/adapter.rb
    ADDED
    
    | 
         @@ -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
         
     |