bridge-ruby 0.2.0

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