class Frame
Represents the base class for all HTTP/2 frames. This class provides common functionality for frame parsing, serialization, and manipulation according to RFC 7540.
Definitions
VALID_STREAM_ID = 0..0x7fffffff
Stream Identifier cannot be bigger than this: https://http2.github.stream/http2-spec/#rfc.section.4.1
VALID_LENGTH = 0..0xffffff
The absolute maximum bounds for the length field:
LENGTH_HISHIFT = 16
Used for generating 24-bit frame length:
TYPE = nil
The base class does not have any specific type index:
def initialize(stream_id = 0, flags = 0, type = self.class::TYPE, length = nil, payload = nil)
Signature
-
parameter
length
Integer
the length of the payload, or nil if the header has not been read yet.
Implementation
def initialize(stream_id = 0, flags = 0, type = self.class::TYPE, length = nil, payload = nil)
@stream_id = stream_id
@flags = flags
@type = type
@length = length
@payload = payload
end
def valid_type?
Check if the frame has a valid type identifier.
Signature
-
returns
Boolean
True if the frame type is valid.
Implementation
def valid_type?
@type == self.class::TYPE
end
def <=>(other)
Compare frames based on their essential properties.
Signature
-
parameter
other
Frame
The frame to compare with.
-
returns
Integer
-1, 0, or 1 for comparison result.
Implementation
def <=> other
to_ary <=> other.to_ary
end
def to_ary
Convert frame to array representation for comparison.
Signature
-
returns
Array
Frame properties as an array.
Implementation
def to_ary
[@length, @type, @flags, @stream_id, @payload]
end
def unpack
Unpack the frame payload data.
Signature
-
returns
String
The frame payload.
Implementation
def unpack
@payload
end
def pack(payload, maximum_size: nil)
Pack payload data into the frame.
Signature
-
parameter
payload
String
The payload data to pack.
-
parameter
maximum_size
Integer | Nil
Optional maximum payload size.
-
raises
ProtocolError
If payload exceeds maximum size.
Implementation
def pack(payload, maximum_size: nil)
@payload = payload
@length = payload.bytesize
if maximum_size and @length > maximum_size
raise ProtocolError, "Frame length bigger than maximum allowed: #{@length} > #{maximum_size}"
end
end
def set_flags(mask)
Set specific flags on the frame.
Signature
-
parameter
mask
Integer
The flag bits to set.
Implementation
def set_flags(mask)
@flags |= mask
end
def clear_flags(mask)
Clear specific flags on the frame.
Signature
-
parameter
mask
Integer
The flag bits to clear.
Implementation
def clear_flags(mask)
@flags &= ~mask
end
def flag_set?(mask)
Check if specific flags are set on the frame.
Signature
-
parameter
mask
Integer
The flag bits to check.
-
returns
Boolean
True if any of the flags are set.
Implementation
def flag_set?(mask)
@flags & mask != 0
end
def connection?
Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any frame addressed to stream ID = 0.
Implementation
def connection?
@stream_id.zero?
end
def header
Generates common 9-byte frame header.
- http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-4.1
Implementation
def header
unless VALID_LENGTH.include? @length
raise ProtocolError, "Invalid frame length: #{@length.inspect}"
end
unless VALID_STREAM_ID.include? @stream_id
raise ProtocolError, "Invalid stream identifier: #{@stream_id.inspect}"
end
[
# These are guaranteed correct due to the length check above.
@length >> LENGTH_HISHIFT,
@length & LENGTH_LOMASK,
@type,
@flags,
@stream_id
].pack(HEADER_FORMAT)
end
def self.parse_header(buffer)
Decodes common 9-byte header.
Signature
-
parameter
buffer
String
Implementation
def self.parse_header(buffer)
length_hi, length_lo, type, flags, stream_id = buffer.unpack(HEADER_FORMAT)
length = (length_hi << LENGTH_HISHIFT) | length_lo
stream_id = stream_id & STREAM_ID_MASK
# puts "parse_header: length=#{length} type=#{type} flags=#{flags} stream_id=#{stream_id}"
return length, type, flags, stream_id
end
def read_header(stream)
Read the frame header from a stream.
Signature
-
parameter
stream
IO
The stream to read from.
-
raises
EOFError
If the header cannot be read completely.
Implementation
def read_header(stream)
if buffer = stream.read(9) and buffer.bytesize == 9
@length, @type, @flags, @stream_id = Frame.parse_header(buffer)
# puts "read_header: #{@length} #{@type} #{@flags} #{@stream_id}"
else
raise EOFError, "Could not read frame header!"
end
end
def read_payload(stream)
Read the frame payload from a stream.
Signature
-
parameter
stream
IO
The stream to read from.
-
raises
EOFError
If the payload cannot be read completely.
Implementation
def read_payload(stream)
if payload = stream.read(@length) and payload.bytesize == @length
@payload = payload
else
raise EOFError, "Could not read frame payload!"
end
end
def read(stream, maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)
Read the complete frame (header and payload) from a stream.
Signature
-
parameter
stream
IO
The stream to read from.
-
parameter
maximum_frame_size
Integer
The maximum allowed frame size.
-
raises
FrameSizeError
If the frame exceeds the maximum size.
-
returns
Frame
Self for method chaining.
Implementation
def read(stream, maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)
read_header(stream) unless @length
if @length > maximum_frame_size
raise FrameSizeError, "#{self.class} (type=#{@type}) frame length #{@length} exceeds maximum frame size #{maximum_frame_size}!"
end
read_payload(stream)
end
def write_header(stream)
Write the frame header to a stream.
Signature
-
parameter
stream
IO
The stream to write to.
Implementation
def write_header(stream)
stream.write self.header
end
def write_payload(stream)
Write the frame payload to a stream.
Signature
-
parameter
stream
IO
The stream to write to.
Implementation
def write_payload(stream)
stream.write(@payload) if @payload
end
def write(stream)
Write the complete frame (header and payload) to a stream.
Signature
-
parameter
stream
IO
The stream to write to.
-
raises
ProtocolError
If frame validation fails.
Implementation
def write(stream)
# Validate the payload size:
if @payload.nil?
if @length != 0
raise ProtocolError, "Invalid frame length: #{@length} != 0"
end
else
if @length != @payload.bytesize
raise ProtocolError, "Invalid payload size: #{@length} != #{@payload.bytesize}"
end
end
self.write_header(stream)
self.write_payload(stream)
end
def apply(connection)
Apply the frame to a connection for processing.
Signature
-
parameter
connection
Connection
The connection to apply the frame to.
Implementation
def apply(connection)
connection.receive_frame(self)
end
def inspect
Provide a readable representation of the frame for debugging.
Signature
-
returns
String
A formatted string representation of the frame.
Implementation
def inspect
"\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} payload=#{self.unpack}>"
end