celluloid_pubsub 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +2 -0
- data/.gitignore +16 -0
- data/.rspec +1 -0
- data/.rubocop.yml +68 -0
- data/.travis.yml +12 -0
- data/CONTRIBUTING.md +44 -0
- data/Gemfile +3 -0
- data/Guardfile +12 -0
- data/LICENSE +20 -0
- data/README.rdoc +96 -0
- data/Rakefile +42 -0
- data/bin/appraisal +16 -0
- data/bin/autospec +16 -0
- data/bin/bundler +16 -0
- data/bin/cdiff +16 -0
- data/bin/coderay +16 -0
- data/bin/colortab +16 -0
- data/bin/coveralls +16 -0
- data/bin/decolor +16 -0
- data/bin/erubis +16 -0
- data/bin/guard +16 -0
- data/bin/htmldiff +16 -0
- data/bin/ldiff +16 -0
- data/bin/listen +16 -0
- data/bin/nokogiri +16 -0
- data/bin/phare +16 -0
- data/bin/pry +16 -0
- data/bin/rackup +16 -0
- data/bin/rails +16 -0
- data/bin/rake +16 -0
- data/bin/restclient +16 -0
- data/bin/rspec +16 -0
- data/bin/rubocop +16 -0
- data/bin/ruby-parse +16 -0
- data/bin/ruby-rewrite +16 -0
- data/bin/sass +16 -0
- data/bin/sass-convert +16 -0
- data/bin/scss +16 -0
- data/bin/scss-lint +16 -0
- data/bin/term_display +16 -0
- data/bin/term_mandel +16 -0
- data/bin/thor +16 -0
- data/bin/yard +16 -0
- data/bin/yardoc +16 -0
- data/bin/yardstick +16 -0
- data/bin/yri +16 -0
- data/celluloid_pubsub.gemspec +39 -0
- data/init.rb +1 -0
- data/lib/celluloid_pubsub/client_pubsub.rb +71 -0
- data/lib/celluloid_pubsub/reactor.rb +114 -0
- data/lib/celluloid_pubsub/registry.rb +9 -0
- data/lib/celluloid_pubsub/version.rb +14 -0
- data/lib/celluloid_pubsub/web_server.rb +92 -0
- data/lib/celluloid_pubsub.rb +6 -0
- data/spec/lib/celluloid_pubsub/client_pubsub_spec.rb +9 -0
- data/spec/lib/celluloid_pubsub/reactor_spec.rb +6 -0
- data/spec/lib/celluloid_pubsub/registry_spec.rb +6 -0
- data/spec/lib/celluloid_pubsub/web_server_spec.rb +6 -0
- data/spec/spec_helper.rb +48 -0
- metadata +459 -0
data/bin/restclient
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'restclient' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('rest-client', 'restclient')
|
data/bin/rspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rspec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('rspec-core', 'rspec')
|
data/bin/rubocop
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rubocop' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('rubocop', 'rubocop')
|
data/bin/ruby-parse
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'ruby-parse' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('parser', 'ruby-parse')
|
data/bin/ruby-rewrite
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'ruby-rewrite' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('parser', 'ruby-rewrite')
|
data/bin/sass
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'sass' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('sass', 'sass')
|
data/bin/sass-convert
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'sass-convert' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('sass', 'sass-convert')
|
data/bin/scss
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'scss' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('sass', 'scss')
|
data/bin/scss-lint
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'scss-lint' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('scss-lint', 'scss-lint')
|
data/bin/term_display
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'term_display' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('term-ansicolor', 'term_display')
|
data/bin/term_mandel
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'term_mandel' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('term-ansicolor', 'term_mandel')
|
data/bin/thor
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'thor' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('thor', 'thor')
|
data/bin/yard
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'yard' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('yard', 'yard')
|
data/bin/yardoc
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'yardoc' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('yard', 'yardoc')
|
data/bin/yardstick
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'yardstick' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('yardstick', 'yardstick')
|
data/bin/yri
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'yri' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('yard', 'yri')
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path('../lib/celluloid_pubsub/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'celluloid_pubsub'
|
5
|
+
s.version = CelluloidPubsub.gem_version
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.summary = 'CelluloidPubsub is a simple ruby implementation of publish subscribe design patterns using celluloid actors and websockets, using Celluloid::Reel server'
|
8
|
+
s.email = 'raoul_ice@yahoo.com'
|
9
|
+
s.homepage = 'http://github.com/bogdanRada/celluloid_pubsub/'
|
10
|
+
s.description = 'CelluloidPubsub is a simple ruby implementation of publish subscribe design patterns using celluloid actors and websockets, using Reel server for inter-process communication'
|
11
|
+
s.authors = ['bogdanRada']
|
12
|
+
s.date = Date.today
|
13
|
+
|
14
|
+
s.licenses = ['MIT']
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = s.files.grep(/^(spec)/)
|
17
|
+
s.require_paths = ['lib']
|
18
|
+
|
19
|
+
s.add_runtime_dependency 'celluloid', '~> 0.16.0', '>= 0.16.0'
|
20
|
+
s.add_runtime_dependency 'celluloid-io', '~> 0.16.2', '>= 0.16.2'
|
21
|
+
s.add_runtime_dependency 'reel', '~> 0.5.0', '>= 0.5.0'
|
22
|
+
s.add_runtime_dependency 'celluloid-websocket-client', '0.0.1'
|
23
|
+
s.add_runtime_dependency 'activesupport', '~> 4.2.0', '>= 4.2.0'
|
24
|
+
|
25
|
+
s.add_development_dependency 'rspec-rails', '~> 2.0', '>= 2.0'
|
26
|
+
s.add_development_dependency 'guard', '~> 2.6.1', '>= 2.6'
|
27
|
+
s.add_development_dependency 'guard-rspec', '~> 4.2.9', '>= 4.2'
|
28
|
+
s.add_development_dependency 'simplecov', '~> 0.9', '>= 0.9'
|
29
|
+
s.add_development_dependency 'simplecov-summary', '~> 0.0.4', '>= 0.0.4'
|
30
|
+
s.add_development_dependency 'mocha', '~> 1.1', '>= 1.1'
|
31
|
+
s.add_development_dependency 'coveralls', '~> 0.7', '>= 0.7'
|
32
|
+
s.add_development_dependency 'rvm-tester', '~> 1.1', '>= 1.1'
|
33
|
+
|
34
|
+
s.add_development_dependency 'rubocop', '0.29'
|
35
|
+
s.add_development_dependency 'phare', '~> 0.6', '>= 0.6'
|
36
|
+
s.add_development_dependency 'scss-lint', '~> 0.34', '>= 0.34'
|
37
|
+
s.add_development_dependency 'yard', '~> 0.8.7', '>= 0.8.7'
|
38
|
+
s.add_development_dependency 'yardstick', '~> 0.9.9', '>= 0.9.9'
|
39
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'celluloid_pubsub'
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module CelluloidPubsub
|
2
|
+
class Client
|
3
|
+
class PubSubWorker
|
4
|
+
include Celluloid
|
5
|
+
include Celluloid::Logger
|
6
|
+
attr_reader :proc, :topic, :message
|
7
|
+
|
8
|
+
def initialize(options, &connect_blk)
|
9
|
+
options = options.stringify_keys!
|
10
|
+
@actor = options['actor'].present? ? options['actor'] : nil
|
11
|
+
raise "#{self}: Please provide an actor in the options list!!!" if @actor.blank?
|
12
|
+
@connect_blk = connect_blk
|
13
|
+
@client = Celluloid::WebSocket::Client.new("ws://#{CelluloidPubsub::WebServer::HOST}:#{CelluloidPubsub::WebServer::PORT}#{CelluloidPubsub::WebServer::PATH}", Actor.current)
|
14
|
+
end
|
15
|
+
|
16
|
+
def debug_enabled?
|
17
|
+
CelluloidPubsub::WebServer.debug_enabled?
|
18
|
+
end
|
19
|
+
|
20
|
+
def subscribe(channel)
|
21
|
+
subscription_data = { 'client_action' => 'subscribe', 'channel' => channel }
|
22
|
+
debug("#{self.class} tries to subscribe #{subscription_data}") if debug_enabled?
|
23
|
+
async.chat(subscription_data)
|
24
|
+
end
|
25
|
+
|
26
|
+
def publish(channel, data)
|
27
|
+
publishing_data = { 'client_action' => 'publish', 'channel' => channel, 'data' => data }
|
28
|
+
debug(" #{self.class} publishl to #{channel} message: #{publishing_data}") if debug_enabled?
|
29
|
+
async.chat(publishing_data)
|
30
|
+
end
|
31
|
+
|
32
|
+
def on_open
|
33
|
+
debug("#{self.class} websocket connection opened") if debug_enabled?
|
34
|
+
@connect_blk.call Actor.current
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_message(data)
|
38
|
+
debug("#{self.class} received plain #{data}") if debug_enabled?
|
39
|
+
message = JSON.parse(data)
|
40
|
+
debug("#{self.class} received JSON #{message}") if debug_enabled?
|
41
|
+
@actor.async.on_message(message)
|
42
|
+
end
|
43
|
+
|
44
|
+
def on_close(code, reason)
|
45
|
+
@client.terminate
|
46
|
+
terminate
|
47
|
+
debug("#{self.class} dispatching on close #{code} #{reason}") if debug_enabled?
|
48
|
+
@actor.async.on_close(code, reason)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def chat(message)
|
54
|
+
final_message = nil
|
55
|
+
if message.is_a?(Hash)
|
56
|
+
debug("#{self.class} sends #{message.to_json}") if debug_enabled?
|
57
|
+
final_message = message.to_json
|
58
|
+
else
|
59
|
+
text_messsage = JSON.dump(action: 'message', message: message)
|
60
|
+
debug("#{self.class} sends JSON #{text_messsage}") if debug_enabled?
|
61
|
+
final_message = text_messsage
|
62
|
+
end
|
63
|
+
@client.text final_message
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.connect(options = {}, &connect_blk)
|
68
|
+
CelluloidPubsub::Client::PubSubWorker.new(options, &connect_blk)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require_relative './registry'
|
2
|
+
module CelluloidPubsub
|
3
|
+
class Reactor
|
4
|
+
include Celluloid
|
5
|
+
include Celluloid::IO
|
6
|
+
include Celluloid::Logger
|
7
|
+
|
8
|
+
attr_accessor :websocket, :server, :mutex
|
9
|
+
|
10
|
+
def work(websocket, server)
|
11
|
+
@server = server
|
12
|
+
@mutex = Mutex.new
|
13
|
+
@websocket = websocket
|
14
|
+
info "Streaming changes for #{websocket.url}" if @server.debug_enabled?
|
15
|
+
async.run
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
while message = @websocket.read
|
20
|
+
handle_websocket_message(message)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_json_data(message)
|
25
|
+
debug "Reactor read message #{message}" if @server.debug_enabled?
|
26
|
+
json_data = nil
|
27
|
+
begin
|
28
|
+
json_data = JSON.parse(message)
|
29
|
+
rescue => e
|
30
|
+
debug "Reactor could not parse #{message} because of #{e.inspect}" if @server.debug_enabled?
|
31
|
+
# do nothing
|
32
|
+
end
|
33
|
+
json_data = message if json_data.nil?
|
34
|
+
json_data
|
35
|
+
end
|
36
|
+
|
37
|
+
def handle_websocket_message(message)
|
38
|
+
json_data = parse_json_data(message)
|
39
|
+
handle_parsed_websocket_message(json_data)
|
40
|
+
end
|
41
|
+
|
42
|
+
def handle_parsed_websocket_message(json_data)
|
43
|
+
if json_data.is_a?(Hash)
|
44
|
+
json_data = json_data.stringify_keys
|
45
|
+
debug "Reactor finds actions for #{json_data}" if @server.debug_enabled?
|
46
|
+
delegate_action(json_data) if json_data['client_action'].present?
|
47
|
+
else
|
48
|
+
handle_unknown_action(json_data)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def delegate_action(json_data)
|
53
|
+
case json_data['client_action']
|
54
|
+
when 'unsubscribe_all'
|
55
|
+
unsubscribe_all
|
56
|
+
when 'unsubscribe'
|
57
|
+
async.unsubscribe_client(json_data['channel'])
|
58
|
+
when 'subscribe'
|
59
|
+
async.start_subscriber(json_data['channel'], json_data)
|
60
|
+
when 'publish'
|
61
|
+
@server.publish_event(json_data['channel'], json_data['data'].to_json)
|
62
|
+
else
|
63
|
+
handle_unknown_action(json_data)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def handle_unknown_action(json_data)
|
68
|
+
debug "Trying to dispatch to server #{json_data}" if @server.debug_enabled?
|
69
|
+
@server.async.handle_dispatched_message(Actor.current, json_data)
|
70
|
+
end
|
71
|
+
|
72
|
+
def unsubscribe_client(channel)
|
73
|
+
return unless channel.present?
|
74
|
+
@websocket.close
|
75
|
+
@server.unsubscribe_client(Actor.current, channel)
|
76
|
+
end
|
77
|
+
|
78
|
+
def shutdown
|
79
|
+
terminate
|
80
|
+
end
|
81
|
+
|
82
|
+
def start_subscriber(channel, message)
|
83
|
+
return unless channel.present?
|
84
|
+
@mutex.lock
|
85
|
+
begin
|
86
|
+
add_subscriber_to_channel(channel, message)
|
87
|
+
debug "Subscribed to #{channel} with #{message}" if @server.debug_enabled?
|
88
|
+
@websocket << message.merge('client_action' => 'successful_subscription', 'channel' => channel).to_json
|
89
|
+
rescue => e
|
90
|
+
raise [e, e.respond_to?(:backtrace) ? e.backtrace : '', channel, message].inspect
|
91
|
+
ensure
|
92
|
+
@mutex.unlock
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_subscriber_to_channel(channel, message)
|
97
|
+
CelluloidPubsub::Registry.channels << channel unless CelluloidPubsub::Registry.channels.include?(channel)
|
98
|
+
@server.subscribers[channel] ||= []
|
99
|
+
@server.subscribers[channel] << { reactor: Actor.current, message: message }
|
100
|
+
end
|
101
|
+
|
102
|
+
def unsubscribe_all
|
103
|
+
CelluloidPubsub::Registry.channels.map do |channel|
|
104
|
+
@subscribers[channel].each do |hash|
|
105
|
+
hash[:reactor].websocket.close
|
106
|
+
end
|
107
|
+
@server.subscribers[channel] = []
|
108
|
+
end
|
109
|
+
|
110
|
+
info 'clearing connections' if @server.debug_enabled?
|
111
|
+
shutdown
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module CelluloidPubsub # Returns the version of the currently loaded Rails as a <tt>Gem::Version</tt>
|
2
|
+
def self.gem_version
|
3
|
+
Gem::Version.new VERSION::STRING
|
4
|
+
end
|
5
|
+
|
6
|
+
module VERSION
|
7
|
+
MAJOR = 0
|
8
|
+
MINOR = 0
|
9
|
+
TINY = 1
|
10
|
+
PRE = nil
|
11
|
+
|
12
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require_relative './reactor'
|
2
|
+
module CelluloidPubsub
|
3
|
+
class WebServer < Reel::Server::HTTP
|
4
|
+
include Celluloid::Logger
|
5
|
+
|
6
|
+
HOST = '0.0.0.0'
|
7
|
+
PORT = 1234
|
8
|
+
PATH = '/ws'
|
9
|
+
|
10
|
+
attr_accessor :options, :subscribers, :backlog
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
parse_options(options)
|
14
|
+
@subscribers = {}
|
15
|
+
info "CelluloidPubsub::WebServer example starting on #{@hostname}:#{@port}" if debug_enabled?
|
16
|
+
super(@hostname, @port, { spy: @spy, backlog: @backlog }, &method(:on_connection))
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse_options(options)
|
20
|
+
raise 'Options is not a hash or is not present ' unless options.is_a?(Hash)
|
21
|
+
@options = options.stringify_keys
|
22
|
+
@backlog = @options.fetch(:backlog, 1024)
|
23
|
+
@hostname = @options.fetch(:hostname, CelluloidPubsub::WebServer::HOST)
|
24
|
+
@port = @options.fetch(:port, CelluloidPubsub::WebServer::PORT)
|
25
|
+
@path = @options.fetch(:path, CelluloidPubsub::WebServer::PATH)
|
26
|
+
@spy = @options.fetch(:spy, false)
|
27
|
+
end
|
28
|
+
|
29
|
+
def debug_enabled?
|
30
|
+
self.class.debug_enabled?
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.debug_enabled?
|
34
|
+
ENV['DEBUG_CELLULOID'].present? && (ENV['DEBUG_CELLULOID'] == 'true' || ENV['DEBUG_CELLULOID'] == true)
|
35
|
+
end
|
36
|
+
|
37
|
+
def publish_event(current_topic, message)
|
38
|
+
return if current_topic.blank? || message.blank?
|
39
|
+
@subscribers[current_topic].each do |hash|
|
40
|
+
hash[:reactor].websocket << message
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def on_connection(connection)
|
45
|
+
while request = connection.request
|
46
|
+
if request.websocket?
|
47
|
+
info 'Received a WebSocket connection' if debug_enabled?
|
48
|
+
|
49
|
+
# We're going to hand off this connection to another actor (Writer/Reader)
|
50
|
+
# However, initially Reel::Connections are "attached" to the
|
51
|
+
# Reel::Server::HTTP actor, meaning that the server manages the connection
|
52
|
+
# lifecycle (e.g. error handling) for us.
|
53
|
+
#
|
54
|
+
# If we want to hand this connection off to another actor, we first
|
55
|
+
# need to detach it from the Reel::Server (in this case, Reel::Server::HTTP)
|
56
|
+
connection.detach
|
57
|
+
route_websocket(request.websocket)
|
58
|
+
return
|
59
|
+
else
|
60
|
+
route_request connection, request
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def route_request(connection, request)
|
66
|
+
info "404 Not Found: #{request.path}" if debug_enabled?
|
67
|
+
connection.respond :not_found, 'Not found'
|
68
|
+
end
|
69
|
+
|
70
|
+
def route_websocket(socket)
|
71
|
+
if socket.url == @path
|
72
|
+
info 'Reactor handles new socket connection' if debug_enabled?
|
73
|
+
reactor = CelluloidPubsub::Reactor.new
|
74
|
+
Actor.current.link reactor
|
75
|
+
reactor.async.work(socket, Actor.current)
|
76
|
+
else
|
77
|
+
info "Received invalid WebSocket request for: #{socket.url}" if debug_enabled?
|
78
|
+
socket.close
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def handle_dispatched_message(reactor, data)
|
83
|
+
debug "Webserver trying to dispatch message #{data.inspect}" if debug_enabled?
|
84
|
+
message = reactor.parse_json_data(data)
|
85
|
+
if message.present? && message.is_a?(Hash)
|
86
|
+
reactor.websocket << message.to_json
|
87
|
+
else
|
88
|
+
reactor.websocket << data.to_json
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|