Protocol::HPACK SourceProtocolHPACKCompressor

class Compressor

Responsible for encoding header key-value pairs using HPACK algorithm.

Definitions

def write_integer(value, bits)

Encodes provided value via integer representation.

  • http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-5.1

If I < 2^N - 1, encode I on N bits Else encode 2^N - 1 on N bits I = I - (2^N - 1) While I >= 128 Encode (I % 128 + 128) on 8 bits I = I / 128 encode (I) on 8 bits

Implementation

def write_integer(value, bits)
	limit = 2**bits - 1
	
	return write_bytes([value].pack('C')) if value < limit
	
	bytes = []
	bytes.push(limit) unless bits.zero?
	
	value -= limit
	while value >= 128
		bytes.push((value % 128) + 128)
		value /= 128
	end
	
	bytes.push(value)
	
	write_bytes(bytes.pack('C*'))
end

def write_string(string, huffman = self.huffman)

Encodes provided value via string literal representation.

  • http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-5.2
  • The string length, defined as the number of bytes needed to store its UTF-8 representation, is represented as an integer with a seven bits prefix. If the string length is strictly less than 127, it is represented as one byte.
  • If the bit 7 of the first byte is 1, the string value is represented as a list of Huffman encoded octets (padded with bit 1's until next octet boundary).
  • If the bit 7 of the first byte is 0, the string value is represented as a list of UTF-8 encoded octets.

+@options [:huffman]+ controls whether to use Huffman encoding: :never Do not use Huffman encoding :always Always use Huffman encoding :shorter Use Huffman when the result is strictly shorter

Implementation

def write_string(string, huffman = self.huffman)
	if huffman != :never
		encoded = Huffman.new.encode(string)
		
		if huffman == :shorter and encoded.bytesize >= string.bytesize
			encoded = nil
		end
	end
	
	if encoded
		first = @buffer.bytesize
		
		write_integer(encoded.bytesize, 7)
		write_bytes(encoded.b)
		
		@buffer.setbyte(first, @buffer.getbyte(first).ord | 0x80)
	else
		write_integer(string.bytesize, 7)
		write_bytes(string.b)
	end
end

def write_header(command)

Encodes header command with appropriate header representation.

Implementation

def write_header(command)
	representation = HEADER_REPRESENTATION[command[:type]]
	
	first = @buffer.bytesize
	
	case command[:type]
	when :indexed
		write_integer(command[:name] + 1, representation[:prefix])
	when :change_table_size
		write_integer(command[:value], representation[:prefix])
	else
		if command[:name].is_a? Integer
			write_integer(command[:name] + 1, representation[:prefix])
		else
			write_integer(0, representation[:prefix])
			write_string(command[:name])
		end
		
		write_string(command[:value])
	end

	# set header representation pattern on first byte
	@buffer.setbyte(first, @buffer.getbyte(first) | representation[:pattern])
end

def encode(headers, table_size = @table_size_limit)

Encodes provided list of HTTP headers.

Implementation

def encode(headers, table_size = @table_size_limit)
	if table_size and table_size != @context.table_size
		command = @context.change_table_size(table_size)
		
		write_header(command)
	end
	
	commands = @context.encode(headers)
	
	commands.each do |command|
		write_header(command)
	end
	
	return @buffer
end