web-socket-ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "rspec", "~> 2.1.0"
5
+ gem "yard", "~> 0.6.0"
6
+ gem "bundler", "~> 1.0.0"
7
+ gem "jeweler", "~> 1.5.1"
8
+ gem "rcov", ">= 0"
9
+ end
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2010, Hiroshi Ichikawa <http://gimite.net/en/>
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of Hiroshi Ichikawa nor the names of its
15
+ contributors may be used to endorse or promote products derived from this
16
+ software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,61 @@
1
+ How to run sample
2
+ =================
3
+
4
+ - Run sample Web Socket server (echo server) with:
5
+ $ ruby samples/echo_server.rb localhost 10081
6
+
7
+ - Run sample Web Socket client and type something:
8
+ $ ruby samples/stdio_client.rb ws://localhost:10081
9
+ Ready
10
+ hoge
11
+ Sent: "hoge"
12
+ Received: "hoge"
13
+
14
+
15
+ Usage example
16
+ =============
17
+
18
+ Server:
19
+
20
+ # Runs the server at port 10081. It allows connections whose origin is example.com.
21
+ server = WebSocketServer.new(:port => 10081, :accepted_domains => ["example.com"])
22
+ server.run() do |ws|
23
+ # The block is called for each connection.
24
+ # Checks requested path.
25
+ if ws.path == "/"
26
+ # Call ws.handshake() without argument first.
27
+ ws.handshake()
28
+ # Receives one message from the client as String.
29
+ while data = ws.receive()
30
+ puts(data)
31
+ # Sends the message to the client.
32
+ ws.send(data)
33
+ end
34
+ else
35
+ # You can call ws.handshake() with argument to return error status.
36
+ ws.handshake("404 Not Found")
37
+ end
38
+ end
39
+
40
+ Client:
41
+
42
+ # Connects to Web Socket server at host example.com port 10081.
43
+ client = WebSocket.new("ws://example.com:10081/")
44
+ # Sends a message to the server.
45
+ client.send("Hello")
46
+ # Receives a message from the server.
47
+ data = client.receive()
48
+ puts(data)
49
+
50
+
51
+ Tips: JavaScript client implementation
52
+ ======================================
53
+
54
+ Google Chrome Dev Channel natively supports Web Socket. For other browsers, you can use an implementation using Flash:
55
+ http://github.com/gimite/web-socket-js/tree/master
56
+
57
+
58
+ License
59
+ =======
60
+
61
+ New BSD License.
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ gem.name = "web-socket-ruby"
15
+ gem.homepage = "http://github.com/jsgoecke/web-socket-ruby"
16
+ gem.license = "New BSD"
17
+ gem.summary = "HTML5 Web Socket server/client implementation in Ruby"
18
+ gem.description = "HTML5 Web Socket server/client implementation in Ruby"
19
+ gem.email = "jason@goecke.net"
20
+ gem.authors = ["Hiroshi Ichikawa", "Jason Goecke"]
21
+ end
22
+ Jeweler::RubygemsDotOrgTasks.new
23
+
24
+ require 'rspec/core'
25
+ require 'rspec/core/rake_task'
26
+ RSpec::Core::RakeTask.new(:spec) do |spec|
27
+ spec.pattern = FileList['spec/**/*_spec.rb']
28
+ end
29
+
30
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
31
+ spec.pattern = 'spec/**/*_spec.rb'
32
+ spec.rcov = true
33
+ end
34
+
35
+ task :default => :spec
36
+
37
+ require 'yard'
38
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1 @@
1
+ require 'web_socket/web_socket'
@@ -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
@@ -0,0 +1,58 @@
1
+ # Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
2
+ # Lincense: New BSD Lincense
3
+
4
+ $LOAD_PATH << File.dirname(__FILE__) + "/../lib"
5
+ require "lib/web_socket"
6
+ require "thread"
7
+
8
+ Thread.abort_on_exception = true
9
+
10
+ if ARGV.size != 2
11
+ $stderr.puts("Usage: ruby sample/chat_server.rb ACCEPTED_DOMAIN PORT")
12
+ exit(1)
13
+ end
14
+
15
+ server = WebSocketServer.new(
16
+ :accepted_domains => [ARGV[0]],
17
+ :port => ARGV[1].to_i())
18
+ puts("Server is running at port %d" % server.port)
19
+ connections = []
20
+ history = [nil] * 20
21
+
22
+ server.run() do |ws|
23
+ begin
24
+
25
+ puts("Connection accepted")
26
+ ws.handshake()
27
+ que = Queue.new()
28
+ connections.push(que)
29
+
30
+ for message in history
31
+ next if !message
32
+ ws.send(message)
33
+ puts("Sent: #{message}")
34
+ end
35
+
36
+ thread = Thread.new() do
37
+ while true
38
+ message = que.pop()
39
+ ws.send(message)
40
+ puts("Sent: #{message}")
41
+ end
42
+ end
43
+
44
+ while data = ws.receive()
45
+ puts("Received: #{data}")
46
+ for conn in connections
47
+ conn.push(data)
48
+ end
49
+ history.push(data)
50
+ history.shift()
51
+ end
52
+
53
+ ensure
54
+ connections.delete(que)
55
+ thread.terminate() if thread
56
+ puts("Connection closed")
57
+ end
58
+ end
@@ -0,0 +1,33 @@
1
+ # Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
2
+ # Lincense: New BSD Lincense
3
+
4
+ $LOAD_PATH << File.dirname(__FILE__) + "/../lib"
5
+ require "lib/web_socket"
6
+
7
+ Thread.abort_on_exception = true
8
+ # WebSocket.debug = true
9
+
10
+ if ARGV.size != 2
11
+ $stderr.puts("Usage: ruby sample/echo_server.rb ACCEPTED_DOMAIN PORT")
12
+ exit(1)
13
+ end
14
+
15
+ server = WebSocketServer.new(
16
+ :accepted_domains => [ARGV[0]],
17
+ :port => ARGV[1].to_i())
18
+ puts("Server is running at port %d" % server.port)
19
+ server.run() do |ws|
20
+ puts("Connection accepted")
21
+ puts("Path: #{ws.path}, Origin: #{ws.origin}")
22
+ if ws.path == "/"
23
+ ws.handshake()
24
+ while data = ws.receive()
25
+ printf("Received: %p\n", data)
26
+ ws.send(data)
27
+ printf("Sent: %p\n", data)
28
+ end
29
+ else
30
+ ws.handshake("404 Not Found")
31
+ end
32
+ puts("Connection closed")
33
+ end
@@ -0,0 +1,25 @@
1
+ # Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
2
+ # Lincense: New BSD Lincense
3
+
4
+ $LOAD_PATH << File.dirname(__FILE__) + "/../lib"
5
+ require "lib/web_socket"
6
+
7
+ if ARGV.size != 1
8
+ $stderr.puts("Usage: ruby samples/stdio_client.rb ws://HOST:PORT/")
9
+ exit(1)
10
+ end
11
+
12
+ client = WebSocket.new(ARGV[0])
13
+ puts("Connected")
14
+ Thread.new() do
15
+ while data = client.receive()
16
+ printf("Received: %p\n", data)
17
+ end
18
+ exit()
19
+ end
20
+ $stdin.each_line() do |line|
21
+ data = line.chomp()
22
+ client.send(data)
23
+ printf("Sent: %p\n", data)
24
+ end
25
+ client.close()
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: web-socket-ruby
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Hiroshi Ichikawa
14
+ - Jason Goecke
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-11-22 00:00:00 -08:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: rspec
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ hash: 11
31
+ segments:
32
+ - 2
33
+ - 1
34
+ - 0
35
+ version: 2.1.0
36
+ type: :development
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: yard
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ hash: 7
47
+ segments:
48
+ - 0
49
+ - 6
50
+ - 0
51
+ version: 0.6.0
52
+ type: :development
53
+ version_requirements: *id002
54
+ - !ruby/object:Gem::Dependency
55
+ name: bundler
56
+ prerelease: false
57
+ requirement: &id003 !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ hash: 23
63
+ segments:
64
+ - 1
65
+ - 0
66
+ - 0
67
+ version: 1.0.0
68
+ type: :development
69
+ version_requirements: *id003
70
+ - !ruby/object:Gem::Dependency
71
+ name: jeweler
72
+ prerelease: false
73
+ requirement: &id004 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ~>
77
+ - !ruby/object:Gem::Version
78
+ hash: 1
79
+ segments:
80
+ - 1
81
+ - 5
82
+ - 1
83
+ version: 1.5.1
84
+ type: :development
85
+ version_requirements: *id004
86
+ - !ruby/object:Gem::Dependency
87
+ name: rcov
88
+ prerelease: false
89
+ requirement: &id005 !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 3
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ type: :development
99
+ version_requirements: *id005
100
+ description: HTML5 Web Socket server/client implementation in Ruby
101
+ email: jason@goecke.net
102
+ executables: []
103
+
104
+ extensions: []
105
+
106
+ extra_rdoc_files:
107
+ - LICENSE.txt
108
+ - README.md
109
+ files:
110
+ - Gemfile
111
+ - LICENSE.txt
112
+ - README.md
113
+ - Rakefile
114
+ - VERSION
115
+ - lib/web_socket.rb
116
+ - lib/web_socket/web_socket.rb
117
+ - samples/chat_server.rb
118
+ - samples/echo_server.rb
119
+ - samples/stdio_client.rb
120
+ has_rdoc: true
121
+ homepage: http://github.com/jsgoecke/web-socket-ruby
122
+ licenses:
123
+ - New BSD
124
+ post_install_message:
125
+ rdoc_options: []
126
+
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ hash: 3
135
+ segments:
136
+ - 0
137
+ version: "0"
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ hash: 3
144
+ segments:
145
+ - 0
146
+ version: "0"
147
+ requirements: []
148
+
149
+ rubyforge_project:
150
+ rubygems_version: 1.3.7
151
+ signing_key:
152
+ specification_version: 3
153
+ summary: HTML5 Web Socket server/client implementation in Ruby
154
+ test_files: []
155
+