shot 0.0.0

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,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: []