├── .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 | --------------------------------------------------------------------------------