rjr 0.5.3

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.
@@ -0,0 +1,589 @@
1
+ # Origincally copied from here:
2
+ # https://raw.github.com/gimite/web-socket-ruby/master/lib/web_socket.rb
3
+ # @ git commit 30977897642e017532dd0fe11d1e9ebe96c870f1
4
+ # with original header text:
5
+ #
6
+ # Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
7
+ # Lincense: New BSD Lincense
8
+ # Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
9
+ # Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
10
+ # Reference: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07
11
+ # Reference: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
12
+
13
+ require "base64"
14
+ require "socket"
15
+ require "uri"
16
+ require "digest/md5"
17
+ require "digest/sha1"
18
+ require "openssl"
19
+ require "stringio"
20
+
21
+
22
+ class WebSocket
23
+
24
+ class << self
25
+
26
+ attr_accessor(:debug)
27
+
28
+ end
29
+
30
+ class Error < RuntimeError
31
+
32
+ end
33
+
34
+ WEB_SOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
35
+ OPCODE_CONTINUATION = 0x00
36
+ OPCODE_TEXT = 0x01
37
+ OPCODE_BINARY = 0x02
38
+ OPCODE_CLOSE = 0x08
39
+ OPCODE_PING = 0x09
40
+ OPCODE_PONG = 0x0a
41
+
42
+ def initialize(arg, params = {})
43
+ if params[:server] # server
44
+
45
+ @server = params[:server]
46
+ @socket = arg
47
+ line = gets().chomp()
48
+ if !(line =~ /\AGET (\S+) HTTP\/1.1\z/n)
49
+ raise(WebSocket::Error, "invalid request: #{line}")
50
+ end
51
+ @path = $1
52
+ read_header()
53
+ if @header["sec-websocket-version"]
54
+ @web_socket_version = @header["sec-websocket-version"]
55
+ @key3 = nil
56
+ elsif @header["sec-websocket-key1"] && @header["sec-websocket-key2"]
57
+ @web_socket_version = "hixie-76"
58
+ @key3 = read(8)
59
+ else
60
+ @web_socket_version = "hixie-75"
61
+ @key3 = nil
62
+ end
63
+ if !@server.accepted_origin?(self.origin)
64
+ raise(WebSocket::Error,
65
+ ("Unaccepted origin: %s (server.accepted_domains = %p)\n\n" +
66
+ "To accept this origin, write e.g. \n" +
67
+ " WebSocketServer.new(..., :accepted_domains => [%p]), or\n" +
68
+ " WebSocketServer.new(..., :accepted_domains => [\"*\"])\n") %
69
+ [self.origin, @server.accepted_domains, @server.origin_to_domain(self.origin)])
70
+ end
71
+ @handshaked = false
72
+
73
+ else # client
74
+
75
+ @web_socket_version = "hixie-76"
76
+ uri = arg.is_a?(String) ? URI.parse(arg) : arg
77
+
78
+ if uri.scheme == "ws"
79
+ default_port = 80
80
+ elsif uri.scheme = "wss"
81
+ default_port = 443
82
+ else
83
+ raise(WebSocket::Error, "unsupported scheme: #{uri.scheme}")
84
+ end
85
+
86
+ @path = (uri.path.empty? ? "/" : uri.path) + (uri.query ? "?" + uri.query : "")
87
+ host = uri.host + ((!uri.port || uri.port == default_port) ? "" : ":#{uri.port}")
88
+ origin = params[:origin] || "http://#{uri.host}"
89
+ key1 = generate_key()
90
+ key2 = generate_key()
91
+ key3 = generate_key3()
92
+
93
+ socket = TCPSocket.new(uri.host, uri.port || default_port)
94
+
95
+ if uri.scheme == "ws"
96
+ @socket = socket
97
+ else
98
+ @socket = ssl_handshake(socket)
99
+ end
100
+
101
+ write(
102
+ "GET #{@path} HTTP/1.1\r\n" +
103
+ "Upgrade: WebSocket\r\n" +
104
+ "Connection: Upgrade\r\n" +
105
+ "Host: #{host}\r\n" +
106
+ "Origin: #{origin}\r\n" +
107
+ "Sec-WebSocket-Key1: #{key1}\r\n" +
108
+ "Sec-WebSocket-Key2: #{key2}\r\n" +
109
+ "\r\n" +
110
+ "#{key3}")
111
+ flush()
112
+
113
+ line = gets().chomp()
114
+ raise(WebSocket::Error, "bad response: #{line}") if !(line =~ /\AHTTP\/1.1 101 /n)
115
+ read_header()
116
+ if (@header["sec-websocket-origin"] || "").downcase() != origin.downcase()
117
+ raise(WebSocket::Error,
118
+ "origin doesn't match: '#{@header["sec-websocket-origin"]}' != '#{origin}'")
119
+ end
120
+ reply_digest = read(16)
121
+ expected_digest = hixie_76_security_digest(key1, key2, key3)
122
+ if reply_digest != expected_digest
123
+ raise(WebSocket::Error,
124
+ "security digest doesn't match: %p != %p" % [reply_digest, expected_digest])
125
+ end
126
+ @handshaked = true
127
+
128
+ end
129
+ @received = []
130
+ @buffer = ""
131
+ @closing_started = false
132
+ end
133
+
134
+ attr_reader(:server, :header, :path)
135
+
136
+ def handshake(status = nil, header = {})
137
+ if @handshaked
138
+ raise(WebSocket::Error, "handshake has already been done")
139
+ end
140
+ status ||= "101 Switching Protocols"
141
+ def_header = {}
142
+ case @web_socket_version
143
+ when "hixie-75"
144
+ def_header["WebSocket-Origin"] = self.origin
145
+ def_header["WebSocket-Location"] = self.location
146
+ extra_bytes = ""
147
+ when "hixie-76"
148
+ def_header["Sec-WebSocket-Origin"] = self.origin
149
+ def_header["Sec-WebSocket-Location"] = self.location
150
+ extra_bytes = hixie_76_security_digest(
151
+ @header["Sec-WebSocket-Key1"], @header["Sec-WebSocket-Key2"], @key3)
152
+ else
153
+ def_header["Sec-WebSocket-Accept"] = security_digest(@header["sec-websocket-key"])
154
+ extra_bytes = ""
155
+ end
156
+ header = def_header.merge(header)
157
+ header_str = header.map(){ |k, v| "#{k}: #{v}\r\n" }.join("")
158
+ # Note that Upgrade and Connection must appear in this order.
159
+ write(
160
+ "HTTP/1.1 #{status}\r\n" +
161
+ "Upgrade: websocket\r\n" +
162
+ "Connection: Upgrade\r\n" +
163
+ "#{header_str}\r\n#{extra_bytes}")
164
+ flush()
165
+ @handshaked = true
166
+ end
167
+
168
+ def send(data)
169
+ if !@handshaked
170
+ raise(WebSocket::Error, "call WebSocket\#handshake first")
171
+ end
172
+ case @web_socket_version
173
+ when "hixie-75", "hixie-76"
174
+ data = force_encoding(data.dup(), "ASCII-8BIT")
175
+ write("\x00#{data}\xff")
176
+ flush()
177
+ else
178
+ send_frame(OPCODE_TEXT, data, !@server)
179
+ end
180
+ end
181
+
182
+ def receive()
183
+ if !@handshaked
184
+ raise(WebSocket::Error, "call WebSocket\#handshake first")
185
+ end
186
+ case @web_socket_version
187
+
188
+ when "hixie-75", "hixie-76"
189
+ packet = gets("\xff")
190
+ return nil if !packet
191
+ if packet =~ /\A\x00(.*)\xff\z/nm
192
+ return force_encoding($1, "UTF-8")
193
+ elsif packet == "\xff" && read(1) == "\x00" # closing
194
+ close(1005, "", :peer)
195
+ return nil
196
+ else
197
+ raise(WebSocket::Error, "input must be either '\\x00...\\xff' or '\\xff\\x00'")
198
+ end
199
+
200
+ else
201
+ begin
202
+ bytes = read(2).unpack("C*")
203
+ fin = (bytes[0] & 0x80) != 0
204
+ opcode = bytes[0] & 0x0f
205
+ mask = (bytes[1] & 0x80) != 0
206
+ plength = bytes[1] & 0x7f
207
+ if plength == 126
208
+ bytes = read(2)
209
+ plength = bytes.unpack("n")[0]
210
+ elsif plength == 127
211
+ bytes = read(8)
212
+ (high, low) = bytes.unpack("NN")
213
+ plength = high * (2 ** 32) + low
214
+ end
215
+ if @server && !mask
216
+ # Masking is required.
217
+ @socket.close()
218
+ raise(WebSocket::Error, "received unmasked data")
219
+ end
220
+ mask_key = mask ? read(4).unpack("C*") : nil
221
+ payload = read(plength)
222
+ payload = apply_mask(payload, mask_key) if mask
223
+ case opcode
224
+ when OPCODE_TEXT
225
+ return force_encoding(payload, "UTF-8")
226
+ when OPCODE_BINARY
227
+ raise(WebSocket::Error, "received binary data, which is not supported")
228
+ when OPCODE_CLOSE
229
+ close(1005, "", :peer)
230
+ return nil
231
+ when OPCODE_PING
232
+ raise(WebSocket::Error, "received ping, which is not supported")
233
+ when OPCODE_PONG
234
+ else
235
+ raise(WebSocket::Error, "received unknown opcode: %d" % opcode)
236
+ end
237
+ rescue EOFError
238
+ return nil
239
+ end
240
+
241
+ end
242
+ end
243
+
244
+ def tcp_socket
245
+ return @socket
246
+ end
247
+
248
+ def host
249
+ return @header["host"]
250
+ end
251
+
252
+ def origin
253
+ case @web_socket_version
254
+ when "7", "8"
255
+ name = "sec-websocket-origin"
256
+ else
257
+ name = "origin"
258
+ end
259
+ if @header[name]
260
+ return @header[name]
261
+ else
262
+ raise(WebSocket::Error, "%s header is missing" % name)
263
+ end
264
+ end
265
+
266
+ def location
267
+ return "ws://#{self.host}#{@path}"
268
+ end
269
+
270
+ # Does closing handshake.
271
+ def close(code = 1005, reason = "", origin = :self)
272
+ if !@closing_started
273
+ case @web_socket_version
274
+ when "hixie-75", "hixie-76"
275
+ write("\xff\x00")
276
+ else
277
+ if code == 1005
278
+ payload = ""
279
+ else
280
+ payload = [code].pack("n") + force_encoding(reason.dup(), "ASCII-8BIT")
281
+ end
282
+ send_frame(OPCODE_CLOSE, payload, false)
283
+ end
284
+ end
285
+ @socket.close() if origin == :peer
286
+ @closing_started = true
287
+ end
288
+
289
+ def close_socket()
290
+ @socket.close()
291
+ end
292
+
293
+ private
294
+
295
+ NOISE_CHARS = ("\x21".."\x2f").to_a() + ("\x3a".."\x7e").to_a()
296
+
297
+ def read_header()
298
+ @header = {}
299
+ while line = gets()
300
+ line = line.chomp()
301
+ break if line.empty?
302
+ if !(line =~ /\A(\S+): (.*)\z/n)
303
+ raise(WebSocket::Error, "invalid request: #{line}")
304
+ end
305
+ @header[$1] = $2
306
+ @header[$1.downcase()] = $2
307
+ end
308
+ if !@header["upgrade"]
309
+ raise(WebSocket::Error, "Upgrade header is missing")
310
+ end
311
+ if !(@header["upgrade"] =~ /\AWebSocket\z/i)
312
+ raise(WebSocket::Error, "invalid Upgrade: " + @header["upgrade"])
313
+ end
314
+ if !@header["connection"]
315
+ raise(WebSocket::Error, "Connection header is missing")
316
+ end
317
+ if @header["connection"].split(/,/).grep(/\A\s*Upgrade\s*\z/i).empty?
318
+ raise(WebSocket::Error, "invalid Connection: " + @header["connection"])
319
+ end
320
+ end
321
+
322
+ def send_frame(opcode, payload, mask)
323
+ payload = force_encoding(payload.dup(), "ASCII-8BIT")
324
+ # Setting StringIO's encoding to ASCII-8BIT.
325
+ buffer = StringIO.new(force_encoding("", "ASCII-8BIT"))
326
+ write_byte(buffer, 0x80 | opcode)
327
+ masked_byte = mask ? 0x80 : 0x00
328
+ if payload.bytesize <= 125
329
+ write_byte(buffer, masked_byte | payload.bytesize)
330
+ elsif payload.bytesize < 2 ** 16
331
+ write_byte(buffer, masked_byte | 126)
332
+ buffer.write([payload.bytesize].pack("n"))
333
+ else
334
+ write_byte(buffer, masked_byte | 127)
335
+ buffer.write([payload.bytesize / (2 ** 32), payload.bytesize % (2 ** 32)].pack("NN"))
336
+ end
337
+ if mask
338
+ mask_key = Array.new(4){ rand(256) }
339
+ buffer.write(mask_key.pack("C*"))
340
+ payload = apply_mask(payload, mask_key)
341
+ end
342
+ buffer.write(payload)
343
+ write(buffer.string)
344
+ end
345
+
346
+ def gets(rs = $/)
347
+ line = @socket.gets(rs)
348
+ $stderr.printf("recv> %p\n", line) if WebSocket.debug
349
+ return line
350
+ end
351
+
352
+ def read(num_bytes)
353
+ str = @socket.read(num_bytes)
354
+ $stderr.printf("recv> %p\n", str) if WebSocket.debug
355
+ if str && str.bytesize == num_bytes
356
+ return str
357
+ else
358
+ raise(EOFError)
359
+ end
360
+ end
361
+
362
+ def write(data)
363
+ if WebSocket.debug
364
+ data.scan(/\G(.*?(\n|\z))/n) do
365
+ $stderr.printf("send> %p\n", $&) if !$&.empty?
366
+ end
367
+ end
368
+ @socket.write(data)
369
+ end
370
+
371
+ def flush()
372
+ @socket.flush()
373
+ end
374
+
375
+ def write_byte(buffer, byte)
376
+ buffer.write([byte].pack("C"))
377
+ end
378
+
379
+ def security_digest(key)
380
+ return Base64.encode64(Digest::SHA1.digest(key + WEB_SOCKET_GUID)).gsub(/\n/, "")
381
+ end
382
+
383
+ def hixie_76_security_digest(key1, key2, key3)
384
+ bytes1 = websocket_key_to_bytes(key1)
385
+ bytes2 = websocket_key_to_bytes(key2)
386
+ return Digest::MD5.digest(bytes1 + bytes2 + key3)
387
+ end
388
+
389
+ def apply_mask(payload, mask_key)
390
+ orig_bytes = payload.unpack("C*")
391
+ new_bytes = []
392
+ orig_bytes.each_with_index() do |b, i|
393
+ new_bytes.push(b ^ mask_key[i % 4])
394
+ end
395
+ return new_bytes.pack("C*")
396
+ end
397
+
398
+ def generate_key()
399
+ spaces = 1 + rand(12)
400
+ max = 0xffffffff / spaces
401
+ number = rand(max + 1)
402
+ key = (number * spaces).to_s()
403
+ (1 + rand(12)).times() do
404
+ char = NOISE_CHARS[rand(NOISE_CHARS.size)]
405
+ pos = rand(key.size + 1)
406
+ key[pos...pos] = char
407
+ end
408
+ spaces.times() do
409
+ pos = 1 + rand(key.size - 1)
410
+ key[pos...pos] = " "
411
+ end
412
+ return key
413
+ end
414
+
415
+ def generate_key3()
416
+ return [rand(0x100000000)].pack("N") + [rand(0x100000000)].pack("N")
417
+ end
418
+
419
+ def websocket_key_to_bytes(key)
420
+ num = key.gsub(/[^\d]/n, "").to_i() / key.scan(/ /).size
421
+ return [num].pack("N")
422
+ end
423
+
424
+ def force_encoding(str, encoding)
425
+ if str.respond_to?(:force_encoding)
426
+ return str.force_encoding(encoding)
427
+ else
428
+ return str
429
+ end
430
+ end
431
+
432
+ def ssl_handshake(socket)
433
+ ssl_context = OpenSSL::SSL::SSLContext.new()
434
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
435
+ ssl_socket.sync_close = true
436
+ ssl_socket.connect()
437
+ return ssl_socket
438
+ end
439
+
440
+ end
441
+
442
+
443
+ class WebSocketServer
444
+
445
+ def initialize(params_or_uri, params = nil)
446
+ if params
447
+ uri = params_or_uri.is_a?(String) ? URI.parse(params_or_uri) : params_or_uri
448
+ params[:port] ||= uri.port
449
+ params[:accepted_domains] ||= [uri.host]
450
+ else
451
+ params = params_or_uri
452
+ end
453
+ @port = params[:port] || 80
454
+ @accepted_domains = params[:accepted_domains]
455
+ if !@accepted_domains
456
+ raise(ArgumentError, "params[:accepted_domains] is required")
457
+ end
458
+ if params[:host]
459
+ @tcp_server = TCPServer.open(params[:host], @port)
460
+ else
461
+ @tcp_server = TCPServer.open(@port)
462
+ end
463
+ end
464
+
465
+ attr_reader(:tcp_server, :port, :accepted_domains)
466
+
467
+ def run(&block)
468
+ while true
469
+ Thread.start(accept()) do |s|
470
+ begin
471
+ ws = create_web_socket(s)
472
+ yield(ws) if ws
473
+ rescue => ex
474
+ print_backtrace(ex)
475
+ ensure
476
+ begin
477
+ ws.close_socket() if ws
478
+ rescue
479
+ end
480
+ end
481
+ end
482
+ end
483
+ end
484
+
485
+ def accept()
486
+ return @tcp_server.accept()
487
+ end
488
+
489
+ def accepted_origin?(origin)
490
+ domain = origin_to_domain(origin)
491
+ return @accepted_domains.any?(){ |d| File.fnmatch(d, domain) }
492
+ end
493
+
494
+ def origin_to_domain(origin)
495
+ if origin == "null" || origin == "file://" # local file
496
+ return "null"
497
+ else
498
+ return URI.parse(origin).host
499
+ end
500
+ end
501
+
502
+ def create_web_socket(socket)
503
+ ch = socket.getc()
504
+ if ch == ?<
505
+ # This is Flash socket policy file request, not an actual Web Socket connection.
506
+ send_flash_socket_policy_file(socket)
507
+ return nil
508
+ else
509
+ socket.ungetc(ch)
510
+ return WebSocket.new(socket, :server => self)
511
+ end
512
+ end
513
+
514
+ private
515
+
516
+ def print_backtrace(ex)
517
+ $stderr.printf("%s: %s (%p)\n", ex.backtrace[0], ex.message, ex.class)
518
+ for s in ex.backtrace[1..-1]
519
+ $stderr.printf(" %s\n", s)
520
+ end
521
+ end
522
+
523
+ # Handles Flash socket policy file request sent when web-socket-js is used:
524
+ # http://github.com/gimite/web-socket-js/tree/master
525
+ def send_flash_socket_policy_file(socket)
526
+ socket.puts('<?xml version="1.0"?>')
527
+ socket.puts('<!DOCTYPE cross-domain-policy SYSTEM ' +
528
+ '"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">')
529
+ socket.puts('<cross-domain-policy>')
530
+ for domain in @accepted_domains
531
+ next if domain == "file://"
532
+ socket.puts("<allow-access-from domain=\"#{domain}\" to-ports=\"#{@port}\"/>")
533
+ end
534
+ socket.puts('</cross-domain-policy>')
535
+ socket.close()
536
+ end
537
+
538
+ end
539
+
540
+
541
+ if __FILE__ == $0
542
+ Thread.abort_on_exception = true
543
+
544
+ if ARGV[0] == "server" && ARGV.size == 3
545
+
546
+ server = WebSocketServer.new(
547
+ :accepted_domains => [ARGV[1]],
548
+ :port => ARGV[2].to_i())
549
+ puts("Server is running at port %d" % server.port)
550
+ server.run() do |ws|
551
+ puts("Connection accepted")
552
+ puts("Path: #{ws.path}, Origin: #{ws.origin}")
553
+ if ws.path == "/"
554
+ ws.handshake()
555
+ while data = ws.receive()
556
+ printf("Received: %p\n", data)
557
+ ws.send(data)
558
+ printf("Sent: %p\n", data)
559
+ end
560
+ else
561
+ ws.handshake("404 Not Found")
562
+ end
563
+ puts("Connection closed")
564
+ end
565
+
566
+ elsif ARGV[0] == "client" && ARGV.size == 2
567
+
568
+ client = WebSocket.new(ARGV[1])
569
+ puts("Connected")
570
+ Thread.new() do
571
+ while data = client.receive()
572
+ printf("Received: %p\n", data)
573
+ end
574
+ end
575
+ $stdin.each_line() do |line|
576
+ data = line.chomp()
577
+ client.send(data)
578
+ printf("Sent: %p\n", data)
579
+ end
580
+
581
+ else
582
+
583
+ $stderr.puts("Usage:")
584
+ $stderr.puts(" ruby web_socket.rb server ACCEPTED_DOMAIN PORT")
585
+ $stderr.puts(" ruby web_socket.rb client ws://HOST:PORT/")
586
+ exit(1)
587
+
588
+ end
589
+ end
@@ -0,0 +1,100 @@
1
+ # RJR WebSockets Endpoint
2
+ #
3
+ # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
+ # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
5
+
6
+ # establish client connection w/ specified args and invoke block w/
7
+ # newly created client, returning it after block terminates
8
+
9
+ require 'em-websocket'
10
+ require 'rjr/web_socket'
11
+
12
+ require 'rjr/node'
13
+ require 'rjr/message'
14
+
15
+ module RJR
16
+
17
+ # Web Socket client node callback interface,
18
+ # send data back to client via established web socket.
19
+ class WSNodeCallback
20
+ def initialize(args = {})
21
+ @socket = args[:socket]
22
+ @message_headers = args[:headers]
23
+
24
+ # FIXME onclose, invalidate this callback / terminate outstanding handlers
25
+ #@socket.onclose {}
26
+ #@socket.onerror { |error|}
27
+ end
28
+
29
+ def invoke(callback_method, *data)
30
+ #msg = CallbackMessage.new(:data => data)
31
+ msg = RequestMessage.new :method => callback_method, :args => data, :headers => @message_headers
32
+ raise RJR::Errors::ConnectionError.new("websocket closed") if @socket.state == :closed
33
+ @socket.send(msg.to_s)
34
+ end
35
+ end
36
+
37
+ # Web node definition, listen for and invoke json-rpc requests via web sockets
38
+ class WSNode < RJR::Node
39
+ RJR_NODE_TYPE = :websockets
40
+
41
+ private
42
+ def handle_request(socket, message)
43
+ msg = RequestMessage.new(:message => message, :headers => @message_headers)
44
+ headers = @message_headers.merge(msg.headers)
45
+ result = Dispatcher.dispatch_request(msg.jr_method,
46
+ :method_args => msg.jr_args,
47
+ :headers => headers,
48
+ :rjr_node_id => @node_id,
49
+ :rjr_node_type => RJR_NODE_TYPE,
50
+ :rjr_callback =>
51
+ WSNodeCallback.new(:socket => socket,
52
+ :headers => headers))
53
+ response = ResponseMessage.new(:id => msg.msg_id, :result => result, :headers => headers)
54
+ socket.send(response.to_s)
55
+ end
56
+
57
+ public
58
+ # initialize the node w/ the specified params
59
+ def initialize(args = {})
60
+ super(args)
61
+ @host = args[:host]
62
+ @port = args[:port]
63
+ end
64
+
65
+ # Initialize the ws subsystem
66
+ def init_node
67
+ end
68
+
69
+ # Instruct Node to start listening for and dispatching rpc requests
70
+ def listen
71
+ em_run do
72
+ init_node
73
+ EventMachine::WebSocket.start(:host => @host, :port => @port) do |ws|
74
+ ws.onopen { }
75
+ ws.onclose { }
76
+ ws.onerror {|e|}
77
+ ws.onmessage { |msg|
78
+ # TODO should delete handler threads as they complete & should handle timeout
79
+ @thread_pool << ThreadPoolJob.new { handle_request(ws, msg) }
80
+ }
81
+ end
82
+ end
83
+ end
84
+
85
+ # Instructs node to send rpc request, and wait for / return response
86
+ def invoke_request(uri, rpc_method, *args)
87
+ init_node
88
+ message = RequestMessage.new :method => rpc_method,
89
+ :args => args,
90
+ :headers => @message_headers
91
+ socket = WebSocket.new(uri)
92
+ socket.send(message.to_s)
93
+ res = socket.receive()
94
+ msg = ResponseMessage.new(:message => res, :headers => @message_headers)
95
+ headers = @message_headers.merge(msg.headers)
96
+ return Dispatcher.handle_response(msg.result)
97
+ end
98
+ end
99
+
100
+ end # module RJR
data/lib/rjr.rb ADDED
@@ -0,0 +1,20 @@
1
+ # json-rpc over qpid
2
+ #
3
+ # Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
4
+ # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
5
+
6
+ lib = File.dirname(__FILE__)
7
+ $: << lib + '/rjr/'
8
+
9
+ require lib + '/rjr/common'
10
+ require lib + '/rjr/errors'
11
+ require lib + '/rjr/thread_pool'
12
+ require lib + '/rjr/semaphore'
13
+ require lib + '/rjr/node'
14
+ require lib + '/rjr/dispatcher'
15
+ require lib + '/rjr/message'
16
+ require lib + '/rjr/local_node'
17
+ require lib + '/rjr/amqp_node'
18
+ require lib + '/rjr/ws_node'
19
+ require lib + '/rjr/web_node'
20
+ require lib + '/rjr/multi_node'