├── .gitignore
├── README.txt
├── lib
└── web_socket.rb
└── samples
├── chat_server.rb
├── echo_server.rb
└── stdio_client.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | exp
2 |
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | HTML5 Web Socket server/client implementation in Ruby.
2 |
3 | For server, em-websocket ( https://github.com/igrigorik/em-websocket ) may be a better choice, especially if you want to use EventMachine.
4 |
5 |
6 | * How to run sample
7 |
8 | - Run sample Web Socket server (echo server) with:
9 | $ ruby samples/echo_server.rb localhost 10081
10 |
11 | - Run sample Web Socket client and type something:
12 | $ ruby samples/stdio_client.rb ws://localhost:10081
13 | Ready
14 | hoge
15 | Sent: "hoge"
16 | Received: "hoge"
17 |
18 |
19 | * Usage example
20 |
21 | Server:
22 |
23 | # Runs the server at port 10081. It allows connections whose origin is example.com.
24 | server = WebSocketServer.new(:port => 10081, :accepted_domains => ["example.com"])
25 | server.run() do |ws|
26 | # The block is called for each connection.
27 | # Checks requested path.
28 | if ws.path == "/"
29 | # Call ws.handshake() without argument first.
30 | ws.handshake()
31 | # Receives one message from the client as String.
32 | while data = ws.receive()
33 | puts(data)
34 | # Sends the message to the client.
35 | ws.send(data)
36 | end
37 | else
38 | # You can call ws.handshake() with argument to return error status.
39 | ws.handshake("404 Not Found")
40 | end
41 | end
42 |
43 | Client:
44 |
45 | # Connects to Web Socket server at host example.com port 10081.
46 | client = WebSocket.new("ws://example.com:10081/")
47 | # Sends a message to the server.
48 | client.send("Hello")
49 | # Receives a message from the server.
50 | data = client.receive()
51 | puts(data)
52 |
53 |
54 | * Supported WebSocket protocol versions
55 |
56 | WebSocketServer class (server) accepts version hixie-75, hixie-76, hybi-07, hybi-10.
57 | WebSocket class (client) speaks version hixie-76.
58 |
59 |
60 | * Tips: JavaScript client implementation
61 |
62 | Google Chrome Dev Channel natively supports Web Socket. For other browsers, you can use an implementation using Flash:
63 | http://github.com/gimite/web-socket-js/tree/master
64 |
65 |
66 | * WebSocket protocol versions
67 |
68 | The server supports the protocol defined in RFC 6455, draft versions hixie-75 and hixie-76.
69 |
70 | The client speaks draft version hixie-76.
71 |
72 |
73 | * License
74 |
75 | New BSD License.
76 |
--------------------------------------------------------------------------------
/lib/web_socket.rb:
--------------------------------------------------------------------------------
1 | # Copyright: Hiroshi Ichikawa
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(force_encoding("\xff", "ASCII-8BIT"))
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 | while true
201 | begin
202 | bytes = read(2).unpack("C*")
203 | fin = (bytes[0] & 0x80) != 0
204 | opcode = bytes[0] & 0x0f
205 | mask = (bytes[1] & 0x80) != 0
206 | plength = bytes[1] & 0x7f
207 | if plength == 126
208 | bytes = read(2)
209 | plength = bytes.unpack("n")[0]
210 | elsif plength == 127
211 | bytes = read(8)
212 | (high, low) = bytes.unpack("NN")
213 | plength = high * (2 ** 32) + low
214 | end
215 | if @server && !mask
216 | # Masking is required.
217 | @socket.close()
218 | raise(WebSocket::Error, "received unmasked data")
219 | end
220 | mask_key = mask ? read(4).unpack("C*") : nil
221 | payload = read(plength)
222 | payload = apply_mask(payload, mask_key) if mask
223 | if WebSocket.debug
224 | $stderr.printf("recv_frame> opcode:%d fin:%d payload:%p\n" % [opcode, fin ? 1 : 0, payload])
225 | end
226 | case opcode
227 | when OPCODE_TEXT
228 | return force_encoding(payload, "UTF-8")
229 | when OPCODE_BINARY
230 | raise(WebSocket::Error, "received binary data, which is not supported")
231 | when OPCODE_CLOSE
232 | close(1005, "", :peer)
233 | return nil
234 | when OPCODE_PING
235 | raise(WebSocket::Error, "received ping, which is not supported")
236 | when OPCODE_PONG
237 | next
238 | else
239 | raise(WebSocket::Error, "received unknown opcode: %d" % opcode)
240 | end
241 | rescue EOFError
242 | return nil
243 | end
244 | end
245 | end
246 | end
247 |
248 | def tcp_socket
249 | return @socket
250 | end
251 |
252 | def host
253 | return @header["host"]
254 | end
255 |
256 | def origin
257 | case @web_socket_version
258 | when "7", "8"
259 | name = "sec-websocket-origin"
260 | else
261 | name = "origin"
262 | end
263 | if @header[name]
264 | return @header[name]
265 | else
266 | raise(WebSocket::Error, "%s header is missing" % name)
267 | end
268 | end
269 |
270 | def location
271 | return "ws://#{self.host}#{@path}"
272 | end
273 |
274 | # Does closing handshake.
275 | def close(code = 1005, reason = "", origin = :self)
276 | if !@closing_started
277 | case @web_socket_version
278 | when "hixie-75", "hixie-76"
279 | write("\xff\x00")
280 | else
281 | if code == 1005
282 | payload = ""
283 | else
284 | payload = [code].pack("n") + force_encoding(reason.dup(), "ASCII-8BIT")
285 | end
286 | send_frame(OPCODE_CLOSE, payload, false)
287 | end
288 | end
289 | @socket.close() if origin == :peer
290 | @closing_started = true
291 | end
292 |
293 | def close_socket()
294 | @socket.close()
295 | end
296 |
297 | private
298 |
299 | NOISE_CHARS = ("\x21".."\x2f").to_a() + ("\x3a".."\x7e").to_a()
300 |
301 | def read_header()
302 | @header = {}
303 | while line = gets()
304 | line = line.chomp()
305 | break if line.empty?
306 | if !(line =~ /\A(\S+): (.*)\z/n)
307 | raise(WebSocket::Error, "invalid request: #{line}")
308 | end
309 | @header[$1] = $2
310 | @header[$1.downcase()] = $2
311 | end
312 | if !@header["upgrade"]
313 | raise(WebSocket::Error, "Upgrade header is missing")
314 | end
315 | if !(@header["upgrade"] =~ /\AWebSocket\z/i)
316 | raise(WebSocket::Error, "invalid Upgrade: " + @header["upgrade"])
317 | end
318 | if !@header["connection"]
319 | raise(WebSocket::Error, "Connection header is missing")
320 | end
321 | if @header["connection"].split(/,/).grep(/\A\s*Upgrade\s*\z/i).empty?
322 | raise(WebSocket::Error, "invalid Connection: " + @header["connection"])
323 | end
324 | end
325 |
326 | def send_frame(opcode, payload, mask)
327 | if WebSocket.debug
328 | $stderr.printf("send_frame> opcode:%d masked:%d payload:%p\n" % [opcode, mask ? 1 : 0, payload])
329 | end
330 | payload = force_encoding(payload.dup(), "ASCII-8BIT")
331 | # Setting StringIO's encoding to ASCII-8BIT.
332 | buffer = StringIO.new(force_encoding("", "ASCII-8BIT"))
333 | write_byte(buffer, 0x80 | opcode)
334 | masked_byte = mask ? 0x80 : 0x00
335 | if payload.bytesize <= 125
336 | write_byte(buffer, masked_byte | payload.bytesize)
337 | elsif payload.bytesize < 2 ** 16
338 | write_byte(buffer, masked_byte | 126)
339 | buffer.write([payload.bytesize].pack("n"))
340 | else
341 | write_byte(buffer, masked_byte | 127)
342 | buffer.write([payload.bytesize / (2 ** 32), payload.bytesize % (2 ** 32)].pack("NN"))
343 | end
344 | if mask
345 | mask_key = Array.new(4){ rand(256) }
346 | buffer.write(mask_key.pack("C*"))
347 | payload = apply_mask(payload, mask_key)
348 | end
349 | buffer.write(payload)
350 | write(buffer.string)
351 | end
352 |
353 | def gets(rs = $/)
354 | line = @socket.gets(rs)
355 | $stderr.printf("recv> %p\n", line) if WebSocket.debug
356 | return line
357 | end
358 |
359 | def read(num_bytes)
360 | str = @socket.read(num_bytes)
361 | $stderr.printf("recv> %p\n", str) if WebSocket.debug
362 | if str && str.bytesize == num_bytes
363 | return str
364 | else
365 | raise(EOFError)
366 | end
367 | end
368 |
369 | def write(data)
370 | if WebSocket.debug
371 | data.scan(/\G(.*?(\n|\z))/n) do
372 | $stderr.printf("send> %p\n", $&) if !$&.empty?
373 | end
374 | end
375 | @socket.write(data)
376 | end
377 |
378 | def flush()
379 | @socket.flush()
380 | end
381 |
382 | def write_byte(buffer, byte)
383 | buffer.write([byte].pack("C"))
384 | end
385 |
386 | def security_digest(key)
387 | return Base64.encode64(Digest::SHA1.digest(key + WEB_SOCKET_GUID)).gsub(/\n/, "")
388 | end
389 |
390 | def hixie_76_security_digest(key1, key2, key3)
391 | bytes1 = websocket_key_to_bytes(key1)
392 | bytes2 = websocket_key_to_bytes(key2)
393 | return Digest::MD5.digest(bytes1 + bytes2 + key3)
394 | end
395 |
396 | def apply_mask(payload, mask_key)
397 | orig_bytes = payload.unpack("C*")
398 | new_bytes = []
399 | orig_bytes.each_with_index() do |b, i|
400 | new_bytes.push(b ^ mask_key[i % 4])
401 | end
402 | return new_bytes.pack("C*")
403 | end
404 |
405 | def generate_key()
406 | spaces = 1 + rand(12)
407 | max = 0xffffffff / spaces
408 | number = rand(max + 1)
409 | key = (number * spaces).to_s()
410 | (1 + rand(12)).times() do
411 | char = NOISE_CHARS[rand(NOISE_CHARS.size)]
412 | pos = rand(key.size + 1)
413 | key[pos...pos] = char
414 | end
415 | spaces.times() do
416 | pos = 1 + rand(key.size - 1)
417 | key[pos...pos] = " "
418 | end
419 | return key
420 | end
421 |
422 | def generate_key3()
423 | return [rand(0x100000000)].pack("N") + [rand(0x100000000)].pack("N")
424 | end
425 |
426 | def websocket_key_to_bytes(key)
427 | num = key.gsub(/[^\d]/n, "").to_i() / key.scan(/ /).size
428 | return [num].pack("N")
429 | end
430 |
431 | def force_encoding(str, encoding)
432 | if str.respond_to?(:force_encoding)
433 | return str.force_encoding(encoding)
434 | else
435 | return str
436 | end
437 | end
438 |
439 | def ssl_handshake(socket)
440 | ssl_context = OpenSSL::SSL::SSLContext.new()
441 | ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
442 | ssl_socket.sync_close = true
443 | ssl_socket.connect()
444 | return ssl_socket
445 | end
446 |
447 | end
448 |
449 |
450 | class WebSocketServer
451 |
452 | def initialize(params_or_uri, params = nil)
453 | if params
454 | uri = params_or_uri.is_a?(String) ? URI.parse(params_or_uri) : params_or_uri
455 | params[:port] ||= uri.port
456 | params[:accepted_domains] ||= [uri.host]
457 | else
458 | params = params_or_uri
459 | end
460 | @port = params[:port] || 80
461 | @accepted_domains = params[:accepted_domains]
462 | if !@accepted_domains
463 | raise(ArgumentError, "params[:accepted_domains] is required")
464 | end
465 | if params[:host]
466 | @tcp_server = TCPServer.open(params[:host], @port)
467 | else
468 | @tcp_server = TCPServer.open(@port)
469 | end
470 | end
471 |
472 | attr_reader(:tcp_server, :port, :accepted_domains)
473 |
474 | def run(&block)
475 | while true
476 | Thread.start(accept()) do |s|
477 | begin
478 | ws = create_web_socket(s)
479 | yield(ws) if ws
480 | rescue => ex
481 | print_backtrace(ex)
482 | ensure
483 | begin
484 | ws.close_socket() if ws
485 | rescue
486 | end
487 | end
488 | end
489 | end
490 | end
491 |
492 | def accept()
493 | return @tcp_server.accept()
494 | end
495 |
496 | def accepted_origin?(origin)
497 | domain = origin_to_domain(origin)
498 | return @accepted_domains.any?(){ |d| File.fnmatch(d, domain) }
499 | end
500 |
501 | def origin_to_domain(origin)
502 | if origin == "null" || origin == "file://" # local file
503 | return "null"
504 | else
505 | return URI.parse(origin).host
506 | end
507 | end
508 |
509 | def create_web_socket(socket)
510 | ch = socket.getc()
511 | if ch == ?<
512 | # This is Flash socket policy file request, not an actual Web Socket connection.
513 | send_flash_socket_policy_file(socket)
514 | return nil
515 | else
516 | socket.ungetc(ch) if ch
517 | return WebSocket.new(socket, :server => self)
518 | end
519 | end
520 |
521 | private
522 |
523 | def print_backtrace(ex)
524 | $stderr.printf("%s: %s (%p)\n", ex.backtrace[0], ex.message, ex.class)
525 | for s in ex.backtrace[1..-1]
526 | $stderr.printf(" %s\n", s)
527 | end
528 | end
529 |
530 | # Handles Flash socket policy file request sent when web-socket-js is used:
531 | # http://github.com/gimite/web-socket-js/tree/master
532 | def send_flash_socket_policy_file(socket)
533 | socket.puts('')
534 | socket.puts('')
536 | socket.puts('')
537 | for domain in @accepted_domains
538 | next if domain == "file://"
539 | socket.puts("")
540 | end
541 | socket.puts('')
542 | socket.close()
543 | end
544 |
545 | end
546 |
547 |
548 | if __FILE__ == $0
549 | Thread.abort_on_exception = true
550 |
551 | if ARGV[0] == "server" && ARGV.size == 3
552 |
553 | server = WebSocketServer.new(
554 | :accepted_domains => [ARGV[1]],
555 | :port => ARGV[2].to_i())
556 | puts("Server is running at port %d" % server.port)
557 | server.run() do |ws|
558 | puts("Connection accepted")
559 | puts("Path: #{ws.path}, Origin: #{ws.origin}")
560 | if ws.path == "/"
561 | ws.handshake()
562 | while data = ws.receive()
563 | printf("Received: %p\n", data)
564 | ws.send(data)
565 | printf("Sent: %p\n", data)
566 | end
567 | else
568 | ws.handshake("404 Not Found")
569 | end
570 | puts("Connection closed")
571 | end
572 |
573 | elsif ARGV[0] == "client" && ARGV.size == 2
574 |
575 | client = WebSocket.new(ARGV[1])
576 | puts("Connected")
577 | Thread.new() do
578 | while data = client.receive()
579 | printf("Received: %p\n", data)
580 | end
581 | end
582 | $stdin.each_line() do |line|
583 | data = line.chomp()
584 | client.send(data)
585 | printf("Sent: %p\n", data)
586 | end
587 |
588 | else
589 |
590 | $stderr.puts("Usage:")
591 | $stderr.puts(" ruby web_socket.rb server ACCEPTED_DOMAIN PORT")
592 | $stderr.puts(" ruby web_socket.rb client ws://HOST:PORT/")
593 | exit(1)
594 |
595 | end
596 | end
597 |
--------------------------------------------------------------------------------
/samples/chat_server.rb:
--------------------------------------------------------------------------------
1 | # Copyright: Hiroshi Ichikawa
2 | # Lincense: New BSD Lincense
3 |
4 | $LOAD_PATH << File.dirname(__FILE__) + "/../lib"
5 | require "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
59 |
--------------------------------------------------------------------------------
/samples/echo_server.rb:
--------------------------------------------------------------------------------
1 | # Copyright: Hiroshi Ichikawa
2 | # Lincense: New BSD Lincense
3 |
4 | $LOAD_PATH << File.dirname(__FILE__) + "/../lib"
5 | require "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
34 |
--------------------------------------------------------------------------------
/samples/stdio_client.rb:
--------------------------------------------------------------------------------
1 | # Copyright: Hiroshi Ichikawa
2 | # Lincense: New BSD Lincense
3 |
4 | $LOAD_PATH << File.dirname(__FILE__) + "/../lib"
5 | require "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()
26 |
--------------------------------------------------------------------------------