myxi 1.0.0
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.
- checksums.yaml +7 -0
- data/lib/myxi.rb +61 -0
- data/lib/myxi/action.rb +22 -0
- data/lib/myxi/default_actions.rb +13 -0
- data/lib/myxi/exchange.rb +51 -0
- data/lib/myxi/server.rb +73 -0
- data/lib/myxi/session.rb +83 -0
- data/lib/myxi/version.rb +3 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: afd97a9d7707f7273a9cc1526c16b838840de982
|
4
|
+
data.tar.gz: c3a82e6d83e6da41657ba23d17bcfec4f4bea14a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c25c8b738510f2e80d1897a85353705474157b3ac6446f4109b896dddede06ca87bd38123b972d1cc6624799560ed2f96c4a7cfc7608778388e64adc775e44f1
|
7
|
+
data.tar.gz: bf1995d685b86924c6a346a932c30f46ba8ce633d8593cb3133a04bc6923d68de22bbb5fd3d0df57dbf621ada5093e32c7e3701b6b24002940a688b7e1150f5c
|
data/lib/myxi.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Myxi
|
2
|
+
class << self
|
3
|
+
|
4
|
+
#
|
5
|
+
# Return a bunny client instance which will be used by the web socket service.
|
6
|
+
# This can be overriden if you already have a connection RabbitMQ available
|
7
|
+
# if your application. By default, it will connect to localhost or use the
|
8
|
+
# RABBITMQ_URL environment variable.
|
9
|
+
#
|
10
|
+
def bunny
|
11
|
+
@bunny ||= begin
|
12
|
+
require 'bunny'
|
13
|
+
bunny = Bunny.new(ENV['RABBITMQ_URL'])
|
14
|
+
bunny.start
|
15
|
+
bunny
|
16
|
+
end
|
17
|
+
end
|
18
|
+
attr_writer :bunny
|
19
|
+
|
20
|
+
#
|
21
|
+
# Return a channel which this process can always use
|
22
|
+
#
|
23
|
+
def channel
|
24
|
+
@channel ||= bunny.create_channel
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Store a bool of configured exchanges
|
29
|
+
#
|
30
|
+
def exchanges
|
31
|
+
@exchanges ||= begin
|
32
|
+
Myxi::Exchange::EXCHANGES.keys.inject({}) do |hash, name|
|
33
|
+
hash[name.to_sym] = channel.direct(name.to_s)
|
34
|
+
hash
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Push data to a given
|
41
|
+
#
|
42
|
+
def push(exchange, routing_key, &block)
|
43
|
+
if exch = exchanges[exchange.to_sym]
|
44
|
+
block.call(exch)
|
45
|
+
else
|
46
|
+
raise Error, "Couldn't send message to '#{exchange}' as it isn't configured"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Send an event to the given exchange
|
52
|
+
#
|
53
|
+
def push_event(exchange, routing_key, event, payload = {})
|
54
|
+
push(exchange, routing_key) do |exch|
|
55
|
+
exch.publish({:event => event, :payload => payload}.to_json, :routing_key => routing_key.to_s)
|
56
|
+
end
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
data/lib/myxi/action.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Myxi
|
2
|
+
class Action
|
3
|
+
|
4
|
+
ACTIONS = {}
|
5
|
+
|
6
|
+
def self.add(name, &block)
|
7
|
+
ACTIONS[name.to_sym] = self.new(name, &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(name, &block)
|
11
|
+
@name = name
|
12
|
+
@block = block
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(session, payload = {})
|
16
|
+
@block.call(session, payload)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'myxi/default_actions'
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Myxi::Action.add(:Subscribe) do |session, payload|
|
2
|
+
session.subscribe(payload['exchange'], payload['routing_key'])
|
3
|
+
end
|
4
|
+
|
5
|
+
Myxi::Action.add(:Unsubscribe) do |session, payload|
|
6
|
+
if payload['exchange'] && payload['routing_key']
|
7
|
+
session.unsubscribe(payload['exchange'], payload['routing_key'])
|
8
|
+
elsif payload['exchange'] && payload['routing_key'].nil?
|
9
|
+
session.unsubscribe_all_for_exchange(payload['exchange'])
|
10
|
+
else
|
11
|
+
session.unsubscribe_all
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Myxi
|
2
|
+
class Exchange
|
3
|
+
|
4
|
+
EXCHANGES = {}
|
5
|
+
|
6
|
+
attr_accessor :exchange_name
|
7
|
+
attr_accessor :model_name
|
8
|
+
attr_accessor :key_type
|
9
|
+
|
10
|
+
def self.add(exchange_name, *args, &block)
|
11
|
+
EXCHANGES[exchange_name.to_sym] = self.new(exchange_name, *args, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.declare_all
|
15
|
+
EXCHANGES.keys.each do |exch|
|
16
|
+
Myxi.channel.direct(exch.to_s)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(exchange_name, model_name = nil, &block)
|
21
|
+
@exchange_name = exchange_name.to_sym
|
22
|
+
@model_name = model_name
|
23
|
+
@permission_block = block
|
24
|
+
end
|
25
|
+
|
26
|
+
def has_model?
|
27
|
+
!!@model_name
|
28
|
+
end
|
29
|
+
|
30
|
+
def model
|
31
|
+
has_model? && model_name.constantize
|
32
|
+
end
|
33
|
+
|
34
|
+
def key_type
|
35
|
+
@key_type || 'id'
|
36
|
+
end
|
37
|
+
|
38
|
+
def model_instance(id)
|
39
|
+
has_model? ? model.where(key_type.to_sym => id.to_i).first : nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def can_subscribe?(routing_key, user)
|
43
|
+
if has_model?
|
44
|
+
@permission_block.call(model_instance(routing_key), user)
|
45
|
+
else
|
46
|
+
@permission_block.call(routing_key, user)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
data/lib/myxi/server.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'em-websocket'
|
3
|
+
require 'myxi'
|
4
|
+
require 'myxi/session'
|
5
|
+
require 'myxi/action'
|
6
|
+
|
7
|
+
module Myxi
|
8
|
+
class Server
|
9
|
+
|
10
|
+
attr_reader :options
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def log(message)
|
17
|
+
if options[:debug]
|
18
|
+
puts message
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
Myxi::Exchange.declare_all
|
24
|
+
port = (options[:port] || ENV['MYXI_PORT'] || ENV['PORT'] || 5005).to_i
|
25
|
+
puts "Running Myxi Web Socket Server on 0.0.0.0:#{port}"
|
26
|
+
EM.run do
|
27
|
+
EM::WebSocket.run(:host => options[:bind_address] || ENV['MYXI_BIND_ADDRESS'] || '0.0.0.0', :port => port) do |ws|
|
28
|
+
|
29
|
+
session = Session.new(ws)
|
30
|
+
|
31
|
+
ws.onopen do |handshake|
|
32
|
+
case handshake.path
|
33
|
+
when /\A\/pushwss/
|
34
|
+
log "[#{session.id}] Connection opened"
|
35
|
+
ws.send({:event => 'Welcome', :payload => {:id => session.id}}.to_json)
|
36
|
+
|
37
|
+
session.queue = Myxi.channel.queue("", :exclusive => true)
|
38
|
+
session.queue.subscribe do |delivery_info, properties, body|
|
39
|
+
ws.send(body)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
log "[#{session.id}] Invalid path"
|
43
|
+
ws.send({:event => 'Error', :payload => {:error => 'PathNotFound'}}.to_json)
|
44
|
+
ws.close
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
ws.onclose do
|
49
|
+
log "[#{session.id}] Disconnected"
|
50
|
+
session.queue.delete if session.queue
|
51
|
+
end
|
52
|
+
|
53
|
+
ws.onmessage do |msg|
|
54
|
+
if ws.state == :connected
|
55
|
+
if json = JSON.parse(msg) rescue nil
|
56
|
+
session.tag = json['tag'] || nil
|
57
|
+
payload = json['payload'] || {}
|
58
|
+
if action = Myxi::Action::ACTIONS[json['action'].to_sym]
|
59
|
+
action.execute(session, payload)
|
60
|
+
else
|
61
|
+
ws.send({:event => 'Error', :tag => session.tag, :payload => {:error => 'InvalidAction'}}.to_json)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
ws.send({:event => 'Error', :payload => {:error => 'InvalidJSON'}}.to_json)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/myxi/session.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'myxi/exchange'
|
3
|
+
|
4
|
+
module Myxi
|
5
|
+
class Session
|
6
|
+
|
7
|
+
def initialize(ws)
|
8
|
+
@ws = ws
|
9
|
+
@id = SecureRandom.hex(8)
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :id
|
13
|
+
attr_reader :ws
|
14
|
+
attr_accessor :queue
|
15
|
+
attr_accessor :auth_object
|
16
|
+
attr_accessor :tag
|
17
|
+
|
18
|
+
#
|
19
|
+
# Keep track of all subscriptions
|
20
|
+
#
|
21
|
+
def subscriptions
|
22
|
+
@subscriptions ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Send an event back to the client on this session
|
27
|
+
#
|
28
|
+
def send(name, payload = {})
|
29
|
+
ws.send({:event => name, :tag => tag, :payload => payload}.to_json)
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Subscribe this session to receive items for the given exchange & routing key
|
34
|
+
#
|
35
|
+
def subscribe(exchange_name, routing_key)
|
36
|
+
if exchange = Myxi::Exchange::EXCHANGES[exchange_name.to_sym]
|
37
|
+
if exchange.can_subscribe?(routing_key, self.auth_object)
|
38
|
+
queue.bind(exchange.exchange_name.to_s, :routing_key => routing_key.to_s)
|
39
|
+
subscriptions[exchange_name.to_s] ||= []
|
40
|
+
subscriptions[exchange_name.to_s] << routing_key.to_s
|
41
|
+
puts "[#{id}] Subscribed to #{exchange_name} / #{routing_key}"
|
42
|
+
send('Subscribed', :exchange => exchange_name, :routing_key => routing_key)
|
43
|
+
else
|
44
|
+
send('Error', :error => 'SubscriptionDenied', :exchange => exchange_name, :routing_key => routing_key)
|
45
|
+
end
|
46
|
+
else
|
47
|
+
send('Error', :error => 'InvalidExchange', :exchange => exchange_name)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Unsubscribe this session from the given exchange name and routing key
|
53
|
+
#
|
54
|
+
def unsubscribe(exchange_name, routing_key)
|
55
|
+
queue.unbind(exchange_name.to_s, :routing_key => routing_key.to_s)
|
56
|
+
if subscriptions[exchange_name.to_s]
|
57
|
+
subscriptions[exchange_name.to_s].delete(routing_key.to_s)
|
58
|
+
end
|
59
|
+
send('Unsubscribed', :exchange_name => exchange_name, :routing_key => routing_key)
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Unscubribe all for an exchange
|
64
|
+
#
|
65
|
+
def unsubscribe_all_for_exchange(exchange_name)
|
66
|
+
if array = self.subscriptions[exchange_name.to_s]
|
67
|
+
array.dup.each do |routing_key|
|
68
|
+
self.unsubscribe(exchange_name.to_s, routing_key)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Unsubscribe all
|
75
|
+
#
|
76
|
+
def unsubscribe_all
|
77
|
+
self.subscriptions.keys.each do |exchange_name|
|
78
|
+
self.unsubscribe_all_for_exchange(exchange_name)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
data/lib/myxi/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: myxi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Cooke
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bunny
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.2.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.2.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: em-websocket
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.5.1
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '1'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 0.5.1
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '1'
|
53
|
+
description: A RabbitMQ-based web socket server & framework
|
54
|
+
email:
|
55
|
+
- me@adamcooke.io
|
56
|
+
executables: []
|
57
|
+
extensions: []
|
58
|
+
extra_rdoc_files: []
|
59
|
+
files:
|
60
|
+
- lib/myxi.rb
|
61
|
+
- lib/myxi/action.rb
|
62
|
+
- lib/myxi/default_actions.rb
|
63
|
+
- lib/myxi/exchange.rb
|
64
|
+
- lib/myxi/server.rb
|
65
|
+
- lib/myxi/session.rb
|
66
|
+
- lib/myxi/version.rb
|
67
|
+
homepage: https://github.com/adamcooke/myxi
|
68
|
+
licenses:
|
69
|
+
- MIT
|
70
|
+
metadata: {}
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
requirements: []
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 2.4.5
|
88
|
+
signing_key:
|
89
|
+
specification_version: 4
|
90
|
+
summary: A RabbitMQ-based web socket server & framework
|
91
|
+
test_files: []
|