kellysutton-socketio-client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in socket.io-client.gemspec
4
+ gemspec
@@ -0,0 +1,58 @@
1
+ # Simple Socket IO client
2
+
3
+ Quick and kinda dirty socket.io client using web sockets
4
+
5
+ ## Features
6
+
7
+ This client currently supports:
8
+
9
+ * Listeners for all 9 possible message
10
+ * Send messages of the type:
11
+ * message
12
+ * json
13
+ * event
14
+
15
+ ## How to use:
16
+
17
+ ```ruby
18
+ require 'socketIO'
19
+
20
+ client = SocketIO.connect("localhost") do
21
+ before_start do
22
+ on_message {|message| puts "incoming message: #{message}"}
23
+ on_event('news') { |data| puts data.first} # data is an array fo things.
24
+ end
25
+
26
+ end
27
+ ```
28
+
29
+ ## Sync vs Async
30
+
31
+ You can start the socket io syncronously and then continue with your work
32
+ this crates threads so be careful.
33
+
34
+ ```ruby
35
+ require 'socketIO'
36
+
37
+ client = SocketIO.connect("localhost", sync: true) do
38
+ before_start do
39
+ on_message {|message| puts message}
40
+ on_disconnect {puts "I GOT A DISCONNECT"}
41
+ end
42
+
43
+ after_start do
44
+ emit("loadLogs", "/var/www/rails_app/log/production.log")
45
+ end
46
+ end
47
+
48
+ puts "socket still running"
49
+ loop do
50
+ sleep 10
51
+ puts 'zzz'
52
+ end
53
+ ```
54
+
55
+ ## Examples
56
+
57
+ examples can be found in the examples/ folder.
58
+ A corrosponding server can be found in the examples/servers
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ desc "Run all specs"
5
+ RSpec::Core::RakeTask.new('spec') do |t|
6
+ t.rspec_opts = ['--colour --format documentation']
7
+ end
@@ -0,0 +1 @@
1
+ # not yet implemented
@@ -0,0 +1,14 @@
1
+ require 'socketIO'
2
+
3
+ client = SocketIO.connect("localhost") do
4
+ before_start do
5
+ on_message {|message| puts "incoming message: #{message}"}
6
+ on_disconnect {puts "I GOT A TDISCONNECT"}
7
+ end
8
+
9
+ after_start do
10
+ emit("loadLogs", "/Users/lyon/test/rails_app/log/development.log")
11
+ end
12
+ end
13
+
14
+ puts "thread exited and I have the power back"
@@ -0,0 +1 @@
1
+ // not yet implemented
@@ -0,0 +1,18 @@
1
+ var io = require('socket.io').listen(80)
2
+ ,spawn = require('child_process').spawn
3
+
4
+ console.log("Up and running. waiting on connections")
5
+ io.sockets.on('connection', function(socket){
6
+ console.log("new connection.")
7
+
8
+ socket.on("loadLogs", function(path){
9
+ var tail = spawn("tail", ["-F", path])
10
+
11
+ console.log("Begining tail on: " + path)
12
+ tail.stdout.on("data", function(data){
13
+ console.log(data.toString("utf8"))
14
+ socket.send(data.toString("utf8"))
15
+ })
16
+ })
17
+
18
+ })
@@ -0,0 +1,10 @@
1
+ var io = require('socket.io').listen(80);
2
+
3
+ io.sockets.on('connection', function (socket) {
4
+
5
+ setInterval(function(){
6
+ socket.send('got something for ya');
7
+ socket.emit('news', { hello: 'world'});
8
+ }, 10000)
9
+
10
+ });
@@ -0,0 +1,10 @@
1
+ require 'socketIO'
2
+
3
+ client = SocketIO.connect("localhost") do
4
+ before_start do
5
+ on_message {|message| puts "incoming message: #{message}"}
6
+ on_event('news') { |data| puts data.first} # data is an array fo things.
7
+ end
8
+
9
+ end
10
+
@@ -0,0 +1,13 @@
1
+ module Parser
2
+ @regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/
3
+
4
+ # returns hash as {type: '1', id: '1', end_point: '4', data: [{key: value}]}
5
+ def self.decode(string)
6
+ if pieces = string.match(@regexp)
7
+ {type: pieces[1], id: pieces[2], end_point: pieces[4], data: pieces[5]}
8
+ else
9
+ {type: '0'}
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,169 @@
1
+ require 'web_socket'
2
+ require 'rest_client'
3
+ require 'json'
4
+ require 'parser'
5
+
6
+ module SocketIO
7
+
8
+ def self.connect(host, options = {}, &block)
9
+ response = RestClient.get "http://#{host}/socket.io/1/"
10
+ # resonse should be in the form of sessionid:heartbeattimeout:closetimeout:supported stuff
11
+ response_array = response.split(':')
12
+ response_array = [host] + response_array << options
13
+ cli = Client.new(*response_array)
14
+ cli.instance_eval(&block) if block
15
+ cli.start
16
+ end
17
+
18
+ class Client
19
+ VERSION = "0.0.1"
20
+
21
+ [:INT, :TERM].each do |sig|
22
+ Signal.trap(sig) do
23
+ puts
24
+ puts "bye"
25
+ exit
26
+ end
27
+ end
28
+
29
+ # The state of the Socket.IO socket can be disconnected, disconnecting, connected and connecting.
30
+ # The transport connection can be closed, closing, open, and opening.
31
+
32
+ def initialize(host, session_id, heartbeat_timeout, connection_timeout, supported_transports, options = {})
33
+ @host = host
34
+ @session_id = session_id
35
+ @hb_timeout = heartbeat_timeout
36
+ @connect_timeout = connection_timeout
37
+ @supported_transports = supported_transports
38
+ @options = options
39
+ @reconnect = options[:reconnect]
40
+ @on_event = {}
41
+ end
42
+
43
+ def start
44
+ self.instance_eval(&@before_start) if @before_start
45
+ connect_transport
46
+ start_recieve_loop
47
+ self.instance_eval(&@after_start) if @after_start
48
+ @thread.join unless @options[:sync]
49
+ self
50
+ end
51
+
52
+ def connect_transport
53
+ if @supported_transports.include? "websocket"
54
+ @transport = WebSocket.new("ws://#{@host}/socket.io/1/websocket/#{@session_id}")
55
+ else
56
+ raise "We only support WebSockets.. and this server doesnt like web sockets.. O NO!!"
57
+ end
58
+ end
59
+
60
+ def start_recieve_loop
61
+ @thread = Thread.new() do
62
+ while data = @transport.receive()
63
+ decoded = Parser.decode(data)
64
+ case decoded[:type]
65
+ when '0'
66
+ @on_disconnect.call if @on_disconnect
67
+ when '1'
68
+ @on_connect.call if @on_connect
69
+ when '2'
70
+ send_heartbeat
71
+ @on_heartbeat.call if @on_heartbeat
72
+ when '3'
73
+ @on_message.call decoded[:data] if @on_message
74
+ when '4'
75
+ @on_json_message.call decoded[:data] if @on_json_message
76
+ when '5'
77
+ message = JSON.parse(decoded[:data])
78
+ @on_event[message['name']].call message['args'] if @on_event[message['name']]
79
+ when '6'
80
+ @on_error.call decoded[:data] if @on_error
81
+ when '7'
82
+ @on_ack.call if @on_ack
83
+ when '8'
84
+ @on_noop.call if @on_noop
85
+ end
86
+ end
87
+ end
88
+ @thread
89
+ end
90
+
91
+ def disconnect
92
+ @transport.send("0::")
93
+ end
94
+
95
+ def disconnected
96
+ if @reconnect
97
+ connect_transport
98
+ start_recieve_loop
99
+ end
100
+ end
101
+
102
+ def join
103
+ @thread.join
104
+ end
105
+
106
+ def send_heartbeat
107
+ @transport.send("2::") #rescue false
108
+ end
109
+
110
+ def send_message(string)
111
+ @transport.send("3:::#{string}") #rescue false
112
+ end
113
+ alias :send :send_message
114
+
115
+ def send_json_message(hash)
116
+ @transport.send("4:::#{hash.to_json}") # rescue false
117
+ end
118
+
119
+ def send_event(name, hash)
120
+ @transport.send("5:::#{{name: name, args: [hash]}.to_json}") # rescue false
121
+ end
122
+ alias :emit :send_event
123
+
124
+ def before_start(&block)
125
+ @before_start = block
126
+ end
127
+
128
+ def after_start(&block)
129
+ @after_start = block
130
+ end
131
+
132
+ def on_disconnect(&block)
133
+ @on_disconnect = block
134
+ end
135
+
136
+ def on_connect(&block)
137
+ @on_connect = block
138
+ end
139
+
140
+ def on_heartbeat(&block)
141
+ @on_heartbeat = block
142
+ end
143
+
144
+ def on_message(&block)
145
+ @on_message = block
146
+ end
147
+
148
+ def on_json_message(&block)
149
+ @on_json_message = block
150
+ end
151
+
152
+ def on_event(name, &block)
153
+ @on_event[name] = block
154
+ end
155
+
156
+ def on_ack(&block)
157
+ @on_ack = block
158
+ end
159
+
160
+ def on_error(&block)
161
+ @on_error = block
162
+ end
163
+
164
+ def on_noop(&block)
165
+ @on_noop = block
166
+ end
167
+ end
168
+
169
+ end
@@ -0,0 +1,429 @@
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().chomp()
43
+ if !(line =~ /\AGET (\S+) HTTP\/1.1\z/n)
44
+ raise(WebSocket::Error, "invalid request: #{line}")
45
+ end
46
+ @path = $1
47
+ read_header()
48
+ if @header["sec-websocket-version"]
49
+ @web_socket_version = @header["sec-websocket-version"]
50
+ @key3 = nil
51
+ elsif @header["sec-websocket-key1"] && @header["sec-websocket-key2"]
52
+ @web_socket_version = "hixie-76"
53
+ @key3 = read(8)
54
+ else
55
+ @web_socket_version = "hixie-75"
56
+ @key3 = nil
57
+ end
58
+ if !@server.accepted_origin?(self.origin)
59
+ raise(WebSocket::Error,
60
+ ("Unaccepted origin: %s (server.accepted_domains = %p)\n\n" +
61
+ "To accept this origin, write e.g. \n" +
62
+ " WebSocketServer.new(..., :accepted_domains => [%p]), or\n" +
63
+ " WebSocketServer.new(..., :accepted_domains => [\"*\"])\n") %
64
+ [self.origin, @server.accepted_domains, @server.origin_to_domain(self.origin)])
65
+ end
66
+ @handshaked = false
67
+
68
+ else # client
69
+
70
+ @web_socket_version = "hixie-76"
71
+ uri = arg.is_a?(String) ? URI.parse(arg) : arg
72
+
73
+ if uri.scheme == "ws"
74
+ default_port = 80
75
+ elsif uri.scheme = "wss"
76
+ default_port = 443
77
+ else
78
+ raise(WebSocket::Error, "unsupported scheme: #{uri.scheme}")
79
+ end
80
+
81
+ @path = (uri.path.empty? ? "/" : uri.path) + (uri.query ? "?" + uri.query : "")
82
+ host = uri.host + (uri.port == default_port ? "" : ":#{uri.port}")
83
+ origin = params[:origin] || "http://#{uri.host}"
84
+ key1 = generate_key()
85
+ key2 = generate_key()
86
+ key3 = generate_key3()
87
+
88
+ socket = TCPSocket.new(uri.host, uri.port || default_port)
89
+
90
+ if uri.scheme == "ws"
91
+ @socket = socket
92
+ else
93
+ @socket = ssl_handshake(socket)
94
+ end
95
+
96
+ write(
97
+ "GET #{@path} HTTP/1.1\r\n" +
98
+ "Upgrade: WebSocket\r\n" +
99
+ "Connection: Upgrade\r\n" +
100
+ "Host: #{host}\r\n" +
101
+ "Origin: #{origin}\r\n" +
102
+ "Sec-WebSocket-Key1: #{key1}\r\n" +
103
+ "Sec-WebSocket-Key2: #{key2}\r\n" +
104
+ "\r\n" +
105
+ "#{key3}")
106
+ flush()
107
+
108
+ line = gets().chomp()
109
+ raise(WebSocket::Error, "bad response: #{line}") if !(line =~ /\AHTTP\/1.1 101 /n)
110
+ read_header()
111
+ if (@header["sec-websocket-origin"] || "").downcase() != origin.downcase()
112
+ raise(WebSocket::Error,
113
+ "origin doesn't match: '#{@header["sec-websocket-origin"]}' != '#{origin}'")
114
+ end
115
+ reply_digest = read(16)
116
+ expected_digest = hixie_76_security_digest(key1, key2, key3)
117
+ if reply_digest != expected_digest
118
+ raise(WebSocket::Error,
119
+ "security digest doesn't match: %p != %p" % [reply_digest, expected_digest])
120
+ end
121
+ @handshaked = true
122
+
123
+ end
124
+ @received = []
125
+ @buffer = ""
126
+ @closing_started = false
127
+ end
128
+
129
+ attr_reader(:server, :header, :path)
130
+
131
+ def handshake(status = nil, header = {})
132
+ if @handshaked
133
+ raise(WebSocket::Error, "handshake has already been done")
134
+ end
135
+ status ||= "101 Switching Protocols"
136
+ def_header = {}
137
+ case @web_socket_version
138
+ when "hixie-75"
139
+ def_header["WebSocket-Origin"] = self.origin
140
+ def_header["WebSocket-Location"] = self.location
141
+ extra_bytes = ""
142
+ when "hixie-76"
143
+ def_header["Sec-WebSocket-Origin"] = self.origin
144
+ def_header["Sec-WebSocket-Location"] = self.location
145
+ extra_bytes = hixie_76_security_digest(
146
+ @header["Sec-WebSocket-Key1"], @header["Sec-WebSocket-Key2"], @key3)
147
+ else
148
+ def_header["Sec-WebSocket-Accept"] = security_digest(@header["sec-websocket-key"])
149
+ extra_bytes = ""
150
+ end
151
+ header = def_header.merge(header)
152
+ header_str = header.map(){ |k, v| "#{k}: #{v}\r\n" }.join("")
153
+ # Note that Upgrade and Connection must appear in this order.
154
+ write(
155
+ "HTTP/1.1 #{status}\r\n" +
156
+ "Upgrade: websocket\r\n" +
157
+ "Connection: Upgrade\r\n" +
158
+ "#{header_str}\r\n#{extra_bytes}")
159
+ flush()
160
+ @handshaked = true
161
+ end
162
+
163
+ def send(data)
164
+ if !@handshaked
165
+ raise(WebSocket::Error, "call WebSocket\#handshake first")
166
+ end
167
+ case @web_socket_version
168
+ when "hixie-75", "hixie-76"
169
+ data = force_encoding(data.dup(), "ASCII-8BIT")
170
+ write("\x00#{data}\xff")
171
+ flush()
172
+ else
173
+ send_frame(OPCODE_TEXT, data, !@server)
174
+ end
175
+ end
176
+
177
+ def receive()
178
+ if !@handshaked
179
+ raise(WebSocket::Error, "call WebSocket\#handshake first")
180
+ end
181
+ case @web_socket_version
182
+
183
+ when "hixie-75", "hixie-76"
184
+ packet = gets("\xff")
185
+ return nil if !packet
186
+ if packet =~ /\A\x00(.*)\xff\z/nm
187
+ return force_encoding($1, "UTF-8")
188
+ elsif packet == "\xff" && read(1) == "\x00" # closing
189
+ close(1005, "", :peer)
190
+ return nil
191
+ else
192
+ raise(WebSocket::Error, "input must be either '\\x00...\\xff' or '\\xff\\x00'")
193
+ end
194
+
195
+ else
196
+ begin
197
+ bytes = read(2).unpack("C*")
198
+ fin = (bytes[0] & 0x80) != 0
199
+ opcode = bytes[0] & 0x0f
200
+ mask = (bytes[1] & 0x80) != 0
201
+ plength = bytes[1] & 0x7f
202
+ if plength == 126
203
+ bytes = read(2)
204
+ plength = bytes.unpack("n")[0]
205
+ elsif plength == 127
206
+ bytes = read(8)
207
+ (high, low) = bytes.unpack("NN")
208
+ plength = high * (2 ** 32) + low
209
+ end
210
+ if @server && !mask
211
+ # Masking is required.
212
+ @socket.close()
213
+ raise(WebSocket::Error, "received unmasked data")
214
+ end
215
+ mask_key = mask ? read(4).unpack("C*") : nil
216
+ payload = read(plength)
217
+ payload = apply_mask(payload, mask_key) if mask
218
+ case opcode
219
+ when OPCODE_TEXT
220
+ return force_encoding(payload, "UTF-8")
221
+ when OPCODE_BINARY
222
+ raise(WebSocket::Error, "received binary data, which is not supported")
223
+ when OPCODE_CLOSE
224
+ close(1005, "", :peer)
225
+ return nil
226
+ when OPCODE_PING
227
+ raise(WebSocket::Error, "received ping, which is not supported")
228
+ when OPCODE_PONG
229
+ else
230
+ raise(WebSocket::Error, "received unknown opcode: %d" % opcode)
231
+ end
232
+ rescue EOFError
233
+ return nil
234
+ end
235
+
236
+ end
237
+ end
238
+
239
+ def tcp_socket
240
+ return @socket
241
+ end
242
+
243
+ def host
244
+ return @header["host"]
245
+ end
246
+
247
+ def origin
248
+ case @web_socket_version
249
+ when "7", "8"
250
+ name = "sec-websocket-origin"
251
+ else
252
+ name = "origin"
253
+ end
254
+ if @header[name]
255
+ return @header[name]
256
+ else
257
+ raise(WebSocket::Error, "%s header is missing" % name)
258
+ end
259
+ end
260
+
261
+ def location
262
+ return "ws://#{self.host}#{@path}"
263
+ end
264
+
265
+ # Does closing handshake.
266
+ def close(code = 1005, reason = "", origin = :self)
267
+ if !@closing_started
268
+ case @web_socket_version
269
+ when "hixie-75", "hixie-76"
270
+ write("\xff\x00")
271
+ else
272
+ if code == 1005
273
+ payload = ""
274
+ else
275
+ payload = [code].pack("n") + force_encoding(reason.dup(), "ASCII-8BIT")
276
+ end
277
+ send_frame(OPCODE_CLOSE, payload, false)
278
+ end
279
+ end
280
+ @socket.close() if origin == :peer
281
+ @closing_started = true
282
+ end
283
+
284
+ def close_socket()
285
+ @socket.close()
286
+ end
287
+
288
+ private
289
+
290
+ NOISE_CHARS = ("\x21".."\x2f").to_a() + ("\x3a".."\x7e").to_a()
291
+
292
+ def read_header()
293
+ @header = {}
294
+ while line = gets()
295
+ line = line.chomp()
296
+ break if line.empty?
297
+ if !(line =~ /\A(\S+): (.*)\z/n)
298
+ raise(WebSocket::Error, "invalid request: #{line}")
299
+ end
300
+ @header[$1] = $2
301
+ @header[$1.downcase()] = $2
302
+ end
303
+ if !(@header["upgrade"] =~ /\AWebSocket\z/i)
304
+ raise(WebSocket::Error, "invalid Upgrade: " + @header["upgrade"])
305
+ end
306
+ if !(@header["connection"] =~ /\AUpgrade\z/i)
307
+ raise(WebSocket::Error, "invalid Connection: " + @header["connection"])
308
+ end
309
+ end
310
+
311
+ def send_frame(opcode, payload, mask)
312
+ payload = force_encoding(payload.dup(), "ASCII-8BIT")
313
+ # Setting StringIO's encoding to ASCII-8BIT.
314
+ buffer = StringIO.new(force_encoding("", "ASCII-8BIT"))
315
+ write_byte(buffer, 0x80 | opcode)
316
+ masked_byte = mask ? 0x80 : 0x00
317
+ if payload.bytesize <= 125
318
+ write_byte(buffer, masked_byte | payload.bytesize)
319
+ elsif payload.bytesize < 2 ** 16
320
+ write_byte(buffer, masked_byte | 126)
321
+ buffer.write([payload.bytesize].pack("n"))
322
+ else
323
+ write_byte(buffer, masked_byte | 127)
324
+ buffer.write([payload.bytesize / (2 ** 32), payload.bytesize % (2 ** 32)].pack("NN"))
325
+ end
326
+ if mask
327
+ mask_key = Array.new(4){ rand(256) }
328
+ buffer.write(mask_key.pack("C*"))
329
+ payload = apply_mask(payload, mask_key)
330
+ end
331
+ buffer.write(payload)
332
+ write(buffer.string)
333
+ end
334
+
335
+ def gets(rs = $/)
336
+ line = @socket.gets(rs)
337
+ $stderr.printf("recv> %p\n", line) if WebSocket.debug
338
+ return line
339
+ end
340
+
341
+ def read(num_bytes)
342
+ str = @socket.read(num_bytes)
343
+ $stderr.printf("recv> %p\n", str) if WebSocket.debug
344
+ if str && str.bytesize == num_bytes
345
+ return str
346
+ else
347
+ raise(EOFError)
348
+ end
349
+ end
350
+
351
+ def write(data)
352
+ if WebSocket.debug
353
+ data.scan(/\G(.*?(\n|\z))/n) do
354
+ $stderr.printf("send> %p\n", $&) if !$&.empty?
355
+ end
356
+ end
357
+ @socket.write(data)
358
+ end
359
+
360
+ def flush()
361
+ @socket.flush()
362
+ end
363
+
364
+ def write_byte(buffer, byte)
365
+ buffer.write([byte].pack("C"))
366
+ end
367
+
368
+ def security_digest(key)
369
+ return Base64.encode64(Digest::SHA1.digest(key + WEB_SOCKET_GUID)).gsub(/\n/, "")
370
+ end
371
+
372
+ def hixie_76_security_digest(key1, key2, key3)
373
+ bytes1 = websocket_key_to_bytes(key1)
374
+ bytes2 = websocket_key_to_bytes(key2)
375
+ return Digest::MD5.digest(bytes1 + bytes2 + key3)
376
+ end
377
+
378
+ def apply_mask(payload, mask_key)
379
+ orig_bytes = payload.unpack("C*")
380
+ new_bytes = []
381
+ orig_bytes.each_with_index() do |b, i|
382
+ new_bytes.push(b ^ mask_key[i % 4])
383
+ end
384
+ return new_bytes.pack("C*")
385
+ end
386
+
387
+ def generate_key()
388
+ spaces = 1 + rand(12)
389
+ max = 0xffffffff / spaces
390
+ number = rand(max + 1)
391
+ key = (number * spaces).to_s()
392
+ (1 + rand(12)).times() do
393
+ char = NOISE_CHARS[rand(NOISE_CHARS.size)]
394
+ pos = rand(key.size + 1)
395
+ key[pos...pos] = char
396
+ end
397
+ spaces.times() do
398
+ pos = 1 + rand(key.size - 1)
399
+ key[pos...pos] = " "
400
+ end
401
+ return key
402
+ end
403
+
404
+ def generate_key3()
405
+ return [rand(0x100000000)].pack("N") + [rand(0x100000000)].pack("N")
406
+ end
407
+
408
+ def websocket_key_to_bytes(key)
409
+ num = key.gsub(/[^\d]/n, "").to_i() / key.scan(/ /).size
410
+ return [num].pack("N")
411
+ end
412
+
413
+ def force_encoding(str, encoding)
414
+ if str.respond_to?(:force_encoding)
415
+ return str.force_encoding(encoding)
416
+ else
417
+ return str
418
+ end
419
+ end
420
+
421
+ def ssl_handshake(socket)
422
+ ssl_context = OpenSSL::SSL::SSLContext.new()
423
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
424
+ ssl_socket.sync_close = true
425
+ ssl_socket.connect()
426
+ return ssl_socket
427
+ end
428
+
429
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'socketio-client'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "kellysutton-socketio-client"
7
+ s.version = SocketIO::Client::VERSION
8
+ s.authors = ["Kelly Sutton"]
9
+ s.email = ["michael.k.sutton@gmail.com"]
10
+ s.homepage = "http://github.com/kellysutton/socket.io-ruby-client"
11
+ s.summary = %q{A basic Socket.io client implememtation written for ruby}
12
+ s.description = %q{uses a very simple web socket}
13
+
14
+ s.rubyforge_project = "socketio-client"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ s.add_runtime_dependency "rest-client"
24
+ end
@@ -0,0 +1,22 @@
1
+ require 'socketIO'
2
+
3
+ describe Parser do
4
+
5
+ it 'should be able to decode all valid messages' do
6
+ Parser.decode("0").should == {type: "0"}
7
+ Parser.decode("1::").should == {type: "1", id: nil, end_point: nil, data: ""}
8
+ Parser.decode("2::").should == {type: "2", id: nil, end_point: nil, data: ""}
9
+ Parser.decode("3:::hay you").should == {type: "3", id: nil, end_point: nil, data: "hay you"}
10
+ Parser.decode("4:::{\"can\":\"youcall\"}").should == {type: "4", id: nil, end_point: nil, data: "{\"can\":\"youcall\"}"}
11
+ Parser.decode("5:::hay you").should == {type: "5", id: nil, end_point: nil, data: "hay you"}
12
+ Parser.decode("6:::").should == {type: "6", id: nil, end_point: nil, data: ""}
13
+ Parser.decode("7:::there is an error").should == {type: "7", id: nil, end_point: nil, data: "there is an error"}
14
+ Parser.decode("8:::").should == {type: "8", id: nil, end_point: nil, data: ""}
15
+ end
16
+
17
+ it "should give a disconnect if bad input" do
18
+ Parser.decode("hay dude").should == {type: "0"}
19
+ Parser.decode("9").should == {type: "0"}
20
+ end
21
+
22
+ end
@@ -0,0 +1,17 @@
1
+ var io = require('socket.io').listen(80);
2
+
3
+ io.sockets.on('connection', function(socket) {
4
+
5
+ socket.on('message', function(msg) {
6
+ socket.send(msg)
7
+ })
8
+
9
+ socket.on('event', function(data) {
10
+ socket.emit('event', data);
11
+ });
12
+
13
+ socket.on('dc', function() {
14
+ socket.close()
15
+ })
16
+
17
+ });
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ echo_server = fork do
4
+ puts `pwd`
5
+ # exec ""
6
+ end
7
+ puts "pid ??? #{echo_server}"
8
+ Process.detach(echo_server)
File without changes
@@ -0,0 +1,46 @@
1
+ describe SocketIO do
2
+
3
+ before :all do
4
+ @client = SocketIO.connect("localhost", :sync => true)
5
+ end
6
+
7
+ it "can send a heartbeat" do
8
+ @client.send_heartbeat
9
+ # should not bonk. haha
10
+ end
11
+
12
+ it "gets a message back when it sends one" do
13
+ count = 0
14
+ @client.on_message do |msg|
15
+ count += 1 if msg == "hay dude"
16
+ end
17
+ @client.send("hay dude")
18
+ sleep 0.5
19
+ count.should == 1
20
+ end
21
+
22
+ it "gets an emit back when it hits the echo" do
23
+ count = 0
24
+ @client.on_event("event") do |data|
25
+ count += 1 if data[0] == {"first"=>"element", "second"=>"guy"}
26
+ end
27
+ @client.emit("event", {first: "element", second: "guy"})
28
+ sleep 0.5
29
+ count.should == 1
30
+ end
31
+
32
+ it "can have a block for every thing" do
33
+ @client.on_disconnect { }
34
+ @client.on_connect { }
35
+ @client.on_heartbeat { }
36
+ @client.on_message { |msg| }
37
+ @client.on_json_message { |json| }
38
+ @client.on_event('en') { |event_hash| }
39
+ @client.on_ack { }
40
+ @client.on_error { |data| }
41
+ @client.on_noop { }
42
+ end
43
+
44
+
45
+
46
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kellysutton-socketio-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kelly Sutton
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-21 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rest-client
16
+ requirement: &70267699385860 !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: *70267699385860
25
+ description: uses a very simple web socket
26
+ email:
27
+ - michael.k.sutton@gmail.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - Gemfile
33
+ - README.md
34
+ - Rakefile
35
+ - example/complex.rb
36
+ - example/remote_log.rb
37
+ - example/servers/complex.js
38
+ - example/servers/remote_log.js
39
+ - example/servers/simple.js
40
+ - example/simple.rb
41
+ - lib/parser.rb
42
+ - lib/socketio-client.rb
43
+ - lib/web_socket.rb
44
+ - pkg/socket.io-client-0.0.1.gem
45
+ - pkg/socketio-client-0.0.1.gem
46
+ - socketio-client.gemspec
47
+ - spec/parser_spec.rb
48
+ - spec/setup/echo.js
49
+ - spec/setup/start.rb
50
+ - spec/setup/stop.rb
51
+ - spec/socketio_spec.rb
52
+ homepage: http://github.com/kellysutton/socket.io-ruby-client
53
+ licenses: []
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubyforge_project: socketio-client
72
+ rubygems_version: 1.8.6
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: A basic Socket.io client implememtation written for ruby
76
+ test_files:
77
+ - spec/parser_spec.rb
78
+ - spec/setup/echo.js
79
+ - spec/setup/start.rb
80
+ - spec/setup/stop.rb
81
+ - spec/socketio_spec.rb