rjr 0.5.3

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