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 tools.ietf.org/html/rfc7230#section-4.1 for more details on the chunked transfer encoding.

Constant Summary collapse

CRLF =
"\r\n"
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.



22
23
24
25
26
27
28
29
30
# File 'lib/protocol/http1/body/chunked.rb', line 22

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

Instance Attribute Details

#countObject (readonly)

Returns the value of attribute count.



33
34
35
# File 'lib/protocol/http1/body/chunked.rb', line 33

def count
  @count
end

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



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

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)



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

attr :count

Instance Method Details

#as_jsonObject



123
124
125
126
127
128
129
# File 'lib/protocol/http1/body/chunked.rb', line 123

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.



51
52
53
54
55
56
57
58
59
60
61
# File 'lib/protocol/http1/body/chunked.rb', line 51

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

#empty?Boolean



44
45
46
# File 'lib/protocol/http1/body/chunked.rb', line 44

def empty?
	@connection.nil?
end

#inspectObject



118
119
120
# File 'lib/protocol/http1/body/chunked.rb', line 118

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

#lengthObject



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

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 tools.ietf.org/html/rfc7230#section-4.1.3



71
72
73
74
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
# File 'lib/protocol/http1/body/chunked.rb', line 71

def read
	if !@finished
		if @connection
			length, _extensions = @connection.read_line.split(";", 2)
			
			unless length =~ VALID_CHUNK_LENGTH
				raise BadRequest, "Invalid chunk length: #{length.inspect}"
			end
			
			# It is possible this line contains chunk extension, so we use `to_i` to only consider the initial integral part:
			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
			
			# Read trailing CRLF:
			chunk = @connection.read(length + 2)
			
			if chunk.bytesize == length + 2
				# ...and chomp it off:
				chunk.chomp!(CRLF)
				
				@length += length
				@count += 1
				
				return chunk
			else
				# The connection has been closed before we have read the requested length:
				@connection.close_read
				@connection = nil
			end
		end
		
		# If the connection has been closed before we have read the final chunk, raise an error:
		raise EOFError, "connection closed before expected length was read!"
	end
end