httpx/lib/httpx/transcoder/chunker.rb
HoneyryderChuck bbf257477b Removing HTTPX::Registry and its usage internally
These internnal registries were a bit magical to use, difficult to
debug, not thread-safe, and overall a nuisance when it came to type
checking. So long.
2023-04-27 22:49:20 +01:00

116 lines
2.7 KiB
Ruby

# frozen_string_literal: true
require "forwardable"
module HTTPX::Transcoder
module Chunker
class Error < HTTPX::Error; end
CRLF = "\r\n".b
class Encoder
extend Forwardable
def initialize(body)
@raw = body
end
def each
return enum_for(__method__) unless block_given?
@raw.each do |chunk|
yield "#{chunk.bytesize.to_s(16)}#{CRLF}#{chunk}#{CRLF}"
end
yield "0#{CRLF}"
end
def respond_to_missing?(meth, *args)
@raw.respond_to?(meth, *args) || super
end
end
class Decoder
extend Forwardable
def_delegator :@buffer, :empty?
def_delegator :@buffer, :<<
def_delegator :@buffer, :clear
def initialize(buffer, trailers = false)
@buffer = buffer
@chunk_buffer = "".b
@finished = false
@state = :length
@trailers = trailers
end
def to_s
@buffer
end
def each
loop do
case @state
when :length
index = @buffer.index(CRLF)
return unless index && index.positive?
# Read hex-length
hexlen = @buffer.byteslice(0, index)
@buffer = @buffer.byteslice(index..-1) || "".b
hexlen[/\h/] || raise(Error, "wrong chunk size line: #{hexlen}")
@chunk_length = hexlen.hex
# check if is last chunk
@finished = @chunk_length.zero?
nextstate(:crlf)
when :crlf
crlf_size = @finished && !@trailers ? 4 : 2
# consume CRLF
return if @buffer.bytesize < crlf_size
raise Error, "wrong chunked encoding format" unless @buffer.start_with?(CRLF * (crlf_size / 2))
@buffer = @buffer.byteslice(crlf_size..-1)
if @chunk_length.nil?
nextstate(:length)
else
return if @finished
nextstate(:data)
end
when :data
chunk = @buffer.byteslice(0, @chunk_length)
@buffer = @buffer.byteslice(@chunk_length..-1) || "".b
@chunk_buffer << chunk
@chunk_length -= chunk.bytesize
if @chunk_length.zero?
yield @chunk_buffer unless @chunk_buffer.empty?
@chunk_buffer.clear
@chunk_length = nil
nextstate(:crlf)
end
end
break if @buffer.empty?
end
end
def finished?
@finished
end
private
def nextstate(state)
@state = state
end
end
module_function
def encode(chunks)
Encoder.new(chunks)
end
end
end