From 9abb447a573751b87e26ce35f4f05d58290f4c41 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 8 Jul 2010 23:21:18 +0000 Subject: restore Rainbows::HttpResponse.write for Cramp Cramp monkey patches Rainbows internals for WebSockets support and we forgot about it. Add a new integration test to ensure this continues to work in the future (and force us to update the test for newer Cramp). --- lib/rainbows.rb | 3 +- lib/rainbows/base.rb | 4 +- lib/rainbows/event_machine.rb | 4 +- lib/rainbows/fiber/body.rb | 2 +- lib/rainbows/fiber/rev.rb | 2 +- lib/rainbows/http_response.rb | 48 +++------------ lib/rainbows/http_response/body.rb | 118 ------------------------------------ lib/rainbows/response.rb | 44 ++++++++++++++ lib/rainbows/response/body.rb | 118 ++++++++++++++++++++++++++++++++++++ lib/rainbows/rev/client.rb | 2 +- lib/rainbows/rev/core.rb | 2 +- lib/rainbows/rev_fiber_spawn.rb | 2 +- lib/rainbows/writer_thread_pool.rb | 2 +- lib/rainbows/writer_thread_spawn.rb | 4 +- t/cramp/rainsocket.ru | 26 ++++++++ t/t0501-cramp-rainsocket.sh | 38 ++++++++++++ 16 files changed, 247 insertions(+), 172 deletions(-) delete mode 100644 lib/rainbows/http_response/body.rb create mode 100644 lib/rainbows/response.rb create mode 100644 lib/rainbows/response/body.rb create mode 100644 t/cramp/rainsocket.ru create mode 100755 t/t0501-cramp-rainsocket.sh diff --git a/lib/rainbows.rb b/lib/rainbows.rb index 7bc9a92..4e9578b 100644 --- a/lib/rainbows.rb +++ b/lib/rainbows.rb @@ -33,7 +33,7 @@ module Rainbows require 'rainbows/const' require 'rainbows/http_server' - require 'rainbows/http_response' + require 'rainbows/response' require 'rainbows/base' require 'rainbows/tee_input' autoload :Sendfile, 'rainbows/sendfile' @@ -127,4 +127,5 @@ module Rainbows autoload :Fiber, 'rainbows/fiber' # core class autoload :ByteSlice, 'rainbows/byte_slice' autoload :StreamFile, 'rainbows/stream_file' + autoload :HttpResponse, 'rainbows/http_response' # deprecated end diff --git a/lib/rainbows/base.rb b/lib/rainbows/base.rb index cd719d2..a619b00 100644 --- a/lib/rainbows/base.rb +++ b/lib/rainbows/base.rb @@ -10,7 +10,7 @@ module Rainbows::Base # :stopdoc: include Rainbows::Const - include Rainbows::HttpResponse + include Rainbows::Response # shortcuts... G = Rainbows::G @@ -21,7 +21,7 @@ module Rainbows::Base # this method is called by all current concurrency models def init_worker_process(worker) super(worker) - Rainbows::HttpResponse.setup(self.class) + Rainbows::Response.setup(self.class) Rainbows::MaxBody.setup G.tmp = worker.tmp diff --git a/lib/rainbows/event_machine.rb b/lib/rainbows/event_machine.rb index 0ad604e..0404493 100644 --- a/lib/rainbows/event_machine.rb +++ b/lib/rainbows/event_machine.rb @@ -50,7 +50,7 @@ module Rainbows class Client < EM::Connection include Rainbows::EvCore - include Rainbows::HttpResponse + include Rainbows::Response G = Rainbows::G def initialize(io) @@ -227,7 +227,7 @@ module Rainbows end def init_worker_process(worker) - Rainbows::HttpResponse.setup(Rainbows::EventMachine::Client) + Rainbows::Response.setup(Rainbows::EventMachine::Client) super end diff --git a/lib/rainbows/fiber/body.rb b/lib/rainbows/fiber/body.rb index b77e310..3de45ee 100644 --- a/lib/rainbows/fiber/body.rb +++ b/lib/rainbows/fiber/body.rb @@ -1,7 +1,7 @@ # -*- encoding: binary -*- # non-portable body handling for Fiber-based concurrency goes here # this module is required and included in worker processes only -# this is meant to be included _after_ Rainbows::HttpResponse::Body +# this is meant to be included _after_ Rainbows::Response::Body module Rainbows::Fiber::Body # :nodoc: # TODO non-blocking splice(2) under Linux diff --git a/lib/rainbows/fiber/rev.rb b/lib/rainbows/fiber/rev.rb index 2e8f076..a1ffe33 100644 --- a/lib/rainbows/fiber/rev.rb +++ b/lib/rainbows/fiber/rev.rb @@ -52,7 +52,7 @@ module Rainbows::Fiber include Unicorn include Rainbows include Rainbows::Const - include Rainbows::HttpResponse + include Rainbows::Response FIO = Rainbows::Fiber::IO def to_io diff --git a/lib/rainbows/http_response.rb b/lib/rainbows/http_response.rb index 677b5a7..cf17aa1 100644 --- a/lib/rainbows/http_response.rb +++ b/lib/rainbows/http_response.rb @@ -1,44 +1,10 @@ # -*- encoding: binary -*- -require 'time' # for Time#httpdate - -# :stopdoc: -module Rainbows::HttpResponse - - CODES = Unicorn::HttpResponse::CODES - - def response_header(response, out) - status, headers = response - status = CODES[status.to_i] || status - - headers.each do |key, value| - next if %r{\A(?:X-Rainbows-|Connection\z|Date\z|Status\z)}i =~ key - if value =~ /\n/ - # avoiding blank, key-only cookies with /\n+/ - out.concat(value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }) - else - out << "#{key}: #{value}\r\n" - end - end - - "HTTP/1.1 #{status}\r\n" \ - "Date: #{Time.now.httpdate}\r\n" \ - "Status: #{status}\r\n" \ - "#{out.join('')}\r\n" - end - - def write_header(socket, response, out) - out and socket.write(response_header(response, out)) - end - - def write_response(socket, response, out) - write_header(socket, response, out) - write_body(socket, response[2]) - end - - # called after forking - def self.setup(klass) - require('rainbows/http_response/body') and - klass.__send__(:include, Rainbows::HttpResponse::Body) +# deprecated, use Rainbows::Response instead +# Cramp 0.11 relies on this +# :enddoc: +class Rainbows::HttpResponse + class << self + include Rainbows::Response + alias write write_response end end -# :startdoc: diff --git a/lib/rainbows/http_response/body.rb b/lib/rainbows/http_response/body.rb deleted file mode 100644 index 2ce09da..0000000 --- a/lib/rainbows/http_response/body.rb +++ /dev/null @@ -1,118 +0,0 @@ -# -*- encoding: binary -*- -# non-portable body response stuff goes here -# -# The sendfile 1.0.0 RubyGem includes IO#sendfile and -# IO#sendfile_nonblock. Previous versions of "sendfile" didn't have -# IO#sendfile_nonblock, and IO#sendfile in previous versions could -# block other threads under 1.8 with large files -# -# IO#sendfile currently (June 2010) beats 1.9 IO.copy_stream with -# non-Linux support and large files on 32-bit. We still fall back to -# IO.copy_stream (if available) if we're dealing with DevFdResponse -# objects, though. -# -# Linux-only splice(2) support via the "io_splice" gem will eventually -# be added for streaming sockets/pipes, too. -# -# * write_body_file - regular files (sendfile or pread+write) -# * write_body_stream - socket/pipes (read+write, splice later) -# * write_body_each - generic fallback -# -# callgraph is as follows: -# -# write_body -# `- write_body_each -# `- write_body_path -# `- write_body_file -# `- write_body_stream -# -module Rainbows::HttpResponse::Body # :nodoc: - ALIASES = {} - - # to_io is not part of the Rack spec, but make an exception here - # since we can conserve path lookups and file descriptors. - # \Rainbows! will never get here without checking for the existence - # of body.to_path first. - def body_to_io(body) - if body.respond_to?(:to_io) - body.to_io - else - # try to take advantage of Rainbows::DevFdResponse, calling File.open - # is a last resort - path = body.to_path - path =~ %r{\A/dev/fd/(\d+)\z} ? IO.new($1.to_i) : File.open(path, 'rb') - end - end - - if IO.method_defined?(:sendfile_nonblock) - def write_body_file(sock, body) - sock.sendfile(body, 0) - end - end - - if IO.respond_to?(:copy_stream) - unless method_defined?(:write_body_file) - # try to use sendfile() via IO.copy_stream, otherwise pread()+write() - def write_body_file(sock, body) - IO.copy_stream(body, sock, nil, 0) - end - end - - # only used when body is a pipe or socket that can't handle - # pread() semantics - def write_body_stream(sock, body) - IO.copy_stream(body, sock) - ensure - body.respond_to?(:close) and body.close - end - else - # fall back to body#each, which is a Rack standard - ALIASES[:write_body_stream] = :write_body_each - end - - if method_defined?(:write_body_file) - - # middlewares/apps may return with a body that responds to +to_path+ - def write_body_path(sock, body) - inp = body_to_io(body) - if inp.stat.file? - begin - write_body_file(sock, inp) - ensure - inp.close if inp != body - end - else - write_body_stream(sock, inp) - end - ensure - body.respond_to?(:close) && inp != body and body.close - end - else - def write_body_path(sock, body) - write_body_stream(sock, body_to_io(body)) - end - end - - if method_defined?(:write_body_path) - def write_body(client, body) - body.respond_to?(:to_path) ? - write_body_path(client, body) : - write_body_each(client, body) - end - else - ALIASES[:write_body] = :write_body_each - end - - # generic body writer, used for most dynamically generated responses - def write_body_each(socket, body) - body.each { |chunk| socket.write(chunk) } - ensure - body.respond_to?(:close) and body.close - end - - def self.included(klass) - ALIASES.each do |new_method, orig_method| - klass.__send__(:alias_method, new_method, orig_method) - end - end -end diff --git a/lib/rainbows/response.rb b/lib/rainbows/response.rb new file mode 100644 index 0000000..8d131b0 --- /dev/null +++ b/lib/rainbows/response.rb @@ -0,0 +1,44 @@ +# -*- encoding: binary -*- +require 'time' # for Time#httpdate + +# :stopdoc: +module Rainbows::Response + + CODES = Unicorn::HttpResponse::CODES + + def response_header(response, out) + status, headers = response + status = CODES[status.to_i] || status + + headers.each do |key, value| + next if %r{\A(?:X-Rainbows-|Connection\z|Date\z|Status\z)}i =~ key + if value =~ /\n/ + # avoiding blank, key-only cookies with /\n+/ + out.concat(value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }) + else + out << "#{key}: #{value}\r\n" + end + end + + "HTTP/1.1 #{status}\r\n" \ + "Date: #{Time.now.httpdate}\r\n" \ + "Status: #{status}\r\n" \ + "#{out.join('')}\r\n" + end + + def write_header(socket, response, out) + out and socket.write(response_header(response, out)) + end + + def write_response(socket, response, out) + write_header(socket, response, out) + write_body(socket, response[2]) + end + + # called after forking + def self.setup(klass) + require('rainbows/response/body') and + klass.__send__(:include, Rainbows::Response::Body) + end +end +# :startdoc: diff --git a/lib/rainbows/response/body.rb b/lib/rainbows/response/body.rb new file mode 100644 index 0000000..8d8ec27 --- /dev/null +++ b/lib/rainbows/response/body.rb @@ -0,0 +1,118 @@ +# -*- encoding: binary -*- +# non-portable body response stuff goes here +# +# The sendfile 1.0.0 RubyGem includes IO#sendfile and +# IO#sendfile_nonblock. Previous versions of "sendfile" didn't have +# IO#sendfile_nonblock, and IO#sendfile in previous versions could +# block other threads under 1.8 with large files +# +# IO#sendfile currently (June 2010) beats 1.9 IO.copy_stream with +# non-Linux support and large files on 32-bit. We still fall back to +# IO.copy_stream (if available) if we're dealing with DevFdResponse +# objects, though. +# +# Linux-only splice(2) support via the "io_splice" gem will eventually +# be added for streaming sockets/pipes, too. +# +# * write_body_file - regular files (sendfile or pread+write) +# * write_body_stream - socket/pipes (read+write, splice later) +# * write_body_each - generic fallback +# +# callgraph is as follows: +# +# write_body +# `- write_body_each +# `- write_body_path +# `- write_body_file +# `- write_body_stream +# +module Rainbows::Response::Body # :nodoc: + ALIASES = {} + + # to_io is not part of the Rack spec, but make an exception here + # since we can conserve path lookups and file descriptors. + # \Rainbows! will never get here without checking for the existence + # of body.to_path first. + def body_to_io(body) + if body.respond_to?(:to_io) + body.to_io + else + # try to take advantage of Rainbows::DevFdResponse, calling File.open + # is a last resort + path = body.to_path + path =~ %r{\A/dev/fd/(\d+)\z} ? IO.new($1.to_i) : File.open(path, 'rb') + end + end + + if IO.method_defined?(:sendfile_nonblock) + def write_body_file(sock, body) + sock.sendfile(body, 0) + end + end + + if IO.respond_to?(:copy_stream) + unless method_defined?(:write_body_file) + # try to use sendfile() via IO.copy_stream, otherwise pread()+write() + def write_body_file(sock, body) + IO.copy_stream(body, sock, nil, 0) + end + end + + # only used when body is a pipe or socket that can't handle + # pread() semantics + def write_body_stream(sock, body) + IO.copy_stream(body, sock) + ensure + body.respond_to?(:close) and body.close + end + else + # fall back to body#each, which is a Rack standard + ALIASES[:write_body_stream] = :write_body_each + end + + if method_defined?(:write_body_file) + + # middlewares/apps may return with a body that responds to +to_path+ + def write_body_path(sock, body) + inp = body_to_io(body) + if inp.stat.file? + begin + write_body_file(sock, inp) + ensure + inp.close if inp != body + end + else + write_body_stream(sock, inp) + end + ensure + body.respond_to?(:close) && inp != body and body.close + end + else + def write_body_path(sock, body) + write_body_stream(sock, body_to_io(body)) + end + end + + if method_defined?(:write_body_path) + def write_body(client, body) + body.respond_to?(:to_path) ? + write_body_path(client, body) : + write_body_each(client, body) + end + else + ALIASES[:write_body] = :write_body_each + end + + # generic body writer, used for most dynamically generated responses + def write_body_each(socket, body) + body.each { |chunk| socket.write(chunk) } + ensure + body.respond_to?(:close) and body.close + end + + def self.included(klass) + ALIASES.each do |new_method, orig_method| + klass.__send__(:alias_method, new_method, orig_method) + end + end +end diff --git a/lib/rainbows/rev/client.rb b/lib/rainbows/rev/client.rb index bc8d7fa..91947b6 100644 --- a/lib/rainbows/rev/client.rb +++ b/lib/rainbows/rev/client.rb @@ -6,7 +6,7 @@ module Rainbows class Client < ::Rev::IO include Rainbows::ByteSlice include Rainbows::EvCore - include Rainbows::HttpResponse + include Rainbows::Response G = Rainbows::G HH = Rack::Utils::HeaderHash diff --git a/lib/rainbows/rev/core.rb b/lib/rainbows/rev/core.rb index 2488cf2..4668cce 100644 --- a/lib/rainbows/rev/core.rb +++ b/lib/rainbows/rev/core.rb @@ -22,7 +22,7 @@ module Rainbows # for connections and doesn't die until the parent dies (or is # given a INT, QUIT, or TERM signal) def worker_loop(worker) - Rainbows::HttpResponse.setup(Rainbows::Rev::Client) + Rainbows::Response.setup(Rainbows::Rev::Client) require 'rainbows/rev/sendfile' Rainbows::Rev::Client.__send__(:include, Rainbows::Rev::Sendfile) init_worker_process(worker) diff --git a/lib/rainbows/rev_fiber_spawn.rb b/lib/rainbows/rev_fiber_spawn.rb index 4d64e39..e9ea1db 100644 --- a/lib/rainbows/rev_fiber_spawn.rb +++ b/lib/rainbows/rev_fiber_spawn.rb @@ -16,7 +16,7 @@ module Rainbows include Fiber::Rev def worker_loop(worker) - Rainbows::HttpResponse.setup(Rainbows::Fiber::Rev::Server) + Rainbows::Response.setup(Rainbows::Fiber::Rev::Server) init_worker_process(worker) Server.const_set(:MAX, @worker_connections) Rainbows::Fiber::Base.setup(Rainbows::Fiber::Rev::Server, nil) diff --git a/lib/rainbows/writer_thread_pool.rb b/lib/rainbows/writer_thread_pool.rb index a2ef1ba..84b750b 100644 --- a/lib/rainbows/writer_thread_pool.rb +++ b/lib/rainbows/writer_thread_pool.rb @@ -61,7 +61,7 @@ module Rainbows end def worker_loop(worker) - Rainbows::HttpResponse.setup(self.class) + Rainbows::Response.setup(self.class) self.class.__send__(:alias_method, :sync_write_body, :write_body) self.class.__send__(:include, Response) diff --git a/lib/rainbows/writer_thread_spawn.rb b/lib/rainbows/writer_thread_spawn.rb index 9e793fc..b9bbad2 100644 --- a/lib/rainbows/writer_thread_spawn.rb +++ b/lib/rainbows/writer_thread_spawn.rb @@ -28,7 +28,7 @@ module Rainbows # used to wrap a BasicSocket to use with +q+ for all writes # this is compatible with IO.select class MySocket < Struct.new(:to_io, :q, :thr) - include Rainbows::HttpResponse + include Rainbows::Response def readpartial(size, buf = "") to_io.readpartial(size, buf) @@ -100,7 +100,7 @@ module Rainbows def worker_loop(worker) MySocket.const_set(:MAX, worker_connections) - Rainbows::HttpResponse.setup(MySocket) + Rainbows::Response.setup(MySocket) super(worker) # accept loop from Unicorn CUR.delete_if do |t,q| q << nil diff --git a/t/cramp/rainsocket.ru b/t/cramp/rainsocket.ru new file mode 100644 index 0000000..0d26f70 --- /dev/null +++ b/t/cramp/rainsocket.ru @@ -0,0 +1,26 @@ +# based on examples/rainsocket.ru git://github.com/lifo/cramp +# Rack::Lint does not like async + EM stuff, so disable it: +#\ -E deployment +require 'cramp/controller' + +Cramp::Controller::Websocket.backend = :rainbows + +class WelcomeController < Cramp::Controller::Websocket + periodic_timer :send_hello_world, :every => 2 + on_data :received_data + + def received_data(data) + if data =~ /fuck/ + render "You cant say fuck in here" + finish + else + render "Got your #{data}" + end + end + + def send_hello_world + render "Hello from the Server!\n" + end +end + +run WelcomeController diff --git a/t/t0501-cramp-rainsocket.sh b/t/t0501-cramp-rainsocket.sh new file mode 100755 index 0000000..6e3aea4 --- /dev/null +++ b/t/t0501-cramp-rainsocket.sh @@ -0,0 +1,38 @@ +#!/bin/sh +. ./test-lib.sh +case $model in +EventMachine) ;; +*) + t_info "skipping $T since it's not compatible with $model" + exit 0 + ;; +esac +require_check cramp Cramp::VERSION + +t_plan 4 "WebSocket monkey patch validity test for Cramp" + +CONFIG_RU=cramp/rainsocket.ru + +t_begin "setup and start" && { + rainbows_setup + rtmpfiles curl_err + + # Like the rest of the EM/async stuff, it's not Rack::Lint compatible + rainbows -E deployment -D $CONFIG_RU -c $unicorn_config + rainbows_wait_start +} + +t_begin "wait for server to say hello to us" && { + ok=$((curl --no-buffer -sS http://$listen/ || :) | \ + awk '/Hello from the Server/ { print "ok"; exit 0 }') + + test x"$ok" = xok +} + +t_begin "termination signal sent" && { + kill $rainbows_pid +} + +t_begin "no errors in stderr" && check_stderr + +t_done -- cgit v1.2.3-24-ge0c7