diff --git a/README.md b/README.md index 60b0c5d..74b237b 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ It's not necessary to include faye.js since that will be handled automatically f ## Serving Faye over HTTPS (with Thin) -To server Faye over HTTPS you could create a thin configuration file `config/private_pub_thin.yml` similar to the following: +To serve Faye over HTTPS you could create a thin configuration file `config/private_pub_thin.yml` similar to the following: ```yaml --- @@ -63,6 +63,21 @@ Finally start up Thin from the project root. thin -C config/private_pub_thin.yml start ``` +## Serving Faye with Redis engine + +To serve Faye with Redis engine, you should create `config/private_pub_redis.yml` + +```yaml +production: + host: redis_host + port: redis_port + password: redis_password + database: redis_database + namespace: '/namespace' +``` + +Note: database and namespace are optional. + ## Usage Use the `subscribe_to` helper method on any page to subscribe to a channel. diff --git a/app/assets/javascripts/private_pub.js b/app/assets/javascripts/private_pub.js index 8775ca3..a5e1f05 100644 --- a/app/assets/javascripts/private_pub.js +++ b/app/assets/javascripts/private_pub.js @@ -1,4 +1,4 @@ -function buildPrivatePub(doc) { +var PrivatePub = (function (doc) { var self = { connecting: false, fayeClient: null, @@ -13,11 +13,15 @@ function buildPrivatePub(doc) { self.fayeCallbacks.push(callback); if (self.subscriptions.server && !self.connecting) { self.connecting = true; - var script = doc.createElement("script"); - script.type = "text/javascript"; - script.src = self.subscriptions.server + ".js"; - script.onload = self.connectToFaye; - doc.documentElement.appendChild(script); + if (typeof Faye === 'undefined') { + var script = doc.createElement("script"); + script.type = "text/javascript"; + script.src = self.subscriptions.server + ".js"; + script.onload = self.connectToFaye; + doc.documentElement.appendChild(script); + } else { + self.connectToFaye(); + } } } }, @@ -47,10 +51,12 @@ function buildPrivatePub(doc) { if (!self.subscriptions.server) { self.subscriptions.server = options.server; } - self.subscriptions[options.channel] = options; - self.faye(function(faye) { - faye.subscribe(options.channel, self.handleResponse); - }); + if (!self.subscriptions[options.channel]) { + self.subscriptions[options.channel] = options; + self.faye(function(faye) { + faye.subscribe(options.channel, self.handleResponse); + }); + } }, handleResponse: function(message) { @@ -67,6 +73,4 @@ function buildPrivatePub(doc) { } }; return self; -} - -var PrivatePub = buildPrivatePub(document); +}(document)); diff --git a/lib/generators/private_pub/templates/private_pub.ru b/lib/generators/private_pub/templates/private_pub.ru index 4892af4..3b7801b 100644 --- a/lib/generators/private_pub/templates/private_pub.ru +++ b/lib/generators/private_pub/templates/private_pub.ru @@ -3,8 +3,18 @@ require "bundler/setup" require "yaml" require "faye" require "private_pub" +require "thin" Faye::WebSocket.load_adapter('thin') PrivatePub.load_config(File.expand_path("../config/private_pub.yml", __FILE__), ENV["RAILS_ENV"] || "development") -run PrivatePub.faye_app +Faye::WebSocket.load_adapter(PrivatePub.config[:adapter]) + +path = File.expand_path("../config/private_pub_redis.yml", __FILE__) +options = {} +if File.exist?(path) + require 'faye/redis' + options.merge(PrivatePub.load_redis_config(path, ENV['RAILS_ENV'] || 'development')) +end + +run PrivatePub.faye_app(options) diff --git a/lib/generators/private_pub/templates/private_pub.yml b/lib/generators/private_pub/templates/private_pub.yml index b827e8b..86b522d 100644 --- a/lib/generators/private_pub/templates/private_pub.yml +++ b/lib/generators/private_pub/templates/private_pub.yml @@ -1,10 +1,13 @@ development: + adapter: thin server: "http://localhost:9292/faye" secret_token: "secret" test: + adapter: thin server: "http://localhost:9292/faye" secret_token: "secret" production: + adapter: thin server: "http://example.com/faye" secret_token: "<%= defined?(SecureRandom) ? SecureRandom.hex(32) : ActiveSupport::SecureRandom.hex(32) %>" signature_expiration: 3600 # one hour diff --git a/lib/private_pub.rb b/lib/private_pub.rb index a595cf8..325ef7f 100644 --- a/lib/private_pub.rb +++ b/lib/private_pub.rb @@ -1,6 +1,7 @@ require "digest/sha1" require "net/http" require "net/https" +require "yajl/json_gem" require "private_pub/faye_extension" require "private_pub/engine" if defined? Rails @@ -10,19 +11,30 @@ class Error < StandardError; end class << self attr_reader :config + attr_reader :default_options - # Resets the configuration to the default (empty hash) + # Resets the configuration and options to the default + # configuration defaults to empty hash def reset_config @config = {} + @default_options = {:mount => "/faye", :timeout => 60, :extensions => [FayeExtension.new]} end - # Loads the configuration from a given YAML file and environment (such as production) + # Loads the configuration from a given YAML file and environment (such as production) def load_config(filename, environment) yaml = YAML.load_file(filename)[environment.to_s] raise ArgumentError, "The #{environment} environment does not exist in #{filename}" if yaml.nil? yaml.each { |k, v| config[k.to_sym] = v } end + # Loads the options from a given YAML file and environment (such as production) + def load_redis_config(filename, environment) + yaml = YAML.load_file(filename)[environment.to_s] + options = {:engine => {:type => Faye::Redis}} + yaml.each {|k, v| options[:engine][k.to_sym] = v} + options + end + # Publish the given data to a specific channel. This ends up sending # a Net::HTTP POST request to the Faye server. def publish_to(channel, data) @@ -31,8 +43,9 @@ def publish_to(channel, data) # Sends the given message hash to the Faye server using Net::HTTP. def publish_message(message) - raise Error, "No server specified, ensure private_pub.yml was loaded properly." unless config[:server] - url = URI.parse(config[:server]) + server = config[:server_server] || config[:server] + raise Error, "No server specified, ensure private_pub.yml was loaded properly." unless server + url = URI.parse(server) form = Net::HTTP::Post.new(url.path.empty? ? '/' : url.path) form.set_form_data(:message => message.to_json) @@ -69,8 +82,7 @@ def signature_expired?(timestamp) # Returns the Faye Rack application. # Any options given are passed to the Faye::RackAdapter. def faye_app(options = {}) - options = {:mount => "/faye", :timeout => 45, :extensions => [FayeExtension.new]}.merge(options) - Faye::RackAdapter.new(options) + Faye::RackAdapter.new(@default_options.merge!(options)) end end diff --git a/lib/private_pub/view_helpers.rb b/lib/private_pub/view_helpers.rb index 9048f36..8d8ea59 100644 --- a/lib/private_pub/view_helpers.rb +++ b/lib/private_pub/view_helpers.rb @@ -15,7 +15,7 @@ def publish_to(channel, data = nil, &block) def subscribe_to(channel) subscription = PrivatePub.subscription(:channel => channel) content_tag "script", :type => "text/javascript" do - raw("PrivatePub.sign(#{subscription.to_json});") + raw("if (typeof PrivatePub != 'undefined') { PrivatePub.sign(#{subscription.to_json}) }") end end end diff --git a/private_pub.gemspec b/private_pub.gemspec index 5c0b872..3897058 100644 --- a/private_pub.gemspec +++ b/private_pub.gemspec @@ -10,7 +10,8 @@ Gem::Specification.new do |s| s.files = Dir["{app,lib,spec}/**/*", "[A-Z]*", "init.rb"] - ["Gemfile.lock"] s.require_path = "lib" - s.add_dependency 'faye' + s.add_dependency 'faye', '>= 0.8.0' + s.add_dependency 'faye-redis' s.add_development_dependency 'rake' s.add_development_dependency 'rspec', '~> 2.8.0' diff --git a/spec/fixtures/private_pub.yml b/spec/fixtures/private_pub.yml index 57eb4d3..9881991 100644 --- a/spec/fixtures/private_pub.yml +++ b/spec/fixtures/private_pub.yml @@ -1,8 +1,10 @@ development: + adapter: thin server: http://dev.local:9292/faye secret_token: DEVELOPMENT_SECRET_TOKEN signature_expiration: 600 production: + adapter: thin server: http://example.com/faye secret_token: PRODUCTION_SECRET_TOKEN signature_expiration: 600 diff --git a/spec/fixtures/private_pub_redis.yml b/spec/fixtures/private_pub_redis.yml new file mode 100644 index 0000000..8930962 --- /dev/null +++ b/spec/fixtures/private_pub_redis.yml @@ -0,0 +1,6 @@ +test: + host: redis_host + port: redis_port + password: redis_password + database: redis_database + namespace: '/namespace' diff --git a/spec/private_pub_spec.rb b/spec/private_pub_spec.rb index 3929009..be795e6 100644 --- a/spec/private_pub_spec.rb +++ b/spec/private_pub_spec.rb @@ -24,6 +24,40 @@ PrivatePub.config[:server].should eq("http://example.com/faye") PrivatePub.config[:secret_token].should eq("PRODUCTION_SECRET_TOKEN") PrivatePub.config[:signature_expiration].should eq(600) + PrivatePub.config[:adapter].should eq('thin') + end + + context "when redis config exists" do + before do + @options = PrivatePub.load_redis_config("spec/fixtures/private_pub_redis.yml", "test") + end + + it "passes redis config to faye engine options" do + @options[:engine][:type].should eq Faye::Redis + @options[:engine][:host].should eq 'redis_host' + @options[:engine][:port].should eq 'redis_port' + @options[:engine][:password].should eq 'redis_password' + @options[:engine][:database].should eq 'redis_database' + @options[:engine][:namespace].should eq '/namespace' + end + + it "should pass redis config and default options to faye" do + Faye::RackAdapter.should_receive(:new) do |options| + options[:engine].should eq @options[:engine] + options[:mount].should eq '/faye' + end + PrivatePub.faye_app(@options) + end + end + + context "when redis config does not exist" do + it "should not have :engine inside of options hash" do + PrivatePub.default_options.should_not include :engine + end + + it "should have mount point" do + PrivatePub.default_options[:mount].should eq '/faye' + end end it "raises an exception if an invalid environment is passed to load_config" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1a884e1..99e55b6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,7 @@ require 'bundler/setup' require 'json' require 'faye' +require 'faye/redis' Bundler.require(:default) RSpec.configure do |config|