flotype-bridge 0.1.0.beta.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.
- data/README.md +6 -0
- data/Rakefile +24 -0
- data/doc/Bridge.html +954 -0
- data/doc/Bridge/Callback.html +495 -0
- data/doc/Bridge/CallbackRef.html +343 -0
- data/doc/Bridge/Conn.html +409 -0
- data/doc/Bridge/Core.html +762 -0
- data/doc/Bridge/LocalRef.html +509 -0
- data/doc/Bridge/Ref.html +647 -0
- data/doc/Bridge/Service.html +135 -0
- data/doc/Bridge/Sys.html +278 -0
- data/doc/Bridge/Util.html +746 -0
- data/doc/_index.html +202 -0
- data/doc/class_list.html +47 -0
- data/doc/classes/Bridge.html +263 -0
- data/doc/classes/Bridge.src/M000001.html +24 -0
- data/doc/classes/Bridge.src/M000002.html +18 -0
- data/doc/classes/Bridge.src/M000003.html +18 -0
- data/doc/classes/Bridge.src/M000004.html +20 -0
- data/doc/classes/Bridge.src/M000005.html +26 -0
- data/doc/classes/Bridge.src/M000006.html +22 -0
- data/doc/classes/Bridge.src/M000007.html +18 -0
- data/doc/classes/Bridge.src/M000008.html +18 -0
- data/doc/created.rid +1 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +55 -0
- data/doc/css/style.css +322 -0
- data/doc/file.README.html +71 -0
- data/doc/file_list.html +49 -0
- data/doc/files/lib/bridge_rb.html +108 -0
- data/doc/fr_class_index.html +27 -0
- data/doc/fr_file_index.html +27 -0
- data/doc/fr_method_index.html +34 -0
- data/doc/frames.html +13 -0
- data/doc/index.html +71 -0
- data/doc/js/app.js +205 -0
- data/doc/js/full_list.js +173 -0
- data/doc/js/jquery.js +16 -0
- data/doc/method_list.html +510 -0
- data/doc/rdoc-style.css +208 -0
- data/doc/top-level-namespace.html +105 -0
- data/examples/chat/chat_client.rb +41 -0
- data/examples/chat/chat_server.rb +24 -0
- data/examples/pong/pong.rb +29 -0
- data/flotype-bridge.gemspec +24 -0
- data/lib/bb/callback.rb +33 -0
- data/lib/bb/conn.rb +31 -0
- data/lib/bb/core.rb +109 -0
- data/lib/bb/localref.rb +37 -0
- data/lib/bb/ref.rb +49 -0
- data/lib/bb/svc.rb +7 -0
- data/lib/bb/sys.rb +17 -0
- data/lib/bb/util.rb +93 -0
- data/lib/bb/version.rb +3 -0
- data/lib/bridge.rb +130 -0
- data/rakelib/package.rake +4 -0
- data/rakelib/test.rake +8 -0
- data/tests/bb_test_helper.rb +3 -0
- data/tests/test_serialize.rb +4 -0
- metadata +196 -0
data/lib/bb/conn.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Bridge
|
2
|
+
module Conn
|
3
|
+
def self.send arg
|
4
|
+
Util::log 'sending: ' + arg
|
5
|
+
@@conn.send_data arg
|
6
|
+
end
|
7
|
+
|
8
|
+
# Methods expected by EventMachine.
|
9
|
+
def initialize
|
10
|
+
Util::log 'Connection initialized.'
|
11
|
+
@@conn = self
|
12
|
+
end
|
13
|
+
|
14
|
+
def post_init
|
15
|
+
Util::log 'Starting handshake.'
|
16
|
+
Core::command(:CONNECT,
|
17
|
+
{ :session => Core::session,
|
18
|
+
:api_key => Bridge::options['api_key'] })
|
19
|
+
end
|
20
|
+
|
21
|
+
def receive_data data
|
22
|
+
Util::log 'Got data.'
|
23
|
+
Core::process(data)
|
24
|
+
end
|
25
|
+
|
26
|
+
def unbind
|
27
|
+
Util::log 'Disconnected.'
|
28
|
+
Core::disconnect
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/bb/core.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
module Bridge
|
2
|
+
# The internals of the Bridge client. Use of this module is strongly
|
3
|
+
# unadvised, as the internal structure may vary greatly from that of
|
4
|
+
# other language implementations.
|
5
|
+
module Core
|
6
|
+
@@services, @@queue, @@sess = {'["system"]' => Bridge::Sys}, [], [nil, nil]
|
7
|
+
@@connected, @@len, @@buffer = false, 0, ''
|
8
|
+
|
9
|
+
def self.session
|
10
|
+
@@sess
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.client_id
|
14
|
+
@@sess[0]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.connected
|
18
|
+
@@connected
|
19
|
+
end
|
20
|
+
|
21
|
+
# The queue is used primarily for Bridge::ready() callbacks.
|
22
|
+
def self.enqueue fun
|
23
|
+
if @@connected
|
24
|
+
fun.call
|
25
|
+
else
|
26
|
+
@@queue = @@queue + [fun]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.store svc, obj = nil, named = true
|
31
|
+
@@services[[named ? svc : "channel:#{svc}"].to_json] = obj
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.lookup ref
|
35
|
+
Util::log 'Looking up ref ' + ref.to_json
|
36
|
+
ref = JSON::parse ref.to_json
|
37
|
+
svc = @@services[ref[2 .. -1].to_json] || @@services[[ref[2]].to_json]
|
38
|
+
if svc != nil
|
39
|
+
if ref[3] != nil
|
40
|
+
svc.method(ref[3])
|
41
|
+
else
|
42
|
+
svc
|
43
|
+
end
|
44
|
+
else
|
45
|
+
Ref.lookup ref
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.process data
|
50
|
+
if @@len == 0
|
51
|
+
@@len = data.unpack('N')[0]
|
52
|
+
return process data[4 .. -1]
|
53
|
+
end
|
54
|
+
Util::log ':::: ' + data
|
55
|
+
(@@buffer << data)
|
56
|
+
if @@buffer.length < @@len
|
57
|
+
return
|
58
|
+
end
|
59
|
+
data = @@buffer
|
60
|
+
@@buffer, @@len = @@buffer[@@len .. -1], 0
|
61
|
+
# If this is the first message, set our SessionId and Secret.
|
62
|
+
m = /^(\w+)\|(\w+)$/.match data
|
63
|
+
if m
|
64
|
+
@@connected = true
|
65
|
+
@@sess = [m[1], m[2]]
|
66
|
+
Util::log "Received secret and session ID: #{@@sess.to_json}"
|
67
|
+
@@queue.each {|fun|
|
68
|
+
fun.call
|
69
|
+
}
|
70
|
+
@@queue = []
|
71
|
+
return
|
72
|
+
end
|
73
|
+
# Else, it is a normal message.
|
74
|
+
unser = Util::unserialize data
|
75
|
+
dest = unser['destination']
|
76
|
+
if dest.respond_to? :call
|
77
|
+
dest.call *unser['args']
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.command cmd, data
|
82
|
+
if cmd == :CONNECT
|
83
|
+
Conn::send(Util::serialize({:command => cmd, :data => data}))
|
84
|
+
else
|
85
|
+
Core::enqueue lambda {
|
86
|
+
Conn::send(Util::serialize({:command => cmd, :data => data}))
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.reconnect timeout
|
92
|
+
opts = Bridge::options
|
93
|
+
if opts[:reconnect]
|
94
|
+
Util::log "Attempting to reconnect; waiting at most #{timeout.to_s}s."
|
95
|
+
EventMachine::connect(opts[:host], opts[:port], Conn)
|
96
|
+
EventMachine::Timer.new(timeout) do
|
97
|
+
if not @@connected
|
98
|
+
reconnect timeout * 2
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.disconnect
|
105
|
+
@@connected = false
|
106
|
+
reconnect 0.1
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/bb/localref.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Bridge
|
2
|
+
# Wrapper for callbacks passed in as arguments.
|
3
|
+
class LocalRef < Ref
|
4
|
+
def initialize path, mod
|
5
|
+
@mod = mod
|
6
|
+
path = ['client', lambda {Core::client_id}] + path
|
7
|
+
super(path)
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing atom, *args, &blk
|
11
|
+
(@mod.method atom).call *args
|
12
|
+
end
|
13
|
+
|
14
|
+
def method atom
|
15
|
+
@mod.method(atom)
|
16
|
+
end
|
17
|
+
|
18
|
+
def operations bool
|
19
|
+
@mod.methods(bool)
|
20
|
+
end
|
21
|
+
|
22
|
+
def call *args
|
23
|
+
@mod.call *args
|
24
|
+
end
|
25
|
+
|
26
|
+
def respond_to? atom
|
27
|
+
@mod.respond_to? atom
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_json *a
|
31
|
+
if @path[1].respond_to? :call
|
32
|
+
@path[1] = @path[1].call
|
33
|
+
end
|
34
|
+
{:ref => @path, :operations => @mod.methods(false)}.to_json *a
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/bb/ref.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Bridge
|
2
|
+
# Instances of this class represent references to remote services.
|
3
|
+
class Ref
|
4
|
+
@@refs = {}
|
5
|
+
|
6
|
+
def self.lookup ref
|
7
|
+
key = ref[2 .. -1].to_json
|
8
|
+
if @@refs[key] == nil
|
9
|
+
@@refs[key] = Ref.new(ref)
|
10
|
+
end
|
11
|
+
@@refs[key]
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize path
|
15
|
+
@path = path
|
16
|
+
@@refs[path[2 .. -1].to_json] = self
|
17
|
+
end
|
18
|
+
|
19
|
+
def path
|
20
|
+
@path
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing atom, *args, &blk
|
24
|
+
Core::lookup(@path + [atom]).call *args
|
25
|
+
end
|
26
|
+
|
27
|
+
def method atom
|
28
|
+
Core::lookup(@path + [atom])
|
29
|
+
end
|
30
|
+
|
31
|
+
def call *args
|
32
|
+
Core::command :SEND, {
|
33
|
+
:destination => {:ref => @path},
|
34
|
+
:args => Util::inflate(args)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def respond_to? atom
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_json *a
|
43
|
+
if @path[1].respond_to? :call
|
44
|
+
@path[1] = @path[1].call
|
45
|
+
end
|
46
|
+
{:ref => @path}.to_json *a
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/bb/svc.rb
ADDED
data/lib/bb/sys.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Bridge
|
2
|
+
# These are internal system functions, which should only be called by the
|
3
|
+
# Erlang gateway.
|
4
|
+
module Sys
|
5
|
+
def self.hook_channel_handler name, handler, fun
|
6
|
+
fun.call(Core::store(name, handler, 'channel'))
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.remoteError msg
|
10
|
+
Util::err(msg)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.get_service name, fun
|
14
|
+
fun.call(Core::lookup(name).methods(false))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/bb/util.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Bridge
|
4
|
+
module Util
|
5
|
+
# Traverses an object, replacing funs with refs.
|
6
|
+
def self.inflate obj
|
7
|
+
if obj.is_a?(Array)
|
8
|
+
obj.map do |v|
|
9
|
+
Util::bloat v
|
10
|
+
end
|
11
|
+
else
|
12
|
+
o = {}
|
13
|
+
obj.each do |k, v|
|
14
|
+
o[k] = Util::bloat v
|
15
|
+
end
|
16
|
+
o
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.bloat v
|
21
|
+
if v.is_a?(Module) || v.is_a?(Bridge::Service)
|
22
|
+
local_ref(v)
|
23
|
+
elsif v.respond_to?(:call) && !v.is_a?(Ref)
|
24
|
+
cb(v)
|
25
|
+
elsif v.is_a?(Hash) || v.is_a?(Array)
|
26
|
+
inflate v
|
27
|
+
else
|
28
|
+
v
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.deflate obj
|
33
|
+
if obj.is_a?(Array)
|
34
|
+
obj.map do |v|
|
35
|
+
deflate v
|
36
|
+
end
|
37
|
+
elsif obj.is_a? Hash
|
38
|
+
if obj.has_key? 'ref'
|
39
|
+
Core::lookup obj['ref']
|
40
|
+
else
|
41
|
+
o = {}
|
42
|
+
obj.each do |k, v|
|
43
|
+
o[k] = deflate v
|
44
|
+
end
|
45
|
+
o
|
46
|
+
end
|
47
|
+
else
|
48
|
+
obj
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.serialize obj
|
53
|
+
obj = inflate(obj)
|
54
|
+
str = JSON::generate obj
|
55
|
+
[str.length].pack("N") + str
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.unserialize str
|
59
|
+
obj = deflate(JSON::parse str)
|
60
|
+
deflate obj
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.err msg
|
64
|
+
$stderr.puts msg
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.log msg, level = 3
|
68
|
+
opts = Bridge::options
|
69
|
+
if level <= opts['log_level']
|
70
|
+
puts msg
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.cb fun
|
75
|
+
Core::store(fun.object_id.to_s(36),
|
76
|
+
LocalRef.new([fun.hash.to_s(36)], Callback.new(fun)))
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.has_keys? obj, *keys
|
80
|
+
keys.each do |k|
|
81
|
+
if !obj.has_key?(k) && !obj.has_key?(k.to_sym)
|
82
|
+
return false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
true
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.local_ref v
|
89
|
+
key = v.object_id.to_s(36)
|
90
|
+
Core::store key, Bridge::LocalRef.new([key], v)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/bb/version.rb
ADDED
data/lib/bridge.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'bb/svc'
|
2
|
+
require 'bb/conn'
|
3
|
+
require 'bb/ref'
|
4
|
+
require 'bb/sys'
|
5
|
+
require 'bb/core'
|
6
|
+
require 'bb/localref'
|
7
|
+
require 'bb/callback'
|
8
|
+
require 'bb/util'
|
9
|
+
|
10
|
+
require 'eventmachine'
|
11
|
+
|
12
|
+
module Bridge
|
13
|
+
|
14
|
+
# Expects to be called inside an EventMachine block in lieu of
|
15
|
+
# EM::connect.
|
16
|
+
# @param [Hash] configuration options
|
17
|
+
@options = {
|
18
|
+
'reconnect' => true,
|
19
|
+
'redir_host' => 'redirector.flotype.com',
|
20
|
+
'redir_port' => 80,
|
21
|
+
'log_level' => 3, # 0 for no output.
|
22
|
+
}
|
23
|
+
def self.initialize(options = {})
|
24
|
+
Util::log 'initialize called.'
|
25
|
+
@options = @options.merge(options)
|
26
|
+
|
27
|
+
if !(@options.has_key? 'api_key')
|
28
|
+
raise ArgumentError, 'No API key specified.'
|
29
|
+
end
|
30
|
+
|
31
|
+
if Util::has_keys?(@options, 'host', 'port')
|
32
|
+
EM::connect(@options['host'], @options['port'], Bridge::Conn)
|
33
|
+
else
|
34
|
+
# Support for redirector.
|
35
|
+
conn = EM::Protocols::HttpClient2.connect(@options['redir_host'],
|
36
|
+
@options['redir_port'])
|
37
|
+
req = conn.get({:uri => "/redirect/#{@options['api_key']}"})
|
38
|
+
req.callback do |obj|
|
39
|
+
obj = JSON::parse obj.content
|
40
|
+
if obj.has_key?('data')
|
41
|
+
obj = obj['data']
|
42
|
+
EM::connect(obj['bridge_host'], obj['bridge_port'], Bridge::Conn)
|
43
|
+
else
|
44
|
+
raise Exception, 'Invalid API key.'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Accessor for @options.
|
51
|
+
# @return [Object] options
|
52
|
+
def self.options
|
53
|
+
@options
|
54
|
+
end
|
55
|
+
|
56
|
+
# Similar to $(document).ready of jQuery as well as now.ready: takes
|
57
|
+
# callbacks that it will call when the connection handshake has been
|
58
|
+
# completed.
|
59
|
+
# @param [#call] fun Callback to be called when the server connects.
|
60
|
+
def self.ready fun
|
61
|
+
Core::enqueue fun
|
62
|
+
end
|
63
|
+
|
64
|
+
# Calls a remote function specified by `dest` with `args`.
|
65
|
+
# @param [Ref] dest The identifier of the remote function to call.
|
66
|
+
# @param [Array] args Arguments to be passed to `dest`.
|
67
|
+
def self.send dest, args
|
68
|
+
Core::command(:SEND,
|
69
|
+
{ :args => Util::serialize(args),
|
70
|
+
:destination => dest })
|
71
|
+
end
|
72
|
+
|
73
|
+
# Broadcasts the availability of certain functionality specified by a
|
74
|
+
# proc `fun` under the name of `svc`.
|
75
|
+
def self.publish_service svc, handler, fun = nil
|
76
|
+
if svc == 'system'
|
77
|
+
Util::err("Invalid service name: #{svc}")
|
78
|
+
else
|
79
|
+
obj = { :name => svc}
|
80
|
+
if fun.respond_to? :call
|
81
|
+
obj[:callback] = Util::cb(fun)
|
82
|
+
end
|
83
|
+
Core::command(:JOINWORKERPOOL, obj)
|
84
|
+
Core::store(svc, Bridge::LocalRef.new([svc], handler))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Join the channel specified by `channel`. Messages from this channel
|
89
|
+
# will be passed in to a handler specified by `handler`. The callback
|
90
|
+
# `fun` is to be called to confirm successful joining of the channel.
|
91
|
+
def self.join_channel channel, handler, fun = nil
|
92
|
+
obj = { :name => channel, :handler => Util::local_ref(handler)}
|
93
|
+
if fun.respond_to? :call
|
94
|
+
obj[:callback] = Util::cb(fun)
|
95
|
+
end
|
96
|
+
Core::command(:JOINCHANNEL, obj)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Leave a channel.
|
100
|
+
def self.leave_channel channel, handler, fun = nil
|
101
|
+
obj = { :name => channel, :handler => Util::local_ref(handler)}
|
102
|
+
if fun.respond_to? :call
|
103
|
+
obj[:callback] = Util::cb(fun)
|
104
|
+
end
|
105
|
+
Core::command(:LEAVECHANNEL, obj)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Leave a channel.
|
109
|
+
def self.leave_channel channel, handler, fun
|
110
|
+
Core::command(:LEAVECHANNEL,
|
111
|
+
{ :name => channel,
|
112
|
+
:handler => Util::local_ref(handler),
|
113
|
+
:callback => Util::cb(fun) })
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns a reference to the service specified by `svc`.
|
117
|
+
def self.get_service svc
|
118
|
+
Core::lookup ['named', svc, svc]
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns a reference to the channel specified by `channel`.
|
122
|
+
def self.get_channel channel
|
123
|
+
Core::lookup ['channel', channel, "channel:#{channel}"]
|
124
|
+
end
|
125
|
+
|
126
|
+
# The client's ID.
|
127
|
+
def self.client_id
|
128
|
+
Core::client_id
|
129
|
+
end
|
130
|
+
end
|