bridge-ruby 0.2.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.
Files changed (67) hide show
  1. data/LICENSE +19 -0
  2. data/README.md +31 -0
  3. data/Rakefile +24 -0
  4. data/bridge-ruby.gemspec +24 -0
  5. data/doc/Bridge.html +276 -0
  6. data/doc/Bridge/Bridge.html +1874 -0
  7. data/doc/Bridge/Bridge/SystemService.html +396 -0
  8. data/doc/Bridge/Client.html +271 -0
  9. data/doc/Bridge/Connection.html +1180 -0
  10. data/doc/Bridge/Connection/SockBuffer.html +322 -0
  11. data/doc/Bridge/Reference.html +605 -0
  12. data/doc/Bridge/Serializer.html +405 -0
  13. data/doc/Bridge/Serializer/Callback.html +498 -0
  14. data/doc/Bridge/Tcp.html +657 -0
  15. data/doc/Bridge/Util.html +643 -0
  16. data/doc/Bridge/Util/CallbackReference.html +557 -0
  17. data/doc/OpenSSL/X509/Certificate.html +275 -0
  18. data/doc/SSLCertificateVerification.html +446 -0
  19. data/doc/_index.html +239 -0
  20. data/doc/class_list.html +53 -0
  21. data/doc/css/common.css +1 -0
  22. data/doc/css/full_list.css +57 -0
  23. data/doc/css/style.css +328 -0
  24. data/doc/file.README.html +106 -0
  25. data/doc/file_list.html +55 -0
  26. data/doc/frames.html +28 -0
  27. data/doc/index.html +106 -0
  28. data/doc/js/app.js +214 -0
  29. data/doc/js/full_list.js +173 -0
  30. data/doc/js/jquery.js +4 -0
  31. data/doc/method_list.html +772 -0
  32. data/doc/top-level-namespace.html +112 -0
  33. data/examples/channels/client-writeable.rb +24 -0
  34. data/examples/channels/client.rb +23 -0
  35. data/examples/channels/server.rb +24 -0
  36. data/examples/chat/chatclient.rb +21 -0
  37. data/examples/chat/chatserver.rb +24 -0
  38. data/examples/client-context/client.rb +21 -0
  39. data/examples/client-context/server.rb +25 -0
  40. data/examples/secure/example.rb +8 -0
  41. data/examples/simple/channels.rb +47 -0
  42. data/examples/simple/services.rb +41 -0
  43. data/include/ssl/cacert.pem +3331 -0
  44. data/lib/bridge-ruby.rb +441 -0
  45. data/lib/client.rb +14 -0
  46. data/lib/connection.rb +162 -0
  47. data/lib/reference.rb +49 -0
  48. data/lib/serializer.rb +104 -0
  49. data/lib/ssl_utils.rb +68 -0
  50. data/lib/tcp.rb +73 -0
  51. data/lib/util.rb +101 -0
  52. data/lib/version.rb +3 -0
  53. data/rakelib/package.rake +4 -0
  54. data/rakelib/test.rake +8 -0
  55. data/test/regression/reconnect.rb +48 -0
  56. data/test/regression/rpc.rb +39 -0
  57. data/test/regression/test.rb +58 -0
  58. data/test/unit/bridge_dummy.rb +26 -0
  59. data/test/unit/connection_dummy.rb +21 -0
  60. data/test/unit/reference_dummy.rb +11 -0
  61. data/test/unit/tcp_dummy.rb +12 -0
  62. data/test/unit/test.rb +20 -0
  63. data/test/unit/test_reference.rb +30 -0
  64. data/test/unit/test_serializer.rb +109 -0
  65. data/test/unit/test_tcp.rb +51 -0
  66. data/test/unit/test_util.rb +59 -0
  67. metadata +162 -0
@@ -0,0 +1,162 @@
1
+ require 'uri'
2
+ require 'util.rb'
3
+ require 'tcp.rb'
4
+ require 'serializer.rb'
5
+
6
+ module Bridge
7
+ class Connection #:nodoc: all
8
+
9
+ attr_accessor :connected, :client_id, :sock, :options
10
+
11
+ def initialize bridge
12
+ # Set associated bridge object
13
+ @bridge = bridge
14
+
15
+ @options = bridge.options
16
+
17
+ # Preconnect buffer
18
+ @sock_buffer = SockBuffer.new
19
+ @sock = @sock_buffer
20
+
21
+ # Connection configuration
22
+ @interval = 0.4
23
+ end
24
+
25
+ # Contact redirector for host and ports
26
+ def redirector
27
+ # Support for redirector.
28
+ uri = URI(@options[:redirector])
29
+ conn = EventMachine::Protocols::HttpClient2.connect(:host => uri.host, :port => uri.port, :ssl => @options[:secure])
30
+ req = conn.get({:uri => "/redirect/#{@options[:api_key]}"})
31
+ req.callback do |obj|
32
+ begin
33
+ obj = JSON::parse obj.content
34
+ rescue Exception => e
35
+ Util.error "Unable to parse redirector response #{obj.content}"
36
+ return
37
+ end
38
+ if obj.has_key?('data') and obj['data'].has_key?('bridge_host') and obj['data'].has_key?('bridge_port')
39
+ obj = obj['data']
40
+ @options[:host] = obj['bridge_host']
41
+ @options[:port] = obj['bridge_port']
42
+ establish_connection
43
+ else
44
+ Util.error "Could not find host and port in JSON body"
45
+ end
46
+ end
47
+ end
48
+
49
+ def reconnect
50
+ Util.info "Attempting to reconnect"
51
+ if @interval < 32768
52
+ EventMachine::Timer.new(@interval) do
53
+ establish_connection
54
+ # Grow timeout for next reconnect attempt
55
+ @interval *= 2
56
+ end
57
+ end
58
+ end
59
+
60
+ def establish_connection
61
+ Util.info "Starting TCP connection #{@options[:host]}, #{@options[:port]}"
62
+ EventMachine::connect(@options[:host], @options[:port], Tcp, self)
63
+ end
64
+
65
+ def onmessage data, sock
66
+ # Parse for client id and secret
67
+ m = /^(\w+)\|(\w+)$/.match data[:data]
68
+ if not m
69
+ # Handle message normally if not a correct CONNECT response
70
+ process_message data
71
+ else
72
+ Util.info "client_id received, #{m[1]}"
73
+ @client_id = m[1]
74
+ @secret = m[2]
75
+ # Reset reconnect interval
76
+ @interval = 0.4
77
+ # Send preconnect queued messages
78
+ @sock_buffer.process_queue sock, @client_id
79
+ # Set connection socket to connected socket
80
+ @sock = sock
81
+ Util.info('Handshake complete')
82
+ # Trigger ready callback
83
+ if not @bridge.is_ready
84
+ @bridge.is_ready = true
85
+ @bridge.emit 'ready'
86
+ end
87
+ end
88
+ end
89
+
90
+ def onopen sock
91
+ Util.info 'Beginning handshake'
92
+ msg = Util.stringify(:command => :CONNECT, :data => {:session => [@client_id || nil, @secret || nil], :api_key => @options[:api_key]})
93
+ sock.send msg
94
+ end
95
+
96
+ def onclose
97
+ Util.warn 'Connection closed'
98
+ # Restore preconnect buffer as socket connection
99
+ @sock = @sock_buffer
100
+ if @options[:reconnect]
101
+ reconnect
102
+ end
103
+ end
104
+
105
+ def process_message message
106
+ begin
107
+ Util.info "Received #{message[:data]}"
108
+ message = Util.parse(message[:data])
109
+ rescue Exception => e
110
+ Util.error "Message parsing failed"
111
+ end
112
+ # Convert serialized ref objects to callable references
113
+ Serializer.unserialize(@bridge, message['args'])
114
+ # Extract RPC destination address
115
+ destination = message['destination']
116
+ if !destination
117
+ Util.warn "No destination in message #{message}"
118
+ return
119
+ end
120
+ if message['source']
121
+ @bridge.context = Client.new(@bridge, message['source'])
122
+ end
123
+ @bridge.execute message['destination']['ref'], message['args']
124
+ end
125
+
126
+ def send_command command, data
127
+ data.delete :callback if data.key? :callback and data[:callback].nil?
128
+ msg = Util.stringify :command => command, :data => data
129
+ Util.info "Sending #{msg}"
130
+ @sock.send msg
131
+ end
132
+
133
+ def start
134
+ if !@options.has_key? :host or !@options.has_key? :port
135
+ redirector
136
+ else
137
+ # Host and port are specified
138
+ establish_connection
139
+ end
140
+ end
141
+
142
+ class SockBuffer
143
+ def initialize
144
+ # Buffer for preconnect messages
145
+ @buffer = []
146
+ end
147
+
148
+ def send msg
149
+ @buffer << msg
150
+ end
151
+
152
+ def process_queue sock, client_id
153
+ @buffer.each do |msg|
154
+ # Replace null client ids with actual client_id after handshake
155
+ sock.send( msg.gsub '"client",null', '"client","'+ client_id + '"' )
156
+ end
157
+ @buffer = []
158
+ end
159
+ end
160
+
161
+ end
162
+ end
@@ -0,0 +1,49 @@
1
+ module Bridge
2
+ # Instances of this class represent references to remote services.
3
+ class Reference #:nodoc: all
4
+
5
+ attr_accessor :operations, :address
6
+
7
+ def initialize bridge, address, operations = nil
8
+ @address = address
9
+ # Store operations supported by this reference if any
10
+ operations = [] if operations.nil?
11
+ @operations = operations.map do |val|
12
+ val.to_s
13
+ end
14
+ @bridge = bridge
15
+ end
16
+
17
+ def to_dict op = nil
18
+ # Serialize the reference
19
+ result = {}
20
+ address = @address
21
+ # Add a method name to address if given
22
+ if op
23
+ address = address.slice(0..-1)
24
+ address << op
25
+ end
26
+ result[:ref] = address
27
+ # Append operations only if address refers to a handler
28
+ if address.length < 4
29
+ result[:operations] = @operations
30
+ end
31
+ return result
32
+ end
33
+
34
+ def method_missing atom, *args, &blk
35
+ # If a block is given, add to arguments list
36
+ args << blk if blk
37
+ Util.info "Calling #{@address}.#{atom}"
38
+ # Serialize destination
39
+ destination = self.to_dict atom.to_s
40
+ # Send RPC
41
+ @bridge.send args, destination
42
+ end
43
+
44
+ def respond_to? atom
45
+ @operations.include?(atom.to_s) || atom == :to_dict || Class.respond_to?(atom)
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,104 @@
1
+ require 'json'
2
+ require 'util.rb'
3
+
4
+ module Bridge
5
+ module Serializer #:nodoc: all
6
+
7
+ def self.serialize bridge, obj
8
+ # Serialize immediately if obj responds to to_dict
9
+ if obj.respond_to? :to_dict
10
+ result = obj.to_dict
11
+ # Enumerate hash and serialize each member
12
+ elsif obj.is_a? Hash
13
+ result = {}
14
+ obj.each do |k, v|
15
+ result[k] = serialize bridge, v
16
+ end
17
+ # Enumerate array and serialize each member
18
+ elsif obj.is_a? Array
19
+ result = obj.map do |v|
20
+ serialize bridge, v
21
+ end
22
+ # Store as callback if callable
23
+ elsif obj.respond_to?(:call)
24
+ result = bridge.store_object(Callback.new(obj), ['callback']).to_dict
25
+ # Return obj itself is JSON serializable
26
+ elsif JSON::Ext::Generator::GeneratorMethods.constants.include? obj.class.name.to_sym
27
+ result = obj
28
+ # Otherwise store as service. Obj is a class instance or module
29
+ else
30
+ result = bridge.store_object(obj, Util.find_ops(obj)).to_dict
31
+ end
32
+ return result
33
+ end
34
+
35
+ def self.unserialize bridge, obj
36
+ if obj.is_a? Hash
37
+ obj.each do |k, v|
38
+ unserialize_helper bridge, obj, k, v
39
+ end
40
+ elsif obj.is_a? Array
41
+ obj.each_with_index do |v, k|
42
+ unserialize_helper bridge, obj, k, v
43
+ end
44
+ end
45
+ end
46
+
47
+ def self.unserialize_helper bridge, obj, k, v
48
+ if v.is_a? Hash
49
+ # If object has ref key, convert to reference
50
+ if v.has_key? 'ref'
51
+ if v['ref'][1] == bridge.connection.client_id and v['ref'][0] == 'client' and
52
+ obj[k] = bridge.store[v['ref'][2]]
53
+ if obj[k].is_a? Callback
54
+ obj[k] = obj[k].method :callback
55
+ end
56
+ else
57
+ # Create reference
58
+ ref = Reference.new(bridge, v['ref'], v['operations'])
59
+ if v.has_key? 'operations' and v['operations'].length == 1 and v['operations'][0] == 'callback'
60
+ # Callback wrapper
61
+ obj[k] = Util.ref_callback ref
62
+ else
63
+ obj[k] = ref
64
+ end
65
+ end
66
+ return
67
+ end
68
+ end
69
+ unserialize bridge, v
70
+ end
71
+
72
+ class Callback
73
+ def initialize fun
74
+ @fun = fun
75
+ end
76
+
77
+ def callback *args
78
+ @fun.call *args
79
+ end
80
+
81
+ def call *args
82
+ @fun.call(*args)
83
+ end
84
+
85
+ def method atom
86
+ if atom.to_s == 'callback'
87
+ @fun
88
+ else
89
+ Class.method atom
90
+ end
91
+ end
92
+
93
+ def methods bool
94
+ [:callback]
95
+ end
96
+
97
+ def respond_to? atom
98
+ atom.to_s == 'callback' || Class.respond_to?(atom)
99
+ end
100
+
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,68 @@
1
+ require 'openssl'
2
+
3
+ class OpenSSL::X509::Certificate
4
+ def ==(other)
5
+ other.respond_to?(:to_pem) && to_pem == other.to_pem
6
+ end
7
+
8
+ # A serial *must* be unique for each certificate. Self-signed certificates,
9
+ # and thus root CA certificates, have the same `issuer' as `subject'.
10
+ def top_level?
11
+ serial == serial && issuer.to_s == subject.to_s
12
+ end
13
+ alias_method :root?, :top_level?
14
+ alias_method :self_signed?, :top_level?
15
+ end
16
+
17
+ # Verifies that the peer certificate is a valid chained certificate. That is,
18
+ # it's signed by a root CA or a CA signed by a root CA.
19
+ #
20
+ # This module will also perform hostname verification against the server’s
21
+ # certificate, but _only_ if an instance variable called +@hostname+ exists.
22
+ module SSLCertificateVerification
23
+ class << self
24
+ # In PEM format.
25
+ #
26
+ # Eg: http://curl.haxx.se/docs/caextract.html
27
+ attr_accessor :ca_cert_file
28
+ end
29
+
30
+ def ca_store
31
+ unless @ca_store
32
+ if file = SSLCertificateVerification.ca_cert_file
33
+ @ca_store = OpenSSL::X509::Store.new
34
+ @ca_store.add_file(file)
35
+ else
36
+ fail "you must specify a file with root CA certificates as `SSLCertificateVerification.ca_cert_file'"
37
+ end
38
+ end
39
+ @ca_store
40
+ end
41
+
42
+ # It's important that we try to not add a certificate to the store that's
43
+ # already in the store, because OpenSSL::X509::Store will raise an exception.
44
+ def ssl_verify_peer(cert_string)
45
+ cert = OpenSSL::X509::Certificate.new(cert_string)
46
+ # Some servers send the same certificate multiple times. I'm not even joking... (gmail.com)
47
+ return true if cert == @last_seen_cert
48
+ @last_seen_cert = cert
49
+
50
+ if ca_store.verify(@last_seen_cert)
51
+ # A server may send the root certifiacte, which we already have and thus
52
+ # should not be added to the store again.
53
+ ca_store.add_cert(@last_seen_cert) unless @last_seen_cert.root?
54
+ true
55
+ else
56
+ fail "unable to verify the server certificate of `#{@hostname}'"
57
+ false
58
+ end
59
+ end
60
+
61
+ def ssl_handshake_completed
62
+ if @hostname
63
+ unless OpenSSL::SSL.verify_certificate_identity(@last_seen_cert, @hostname)
64
+ fail "the hostname `HOSTNAME' does not match the server certificate"
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,73 @@
1
+ require 'eventmachine'
2
+ require 'openssl'
3
+ require 'ssl_utils.rb'
4
+
5
+ module Bridge
6
+ class Tcp < EventMachine::Connection #:nodoc: all
7
+
8
+ include SSLCertificateVerification
9
+
10
+ def initialize connection
11
+ @buffer = ''
12
+ @len = 0
13
+ @pos = 0
14
+ @callback = nil
15
+ @connection = connection
16
+ SSLCertificateVerification.ca_cert_file = File.expand_path('../../include/ssl/cacert.pem', __FILE__)
17
+ start
18
+ end
19
+
20
+ def post_init
21
+ start_tls(:verify_peer => true) if @connection.options[:secure]
22
+ end
23
+
24
+ def connection_completed
25
+ # connection now ready. call the callback
26
+ @connection.onopen self
27
+ end
28
+
29
+ def receive_data data
30
+ left = @len - @pos
31
+ if data.length >= left
32
+ @buffer << data.slice(0, left)
33
+ @callback.call @buffer
34
+ receive_data data.slice(left..-1) unless data.nil?
35
+ else
36
+ @buffer << data
37
+ @pos = @pos + data.length
38
+ end
39
+ end
40
+
41
+ def read len, &cb
42
+ @buffer = ''
43
+ @len = len
44
+ @pos = 0
45
+ @callback = cb
46
+ end
47
+
48
+ def start
49
+ # Read header bytes
50
+ read 4 do |data|
51
+ # Read body bytes
52
+ read data.unpack('N')[0] do |data|
53
+ # Call message handler
54
+ @connection.onmessage({:data => data}, self)
55
+ # Await next message
56
+ start
57
+ end
58
+ end
59
+ end
60
+
61
+ def send arg
62
+ # Prepend length header to message
63
+ send_data([arg.length].pack("N") + arg)
64
+ end
65
+
66
+ def unbind
67
+ @connection.onclose
68
+ end
69
+
70
+ end
71
+ end
72
+
73
+