File: buffered.rb

package info (click to toggle)
ruby-protocol-http 0.55.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 840 kB
  • sloc: ruby: 6,904; makefile: 4
file content (165 lines) | stat: -rw-r--r-- 4,150 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2019-2025, by Samuel Williams.
# Copyright, 2020, by Bryan Powell.
# Copyright, 2025, by William T. Nelson.

require_relative "readable"

module Protocol
	module HTTP
		module Body
			# A body which buffers all its contents.
			class Buffered < Readable
				# Tries to wrap an object in a {Buffered} instance.
				#
				# For compatibility, also accepts anything that behaves like an `Array(String)`.
				#
				# @parameter body [String | Array(String) | Readable | nil] the body to wrap.
				# @returns [Readable | nil] the wrapped body or nil if nil was given.
				def self.wrap(object)
					if object.is_a?(Readable)
						return object
					elsif object.is_a?(Array)
						return self.new(object)
					elsif object.is_a?(String)
						return self.new([object])
					elsif object
						return self.read(object)
					end
				end
				
				# Read the entire body into a buffered representation.
				#
				# @parameter body [Readable] the body to read.
				# @returns [Buffered] the buffered body.
				def self.read(body)
					chunks = []
					
					body.each do |chunk|
						chunks << chunk
					end
					
					self.new(chunks)
				end
				
				# Initialize the buffered body with some chunks.
				#
				# @parameter chunks [Array(String)] the chunks to buffer.
				# @parameter length [Integer] the length of the body, if known.
				def initialize(chunks = [], length = nil)
					@chunks = chunks
					@length = length
					
					@index = 0
				end
				
				# @attribute [Array(String)] chunks the buffered chunks.
				attr :chunks
				
				# A rewindable body wraps some other body. Convert it to a buffered body. The buffered body will share the same chunks as the rewindable body.
				#
				# @returns [Buffered] the buffered body.
				def buffered
					self.class.new(@chunks)
				end
				
				# Finish the body, this is a no-op.
				#
				# @returns [Buffered] self.
				def finish
					self
				end
				
				# Ensure that future reads return `nil`, but allow for rewinding.
				#
				# @parameter error [Exception | Nil] the error that caused the body to be closed, if any.
				def close(error = nil)
					@index = @chunks.length
					
					return nil
				end
				
				# Clear the buffered chunks.
				def clear
					@chunks = []
					@length = 0
					@index = 0
				end
				
				# The length of the body. Will compute and cache the length of the body, if it was not provided.
				def length
					@length ||= @chunks.inject(0) {|sum, chunk| sum + chunk.bytesize}
				end
				
				# @returns [Boolean] if the body is empty.
				def empty?
					@index >= @chunks.length
				end
				
				# Whether the body is ready to be read.
				# @returns [Boolean] a buffered response is always ready.
				def ready?
					true
				end
				
				# Read the next chunk from the buffered body.
				#
				# @returns [String | Nil] the next chunk or nil if there are no more chunks.
				def read
					return nil unless @chunks
					
					if chunk = @chunks[@index]
						@index += 1
						
						return chunk.dup
					end
				end
				
				# Discard the body. Invokes {#close}.
				def discard
					# It's safe to call close here because there is no underlying stream to close:
					self.close
				end
				
				# Write a chunk to the buffered body.
				def write(chunk)
					@chunks << chunk
				end
				
				# Close the body for writing. This is a no-op.
				def close_write(error)
					# Nothing to do.
				end
				
				# Whether the body can be rewound.
				#
				# @returns [Boolean] if the body has chunks.
				def rewindable?
					@chunks != nil
				end
				
				# Rewind the body to the beginning, causing a subsequent read to return the first chunk.
				def rewind
					return false unless @chunks
					
					@index = 0
					
					return true
				end
				
				# Inspect the buffered body.
				#
				# @returns [String] a string representation of the buffered body.
				def inspect
					if @chunks and @chunks.size > 0
						"#<#{self.class} #{@index}/#{@chunks.size} chunks, #{self.length} bytes>"
					else
						"#<#{self.class} empty>"
					end
				end
			end
		end
	end
end