wampus 0.0.2
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/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/wampus/after_init.rb +36 -0
- data/lib/wampus/backends/redis.rb +97 -0
- data/lib/wampus/clients/wamp.rb +7 -0
- data/lib/wampus/connection.rb +48 -0
- data/lib/wampus/errors/call_error.rb +17 -0
- data/lib/wampus/protocols/wamp.rb +65 -0
- data/lib/wampus/protocols/wamp_cra.rb +39 -0
- data/lib/wampus/pubsub/handler.rb +25 -0
- data/lib/wampus/pubsub/server_ext.rb +167 -0
- data/lib/wampus/rpc/handler.rb +19 -0
- data/lib/wampus/rpc/server_ext.rb +98 -0
- data/lib/wampus/servers/wamp.rb +120 -0
- data/lib/wampus/servers/wamp_cra.rb +127 -0
- data/lib/wampus/topic.rb +45 -0
- data/lib/wampus/version.rb +3 -0
- data/lib/wampus.rb +51 -0
- data/wampus.gemspec +28 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 14ca9589d293012e05857f4bae923a158b794099
|
4
|
+
data.tar.gz: a4da144a3fbf35345d47b75d56ebcd9a9b715c66
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bde24c014aa16619a31a9c591df246b4782884586009c02971dfe8dd4c9c6744f745825524e06e0c3175adcd480dccbc4f5ac1eac3225fedcb421758dac437bd
|
7
|
+
data.tar.gz: 7b049c53ba81457f85279a43991b6b8044a961363aacf662aae9203d5f93ee2d5dc5069ea98898ecb6ecc12644ee2a8e6c2937acbfcb1e99a771739704358667
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Michael Stack
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Wamp
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'wampus'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install wampus
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Wampus
|
2
|
+
module AfterInit
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :after_init_blocks
|
7
|
+
self.after_init_blocks = []
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
private
|
12
|
+
|
13
|
+
def after_init(&block)
|
14
|
+
after_init_blocks << block
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(*args)
|
19
|
+
super if defined? super
|
20
|
+
exec_after_init
|
21
|
+
end
|
22
|
+
|
23
|
+
def exec_after_init
|
24
|
+
after_init_blocks.each do |block|
|
25
|
+
self.instance_exec &block
|
26
|
+
end
|
27
|
+
after_init_blocks = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def after_init(&block)
|
33
|
+
self.instance_exec &block
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Wampus
|
2
|
+
module Backends
|
3
|
+
module Redis
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
class_attribute :backend_redis_defaults
|
8
|
+
self.backend_defaults = {
|
9
|
+
:redis => {
|
10
|
+
:host => 'localhost',
|
11
|
+
:port => 6379,
|
12
|
+
:database => 0,
|
13
|
+
:password => nil,
|
14
|
+
:gc => 60,
|
15
|
+
:lock_timeout => 120,
|
16
|
+
:namespace => ''
|
17
|
+
}
|
18
|
+
}
|
19
|
+
class_attribute :backend_redis_config
|
20
|
+
self.backend_config = {:redis => {}}
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def configure_backend(backend, options = {})
|
25
|
+
backend_config[backend] = backend_defaults[backend].merge options
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def connect_backend
|
31
|
+
@events_namespace = backend_config[:redis][:namespace] + ':events'
|
32
|
+
|
33
|
+
@completed_events = []
|
34
|
+
|
35
|
+
@redis = ::Redis.new(:host => backend_config[:redis][:host], :port => backend_config[:redis][:port])
|
36
|
+
@subscriber = ::Redis.new(:host => backend_config[:redis][:host], :port => backend_config[:redis][:port])
|
37
|
+
|
38
|
+
if backend_config[:redis][:password]
|
39
|
+
@redis.auth(backend_config[:redis][:password])
|
40
|
+
@subscriber.auth(backend_config[:redis][:password])
|
41
|
+
end
|
42
|
+
|
43
|
+
@redis.select(backend_config[:redis][:database])
|
44
|
+
@subscriber.select(backend_config[:redis][:database])
|
45
|
+
|
46
|
+
Thread.new do
|
47
|
+
@subscriber.subscribe(@events_namespace) do |on|
|
48
|
+
on.message do |channel, message|
|
49
|
+
handle_message(message)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
def create_event(connection, topic, payload, excluded, included)
|
57
|
+
create_event_on_redis(connection.id, topic.uri, payload, excluded, included)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def create_event_on_redis(connection_id, topic_uri, payload, excluded, included)
|
63
|
+
@redis.publish @events_namespace, [SecureRandom.uuid, connection_id, topic_uri, payload, excluded, included].to_json
|
64
|
+
end
|
65
|
+
|
66
|
+
def subscribe_to_events
|
67
|
+
Thread.new do
|
68
|
+
@subscriber.subscribe(@events_ns) do |on|
|
69
|
+
on.message do |channel, message|
|
70
|
+
handle_message_from_redis(message)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def handle_message_from_redis(message)
|
77
|
+
begin
|
78
|
+
id, connection_id, uri, payload, excluded, included = JSON.parse(message)
|
79
|
+
|
80
|
+
connection = find_connections(:id => connection_id).first
|
81
|
+
topic = topic_for_uri uri
|
82
|
+
|
83
|
+
return unless topic
|
84
|
+
|
85
|
+
topic.subscribers(connection, excluded, included) do |subscribers|
|
86
|
+
subscribers.each do |connection|
|
87
|
+
connection.websocket.send event_msg(topic.uri, payload)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
rescue => error
|
91
|
+
# TODO Handle Error
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Wampus
|
2
|
+
class Connection
|
3
|
+
|
4
|
+
attr_reader :id, :websocket, :prefixes, :topics
|
5
|
+
|
6
|
+
attr_accessor :authenticated, :pending_auth, :auth_timeout_call
|
7
|
+
|
8
|
+
def initialize(id, websocket)
|
9
|
+
@id = id
|
10
|
+
@websocket = websocket
|
11
|
+
@prefixes = {}
|
12
|
+
@topics = []
|
13
|
+
|
14
|
+
@authenticated = false
|
15
|
+
@pending_auth = nil,
|
16
|
+
@auth_timeout_call = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def subscribe_to_topic(topic)
|
20
|
+
@topics << topic
|
21
|
+
end
|
22
|
+
|
23
|
+
def unsubscribe_from_topic(topic)
|
24
|
+
@topics.delete topic
|
25
|
+
end
|
26
|
+
|
27
|
+
def close_connection
|
28
|
+
@topics.each do |topic|
|
29
|
+
topic.remove_subscriber self
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_prefix(prefix, uri)
|
34
|
+
@prefixes[prefix] = uri
|
35
|
+
end
|
36
|
+
|
37
|
+
def resolve_prefix(uri)
|
38
|
+
prefix = @prefixes.keys.select{|p| uri.include? p}.sort(&:length).last
|
39
|
+
prefix ? @prefixes[prefix] + uri[/(?<=:)(.+)/].to_s : uri
|
40
|
+
end
|
41
|
+
|
42
|
+
# TODO Use
|
43
|
+
def shrink_prefix(uri)
|
44
|
+
# TODO Implement
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Wampus
|
2
|
+
module Errors
|
3
|
+
class CallError < StandardError
|
4
|
+
|
5
|
+
def initialize(error_uri, error_desc, error_details = nil)
|
6
|
+
super error_desc
|
7
|
+
@error_uri = error_uri
|
8
|
+
@error_desc = error_desc
|
9
|
+
@error_details = error_details
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_call_error
|
13
|
+
[@error_uri, @error_desc, @error_details]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Wampus
|
2
|
+
module Protocols
|
3
|
+
module Wamp
|
4
|
+
|
5
|
+
URI_BASE = 'http://api.wamp.ws/'
|
6
|
+
URI_TOPIC = URI_BASE + 'topic#'
|
7
|
+
URI_RPC = URI_BASE + 'rpc#'
|
8
|
+
URI_ERROR = URI_BASE + 'error#'
|
9
|
+
|
10
|
+
PROTOCOL_VERSION = 1
|
11
|
+
|
12
|
+
WELCOME_MSG_ID = 0
|
13
|
+
def welcome_msg(connection_id)
|
14
|
+
[WELCOME_MSG_ID, connection_id, PROTOCOL_VERSION, server_ident].to_json
|
15
|
+
end
|
16
|
+
|
17
|
+
PREFIX_MSG_ID = 1
|
18
|
+
def prefix_msg(prefix, uri)
|
19
|
+
[PREFIX_MSG_ID, prefix, uri].to_json
|
20
|
+
end
|
21
|
+
|
22
|
+
CALL_MSG_ID = 2
|
23
|
+
def call_msg(call_id, proc_uri, *args)
|
24
|
+
[CALL_MSG_ID, call_id, proc_uri, *args].to_json
|
25
|
+
end
|
26
|
+
|
27
|
+
CALL_RESULT_MSG_ID = 3
|
28
|
+
def call_result_msg(call_id, result = nil)
|
29
|
+
[CALL_RESULT_MSG_ID, call_id, result].to_json
|
30
|
+
end
|
31
|
+
|
32
|
+
CALL_ERROR_MSG_ID = 4
|
33
|
+
def call_error_msg(call_id, error_uri, error_desc, error_details = nil)
|
34
|
+
msg = [CALL_ERROR_MSG_ID, call_id, error_uri, error_desc, error_details]
|
35
|
+
msg.delete_if { |x| x.nil? }
|
36
|
+
msg.to_json
|
37
|
+
end
|
38
|
+
|
39
|
+
SUBSCRIBE_MSG_ID = 5
|
40
|
+
def subscribe_msg(topic_uri)
|
41
|
+
[SUBSCRIBE_MSG_ID, topic_uri].to_json
|
42
|
+
end
|
43
|
+
|
44
|
+
UNSUBSCRIBE_MSG_ID = 6
|
45
|
+
def unsubscribe_msg(topic_uri)
|
46
|
+
[UNSUBSCRIBE_MSG_ID, topic_uri].to_json
|
47
|
+
end
|
48
|
+
|
49
|
+
PUBLISH_MSG_ID = 7
|
50
|
+
def publish_msg(topic_uri, event, exclude = nil, eligible = nil)
|
51
|
+
msg = [PUBLISH_MSG_ID, topic_uri, event]
|
52
|
+
msg[3] = exclude unless exclude.nil?
|
53
|
+
msg[4] = eligible unless eligible.nil?
|
54
|
+
|
55
|
+
msg.to_json
|
56
|
+
end
|
57
|
+
|
58
|
+
EVENT_MSG_ID = 8
|
59
|
+
def event_msg(topic_uri, event)
|
60
|
+
[EVENT_MSG_ID, topic_uri, event].to_json
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module Wampus
|
5
|
+
module Protocols
|
6
|
+
module WampCra
|
7
|
+
|
8
|
+
include Wamp
|
9
|
+
|
10
|
+
URI_PROCEDURE = URI_BASE + 'procedure#'
|
11
|
+
|
12
|
+
def derive_key(secret, extra = nil)
|
13
|
+
if extra.is_a?(Hash) && extra.has_key?(:salt)
|
14
|
+
|
15
|
+
salt = extra[:salt]
|
16
|
+
iterations = extra.fetch(:iterations, 10000)
|
17
|
+
key_length = extra.fetch(:keylen, 32)
|
18
|
+
digest = OpenSSL::Digest::SHA256.new
|
19
|
+
|
20
|
+
key = OpenSSL::PKCS5.pbkdf2_hmac(secret, salt, iterations, key_length, digest).unpack('H*').first
|
21
|
+
Base64.encode64(key).strip
|
22
|
+
else
|
23
|
+
secret
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def auth_signature(auth_challenge, auth_secret = nil, auth_extra = nil)
|
28
|
+
auth_secret = '' if auth_secret.nil?
|
29
|
+
auth_secret.encode! 'UTF-8' if auth_secret.encoding.name != 'UTF-8'
|
30
|
+
auth_secret = derive_key auth_secret, auth_extra
|
31
|
+
digest = OpenSSL::Digest::SHA256.new
|
32
|
+
|
33
|
+
hmac = OpenSSL::HMAC.hexdigest(digest, auth_secret, auth_challenge)
|
34
|
+
Base64.encode64(hmac).strip
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Wampus
|
2
|
+
module Pubsub
|
3
|
+
module Handler
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
class_attribute :subscribe_handlers, :publish_handlers
|
8
|
+
self.subscribe_handlers = {}
|
9
|
+
self.publish_handlers = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def expose_pub(method, options = {})
|
14
|
+
as = options.fetch :as, method
|
15
|
+
subscribe_handlers[as] = method
|
16
|
+
end
|
17
|
+
|
18
|
+
def expose_pub(method, options = {})
|
19
|
+
as = options.fetch :as, method
|
20
|
+
publish_handlers[as] = method
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module Wampus
|
2
|
+
module Pubsub
|
3
|
+
module ServerExt
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
attr_accessor :topics
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
@topics = {}
|
10
|
+
super(*args) if defined? super
|
11
|
+
end
|
12
|
+
|
13
|
+
included do
|
14
|
+
include ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# -- Topic Handler Registration
|
19
|
+
def register_topic(base_uri, allow_partial_uri_match = false)
|
20
|
+
after_init do
|
21
|
+
topics[base_uri] = Wampus::Topic.new(base_uri, allow_partial_uri_match)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def register_topic_handler(handler, base_uri = '', allow_partial_uri_match = false)
|
26
|
+
after_init do
|
27
|
+
handler.subscribe_handlers.each do |uri_or_fragment, handler_method|
|
28
|
+
uri = base_uri + uri_or_fragment
|
29
|
+
register_topic_subscribe_method uri, handler, handler_method, allow_partial_uri_match
|
30
|
+
end
|
31
|
+
|
32
|
+
handler.publish_handlers.each do |uri_or_fragment, handler_method|
|
33
|
+
uri = base_uri + uri_or_fragment
|
34
|
+
register_topic_publish_method uri, handler, handler_method, allow_partial_uri_match
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def register_topic_subscribe_method(uri, handler, handler_method, allow_partial_uri_match = false)
|
40
|
+
after_init do
|
41
|
+
topic = topics[uri] ||= register_topic(uri, allow_partial_uri_match)
|
42
|
+
topic.subscribe_handler = [handler, handler_method, allow_partial_uri_match]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def register_topic_publish_method(uri, handler, handler_method, allow_partial_uri_match = false)
|
47
|
+
after_init do
|
48
|
+
topic = topics[uri] ||= register_topic(uri, allow_partial_uri_match)
|
49
|
+
topic.publish_handler = [handler, handler_method, allow_partial_uri_match]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def register_topic_subscribe_block(uri, allow_partial_uri_match = false, &block)
|
54
|
+
after_init do
|
55
|
+
topic = topics[uri] ||= register_topic(uri, allow_partial_uri_match)
|
56
|
+
topic.subscribe_handler = [nil, block, allow_partial_uri_match]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def register_topic_publish_block(uri, allow_partial_uri_match = false, &block)
|
61
|
+
after_init do
|
62
|
+
topic = topics[uri] ||= register_topic(uri, allow_partial_uri_match)
|
63
|
+
topic.publish_handler = [nil, block, allow_partial_uri_match]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def handle_subscribe_msg(connection, data)
|
69
|
+
topic_uri = connection.resolve_prefix data[0]
|
70
|
+
handler = get_subscribe_handler topic_uri
|
71
|
+
|
72
|
+
# Do nothing unless the server recognizes the topic
|
73
|
+
return unless handler
|
74
|
+
|
75
|
+
# Do nothing unless the handler covers partial uri matches
|
76
|
+
return unless handler[1] == '' && !handler[4]
|
77
|
+
|
78
|
+
topic = topic_for_uri topic_uri
|
79
|
+
|
80
|
+
if handler[2].nil? && handler[3].nil?
|
81
|
+
topic.add_subscriber connection
|
82
|
+
else
|
83
|
+
begin
|
84
|
+
if handler[2]
|
85
|
+
result = handler[2].send handler[3], [connection, topic_uri]
|
86
|
+
elsif handler[3].is_a? Proc
|
87
|
+
result = handler[3].call connection, topic_uri
|
88
|
+
end
|
89
|
+
topic.add_subscriber connection
|
90
|
+
rescue => error
|
91
|
+
# Nothing
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def handle_unsubscribe_msg(connection, data)
|
97
|
+
topic = topic_for_uri connection.resolve_prefix data[0]
|
98
|
+
return unless topic
|
99
|
+
topic.remove_subscriber connection
|
100
|
+
connection.unsubscribe_from_topic topic
|
101
|
+
end
|
102
|
+
|
103
|
+
def handle_publish_msg(connection, data)
|
104
|
+
topic_uri, event, exclude, include = data
|
105
|
+
uri = connection.resolve_prefix topic_uri
|
106
|
+
handler = get_publish_handler uri
|
107
|
+
|
108
|
+
# Do nothing unless the server recognizes the topic
|
109
|
+
return unless handler
|
110
|
+
|
111
|
+
# Do nothing unless the handler covers partial uri matches
|
112
|
+
return unless handler[1] == '' && !handler[4]
|
113
|
+
|
114
|
+
topic = topic_for_uri uri
|
115
|
+
|
116
|
+
# Exclude true means to exclude myself from the message
|
117
|
+
exclude = [connection.id] if exclude == true
|
118
|
+
|
119
|
+
if handler[2].nil? && handler[3].nil?
|
120
|
+
dispatch_event topic.uri, event, exclude, include
|
121
|
+
else
|
122
|
+
begin
|
123
|
+
begin
|
124
|
+
if handler[2]
|
125
|
+
result = handler[2].send handler[3], [connection, topic_uri]
|
126
|
+
elsif handler[3].is_a? Proc
|
127
|
+
result = handler[1].call connection, topic_uri
|
128
|
+
end
|
129
|
+
dispatch_event topic.uri, event, exclude, include
|
130
|
+
rescue => error
|
131
|
+
# Nothing
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def dispatch_event(topic_uri, event, exclude, include)
|
138
|
+
topic = topics[topic_uri]
|
139
|
+
topic.subscribers(exclude, include).each do |subscriber|
|
140
|
+
subscriber.websocket.send event_msg(topic_uri, event)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def topic_for_uri(topic_uri)
|
147
|
+
uri = topics.keys.select { |uri| topic_uri.include? uri }.sort_by(&:length).last
|
148
|
+
topics[uri]
|
149
|
+
end
|
150
|
+
|
151
|
+
def get_subscribe_handler(topic_uri)
|
152
|
+
topic = topic_for_uri topic_uri
|
153
|
+
nil unless topic
|
154
|
+
handler = topic.subscribe_handler
|
155
|
+
[topic.uri, topic_uri[topic.uri.length..-1], *handler]
|
156
|
+
end
|
157
|
+
|
158
|
+
def get_publish_handler(topic_uri)
|
159
|
+
topic = topic_for_uri topic_uri
|
160
|
+
nil unless topic
|
161
|
+
handler = topic.publish_handler
|
162
|
+
[topic.uri, topic_uri[topic.uri.length..-1], *handler]
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Wampus
|
2
|
+
module Rpc
|
3
|
+
module Handler
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
class_attribute :rpc_handlers
|
8
|
+
self.rpc_handlers = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def expose_rpc(method, options = {})
|
13
|
+
as = options.fetch :as, method
|
14
|
+
rpc_handlers[as] = method
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Wampus
|
2
|
+
module Rpc
|
3
|
+
module ServerExt
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
attr_reader :rpcs
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
@rpcs = {}
|
10
|
+
super(*args) if defined? super
|
11
|
+
end
|
12
|
+
|
13
|
+
included do
|
14
|
+
include ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# -- RPC Handler Registration
|
19
|
+
def register_rpc_handler(handler, base_uri)
|
20
|
+
after_init do
|
21
|
+
handler.rpc_handlers.each do |procedure, handler_method|
|
22
|
+
uri = URI_RPC_BASE + procedure
|
23
|
+
register_rpc_method uri, handler, handler_method
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def register_rpc_method(uri, handler, handler_method)
|
29
|
+
after_init do
|
30
|
+
rpcs[uri] = [handler, handler_method]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def register_rpc_block(uri, &block)
|
35
|
+
after_init do
|
36
|
+
rpcs[uri] = [nil, block]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# TODO FIXME Use EM Deferred
|
42
|
+
def handle_call_msg(connection, data)
|
43
|
+
call_id, proc_uri, *args = data
|
44
|
+
|
45
|
+
uri = connection.resolve_prefix proc_uri
|
46
|
+
handler = rpcs[uri]
|
47
|
+
|
48
|
+
begin
|
49
|
+
rpc_call_exists = rpcs.has_key?(uri)
|
50
|
+
uri, args = on_before_call connection, call_id, uri, args, rpc_call_exists
|
51
|
+
unless rpc_call_exists
|
52
|
+
raise Wampus::Errors::CallError.new(URI_ERROR+'NoSuchRPCEndpoint', 'Missing Method')
|
53
|
+
end
|
54
|
+
if handler[0]
|
55
|
+
result = handler[0].send handler[1], [connection, *args]
|
56
|
+
elsif handler[1].is_a? Proc
|
57
|
+
result = handler[1].call connection, *args
|
58
|
+
end
|
59
|
+
result = on_after_call_success connection, result
|
60
|
+
connection.send call_result_msg call_id, result
|
61
|
+
rescue => error
|
62
|
+
error = on_after_call_error connection, error
|
63
|
+
msg = call_error_msg call_id, *error.to_call_error
|
64
|
+
connection.send msg
|
65
|
+
on_after_send_call_error connection, msg
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# -- RPC Hooks
|
70
|
+
|
71
|
+
def on_before_call(connection, call_id, uri, args, call_exists)
|
72
|
+
[uri, args]
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_after_call_success(connection, result)
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_after_call_error(connection, error)
|
80
|
+
unless error.is_a? Wampus::Errors::CallError
|
81
|
+
error = Wampus::Errors::CallError.new(URI_ERROR+'generic', 'RPC Call Error')
|
82
|
+
end
|
83
|
+
error
|
84
|
+
end
|
85
|
+
|
86
|
+
def on_after_send_call_error(connection, error_msg)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def proc_for_uri(rpc_uri)
|
92
|
+
uri = rpcs.keys.select { |uri| uri.include? rpc_uri }.sort_by(&:length).last
|
93
|
+
rpcs[uri]
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Wampus
|
2
|
+
module Servers
|
3
|
+
class Wamp
|
4
|
+
include Wampus::AfterInit
|
5
|
+
include Wampus::Protocols::Wamp
|
6
|
+
|
7
|
+
attr_reader :connections
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
@connections = {}
|
11
|
+
|
12
|
+
super(*args) if defined? super
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(options = {})
|
16
|
+
options[:protocol] ||= ['wamp']
|
17
|
+
options['sec-websocket-protocol'] = 'wamp'
|
18
|
+
EM::WebSocket.run(options) do |ws|
|
19
|
+
ws.onopen { |handshake| on_connection_open ws, handshake }
|
20
|
+
ws.onmessage { |event| on_connection_message ws, event }
|
21
|
+
ws.onerror { |error| on_connection_error ws, error }
|
22
|
+
ws.onclose { |event| on_connection_close ws, event }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_session_open(connection)
|
27
|
+
# Nothing
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_connection_open(websocket, handshake)
|
31
|
+
connection = new_connection(websocket)
|
32
|
+
connection.websocket.send welcome_msg connection.id
|
33
|
+
on_session_open connection
|
34
|
+
end
|
35
|
+
|
36
|
+
def on_connection_message(websocket, message)
|
37
|
+
connection = find_connections(:websocket => websocket).first
|
38
|
+
|
39
|
+
# Consume any Non-WAMP WS messages
|
40
|
+
begin
|
41
|
+
data = JSON.parse(message)
|
42
|
+
msg_type = data.shift
|
43
|
+
rescue => error
|
44
|
+
# TODO Handle Error
|
45
|
+
return
|
46
|
+
end
|
47
|
+
|
48
|
+
case msg_type
|
49
|
+
when PREFIX_MSG_ID
|
50
|
+
handle_prefix_msg connection, data
|
51
|
+
when CALL_MSG_ID
|
52
|
+
handle_call_msg connection, data
|
53
|
+
when SUBSCRIBE_MSG_ID
|
54
|
+
handle_subscribe_msg connection, data
|
55
|
+
when UNSUBSCRIBE_MSG_ID
|
56
|
+
handle_unsubscribe_msg connection, data
|
57
|
+
when PUBLISH_MSG_ID
|
58
|
+
handle_publish_msg connection, data
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def on_connection_close(websocket, event)
|
63
|
+
connection = delete_connection websocket
|
64
|
+
connection.close_connection
|
65
|
+
end
|
66
|
+
|
67
|
+
# TODO Handle Errors
|
68
|
+
def on_connection_error(websocket, error)
|
69
|
+
p [:error!, error]
|
70
|
+
end
|
71
|
+
|
72
|
+
def handle_prefix_msg(connection, data)
|
73
|
+
prefix, uri = data
|
74
|
+
|
75
|
+
connection.add_prefix prefix, uri
|
76
|
+
end
|
77
|
+
|
78
|
+
def handle_call_msg(connection, data)
|
79
|
+
super if defined? super
|
80
|
+
end
|
81
|
+
|
82
|
+
def handle_subscribe_msg(connection, data)
|
83
|
+
super if defined? super
|
84
|
+
end
|
85
|
+
|
86
|
+
def handle_unsubscribe_msg(connection, data)
|
87
|
+
super if defined? super
|
88
|
+
end
|
89
|
+
|
90
|
+
def handle_publish_msg(connection, data)
|
91
|
+
super if defined? super
|
92
|
+
end
|
93
|
+
|
94
|
+
def server_ident
|
95
|
+
Wampus::identity
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# -- Connection Management
|
101
|
+
|
102
|
+
def new_connection(websocket)
|
103
|
+
connection = Wampus::Connection.new SecureRandom.uuid, websocket
|
104
|
+
connections[connection.id] = connection
|
105
|
+
end
|
106
|
+
|
107
|
+
def find_connections(filters = {})
|
108
|
+
connections.select do |id, connection|
|
109
|
+
filters.all? { |message, value| connection.send(message) == value }
|
110
|
+
end.flat_map { |conn| conn[1] }
|
111
|
+
end
|
112
|
+
|
113
|
+
def delete_connection(websocket)
|
114
|
+
connection = find_connections(:websocket => websocket).first
|
115
|
+
connections.delete connection.id
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Wampus
|
2
|
+
module Servers
|
3
|
+
class WampCra < Wamp
|
4
|
+
include Wampus::Protocols::WampCra
|
5
|
+
include Wampus::Rpc::ServerExt
|
6
|
+
|
7
|
+
class_attribute :client_auth_timeout, :client_auth_allow_anonymous
|
8
|
+
self.client_auth_timeout = 0
|
9
|
+
self.client_auth_allow_anonymous = true
|
10
|
+
|
11
|
+
after_init do
|
12
|
+
register_rpc_method URI_RPC+'authRequest', self, :auth_request
|
13
|
+
register_rpc_method URI_RPC+'auth', self, :auth
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_auth_permissions(connection, auth_key, auth_extra)
|
17
|
+
{:permissions => { :pubsub => [], :rpc => [] } }
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_auth_secret(connection, auth_key)
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_auth_timeout(connection)
|
25
|
+
# Nothing
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_authenticated(connection, auth_key, permissions)
|
29
|
+
# Nothing
|
30
|
+
end
|
31
|
+
|
32
|
+
def on_session_open(connection)
|
33
|
+
if client_auth_timeout > 0
|
34
|
+
connection.auth_timeout_call = EventMachine.add_timer client_auth_timeout {
|
35
|
+
on_auth_timeout(connection)
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def auth_request(connection, auth_key = nil, extra = {})
|
41
|
+
if connection.authenticated
|
42
|
+
raise Wampus::Errors::CallError.new(URI_ERROR+'already-authenticated', 'Already Authenticated')
|
43
|
+
end
|
44
|
+
|
45
|
+
unless connection.pending_auth.nil?
|
46
|
+
raise Wampus::Errors::CallError.new(URI_ERROR+'authentication-already-requested', 'Authentication Already Requested')
|
47
|
+
end
|
48
|
+
|
49
|
+
unless extra.is_a? Hash
|
50
|
+
raise Wampus::Errors::CallError.new(URI_ERROR+'invalid-argument', 'Extra is not a Dictionary/Hash')
|
51
|
+
end
|
52
|
+
|
53
|
+
if auth_key.nil? && !client_auth_allow_anonymous
|
54
|
+
raise Wampus::Errors::CallError.new(URI_ERROR+'anonymous-auth-forbidden', 'Anonymous Auth Forbidden')
|
55
|
+
end
|
56
|
+
|
57
|
+
auth_secret = get_auth_secret connection, auth_key
|
58
|
+
on_get_auth_secrek_ok connection, auth_secret, auth_key, extra
|
59
|
+
end
|
60
|
+
|
61
|
+
def auth(connection, signature = nil)
|
62
|
+
if connection.authenticated
|
63
|
+
raise Wampus::Errors::CallError.new(URI_ERROR+'already-authenticated', 'Already Authenticated')
|
64
|
+
end
|
65
|
+
|
66
|
+
if connection.pending_auth.nil?
|
67
|
+
raise Wampus::Errors::CallError.new(URI_ERROR+'no-authentication-requested', 'No Authentication Requested')
|
68
|
+
end
|
69
|
+
|
70
|
+
if connection.pending_auth[1] != signature
|
71
|
+
connection.pending_auth = nil
|
72
|
+
|
73
|
+
return EventMachine.add_timer expo(1.25) {
|
74
|
+
raise Wampus::Errors::CallError.new URI_ERROR+'invalid-signature', 'Signature for authentication request is invalid'
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
permissions = connection.pending_auth
|
79
|
+
auth_key = connection.pending_auth[0][:auth_key]
|
80
|
+
connection.authenticated = true
|
81
|
+
connection.pending_auth = nil
|
82
|
+
unless connection.auth_timeout_call.nil?
|
83
|
+
connection.auth_timeout_call.cancel
|
84
|
+
connection.auth_timeout_call = nil
|
85
|
+
end
|
86
|
+
|
87
|
+
on_authenticated connection, auth_key, permissions
|
88
|
+
|
89
|
+
return permissions[:permissions]
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def on_get_auth_secret_ok(connection, auth_secret, auth_key, extra)
|
95
|
+
auth_id = SecureRandom.uuid
|
96
|
+
|
97
|
+
auth_info = {
|
98
|
+
:authid => auth_id,
|
99
|
+
:auth_key => auth_key,
|
100
|
+
:timestamp => Time.now.to_i,
|
101
|
+
:session_id => connection.id,
|
102
|
+
:extra => extra
|
103
|
+
}
|
104
|
+
|
105
|
+
results = get_auth_permissions connection, auth_key, extra
|
106
|
+
auth_info[:permissions] = results[:permissions]
|
107
|
+
auth_info[:auth_extra] = results[:auth_extra] if results.has_key? :auth_extra
|
108
|
+
|
109
|
+
if auth_key
|
110
|
+
auth_sig = auth_signature auth_info, auth_secret
|
111
|
+
connection.pending_auth = [auth_info, auth_sig, results]
|
112
|
+
return auth_info
|
113
|
+
else
|
114
|
+
connection.pending_auth = [auth_info, nil, results]
|
115
|
+
return nil
|
116
|
+
end
|
117
|
+
rescue => error
|
118
|
+
raise CallError.new(URI_ERROR+'auth-permissions-error', error.message)
|
119
|
+
end
|
120
|
+
|
121
|
+
def expo(l)
|
122
|
+
((-Math.log(1-Random.rand)).quo l)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/wampus/topic.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Wampus
|
4
|
+
class Topic
|
5
|
+
|
6
|
+
attr_accessor :subscribe_handler, :publish_handler
|
7
|
+
attr_reader :uri, :allow_partial_uri_match, :connections
|
8
|
+
|
9
|
+
def initialize(uri, allow_partial_uri_match = false)
|
10
|
+
@uri = uri
|
11
|
+
@allow_partial_uri_match = allow_partial_uri_match
|
12
|
+
@connections = Set.new
|
13
|
+
@subscribe_handler = [nil, nil, false]
|
14
|
+
@publish_handler = [nil, nil, false]
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_subscriber(connection)
|
18
|
+
@connections << connection
|
19
|
+
connection.subscribe_to_topic self
|
20
|
+
end
|
21
|
+
|
22
|
+
def remove_subscriber(connection)
|
23
|
+
@connections.delete connection
|
24
|
+
connection.unsubscribe_from_topic self
|
25
|
+
end
|
26
|
+
|
27
|
+
def subscribers(excluded = [], eligible = [])
|
28
|
+
connections = @connections.to_a
|
29
|
+
|
30
|
+
if !!excluded == false
|
31
|
+
excluded = []
|
32
|
+
end
|
33
|
+
|
34
|
+
if !!eligible == false
|
35
|
+
eligible = []
|
36
|
+
end
|
37
|
+
|
38
|
+
connections.delete_if { |connection| excluded.include? connection.id } unless excluded.empty?
|
39
|
+
connections.keep_if { |connection| eligible.include? connection.id } unless eligible.empty?
|
40
|
+
|
41
|
+
connections
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
data/lib/wampus.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Wampus
|
2
|
+
|
3
|
+
ROOT = File.expand_path File.dirname __FILE__
|
4
|
+
|
5
|
+
autoload :AfterInit, File.join(ROOT, 'wampus', 'after_init')
|
6
|
+
autoload :Connection, File.join(ROOT, 'wampus', 'connection')
|
7
|
+
autoload :Topic, File.join(ROOT, 'wampus', 'topic')
|
8
|
+
require File.join(ROOT, 'wampus', 'version')
|
9
|
+
|
10
|
+
module Backends
|
11
|
+
autoload :Redis, File.join(ROOT, 'wampus', 'backends', 'redis')
|
12
|
+
end
|
13
|
+
|
14
|
+
module Clients
|
15
|
+
autoload :Wamp, File.join(ROOT, 'wampus', 'clients', 'wamp')
|
16
|
+
end
|
17
|
+
|
18
|
+
module Errors
|
19
|
+
autoload :CallError, File.join(ROOT, 'wampus', 'errors', 'call_error')
|
20
|
+
end
|
21
|
+
|
22
|
+
module Protocols
|
23
|
+
autoload :Wamp, File.join(ROOT, 'wampus', 'protocols', 'wamp')
|
24
|
+
autoload :WampCra, File.join(ROOT, 'wampus', 'protocols', 'wamp_cra')
|
25
|
+
end
|
26
|
+
|
27
|
+
module Pubsub
|
28
|
+
autoload :Handler, File.join(ROOT, 'wampus', 'pubsub', 'handler')
|
29
|
+
autoload :ServerExt, File.join(ROOT, 'wampus', 'pubsub', 'server_ext')
|
30
|
+
end
|
31
|
+
|
32
|
+
module Rpc
|
33
|
+
autoload :Handler, File.join(ROOT, 'wampus', 'rpc', 'handler')
|
34
|
+
autoload :ServerExt, File.join(ROOT, 'wampus', 'rpc', 'server_ext')
|
35
|
+
end
|
36
|
+
|
37
|
+
module Servers
|
38
|
+
autoload :Wamp, File.join(ROOT, 'wampus', 'servers', 'wamp')
|
39
|
+
autoload :WampCra, File.join(ROOT, 'wampus', 'servers', 'wamp_cra')
|
40
|
+
end
|
41
|
+
|
42
|
+
class << self
|
43
|
+
def version
|
44
|
+
Wampus::VERSION
|
45
|
+
end
|
46
|
+
|
47
|
+
def identity
|
48
|
+
"Wampus/#{Wampus::VERSION}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/wampus.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'wampus/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'wampus'
|
8
|
+
spec.version = Wampus::VERSION
|
9
|
+
spec.authors = ['Michael Stack']
|
10
|
+
spec.email = ['michael.stack@gmail.com']
|
11
|
+
spec.description = 'WebSocket server supporting the WAMP subprotocol'
|
12
|
+
spec.summary = ''
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
|
24
|
+
spec.add_dependency 'eventmachine'
|
25
|
+
spec.add_dependency 'em-websocket'
|
26
|
+
spec.add_dependency 'activesupport'
|
27
|
+
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wampus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Stack
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: eventmachine
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: em-websocket
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activesupport
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: WebSocket server supporting the WAMP subprotocol
|
84
|
+
email:
|
85
|
+
- michael.stack@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- .gitignore
|
91
|
+
- Gemfile
|
92
|
+
- LICENSE.txt
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- lib/wampus.rb
|
96
|
+
- lib/wampus/after_init.rb
|
97
|
+
- lib/wampus/backends/redis.rb
|
98
|
+
- lib/wampus/clients/wamp.rb
|
99
|
+
- lib/wampus/connection.rb
|
100
|
+
- lib/wampus/errors/call_error.rb
|
101
|
+
- lib/wampus/protocols/wamp.rb
|
102
|
+
- lib/wampus/protocols/wamp_cra.rb
|
103
|
+
- lib/wampus/pubsub/handler.rb
|
104
|
+
- lib/wampus/pubsub/server_ext.rb
|
105
|
+
- lib/wampus/rpc/handler.rb
|
106
|
+
- lib/wampus/rpc/server_ext.rb
|
107
|
+
- lib/wampus/servers/wamp.rb
|
108
|
+
- lib/wampus/servers/wamp_cra.rb
|
109
|
+
- lib/wampus/topic.rb
|
110
|
+
- lib/wampus/version.rb
|
111
|
+
- wampus.gemspec
|
112
|
+
homepage: ''
|
113
|
+
licenses:
|
114
|
+
- MIT
|
115
|
+
metadata: {}
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options: []
|
118
|
+
require_paths:
|
119
|
+
- lib
|
120
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
requirements: []
|
131
|
+
rubyforge_project:
|
132
|
+
rubygems_version: 2.1.10
|
133
|
+
signing_key:
|
134
|
+
specification_version: 4
|
135
|
+
summary: ''
|
136
|
+
test_files: []
|