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.
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Myxi
2
+ VERSION = '1.0.0'
3
+ end
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: []