myxi 1.0.0

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