1 # -*- encoding: binary -*-
5 # Wraps IO objects and provides transparent framing of #gets and
8 # It is compatible with core Ruby IO objects, Revactor and
9 # Rainbows::Fiber::IO objects.
10 class IO < Struct.new(:to_io, :buf)
12 # most Web Socket messages from clients are expected to be small text,
13 # so we only read 128 bytes off the socket at a time
16 # maximum size of a UTF-8 buffer we'll allow in memory (16K)
17 # this is a soft limit and may be offset by the value of RD_SIZE
18 MAX_UTF8_SIZE = 1024 * 16
20 # maximum size of a binary buffer we'll allow in memory (112K)
21 # this is a soft limit and may be offset by the value of RD_SIZE
22 MAX_BINARY_SIZE = 1024 * 112
24 # Web Sockets usually uses UTF-8 when interfacing with the client
25 # Ruby Sockets always return strings of Encoding::Binary under 1.9
26 ENC = defined?(Encoding::UTF_8) ? Encoding::UTF_8 : nil
30 # Wraps the given +io+ with Web Sockets-compatible framing.
32 # TCPSocket.new('example.com', 80)
33 # io = Sunshowers::IO.new(socket)
34 def initialize(io, buffer = Z.dup)
38 # iterates through each message until a client closes the connection
39 # or block the issues a break/return
47 # Retrieves the next record, returns nil if client closes the connection.
48 # The record may be either a UTF-8 or binary String, under
49 # Ruby 1.9, the String encoding will be set appropriately.
53 buf.gsub!(/\A\x00(.*?)\xff/m, Z) and return utf8!($1)
54 rv = read_binary and return rv
55 buf.size > MAX_UTF8_SIZE and
56 raise ProtocolError, "buffer too large #{buf.size}"
64 def syswrite(buf) # :nodoc:
66 rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,
67 Errno::EINVAL,Errno::EBADF => e
68 raise ClientShutdown, e.message, []
71 # writes a UTF-8 frame containing +buf+. We do not validate that
72 # +buf+ contains valid UTF-8, we assume you know what you are
73 # doing if you call this method directly.
75 syswrite("\0#{binary(buf)}\xff")
78 # writes a binary frame containing +buf+
84 length.unshift((n % 128) | 0x80)
85 end while (n /= 128) > 0
88 syswrite("\x80#{length.pack("C*")}#{buf}")
102 valid_utf8?(buf) ? write_utf8(buf) : write_binary(buf)
107 # Writes out +buf+ as a Web Socket frame. If +buf+ encoding is UTF-8,
108 # then it will be framed as UTF-8, otherwise it will be framed as
109 # binary with an explicit length set.
111 buf.encoding == ENC ? write_utf8(buf) : write_binary(buf)
118 # read with no args for Revactor compat
119 i.respond_to?(:readpartial) ?
120 i.readpartial(size.nil? ? RD_SIZE : size) :
121 i.read(size.nil? ? nil : size)
126 valid_utf8?(buf) or raise ProtocolError, "not UTF-8: #{buf.inspect}"
129 def binary(buf); buf; end
132 buf.force_encoding(ENC)
133 buf.valid_encoding? or raise ProtocolError, "not UTF-8: #{buf.inspect}"
137 buf.encoding == Encoding::BINARY ?
138 buf : buf.dup.force_encoding(Encoding::BINARY)
142 if Z.respond_to?(:ord)
143 def ord(byte_str); byte_str.ord; end
145 def ord(byte_str); byte_str; end
149 (ord(buf[0]) & 0x80) == 0x80 or return
154 buf << read while (b = buf[i]).nil?
156 length = length * 128 + (b & 0x7f)
158 end while (b & 0x80) != 0
160 length > MAX_BINARY_SIZE and
161 raise ProtocolError, "chunk too large: #{length} bytes"
163 to_read = length - buf.size + i
169 buf.replace(buf[i+length, buf.size])