Class: LibWebSocket::Frame
- Inherits:
-
Object
- Object
- LibWebSocket::Frame
- Defined in:
- lib/libwebsocket/frame.rb,
lib/libwebsocket/frame/error.rb
Overview
Construct or parse a WebSocket frame.
SYNOPSIS
# Create frame
frame = LibWebSocket::Frame.new('123')
frame.to_bytes
# Parse frames
frame = LibWebSocket::Frame.new
frame.append(...)
frame.next # get next message
frame.next # get another next message
Defined Under Namespace
Classes: Error
Constant Summary collapse
- MAX_RAND_INT =
2 ** 32
- TYPES =
{ :text => 0x01, :binary => 0x02, :ping => 0x09, :pong => 0x0a, :close => 0x08 }
Instance Attribute Summary collapse
-
#buffer ⇒ Object
Returns the value of attribute buffer.
-
#fin ⇒ Object
Returns the value of attribute fin.
-
#masked ⇒ Object
Returns the value of attribute masked.
-
#max_fragments_amount ⇒ Object
Returns the value of attribute max_fragments_amount.
-
#max_payload_size ⇒ Object
Returns the value of attribute max_payload_size.
-
#opcode ⇒ Object
Returns the value of attribute opcode.
-
#rsv ⇒ Object
Returns the value of attribute rsv.
-
#version ⇒ Object
Returns the value of attribute version.
Instance Method Summary collapse
-
#append(string = nil) ⇒ Object
Append a frame chunk.
-
#binary? ⇒ Boolean
Check if frame is of binary type.
-
#close? ⇒ Boolean
Check if frame is of close type.
-
#initialize(options = '') ⇒ Frame
constructor
Create a new Frame instance.
-
#next ⇒ Object
Return the next frame.
-
#next_bytes ⇒ Object
Return the next message as a UTF-8 encoded string.
-
#ping? ⇒ Boolean
Check if frame is a ping request.
-
#pong? ⇒ Boolean
Check if frame is a pong response.
-
#text? ⇒ Boolean
Check if frame is of text type.
Constructor Details
#initialize(options = '') ⇒ Frame
Create a new Frame instance. Automatically detect if the passed data is a string or bytes. Options can be buffer or hash with options:
:buffer - content of buffer
:type - frame type(allowed values: text, binary, ping, pong, close)
:version - protocol version(see readme for supported versions)
:max_fragments_amount - max number of message parts per single frame
:max_payload_size - max bytesize of single message
42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/libwebsocket/frame.rb', line 42 def initialize( = '') if .is_a?(Hash) .each {|k,v| instance_variable_set("@#{k}",v) } else @buffer = end @buffer ||= '' @version ||= 'draft-ietf-hybi-10' @fragments = [] @max_fragments_amount ||= 128 @max_payload_size ||= 65536 end |
Instance Attribute Details
#buffer ⇒ Object
Returns the value of attribute buffer.
30 31 32 |
# File 'lib/libwebsocket/frame.rb', line 30 def buffer @buffer end |
#fin ⇒ Object
Returns the value of attribute fin.
30 31 32 |
# File 'lib/libwebsocket/frame.rb', line 30 def fin @fin end |
#masked ⇒ Object
Returns the value of attribute masked.
30 31 32 |
# File 'lib/libwebsocket/frame.rb', line 30 def masked @masked end |
#max_fragments_amount ⇒ Object
Returns the value of attribute max_fragments_amount.
30 31 32 |
# File 'lib/libwebsocket/frame.rb', line 30 def max_fragments_amount @max_fragments_amount end |
#max_payload_size ⇒ Object
Returns the value of attribute max_payload_size.
30 31 32 |
# File 'lib/libwebsocket/frame.rb', line 30 def max_payload_size @max_payload_size end |
#opcode ⇒ Object
Returns the value of attribute opcode.
30 31 32 |
# File 'lib/libwebsocket/frame.rb', line 30 def opcode @opcode end |
#rsv ⇒ Object
Returns the value of attribute rsv.
30 31 32 |
# File 'lib/libwebsocket/frame.rb', line 30 def rsv @rsv end |
#version ⇒ Object
Returns the value of attribute version.
30 31 32 |
# File 'lib/libwebsocket/frame.rb', line 30 def version @version end |
Instance Method Details
#append(string = nil) ⇒ Object
Append a frame chunk.
60 61 62 63 64 65 66 67 |
# File 'lib/libwebsocket/frame.rb', line 60 def append(string = nil) return unless string.is_a?(String) string.force_encoding("ASCII-8BIT") if string.respond_to?(:force_encoding) self.buffer += string return self end |
#binary? ⇒ Boolean
Check if frame is of binary type.
89 |
# File 'lib/libwebsocket/frame.rb', line 89 def binary?; opcode == TYPES[:binary]; end |
#close? ⇒ Boolean
Check if frame is of close type.
87 |
# File 'lib/libwebsocket/frame.rb', line 87 def close?; opcode == TYPES[:close]; end |
#next ⇒ Object
Return the next frame.
73 74 75 76 77 78 79 |
# File 'lib/libwebsocket/frame.rb', line 73 def next bytes = self.next_bytes return unless bytes bytes.force_encoding('UTF-8') if bytes.respond_to?(:force_encoding) return bytes end |
#next_bytes ⇒ Object
Return the next message as a UTF-8 encoded string.
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/libwebsocket/frame.rb', line 92 def next_bytes if ['draft-hixie-75', 'draft-ietf-hybi-00'].include? self.version if self.buffer.slice!(/\A\xff\x00/m) self.opcode = TYPES[:close] return '' end return unless self.buffer.slice!(/^[^\x00]*\x00(.*?)\xff/m) return $1 end return unless self.buffer.length >= 2 while self.buffer.length > 0 hdr = self.buffer[0..0] bits = hdr.unpack("B*").first.split(//) self.fin = bits[0] self.rsv = bits[1..3] opcode = hdr.unpack('C').first & 0b00001111 offset = 1 # FIN,RSV[1-3],OPCODE payload_len = buffer[1..1].unpack('C').first self.masked = (payload_len & 0b10000000) >> 7 offset += 1 # + MASKED,PAYLOAD_LEN payload_len = payload_len & 0b01111111 if payload_len == 126 return unless self.buffer.length >= offset + 2 payload_len = self.buffer[offset..offset+2].unpack('n').first offset += 2 elsif payload_len > 126 return unless self.buffer.length >= offset + 4 bits = self.buffer[offset..offset+7].unpack('B*').first bits.gsub!(/^./,'0') # Most significant bit must be 0. bits = bits[32..-1] # No idea how to unpack 64-bit unsigned integer with big-endian byte order payload_len = Array(bits).pack("B*").unpack("N").first offset += 8 end if payload_len > self.max_payload_size self.buffer = '' raise Error::MessageTooBig.new("Payload is too big. Deny big message (#{payload_len}) or increase max_payload_size (#{self.max_payload_size})") end mask = '' if self.masked == 1 return unless self.buffer.length >= offset + 4 mask = self.buffer[offset..offset+4] offset += 4 end return if self.buffer.length < offset + payload_len payload = self.buffer[offset..offset+payload_len-1] payload = self.mask(payload, mask) if self.masked == 1 self.buffer[0..offset+payload_len-1] = '' # Inject control frame if !@fragments.empty? && (opcode & 0b1000 != 0) self.opcode = opcode return payload end if self.fin != '0' if @fragments.empty? self.opcode = opcode else self.opcode = @fragments.shift end payload = (@fragments + Array(payload)).join @fragments = [] return payload else # Remember first fragment opcode @fragments.push(opcode) if @fragments.empty? @fragments.push(payload) raise Error::PolicyViolation.new("Too many fragments") if @fragments.size > self.max_fragments_amount end end return end |
#ping? ⇒ Boolean
Check if frame is a ping request.
85 |
# File 'lib/libwebsocket/frame.rb', line 85 def ping?; opcode == TYPES[:ping]; end |
#pong? ⇒ Boolean
Check if frame is a pong response.
86 |
# File 'lib/libwebsocket/frame.rb', line 86 def pong?; opcode == TYPES[:pong]; end |
#text? ⇒ Boolean
Check if frame is of text type.
88 |
# File 'lib/libwebsocket/frame.rb', line 88 def text?; opcode == TYPES[:text]; end |