lib/ftw/websocket.rb in ftw-0.0.6 vs lib/ftw/websocket.rb in ftw-0.0.7

- old
+ new

@@ -2,10 +2,11 @@ require "openssl" require "base64" # stdlib require "digest/sha1" # stdlib require "cabin" require "ftw/websocket/parser" +require "ftw/websocket/writer" require "ftw/crlf" # WebSockets, RFC6455. # # TODO(sissel): Find a comfortable way to make this websocket stuff @@ -14,21 +15,24 @@ # TODO(sissel): Also consider SPDY and the kittens. class FTW::WebSocket include FTW::CRLF include Cabin::Inspectable + # The frame identifier for a 'text' frame TEXTFRAME = 0x0001 + # Search RFC6455 for this string and you will find its definitions. + # It is used in servers accepting websocket upgrades. WEBSOCKET_ACCEPT_UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" - private - # Protocol phases # 1. tcp connect # 2. http handshake (RFC6455 section 4) # 3. websocket protocol + private + # Creates a new websocket and fills in the given http request with any # necessary settings. def initialize(request) @key_nonce = generate_key_nonce @request = request @@ -113,78 +117,21 @@ # break from it. # # The text payload of each message will be yielded to the block. def each(&block) loop do - payload = @parser.feed(@connection.read) - next if payload.nil? - yield payload + @parser.feed(@connection.read(16384)) do |payload| + yield payload + end end end # def each - # Implement masking as described by http://tools.ietf.org/html/rfc6455#section-5.3 - # Basically, we take a 4-byte random string and use it, round robin, to XOR - # every byte. Like so: - # message[0] ^ key[0] - # message[1] ^ key[1] - # message[2] ^ key[2] - # message[3] ^ key[3] - # message[4] ^ key[0] - # ... - def mask(message, key) - masked = [] - mask_bytes = key.unpack("C4") - i = 0 - message.each_byte do |byte| - masked << (byte ^ mask_bytes[i % 4]) - i += 1 - end - return masked.pack("C*") - end # def mask - # Publish a message text. # # This will send a websocket text frame over the connection. def publish(message) - # TODO(sissel): Support server and client modes. - # Server MUST NOT mask. Client MUST mask. - # - # 0 1 2 3 - # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - # +-+-+-+-+-------+-+-------------+-------------------------------+ - # |F|R|R|R| opcode|M| Payload len | Extended payload length | - # |I|S|S|S| (4) |A| (7) | (16/64) | - # |N|V|V|V| |S| | (if payload len==126/127) | - # | |1|2|3| |K| | | - # +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + - # | Extended payload length continued, if payload len == 127 | - # + - - - - - - - - - - - - - - - +-------------------------------+ - # | |Masking-key, if MASK set to 1 | - # +-------------------------------+-------------------------------+ - # | Masking-key (continued) | Payload Data | - # +-------------------------------- - - - - - - - - - - - - - - - + - # : Payload Data continued ... : - # + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - # | Payload Data continued ... | - # +---------------------------------------------------------------+ - # TODO(sissel): Support 'fin' flag - # Set 'fin' flag and opcode of 'text frame' - length = message.length - mask_key = [rand(1 << 32)].pack("Q") - if message.length >= (1 << 16) - pack = "CCSA4A*" # flags+opcode, mask+len, 2-byte len, payload - data = [ 0x80 | TEXTFRAME, 0x80 | 126, message.length, mask_key, mask(message, mask_key) ] - @connection.write(data.pack(pack)) - elsif message.length >= (1 << 7) - length = 126 - pack = "CCQA4A*" # flags+opcode, mask+len, 8-byte len, payload - data = [ 0x80 | TEXTFRAME, 0x80 | 127, message.length, mask_key, mask(message, mask_key) ] - @connection.write(data.pack(pack)) - else - data = [ 0x80 | TEXTFRAME, 0x80 | message.length, mask_key, mask(message, mask_key) ] - pack = "CCA4A*" # flags+opcode, mask+len, payload - @connection.write(data.pack(pack)) - end + writer = FTW::WebSocket::Writer.singleton + writer.write_text(@connection, message) end # def publish public(:initialize, :connection=, :handshake_ok?, :each, :publish) end # class FTW::WebSocket