From 7d4d15370270a4cb08f304737ae1196441cfea4e Mon Sep 17 00:00:00 2001 From: James Coglan Date: Fri, 3 May 2013 09:06:48 +0100 Subject: [PATCH] Sort our server/ruby compatibility. Now running on Puma, Thin, old and new Rainbows, and Goliath, on MRI/JRuby/RBX. --- .travis.yml | 6 ++- README.rdoc | 62 ++++++++++++++++++++++-------- examples/config.ru | 12 ++---- {spec => examples}/rainbows.conf | 0 examples/server.rb | 7 +--- faye-websocket.gemspec | 36 +++++++++-------- lib/faye/rack_stream.rb | 2 +- lib/faye/websocket.rb | 8 ++-- spec/faye/websocket/client_spec.rb | 20 ++++++++-- spec/spec_helper.rb | 27 ++++++++----- vendor/protocol | 2 +- 11 files changed, 118 insertions(+), 64 deletions(-) rename {spec => examples}/rainbows.conf (100%) diff --git a/.travis.yml b/.travis.yml index 1b8099e..11aec5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: ruby + rvm: - 1.8.7 - 1.9.2 @@ -6,10 +7,13 @@ rvm: - 2.0.0 - jruby-18mode - jruby-19mode + - rbx-18mode + - rbx-19mode before_script: - git submodule update --init --recursive - bundle install - cd vendor/parser && rake compile -script: bundle exec rspec spec/ +script: bundle exec rspec -c spec/ + diff --git a/README.rdoc b/README.rdoc index 4e41b80..73e9055 100644 --- a/README.rdoc +++ b/README.rdoc @@ -19,9 +19,12 @@ access via proxies than WebSockets. Currently, the following web servers are supported, and can be accessed directly or via HAProxy: -* {Thin}[http://code.macournoyer.com/thin/] -* {Rainbows}[http://rainbows.rubyforge.org/] using EventMachine * {Goliath}[http://postrank-labs.github.com/goliath/] +* {Puma}[http://puma.io/] +* {Rainbows}[http://rainbows.rubyforge.org/] using EventMachine +* {Thin}[http://code.macournoyer.com/thin/] + +Any web server that supports the rack.hijack API should also work. The server-side socket can process {draft-75}[http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75], {draft-76}[http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76], @@ -31,6 +34,11 @@ supports both +text+ and +binary+ messages, and transparently handles +ping+, +pong+, +close+ and fragmented messages. +== Installation + + $ gem install faye-websocket + + == Handling WebSocket connections in Rack You can handle WebSockets on the server side by listening for requests using the @@ -65,10 +73,14 @@ and sending messages. For example this is how you'd implement an echo server: This is a standard Rack app, so it can be run using a config.ru file. However, so that incoming requests can be properly prepared to process WebSocket -connections, you need to tell Faye::WebSocket which adapter to load; -this can be either +thin+, +rainbows+ or +goliath+. If one of these servers is -already loaded before faye/websocket is loaded, it will load -appropriate adapters automatically. +connections, Faye::WebSocket needs to monkey-patch certain web +servers. If you're using one of these servers, you need to load an adapter: + +* Goliath +* Rainbows! version < 4.5.0 +* Thin + +You load the adapter like so, passing either +goliath+, +rainbows+ or +thin+. # config.ru require './app' @@ -261,6 +273,8 @@ further in a block passed to +run+: require 'thin' require './app' + Faye::WebSocket.load_adapter('thin') + EM.run { thin = Rack::Handler.get('thin') @@ -275,18 +289,19 @@ further in a block passed to +run+: } +=== Running the app with Puma + +Puma has a command line interface for starting your application: + + puma config.ru -p 9292 + +You do not need to call Faye::WebSocket.load_adapter to work with Puma +but you must use at least version 2.0.0 of the +puma+ gem. + + === Running the app with Rainbows -Faye::WebSocket can only be run using EventMachine. To begin with, -you'll need a Rainbows config file that tells it to use EventMachine, along with -whatever Rainbows/Unicorn configuration you require. - - # rainbows.conf - Rainbows! do - use :EventMachine - end - -You can then run your config.ru file from the command line. Again, +You can run your config.ru file from the command line. Again, Rack::Lint will complain unless you put the application in production mode. @@ -308,6 +323,19 @@ Rainbows also has a Ruby API for starting a server: # This is non-blocking; use server.start.join to block server.start +If you're using version 4.4 or lower of Rainbows, you need to run it with the +EventMachine backend and enable the adapter. Put this in your +rainbows.conf file: + + # rainbows.conf + Rainbows! do + use :EventMachine + end + +And make sure you load the adapter in your application: + + Faye::WebSocket.load_adapter('rainbows') + === Running the app with Goliath @@ -316,6 +344,7 @@ Goliath can be made to run arbitrary Rack apps by delegating to them from a require 'goliath' require './app' + Faye::WebSocket.load_adapter('goliath') class EchoServer < Goliath::API def response(env) @@ -327,6 +356,7 @@ Goliath can be made to run arbitrary Rack apps by delegating to them from a require 'goliath' require 'faye/websocket' + Faye::WebSocket.load_adapter('goliath') class EchoServer < Goliath::API def response(env) diff --git a/examples/config.ru b/examples/config.ru index 4297b5a..169e3d1 100644 --- a/examples/config.ru +++ b/examples/config.ru @@ -1,17 +1,13 @@ # Run using your favourite server: # # thin start -R examples/config.ru -p 7000 -# rainbows -E production examples/config.ru -p 7000 -# -# If you run using one of these commands, the webserver is loaded before this -# file, so Faye::WebSocket can figure out which adapter to load. If instead you -# run using `rackup`, you need the `load_adapter` line below. -# -# rackup -E production -s thin examples/config.ru -p 7000 +# rainbows -c examples/rainbows.conf -E production examples/config.ru -p 7000 require 'rubygems' require File.expand_path('../app', __FILE__) -# Faye::WebSocket.load_adapter('thin') + +Faye::WebSocket.load_adapter('thin') +Faye::WebSocket.load_adapter('rainbows') run App diff --git a/spec/rainbows.conf b/examples/rainbows.conf similarity index 100% rename from spec/rainbows.conf rename to examples/rainbows.conf diff --git a/examples/server.rb b/examples/server.rb index 29256e3..1b6de8b 100644 --- a/examples/server.rb +++ b/examples/server.rb @@ -8,11 +8,7 @@ engine = ARGV[2] || 'thin' spec = File.expand_path('../../spec', __FILE__) require File.expand_path('../app', __FILE__) -if %w[goliath thin].include?(engine) - Faye::WebSocket.load_adapter(engine) -else - require engine -end +Faye::WebSocket.load_adapter(engine) case engine @@ -36,6 +32,7 @@ when 'rainbows' rackup[:port] = port rackup[:set_listener] = true options = rackup[:options] + options[:config_file] = File.expand_path('../rainbows.conf', __FILE__) Rainbows::HttpServer.new(App, options).start.join when 'thin' diff --git a/faye-websocket.gemspec b/faye-websocket.gemspec index 816b80a..3c8f869 100644 --- a/faye-websocket.gemspec +++ b/faye-websocket.gemspec @@ -1,30 +1,34 @@ Gem::Specification.new do |s| - s.name = "faye-websocket" - s.version = "0.4.7" - s.summary = "Standards-compliant WebSocket server and client" - s.author = "James Coglan" - s.email = "jcoglan@gmail.com" - s.homepage = "http://github.com/faye/faye-websocket-ruby" + s.name = 'faye-websocket' + s.version = '0.4.7' + s.summary = 'Standards-compliant WebSocket server and client' + s.author = 'James Coglan' + s.email = 'jcoglan@gmail.com' + s.homepage = 'http://github.com/faye/faye-websocket-ruby' s.extra_rdoc_files = %w[README.rdoc] s.rdoc_options = %w[--main README.rdoc] s.require_paths = %w[lib] s.files = %w[README.rdoc CHANGELOG.txt] + - Dir.glob("lib/**/*.rb") + - Dir.glob("{examples,spec}/**/*") + Dir.glob('lib/**/*.rb') + + Dir.glob('{examples,spec}/**/*') - s.add_dependency "eventmachine", ">= 0.12.0" - s.add_dependency "websocket-protocol" + s.add_dependency 'eventmachine', '>= 0.12.0' + s.add_dependency 'websocket-protocol' - s.add_development_dependency "progressbar" - s.add_development_dependency "rack" - s.add_development_dependency "rspec" + s.add_development_dependency 'progressbar' + s.add_development_dependency 'puma', '>= 2.0.0' + s.add_development_dependency 'rack' + s.add_development_dependency 'rspec' unless RUBY_PLATFORM =~ /java/ - s.add_development_dependency "puma", ">= 2.0.0" - s.add_development_dependency "rainbows", ">= 4.5.0" - s.add_development_dependency "thin", ">= 1.2.0" + s.add_development_dependency 'rainbows', '~> 4.4.0' + s.add_development_dependency 'thin', '>= 1.2.0' + end + + unless (defined?(RUBY_ENGINE) and RUBY_ENGINE =~ /rbx/) or RUBY_VERSION < '1.9' + s.add_development_dependency 'goliath' end end diff --git a/lib/faye/rack_stream.rb b/lib/faye/rack_stream.rb index e9e4a0c..54947b1 100644 --- a/lib/faye/rack_stream.rb +++ b/lib/faye/rack_stream.rb @@ -20,7 +20,7 @@ module Faye @connection = socket_object.env['em.connection'] @stream_send = socket_object.env['stream.send'] - if socket_object.env['rack.hijack?'] + if socket_object.env['rack.hijack'] socket_object.env['rack.hijack'].call @rack_hijack_io = socket_object.env['rack.hijack_io'] EventMachine.attach(@rack_hijack_io, Reader) do |reader| diff --git a/lib/faye/websocket.rb b/lib/faye/websocket.rb index 6bb3d24..7a1f6d5 100644 --- a/lib/faye/websocket.rb +++ b/lib/faye/websocket.rb @@ -22,9 +22,10 @@ module Faye autoload :Client, root + '/client' ADAPTERS = { - 'thin' => :Thin, + 'goliath' => :Goliath, + 'puma' => :Puma, 'rainbows' => :Rainbows, - 'goliath' => :Goliath + 'thin' => :Thin } def self.determine_url(env) @@ -41,7 +42,8 @@ module Faye def self.load_adapter(backend) const = Kernel.const_get(ADAPTERS[backend]) rescue nil require(backend) unless const - require File.expand_path("../adapters/#{backend}", __FILE__) + path = File.expand_path("../adapters/#{backend}.rb", __FILE__) + require(path) if File.file?(path) end def self.websocket?(env) diff --git a/spec/faye/websocket/client_spec.rb b/spec/faye/websocket/client_spec.rb index 8df44cb..15f5cb3 100644 --- a/spec/faye/websocket/client_spec.rb +++ b/spec/faye/websocket/client_spec.rb @@ -3,6 +3,8 @@ require "spec_helper" require "socket" +IS_JRUBY = (defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby') + WebSocketSteps = EM::RSpec.async_steps do def server(port, backend, secure, &callback) @server = EchoServer.new @@ -77,13 +79,13 @@ WebSocketSteps = EM::RSpec.async_steps do end describe Faye::WebSocket::Client do - next if WebSocket::Protocol.jruby? include WebSocketSteps let(:port) { 4180 } - let(:protocols) { ["foo", "echo"] } + let(:protocols) { ["foo", "echo"] } let(:plain_text_url) { "ws://0.0.0.0:#{port}/" } + let(:wrong_url) { "ws://0.0.0.0:9999/" } let(:secure_url) { "wss://0.0.0.0:#{port}/" } shared_examples_for "socket client" do @@ -134,7 +136,19 @@ describe Faye::WebSocket::Client do end end + describe "with a Puma server" do + let(:socket_url) { plain_text_url } + let(:blocked_url) { wrong_url } + + before { server port, :puma, false } + after { stop } + + it_should_behave_like "socket client" + end + describe "with a plain-text Thin server" do + next if IS_JRUBY + let(:socket_url) { plain_text_url } let(:blocked_url) { secure_url } @@ -145,7 +159,7 @@ describe Faye::WebSocket::Client do end describe "with a secure Thin server" do - next if WebSocket::Protocol.rbx? + next if IS_JRUBY let(:socket_url) { secure_url } let(:blocked_url) { plain_text_url } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0fc5951..ef9890d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,11 +4,11 @@ require 'bundler/setup' require File.expand_path('../../lib/faye/websocket', __FILE__) require File.expand_path('../../vendor/em-rspec/lib/em-rspec', __FILE__) +require 'puma' + unless RUBY_PLATFORM =~ /java/ Faye::WebSocket.load_adapter('thin') Thin::Logging.silent = true - require 'rainbows' - Unicorn::Configurator::DEFAULTS[:logger] = Logger.new(StringIO.new) end class EchoServer @@ -20,15 +20,19 @@ class EchoServer socket.rack_response end + def log(*args) + end + def listen(port, backend, ssl = false) case backend - when :rainbows - rackup = Unicorn::Configurator::RACKUP - rackup[:port] = port - rackup[:set_listener] = true - options = rackup[:options] - @server = Rainbows::HttpServer.new(self, options) - @server.start + when :puma + events = Puma::Events.new(StringIO.new, StringIO.new) + binder = Puma::Binder.new(events) + binder.parse(["tcp://0.0.0.0:#{port}"], self) + @server = Puma::Server.new(self, events) + @server.binder = binder + @server.run + when :thin Rack::Handler.get('thin').run(self, :Port => port) do |s| if ssl @@ -44,7 +48,10 @@ class EchoServer end def stop - @server.stop + case @server + when Puma::Server then @server.stop(true) + else @server.stop + end end end diff --git a/vendor/protocol b/vendor/protocol index 86b3235..9a28b3f 160000 --- a/vendor/protocol +++ b/vendor/protocol @@ -1 +1 @@ -Subproject commit 86b3235e82548047ec71da28a56b9780221d4c9d +Subproject commit 9a28b3f90d370c1c129accf7b83594b68df0cd0f