Class: Protocol::HTTP1::Body::Chunked

Inherits:
HTTP::Body::Readable
  • Object
show all
Defined in:
lib/protocol/http1/body/chunked.rb

Overview

Represents a chunked body, which is a series of chunks, each with a length prefix.

See https://tools.ietf.org/html/rfc7230#section-4.1 for more details on the chunked transfer encoding.

Constant Summary collapse

CRLF =
"\r\n"
BLOCK_SIZE =

The maximum amount of body data returned by a single read.

1024 * 64
VALID_CHUNK_LENGTH =
/\A[0-9a-fA-F]+\z/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection, headers) ⇒ Chunked

Initialize the chunked body.



25
26
27
28
29
30
31
32
33
34
# File 'lib/protocol/http1/body/chunked.rb', line 25

def initialize(connection, headers)
	@connection = connection
	@finished = false
	
	@headers = headers
	
	@length = 0
	@count = 0
	@remaining = nil
end

Instance Attribute Details

#countObject (readonly)

Returns the value of attribute count.



37
38
39
# File 'lib/protocol/http1/body/chunked.rb', line 37

def count
  @count
end

#the length of the body if known.(lengthofthebody) ⇒ Object (readonly)



40
41
42
43
44
45
# File 'lib/protocol/http1/body/chunked.rb', line 40

def length
	# We only know the length once we've read the final chunk:
	if @finished
		@length
	end
end

#the number of chunks read so far.(numberofchunksreadsofar.) ⇒ Object (readonly)



37
# File 'lib/protocol/http1/body/chunked.rb', line 37

attr :count

Instance Method Details

#as_jsonObject



140
141
142
143
144
145
146
# File 'lib/protocol/http1/body/chunked.rb', line 140

def as_json(...)
	super.merge(
		count: @count,
		finished: @finished,
		state: @connection ? "open" : "closed"
	)
end

#close(error = nil) ⇒ Object

Close the connection and mark the body as finished.



55
56
57
58
59
60
61
62
63
64
65
# File 'lib/protocol/http1/body/chunked.rb', line 55

def close(error = nil)
	if connection = @connection
		@connection = nil
		
		unless @finished
			connection.close_read
		end
	end
	
	super
end

#empty?Boolean

Returns:

  • (Boolean)


48
49
50
# File 'lib/protocol/http1/body/chunked.rb', line 48

def empty?
	@connection.nil?
end

#inspectObject



135
136
137
# File 'lib/protocol/http1/body/chunked.rb', line 135

def inspect
	"\#<#{self.class} #{@length} bytes read in #{@count} chunks, #{@finished ? 'finished' : 'reading'}>"
end

#lengthObject



40
41
42
43
44
45
# File 'lib/protocol/http1/body/chunked.rb', line 40

def length
	# We only know the length once we've read the final chunk:
	if @finished
		@length
	end
end

#readObject

Read a chunk of data.

Follows the procedure outlined in https://tools.ietf.org/html/rfc7230#section-4.1.3



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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
# File 'lib/protocol/http1/body/chunked.rb', line 75

def read
	while !@finished
		unless @connection
			raise EOFError, "Connection closed before expected length was read!"
		end
		
		if @remaining
			if @remaining > 0
				chunk = @connection.readpartial([@remaining, BLOCK_SIZE].min)
				@remaining -= chunk.bytesize
				@length += chunk.bytesize
				
				return chunk
			end
			
			terminator = @connection.read(CRLF.bytesize)
			
			unless terminator&.bytesize == CRLF.bytesize
				raise EOFError, "Connection closed before expected length was read!"
			end
			
			unless terminator == CRLF
				raise BadRequest, "Invalid chunk terminator: #{terminator.inspect}"
			end
			
			@remaining = nil
			@count += 1
		else
			length, _extensions = @connection.read_line.split(";", 2)
			
			unless length =~ VALID_CHUNK_LENGTH
				raise BadRequest, "Invalid chunk length: #{length.inspect}"
			end
			
			length = Integer(length, 16)
			
			if length == 0
				read_trailer
				
				# The final chunk has been read and the connection is now closed:
				@connection.receive_end_stream!
				@connection = nil
				@finished = true
				
				return nil
			end
			
			@remaining = length
		end
	end
rescue EOFError
	if connection = @connection
		@connection = nil
		connection.close_read
	end
	
	raise EOFError, "Connection closed before expected length was read!"
end