lib/ftw/websocket/parser.rb in ftw-0.0.4 vs lib/ftw/websocket/parser.rb in ftw-0.0.5
- old
+ new
@@ -20,16 +20,33 @@
# +-------------------------------- - - - - - - - - - - - - - - - +
# : Payload Data continued ... :
# + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
# | Payload Data continued ... |
# +---------------------------------------------------------------+
+#
+# Example use:
+#
+# socket = FTW::Connection.new("example.com:80")
+# parser = FTW::WebSocket::Parser.new
+# # ... do HTTP Upgrade request to websockets
+# loop do
+# data = socket.sysread(4096)
+# payload = parser.feed(data)
+# if payload
+# # We got a full websocket frame, print the payload.
+# p :payload => payload
+# end
+# end
+#
class FTW::WebSocket::Parser
# XXX: Implement control frames: http://tools.ietf.org/html/rfc6455#section-5.5
# States are based on the minimal unit of 'byte'
STATES = [ :flags_and_opcode, :mask_and_payload_init, :payload_length, :payload ]
+ private
+
# A new WebSocket protocol parser.
def initialize
@logger = Cabin::Channel.get($0)
@opcode = 0
@masking_key = ""
@@ -40,22 +57,23 @@
@buffer = ""
@buffer.force_encoding("BINARY")
end # def initialize
# Transition to a specified state and set the next required read length.
- private
def transition(state, next_length)
@logger.debug("Transitioning", :transition => state, :nextlen => next_length)
@state = state
need(next_length)
end # def transition
# Feed data to this parser.
#
# Currently, it will return the raw payload of websocket messages.
# Otherwise, it returns nil if no complete message has yet been consumed.
- public
+ #
+ # @param [String] the string data to feed into the parser.
+ # @return [String, nil] the websocket message payload, if any, nil otherwise.
def feed(data)
@buffer << data
while have?(@need)
value = send(@state)
# Return if our state yields a value.
@@ -64,33 +82,29 @@
end
return nil
end # def <<
# Do we have at least 'length' bytes in the buffer?
- private
def have?(length)
return length <= @buffer.size
end # def have?
# Get 'length' string from the buffer.
- private
def get(length=nil)
length = @need if length.nil?
data = @buffer[0 ... length]
@buffer = @buffer[length .. -1]
return data
end # def get
# Set the minimum number of bytes we need in the buffer for the next read.
- private
def need(length)
@need = length
end # def need
# State: Flags (fin, etc) and Opcode.
# See: http://tools.ietf.org/html/rfc6455#section-5.3
- private
def flags_and_opcode
# 0
# 0 1 2 3 4 5 6 7
# +-+-+-+-+-------
# |F|R|R|R| opcode
@@ -110,11 +124,10 @@
return nil
end # def flags_and_opcode
# State: mask_and_payload_init
# See: http://tools.ietf.org/html/rfc6455#section-5.2
- private
def mask_and_payload_init
byte = get.bytes.first
@mask = byte & 0x80 # first bit (msb)
@payload_length = byte & 0x7F # remaining bits are the length
case @payload_length
@@ -134,11 +147,10 @@
# State: payload_length
# This is the 'extended payload length' with support for both 16
# and 64 bit lengths.
# See: http://tools.ietf.org/html/rfc6455#section-5.2
- private
def payload_length
# 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) |
@@ -167,17 +179,18 @@
# State: payload
# Read the full payload and return it.
# See: http://tools.ietf.org/html/rfc6455#section-5.3
#
- private
def payload
# TODO(sissel): Handle massive payload lengths without exceeding memory.
# Perhaps if the payload is large (say, larger than 500KB by default),
# instead of returning the whole thing, simply return an Enumerable that
# yields chunks of the payload. There's no reason to buffer the entire
# thing. Have the consumer of this library make that decision.
data = get(@need)
transition(:flags_and_opcode, 1)
return data
end # def payload
+
+ public(:feed)
end # class FTW::WebSocket::Parser