myxi 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|