dripdrop 0.10.0-java

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 (47) hide show
  1. data/.document +5 -0
  2. data/.gitignore +31 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +20 -0
  5. data/README.md +164 -0
  6. data/Rakefile +16 -0
  7. data/dripdrop.gemspec +37 -0
  8. data/example/agent_test.rb +14 -0
  9. data/example/combined.rb +33 -0
  10. data/example/complex/README +22 -0
  11. data/example/complex/client.rb +20 -0
  12. data/example/complex/server.rb +102 -0
  13. data/example/complex/service.rb +8 -0
  14. data/example/complex/websocket.rb +442 -0
  15. data/example/http.rb +23 -0
  16. data/example/pubsub.rb +29 -0
  17. data/example/pushpull.rb +21 -0
  18. data/example/subclass.rb +54 -0
  19. data/example/xreq_xrep.rb +24 -0
  20. data/js/dripdrop.html +186 -0
  21. data/js/dripdrop.js +107 -0
  22. data/js/qunit.css +155 -0
  23. data/js/qunit.js +1261 -0
  24. data/lib/dripdrop.rb +2 -0
  25. data/lib/dripdrop/agent.rb +40 -0
  26. data/lib/dripdrop/handlers/base.rb +42 -0
  27. data/lib/dripdrop/handlers/http_client.rb +38 -0
  28. data/lib/dripdrop/handlers/http_server.rb +59 -0
  29. data/lib/dripdrop/handlers/mongrel2.rb +163 -0
  30. data/lib/dripdrop/handlers/websocket_server.rb +86 -0
  31. data/lib/dripdrop/handlers/zeromq.rb +300 -0
  32. data/lib/dripdrop/message.rb +190 -0
  33. data/lib/dripdrop/node.rb +351 -0
  34. data/lib/dripdrop/node/nodelet.rb +35 -0
  35. data/lib/dripdrop/version.rb +3 -0
  36. data/spec/gimite-websocket.rb +442 -0
  37. data/spec/message_spec.rb +94 -0
  38. data/spec/node/http_spec.rb +77 -0
  39. data/spec/node/nodelet_spec.rb +67 -0
  40. data/spec/node/routing_spec.rb +67 -0
  41. data/spec/node/websocket_spec.rb +98 -0
  42. data/spec/node/zmq_m2_spec.rb +77 -0
  43. data/spec/node/zmq_pushpull_spec.rb +54 -0
  44. data/spec/node/zmq_xrepxreq_spec.rb +108 -0
  45. data/spec/node_spec.rb +85 -0
  46. data/spec/spec_helper.rb +20 -0
  47. metadata +167 -0
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'sinatra'
3
+ require 'dripdrop/message'
4
+
5
+ post '/endpoint' do
6
+ puts DripDrop::Message.decode_json(request.body.read).inspect
7
+ DripDrop::Message.new('ack').json_encoded
8
+ end
@@ -0,0 +1,442 @@
1
+ # Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
2
+ # Lincense: New BSD Lincense
3
+ # Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
4
+
5
+ require "socket"
6
+ require "uri"
7
+ require "digest/md5"
8
+ require "openssl"
9
+
10
+
11
+ class WebSocket
12
+
13
+ class << self
14
+
15
+ attr_accessor(:debug)
16
+
17
+ end
18
+
19
+ class Error < RuntimeError
20
+
21
+ end
22
+
23
+ def initialize(arg, params = {})
24
+ if params[:server] # server
25
+
26
+ @server = params[:server]
27
+ @socket = arg
28
+ line = gets().chomp()
29
+ if !(line =~ /\AGET (\S+) HTTP\/1.1\z/n)
30
+ raise(WebSocket::Error, "invalid request: #{line}")
31
+ end
32
+ @path = $1
33
+ read_header()
34
+ if @header["sec-websocket-key1"] && @header["sec-websocket-key2"]
35
+ @key3 = read(8)
36
+ else
37
+ # Old Draft 75 protocol
38
+ @key3 = nil
39
+ end
40
+ if !@server.accepted_origin?(self.origin)
41
+ raise(WebSocket::Error,
42
+ ("Unaccepted origin: %s (server.accepted_domains = %p)\n\n" +
43
+ "To accept this origin, write e.g. \n" +
44
+ " WebSocketServer.new(..., :accepted_domains => [%p]), or\n" +
45
+ " WebSocketServer.new(..., :accepted_domains => [\"*\"])\n") %
46
+ [self.origin, @server.accepted_domains, @server.origin_to_domain(self.origin)])
47
+ end
48
+ @handshaked = false
49
+
50
+ else # client
51
+
52
+ uri = arg.is_a?(String) ? URI.parse(arg) : arg
53
+
54
+ if uri.scheme == "ws"
55
+ default_port = 80
56
+ elsif uri.scheme = "wss"
57
+ default_port = 443
58
+ else
59
+ raise(WebSocket::Error, "unsupported scheme: #{uri.scheme}")
60
+ end
61
+
62
+ @path = (uri.path.empty? ? "/" : uri.path) + (uri.query ? "?" + uri.query : "")
63
+ host = uri.host + (uri.port == default_port ? "" : ":#{uri.port}")
64
+ origin = params[:origin] || "http://#{uri.host}"
65
+ key1 = generate_key()
66
+ key2 = generate_key()
67
+ key3 = generate_key3()
68
+
69
+ socket = TCPSocket.new(uri.host, uri.port || default_port)
70
+
71
+ if uri.scheme == "ws"
72
+ @socket = socket
73
+ else
74
+ @socket = ssl_handshake(socket)
75
+ end
76
+
77
+ write(
78
+ "GET #{@path} HTTP/1.1\r\n" +
79
+ "Upgrade: WebSocket\r\n" +
80
+ "Connection: Upgrade\r\n" +
81
+ "Host: #{host}\r\n" +
82
+ "Origin: #{origin}\r\n" +
83
+ "Sec-WebSocket-Key1: #{key1}\r\n" +
84
+ "Sec-WebSocket-Key2: #{key2}\r\n" +
85
+ "\r\n" +
86
+ "#{key3}")
87
+ flush()
88
+
89
+ line = gets().chomp()
90
+ raise(WebSocket::Error, "bad response: #{line}") if !(line =~ /\AHTTP\/1.1 101 /n)
91
+ read_header()
92
+ if (@header["sec-websocket-origin"] || "").downcase() != origin.downcase()
93
+ raise(WebSocket::Error,
94
+ "origin doesn't match: '#{@header["sec-websocket-origin"]}' != '#{origin}'")
95
+ end
96
+ reply_digest = read(16)
97
+ expected_digest = security_digest(key1, key2, key3)
98
+ if reply_digest != expected_digest
99
+ raise(WebSocket::Error,
100
+ "security digest doesn't match: %p != %p" % [reply_digest, expected_digest])
101
+ end
102
+ @handshaked = true
103
+
104
+ end
105
+ @received = []
106
+ @buffer = ""
107
+ @closing_started = false
108
+ end
109
+
110
+ attr_reader(:server, :header, :path)
111
+
112
+ def handshake(status = nil, header = {})
113
+ if @handshaked
114
+ raise(WebSocket::Error, "handshake has already been done")
115
+ end
116
+ status ||= "101 Web Socket Protocol Handshake"
117
+ sec_prefix = @key3 ? "Sec-" : ""
118
+ def_header = {
119
+ "#{sec_prefix}WebSocket-Origin" => self.origin,
120
+ "#{sec_prefix}WebSocket-Location" => self.location,
121
+ }
122
+ header = def_header.merge(header)
123
+ header_str = header.map(){ |k, v| "#{k}: #{v}\r\n" }.join("")
124
+ if @key3
125
+ digest = security_digest(
126
+ @header["Sec-WebSocket-Key1"], @header["Sec-WebSocket-Key2"], @key3)
127
+ else
128
+ digest = ""
129
+ end
130
+ # Note that Upgrade and Connection must appear in this order.
131
+ write(
132
+ "HTTP/1.1 #{status}\r\n" +
133
+ "Upgrade: WebSocket\r\n" +
134
+ "Connection: Upgrade\r\n" +
135
+ "#{header_str}\r\n#{digest}")
136
+ flush()
137
+ @handshaked = true
138
+ end
139
+
140
+ def send(data)
141
+ if !@handshaked
142
+ raise(WebSocket::Error, "call WebSocket\#handshake first")
143
+ end
144
+ data = force_encoding(data.dup(), "ASCII-8BIT")
145
+ write("\x00#{data}\xff")
146
+ flush()
147
+ end
148
+
149
+ def receive()
150
+ if !@handshaked
151
+ raise(WebSocket::Error, "call WebSocket\#handshake first")
152
+ end
153
+ packet = gets("\xff")
154
+ return nil if !packet
155
+ if packet =~ /\A\x00(.*)\xff\z/nm
156
+ return force_encoding($1, "UTF-8")
157
+ elsif packet == "\xff" && read(1) == "\x00" # closing
158
+ if @server
159
+ @socket.close()
160
+ else
161
+ close()
162
+ end
163
+ return nil
164
+ else
165
+ raise(WebSocket::Error, "input must be either '\\x00...\\xff' or '\\xff\\x00'")
166
+ end
167
+ end
168
+
169
+ def tcp_socket
170
+ return @socket
171
+ end
172
+
173
+ def host
174
+ return @header["host"]
175
+ end
176
+
177
+ def origin
178
+ return @header["origin"]
179
+ end
180
+
181
+ def location
182
+ return "ws://#{self.host}#{@path}"
183
+ end
184
+
185
+ # Does closing handshake.
186
+ def close()
187
+ return if @closing_started
188
+ write("\xff\x00")
189
+ @socket.close() if !@server
190
+ @closing_started = true
191
+ end
192
+
193
+ def close_socket()
194
+ @socket.close()
195
+ end
196
+
197
+ private
198
+
199
+ NOISE_CHARS = ("\x21".."\x2f").to_a() + ("\x3a".."\x7e").to_a()
200
+
201
+ def read_header()
202
+ @header = {}
203
+ while line = gets()
204
+ line = line.chomp()
205
+ break if line.empty?
206
+ if !(line =~ /\A(\S+): (.*)\z/n)
207
+ raise(WebSocket::Error, "invalid request: #{line}")
208
+ end
209
+ @header[$1] = $2
210
+ @header[$1.downcase()] = $2
211
+ end
212
+ if !(@header["upgrade"] =~ /\AWebSocket\z/i)
213
+ raise(WebSocket::Error, "invalid Upgrade: " + @header["upgrade"])
214
+ end
215
+ if !(@header["connection"] =~ /\AUpgrade\z/i)
216
+ raise(WebSocket::Error, "invalid Connection: " + @header["connection"])
217
+ end
218
+ end
219
+
220
+ def gets(rs = $/)
221
+ line = @socket.gets(rs)
222
+ $stderr.printf("recv> %p\n", line) if WebSocket.debug
223
+ return line
224
+ end
225
+
226
+ def read(num_bytes)
227
+ str = @socket.read(num_bytes)
228
+ $stderr.printf("recv> %p\n", str) if WebSocket.debug
229
+ return str
230
+ end
231
+
232
+ def write(data)
233
+ if WebSocket.debug
234
+ data.scan(/\G(.*?(\n|\z))/n) do
235
+ $stderr.printf("send> %p\n", $&) if !$&.empty?
236
+ end
237
+ end
238
+ @socket.write(data)
239
+ end
240
+
241
+ def flush()
242
+ @socket.flush()
243
+ end
244
+
245
+ def security_digest(key1, key2, key3)
246
+ bytes1 = websocket_key_to_bytes(key1)
247
+ bytes2 = websocket_key_to_bytes(key2)
248
+ return Digest::MD5.digest(bytes1 + bytes2 + key3)
249
+ end
250
+
251
+ def generate_key()
252
+ spaces = 1 + rand(12)
253
+ max = 0xffffffff / spaces
254
+ number = rand(max + 1)
255
+ key = (number * spaces).to_s()
256
+ (1 + rand(12)).times() do
257
+ char = NOISE_CHARS[rand(NOISE_CHARS.size)]
258
+ pos = rand(key.size + 1)
259
+ key[pos...pos] = char
260
+ end
261
+ spaces.times() do
262
+ pos = 1 + rand(key.size - 1)
263
+ key[pos...pos] = " "
264
+ end
265
+ return key
266
+ end
267
+
268
+ def generate_key3()
269
+ return [rand(0x100000000)].pack("N") + [rand(0x100000000)].pack("N")
270
+ end
271
+
272
+ def websocket_key_to_bytes(key)
273
+ num = key.gsub(/[^\d]/n, "").to_i() / key.scan(/ /).size
274
+ return [num].pack("N")
275
+ end
276
+
277
+ def force_encoding(str, encoding)
278
+ if str.respond_to?(:force_encoding)
279
+ return str.force_encoding(encoding)
280
+ else
281
+ return str
282
+ end
283
+ end
284
+
285
+ def ssl_handshake(socket)
286
+ ssl_context = OpenSSL::SSL::SSLContext.new()
287
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
288
+ ssl_socket.sync_close = true
289
+ ssl_socket.connect()
290
+ return ssl_socket
291
+ end
292
+
293
+ end
294
+
295
+
296
+ class WebSocketServer
297
+
298
+ def initialize(params_or_uri, params = nil)
299
+ if params
300
+ uri = params_or_uri.is_a?(String) ? URI.parse(params_or_uri) : params_or_uri
301
+ params[:port] ||= uri.port
302
+ params[:accepted_domains] ||= [uri.host]
303
+ else
304
+ params = params_or_uri
305
+ end
306
+ @port = params[:port] || 80
307
+ @accepted_domains = params[:accepted_domains]
308
+ if !@accepted_domains
309
+ raise(ArgumentError, "params[:accepted_domains] is required")
310
+ end
311
+ if params[:host]
312
+ @tcp_server = TCPServer.open(params[:host], @port)
313
+ else
314
+ @tcp_server = TCPServer.open(@port)
315
+ end
316
+ end
317
+
318
+ attr_reader(:tcp_server, :port, :accepted_domains)
319
+
320
+ def run(&block)
321
+ while true
322
+ Thread.start(accept()) do |s|
323
+ begin
324
+ ws = create_web_socket(s)
325
+ yield(ws) if ws
326
+ rescue => ex
327
+ print_backtrace(ex)
328
+ ensure
329
+ begin
330
+ ws.close_socket() if ws
331
+ rescue
332
+ end
333
+ end
334
+ end
335
+ end
336
+ end
337
+
338
+ def accept()
339
+ return @tcp_server.accept()
340
+ end
341
+
342
+ def accepted_origin?(origin)
343
+ domain = origin_to_domain(origin)
344
+ return @accepted_domains.any?(){ |d| File.fnmatch(d, domain) }
345
+ end
346
+
347
+ def origin_to_domain(origin)
348
+ if origin == "null" || origin == "file://" # local file
349
+ return "null"
350
+ else
351
+ return URI.parse(origin).host
352
+ end
353
+ end
354
+
355
+ def create_web_socket(socket)
356
+ ch = socket.getc()
357
+ if ch == ?<
358
+ # This is Flash socket policy file request, not an actual Web Socket connection.
359
+ send_flash_socket_policy_file(socket)
360
+ return nil
361
+ else
362
+ socket.ungetc(ch)
363
+ return WebSocket.new(socket, :server => self)
364
+ end
365
+ end
366
+
367
+ private
368
+
369
+ def print_backtrace(ex)
370
+ $stderr.printf("%s: %s (%p)\n", ex.backtrace[0], ex.message, ex.class)
371
+ for s in ex.backtrace[1..-1]
372
+ $stderr.printf(" %s\n", s)
373
+ end
374
+ end
375
+
376
+ # Handles Flash socket policy file request sent when web-socket-js is used:
377
+ # http://github.com/gimite/web-socket-js/tree/master
378
+ def send_flash_socket_policy_file(socket)
379
+ socket.puts('<?xml version="1.0"?>')
380
+ socket.puts('<!DOCTYPE cross-domain-policy SYSTEM ' +
381
+ '"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">')
382
+ socket.puts('<cross-domain-policy>')
383
+ for domain in @accepted_domains
384
+ next if domain == "file://"
385
+ socket.puts("<allow-access-from domain=\"#{domain}\" to-ports=\"#{@port}\"/>")
386
+ end
387
+ socket.puts('</cross-domain-policy>')
388
+ socket.close()
389
+ end
390
+
391
+ end
392
+
393
+
394
+ if __FILE__ == $0
395
+ Thread.abort_on_exception = true
396
+
397
+ if ARGV[0] == "server" && ARGV.size == 3
398
+
399
+ server = WebSocketServer.new(
400
+ :accepted_domains => [ARGV[1]],
401
+ :port => ARGV[2].to_i())
402
+ puts("Server is running at port %d" % server.port)
403
+ server.run() do |ws|
404
+ puts("Connection accepted")
405
+ puts("Path: #{ws.path}, Origin: #{ws.origin}")
406
+ if ws.path == "/"
407
+ ws.handshake()
408
+ while data = ws.receive()
409
+ printf("Received: %p\n", data)
410
+ ws.send(data)
411
+ printf("Sent: %p\n", data)
412
+ end
413
+ else
414
+ ws.handshake("404 Not Found")
415
+ end
416
+ puts("Connection closed")
417
+ end
418
+
419
+ elsif ARGV[0] == "client" && ARGV.size == 2
420
+
421
+ client = WebSocket.new(ARGV[1])
422
+ puts("Connected")
423
+ Thread.new() do
424
+ while data = client.receive()
425
+ printf("Received: %p\n", data)
426
+ end
427
+ end
428
+ $stdin.each_line() do |line|
429
+ data = line.chomp()
430
+ client.send(data)
431
+ printf("Sent: %p\n", data)
432
+ end
433
+
434
+ else
435
+
436
+ $stderr.puts("Usage:")
437
+ $stderr.puts(" ruby web_socket.rb server ACCEPTED_DOMAIN PORT")
438
+ $stderr.puts(" ruby web_socket.rb client ws://HOST:PORT/")
439
+ exit(1)
440
+
441
+ end
442
+ end