shot 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,44 @@
1
+ require 'json'
2
+ require 'eventd'
3
+
4
+ ##
5
+ # An enhanced event-based client. Each connection to a MagicSocketServer initializes
6
+ # a new instance of MagicSocketClient for easier interaction.
7
+ class MagicSocketClient < Eventd
8
+ ##
9
+ # MagicSocketClient constructor
10
+ def initialize(socket)
11
+ super()
12
+ @socket = socket
13
+
14
+ @socket.onmessage do |message|
15
+ begin
16
+ json = JSON.parse message
17
+
18
+ event = json['event']
19
+ parameters = json['parameters']
20
+
21
+ # Passing false as third parameter to prevent this being sent
22
+ # back over the socket to the client
23
+ self.emit event, parameters, false
24
+ self.emit 'any', { :event => event, :parameters => parameters }, false
25
+ rescue Exception => e
26
+ $stderr.puts e
27
+ $stderr.puts e.backtrace
28
+ end
29
+ end
30
+
31
+ @socket.onclose do |e|
32
+ self.emit 'disconnect', e
33
+ end
34
+ end
35
+
36
+ def emit(event_name, parameters, allow_socket = true)
37
+ if allow_socket
38
+ json = { :event => event_name, :parameters => parameters }
39
+ @socket.send json.to_json
40
+ end
41
+
42
+ super(event_name, parameters)
43
+ end
44
+ end
@@ -0,0 +1,59 @@
1
+ require 'json'
2
+ require 'eventd'
3
+ require 'em-websocket'
4
+ require_relative 'shot_client'
5
+
6
+ ##
7
+ # Implements an event-based WebSocket server that accepts client connections
8
+ # utilizing the WebSocket protocol. Uses the em-websocket library to create
9
+ # a server, accept connections, and handshake with them.
10
+ #
11
+ # Author:: Jesse A. Dunlap <me@jessedunlap.me>
12
+ # License:: MIT
13
+
14
+ class ShotServer < Eventd
15
+ ##
16
+ # MagicSocketServer constructor
17
+ def initialize(host = '127.0.0.1', port = 8080)
18
+ super()
19
+
20
+ @configuration = {
21
+ :host => host,
22
+ :port => port
23
+ }
24
+
25
+ @clients = []
26
+ end
27
+
28
+ ##
29
+ # Merges a new configuration object with the existing default configuration.
30
+ # Configuration is passed into the EM::WebSocket.run method. This method can
31
+ # be used to set up SSL and other em-websocket configuration.
32
+ def configure(configuration)
33
+ @configuration = @configuration.merge configuration
34
+ end
35
+
36
+ ##
37
+ # Calls the specified callback when the server is running, so that you can then
38
+ # handle connections properly.
39
+ def run(&callback)
40
+ @run = callback
41
+ end
42
+
43
+ ##
44
+ # Binds the websocket connection to the configured host and port, and begins
45
+ # handling incoming connections. NOTE: It is important to call this method
46
+ # **after** you implement the run method. No code will execute past the point
47
+ # at which you call listen.
48
+ def listen
49
+ EM.run do
50
+ if @run then @run.call end
51
+
52
+ EM::WebSocket.run(@configuration) do |ws|
53
+ ws.onopen do |handshake|
54
+ self.emit 'connection', MagicSocketClient.new(ws)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,588 @@
1
+ # Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
2
+ # Lincense: New BSD Lincense
3
+ # Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
4
+ # Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
5
+ # Reference: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07
6
+ # Reference: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
7
+
8
+ require "base64"
9
+ require "socket"
10
+ require "uri"
11
+ require "digest/md5"
12
+ require "digest/sha1"
13
+ require "openssl"
14
+ require "stringio"
15
+
16
+
17
+ class WebSocket
18
+
19
+ class << self
20
+
21
+ attr_accessor(:debug)
22
+
23
+ end
24
+
25
+ class Error < RuntimeError
26
+
27
+ end
28
+
29
+ WEB_SOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
30
+ OPCODE_CONTINUATION = 0x00
31
+ OPCODE_TEXT = 0x01
32
+ OPCODE_BINARY = 0x02
33
+ OPCODE_CLOSE = 0x08
34
+ OPCODE_PING = 0x09
35
+ OPCODE_PONG = 0x0a
36
+
37
+ def initialize(arg, params = {})
38
+ if params[:server] # server
39
+
40
+ @server = params[:server]
41
+ @socket = arg
42
+ line = gets()
43
+ if !line
44
+ raise(WebSocket::Error, "Client disconnected without sending anything.")
45
+ end
46
+ line = line.chomp()
47
+ if !(line =~ /\AGET (\S+) HTTP\/1.1\z/n)
48
+ raise(WebSocket::Error, "Invalid request: #{line}")
49
+ end
50
+ @path = $1
51
+ read_header()
52
+ if @header["sec-websocket-version"]
53
+ @web_socket_version = @header["sec-websocket-version"]
54
+ @key3 = nil
55
+ elsif @header["sec-websocket-key1"] && @header["sec-websocket-key2"]
56
+ @web_socket_version = "hixie-76"
57
+ @key3 = read(8)
58
+ else
59
+ @web_socket_version = "hixie-75"
60
+ @key3 = nil
61
+ end
62
+ if !@server.accepted_origin?(self.origin)
63
+ raise(WebSocket::Error,
64
+ ("Unaccepted origin: %s (server.accepted_domains = %p)\n\n" +
65
+ "To accept this origin, write e.g. \n" +
66
+ " WebSocketServer.new(..., :accepted_domains => [%p]), or\n" +
67
+ " WebSocketServer.new(..., :accepted_domains => [\"*\"])\n") %
68
+ [self.origin, @server.accepted_domains, @server.origin_to_domain(self.origin)])
69
+ end
70
+ @handshaked = false
71
+
72
+ else # client
73
+
74
+ @web_socket_version = "hixie-76"
75
+ uri = arg.is_a?(String) ? URI.parse(arg) : arg
76
+
77
+ if uri.scheme == "ws"
78
+ default_port = 80
79
+ elsif uri.scheme = "wss"
80
+ default_port = 443
81
+ else
82
+ raise(WebSocket::Error, "unsupported scheme: #{uri.scheme}")
83
+ end
84
+
85
+ @path = (uri.path.empty? ? "/" : uri.path) + (uri.query ? "?" + uri.query : "")
86
+ host = uri.host + ((!uri.port || uri.port == default_port) ? "" : ":#{uri.port}")
87
+ origin = params[:origin] || "http://#{uri.host}"
88
+ key1 = generate_key()
89
+ key2 = generate_key()
90
+ key3 = generate_key3()
91
+
92
+ socket = TCPSocket.new(uri.host, uri.port || default_port)
93
+
94
+ if uri.scheme == "ws"
95
+ @socket = socket
96
+ else
97
+ @socket = ssl_handshake(socket)
98
+ end
99
+
100
+ write(
101
+ "GET #{@path} HTTP/1.1\r\n" +
102
+ "Upgrade: WebSocket\r\n" +
103
+ "Connection: Upgrade\r\n" +
104
+ "Host: #{host}\r\n" +
105
+ "Origin: #{origin}\r\n" +
106
+ "Sec-WebSocket-Key1: #{key1}\r\n" +
107
+ "Sec-WebSocket-Key2: #{key2}\r\n" +
108
+ "\r\n" +
109
+ "#{key3}")
110
+ flush()
111
+
112
+ line = gets().chomp()
113
+ raise(WebSocket::Error, "bad response: #{line}") if !(line =~ /\AHTTP\/1.1 101 /n)
114
+ read_header()
115
+ if (@header["sec-websocket-origin"] || "").downcase() != origin.downcase()
116
+ raise(WebSocket::Error,
117
+ "origin doesn't match: '#{@header["sec-websocket-origin"]}' != '#{origin}'")
118
+ end
119
+ reply_digest = read(16)
120
+ expected_digest = hixie_76_security_digest(key1, key2, key3)
121
+ if reply_digest != expected_digest
122
+ raise(WebSocket::Error,
123
+ "security digest doesn't match: %p != %p" % [reply_digest, expected_digest])
124
+ end
125
+ @handshaked = true
126
+
127
+ end
128
+ @received = []
129
+ @buffer = ""
130
+ @closing_started = false
131
+ end
132
+
133
+ attr_reader(:server, :header, :path)
134
+
135
+ def handshake(status = nil, header = {})
136
+ if @handshaked
137
+ raise(WebSocket::Error, "handshake has already been done")
138
+ end
139
+ status ||= "101 Switching Protocols"
140
+ def_header = {}
141
+ case @web_socket_version
142
+ when "hixie-75"
143
+ def_header["WebSocket-Origin"] = self.origin
144
+ def_header["WebSocket-Location"] = self.location
145
+ extra_bytes = ""
146
+ when "hixie-76"
147
+ def_header["Sec-WebSocket-Origin"] = self.origin
148
+ def_header["Sec-WebSocket-Location"] = self.location
149
+ extra_bytes = hixie_76_security_digest(
150
+ @header["Sec-WebSocket-Key1"], @header["Sec-WebSocket-Key2"], @key3)
151
+ else
152
+ def_header["Sec-WebSocket-Accept"] = security_digest(@header["sec-websocket-key"])
153
+ extra_bytes = ""
154
+ end
155
+ header = def_header.merge(header)
156
+ header_str = header.map(){ |k, v| "#{k}: #{v}\r\n" }.join("")
157
+ # Note that Upgrade and Connection must appear in this order.
158
+ write(
159
+ "HTTP/1.1 #{status}\r\n" +
160
+ "Upgrade: websocket\r\n" +
161
+ "Connection: Upgrade\r\n" +
162
+ "#{header_str}\r\n#{extra_bytes}")
163
+ flush()
164
+ @handshaked = true
165
+ end
166
+
167
+ def send(data)
168
+ if !@handshaked
169
+ raise(WebSocket::Error, "call WebSocket\#handshake first")
170
+ end
171
+ case @web_socket_version
172
+ when "hixie-75", "hixie-76"
173
+ data = force_encoding(data.dup(), "ASCII-8BIT")
174
+ write("\x00#{data}\xff")
175
+ flush()
176
+ else
177
+ send_frame(OPCODE_TEXT, data, !@server)
178
+ end
179
+ end
180
+
181
+ def receive()
182
+ if !@handshaked
183
+ raise(WebSocket::Error, "call WebSocket\#handshake first")
184
+ end
185
+ case @web_socket_version
186
+
187
+ when "hixie-75", "hixie-76"
188
+ packet = gets("\xff")
189
+ return nil if !packet
190
+ if packet =~ /\A\x00(.*)\xff\z/nm
191
+ return force_encoding($1, "UTF-8")
192
+ elsif packet == "\xff" && read(1) == "\x00" # closing
193
+ close(1005, "", :peer)
194
+ return nil
195
+ else
196
+ raise(WebSocket::Error, "input must be either '\\x00...\\xff' or '\\xff\\x00'")
197
+ end
198
+
199
+ else
200
+ begin
201
+ bytes = read(2).unpack("C*")
202
+ fin = (bytes[0] & 0x80) != 0
203
+ opcode = bytes[0] & 0x0f
204
+ mask = (bytes[1] & 0x80) != 0
205
+ plength = bytes[1] & 0x7f
206
+ if plength == 126
207
+ bytes = read(2)
208
+ plength = bytes.unpack("n")[0]
209
+ elsif plength == 127
210
+ bytes = read(8)
211
+ (high, low) = bytes.unpack("NN")
212
+ plength = high * (2 ** 32) + low
213
+ end
214
+ if @server && !mask
215
+ # Masking is required.
216
+ @socket.close()
217
+ raise(WebSocket::Error, "received unmasked data")
218
+ end
219
+ mask_key = mask ? read(4).unpack("C*") : nil
220
+ payload = read(plength)
221
+ payload = apply_mask(payload, mask_key) if mask
222
+ case opcode
223
+ when OPCODE_TEXT
224
+ return force_encoding(payload, "UTF-8")
225
+ when OPCODE_BINARY
226
+ raise(WebSocket::Error, "received binary data, which is not supported")
227
+ when OPCODE_CLOSE
228
+ close(1005, "", :peer)
229
+ return nil
230
+ when OPCODE_PING
231
+ raise(WebSocket::Error, "received ping, which is not supported")
232
+ when OPCODE_PONG
233
+ else
234
+ raise(WebSocket::Error, "received unknown opcode: %d" % opcode)
235
+ end
236
+ rescue EOFError
237
+ return nil
238
+ end
239
+
240
+ end
241
+ end
242
+
243
+ def tcp_socket
244
+ return @socket
245
+ end
246
+
247
+ def host
248
+ return @header["host"]
249
+ end
250
+
251
+ def origin
252
+ case @web_socket_version
253
+ when "7", "8"
254
+ name = "sec-websocket-origin"
255
+ else
256
+ name = "origin"
257
+ end
258
+ if @header[name]
259
+ return @header[name]
260
+ else
261
+ raise(WebSocket::Error, "%s header is missing" % name)
262
+ end
263
+ end
264
+
265
+ def location
266
+ return "ws://#{self.host}#{@path}"
267
+ end
268
+
269
+ # Does closing handshake.
270
+ def close(code = 1005, reason = "", origin = :self)
271
+ if !@closing_started
272
+ case @web_socket_version
273
+ when "hixie-75", "hixie-76"
274
+ write("\xff\x00")
275
+ else
276
+ if code == 1005
277
+ payload = ""
278
+ else
279
+ payload = [code].pack("n") + force_encoding(reason.dup(), "ASCII-8BIT")
280
+ end
281
+ send_frame(OPCODE_CLOSE, payload, false)
282
+ end
283
+ end
284
+ @socket.close() if origin == :peer
285
+ @closing_started = true
286
+ end
287
+
288
+ def close_socket()
289
+ @socket.close()
290
+ end
291
+
292
+ private
293
+
294
+ NOISE_CHARS = ("\x21".."\x2f").to_a() + ("\x3a".."\x7e").to_a()
295
+
296
+ def read_header()
297
+ @header = {}
298
+ while line = gets()
299
+ line = line.chomp()
300
+ break if line.empty?
301
+ if !(line =~ /\A(\S+): (.*)\z/n)
302
+ raise(WebSocket::Error, "invalid request: #{line}")
303
+ end
304
+ @header[$1] = $2
305
+ @header[$1.downcase()] = $2
306
+ end
307
+ if !@header["upgrade"]
308
+ raise(WebSocket::Error, "Upgrade header is missing")
309
+ end
310
+ if !(@header["upgrade"] =~ /\AWebSocket\z/i)
311
+ raise(WebSocket::Error, "invalid Upgrade: " + @header["upgrade"])
312
+ end
313
+ if !@header["connection"]
314
+ raise(WebSocket::Error, "Connection header is missing")
315
+ end
316
+ if @header["connection"].split(/,/).grep(/\A\s*Upgrade\s*\z/i).empty?
317
+ raise(WebSocket::Error, "invalid Connection: " + @header["connection"])
318
+ end
319
+ end
320
+
321
+ def send_frame(opcode, payload, mask)
322
+ payload = force_encoding(payload.dup(), "ASCII-8BIT")
323
+ # Setting StringIO's encoding to ASCII-8BIT.
324
+ buffer = StringIO.new(force_encoding("", "ASCII-8BIT"))
325
+ write_byte(buffer, 0x80 | opcode)
326
+ masked_byte = mask ? 0x80 : 0x00
327
+ if payload.bytesize <= 125
328
+ write_byte(buffer, masked_byte | payload.bytesize)
329
+ elsif payload.bytesize < 2 ** 16
330
+ write_byte(buffer, masked_byte | 126)
331
+ buffer.write([payload.bytesize].pack("n"))
332
+ else
333
+ write_byte(buffer, masked_byte | 127)
334
+ buffer.write([payload.bytesize / (2 ** 32), payload.bytesize % (2 ** 32)].pack("NN"))
335
+ end
336
+ if mask
337
+ mask_key = Array.new(4){ rand(256) }
338
+ buffer.write(mask_key.pack("C*"))
339
+ payload = apply_mask(payload, mask_key)
340
+ end
341
+ buffer.write(payload)
342
+ write(buffer.string)
343
+ end
344
+
345
+ def gets(rs = $/)
346
+ line = @socket.gets(rs)
347
+ $stderr.printf("recv> %p\n", line) if WebSocket.debug
348
+ return line
349
+ end
350
+
351
+ def read(num_bytes)
352
+ str = @socket.read(num_bytes)
353
+ $stderr.printf("recv> %p\n", str) if WebSocket.debug
354
+ if str && str.bytesize == num_bytes
355
+ return str
356
+ else
357
+ raise(EOFError)
358
+ end
359
+ end
360
+
361
+ def write(data)
362
+ if WebSocket.debug
363
+ data.scan(/\G(.*?(\n|\z))/n) do
364
+ $stderr.printf("send> %p\n", $&) if !$&.empty?
365
+ end
366
+ end
367
+ @socket.write(data)
368
+ end
369
+
370
+ def flush()
371
+ @socket.flush()
372
+ end
373
+
374
+ def write_byte(buffer, byte)
375
+ buffer.write([byte].pack("C"))
376
+ end
377
+
378
+ def security_digest(key)
379
+ return Base64.encode64(Digest::SHA1.digest(key + WEB_SOCKET_GUID)).gsub(/\n/, "")
380
+ end
381
+
382
+ def hixie_76_security_digest(key1, key2, key3)
383
+ bytes1 = websocket_key_to_bytes(key1)
384
+ bytes2 = websocket_key_to_bytes(key2)
385
+ return Digest::MD5.digest(bytes1 + bytes2 + key3)
386
+ end
387
+
388
+ def apply_mask(payload, mask_key)
389
+ orig_bytes = payload.unpack("C*")
390
+ new_bytes = []
391
+ orig_bytes.each_with_index() do |b, i|
392
+ new_bytes.push(b ^ mask_key[i % 4])
393
+ end
394
+ return new_bytes.pack("C*")
395
+ end
396
+
397
+ def generate_key()
398
+ spaces = 1 + rand(12)
399
+ max = 0xffffffff / spaces
400
+ number = rand(max + 1)
401
+ key = (number * spaces).to_s()
402
+ (1 + rand(12)).times() do
403
+ char = NOISE_CHARS[rand(NOISE_CHARS.size)]
404
+ pos = rand(key.size + 1)
405
+ key[pos...pos] = char
406
+ end
407
+ spaces.times() do
408
+ pos = 1 + rand(key.size - 1)
409
+ key[pos...pos] = " "
410
+ end
411
+ return key
412
+ end
413
+
414
+ def generate_key3()
415
+ return [rand(0x100000000)].pack("N") + [rand(0x100000000)].pack("N")
416
+ end
417
+
418
+ def websocket_key_to_bytes(key)
419
+ num = key.gsub(/[^\d]/n, "").to_i() / key.scan(/ /).size
420
+ return [num].pack("N")
421
+ end
422
+
423
+ def force_encoding(str, encoding)
424
+ if str.respond_to?(:force_encoding)
425
+ return str.force_encoding(encoding)
426
+ else
427
+ return str
428
+ end
429
+ end
430
+
431
+ def ssl_handshake(socket)
432
+ ssl_context = OpenSSL::SSL::SSLContext.new()
433
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
434
+ ssl_socket.sync_close = true
435
+ ssl_socket.connect()
436
+ return ssl_socket
437
+ end
438
+
439
+ end
440
+
441
+
442
+ class WebSocketServer
443
+
444
+ def initialize(params_or_uri, params = nil)
445
+ if params
446
+ uri = params_or_uri.is_a?(String) ? URI.parse(params_or_uri) : params_or_uri
447
+ params[:port] ||= uri.port
448
+ params[:accepted_domains] ||= [uri.host]
449
+ else
450
+ params = params_or_uri
451
+ end
452
+ @port = params[:port] || 80
453
+ @accepted_domains = params[:accepted_domains]
454
+ if !@accepted_domains
455
+ raise(ArgumentError, "params[:accepted_domains] is required")
456
+ end
457
+ if params[:host]
458
+ @tcp_server = TCPServer.open(params[:host], @port)
459
+ else
460
+ @tcp_server = TCPServer.open(@port)
461
+ end
462
+ end
463
+
464
+ attr_reader(:tcp_server, :port, :accepted_domains)
465
+
466
+ def run(&block)
467
+ while true
468
+ Thread.start(accept()) do |s|
469
+ begin
470
+ ws = create_web_socket(s)
471
+ yield(ws) if ws
472
+ rescue => ex
473
+ print_backtrace(ex)
474
+ ensure
475
+ begin
476
+ ws.close_socket() if ws
477
+ rescue
478
+ end
479
+ end
480
+ end
481
+ end
482
+ end
483
+
484
+ def accept()
485
+ return @tcp_server.accept()
486
+ end
487
+
488
+ def accepted_origin?(origin)
489
+ domain = origin_to_domain(origin)
490
+ return @accepted_domains.any?(){ |d| File.fnmatch(d, domain) }
491
+ end
492
+
493
+ def origin_to_domain(origin)
494
+ if origin == "null" || origin == "file://" # local file
495
+ return "null"
496
+ else
497
+ return URI.parse(origin).host
498
+ end
499
+ end
500
+
501
+ def create_web_socket(socket)
502
+ ch = socket.getc()
503
+ if ch == ?<
504
+ # This is Flash socket policy file request, not an actual Web Socket connection.
505
+ send_flash_socket_policy_file(socket)
506
+ return nil
507
+ else
508
+ socket.ungetc(ch) if ch
509
+ return WebSocket.new(socket, :server => self)
510
+ end
511
+ end
512
+
513
+ private
514
+
515
+ def print_backtrace(ex)
516
+ $stderr.printf("%s: %s (%p)\n", ex.backtrace[0], ex.message, ex.class)
517
+ for s in ex.backtrace[1..-1]
518
+ $stderr.printf(" %s\n", s)
519
+ end
520
+ end
521
+
522
+ # Handles Flash socket policy file request sent when web-socket-js is used:
523
+ # http://github.com/gimite/web-socket-js/tree/master
524
+ def send_flash_socket_policy_file(socket)
525
+ socket.puts('<?xml version="1.0"?>')
526
+ socket.puts('<!DOCTYPE cross-domain-policy SYSTEM ' +
527
+ '"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">')
528
+ socket.puts('<cross-domain-policy>')
529
+ for domain in @accepted_domains
530
+ next if domain == "file://"
531
+ socket.puts("<allow-access-from domain=\"#{domain}\" to-ports=\"#{@port}\"/>")
532
+ end
533
+ socket.puts('</cross-domain-policy>')
534
+ socket.close()
535
+ end
536
+
537
+ end
538
+
539
+
540
+ if __FILE__ == $0
541
+ Thread.abort_on_exception = true
542
+
543
+ if ARGV[0] == "server" && ARGV.size == 3
544
+
545
+ server = WebSocketServer.new(
546
+ :accepted_domains => [ARGV[1]],
547
+ :port => ARGV[2].to_i())
548
+ puts("Server is running at port %d" % server.port)
549
+ server.run() do |ws|
550
+ puts("Connection accepted")
551
+ puts("Path: #{ws.path}, Origin: #{ws.origin}")
552
+ if ws.path == "/"
553
+ ws.handshake()
554
+ while data = ws.receive()
555
+ printf("Received: %p\n", data)
556
+ ws.send(data)
557
+ printf("Sent: %p\n", data)
558
+ end
559
+ else
560
+ ws.handshake("404 Not Found")
561
+ end
562
+ puts("Connection closed")
563
+ end
564
+
565
+ elsif ARGV[0] == "client" && ARGV.size == 2
566
+
567
+ client = WebSocket.new(ARGV[1])
568
+ puts("Connected")
569
+ Thread.new() do
570
+ while data = client.receive()
571
+ printf("Received: %p\n", data)
572
+ end
573
+ end
574
+ $stdin.each_line() do |line|
575
+ data = line.chomp()
576
+ client.send(data)
577
+ printf("Sent: %p\n", data)
578
+ end
579
+
580
+ else
581
+
582
+ $stderr.puts("Usage:")
583
+ $stderr.puts(" ruby web_socket.rb server ACCEPTED_DOMAIN PORT")
584
+ $stderr.puts(" ruby web_socket.rb client ws://HOST:PORT/")
585
+ exit(1)
586
+
587
+ end
588
+ end
data/lib/shot.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'magic_socket/server'
2
+ require 'magic_socket/client'
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jesse Dunlap
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventd
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Shot provides an event based WebSocket client and server for Ruby.
31
+ email: me@jessedunlap.me
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - lib/shot.rb
37
+ - lib/shot/shot_server.rb
38
+ - lib/shot/shot_client.rb
39
+ - lib/shot/web_socket.rb
40
+ homepage: ''
41
+ licenses: []
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 1.8.24
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: An event based WebSocket layer.
64
+ test_files: []