Skip to content

Commit dd3b233

Browse files
committed
Improved documentatio for Protocol::HTTP::Body.
1 parent 437268a commit dd3b233

File tree

16 files changed

+390
-42
lines changed

16 files changed

+390
-42
lines changed

lib/protocol/http/body.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2024, by Samuel Williams.
5+
6+
require_relative "body/readable"
7+
require_relative "body/writable"
8+
require_relative "body/wrapper"
9+
10+
module Protocol
11+
module HTTP
12+
# @namespace
13+
module Body
14+
end
15+
end
16+
end

lib/protocol/http/body/buffered.rb

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,18 @@ def self.read(body)
4343
self.new(chunks)
4444
end
4545

46+
# Initialize the buffered body with some chunks.
47+
#
48+
# @parameter chunks [Array(String)] the chunks to buffer.
49+
# @parameter length [Integer] the length of the body, if known.
4650
def initialize(chunks = [], length = nil)
4751
@chunks = chunks
4852
@length = length
4953

5054
@index = 0
5155
end
5256

57+
# @attribute [Array(String)] chunks the buffered chunks.
5358
attr :chunks
5459

5560
# 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.
@@ -59,36 +64,48 @@ def buffered
5964
self.class.new(@chunks)
6065
end
6166

67+
# Finish the body, this is a no-op.
68+
#
69+
# @returns [Buffered] self.
6270
def finish
6371
self
6472
end
6573

66-
# Ensure that future reads return nil, but allow for rewinding.
74+
# Ensure that future reads return `nil`, but allow for rewinding.
75+
#
76+
# @parameter error [Exception | Nil] the error that caused the body to be closed, if any.
6777
def close(error = nil)
6878
@index = @chunks.length
6979

7080
return nil
7181
end
7282

83+
# Clear the buffered chunks.
7384
def clear
7485
@chunks = []
7586
@length = 0
7687
@index = 0
7788
end
7889

90+
# The length of the body. Will compute and cache the length of the body, if it was not provided.
7991
def length
8092
@length ||= @chunks.inject(0) {|sum, chunk| sum + chunk.bytesize}
8193
end
8294

95+
# @returns [Boolean] if the body is empty.
8396
def empty?
8497
@index >= @chunks.length
8598
end
8699

87-
# A buffered response is always ready.
100+
# Whether the body is ready to be read.
101+
# @returns [Boolean] a buffered response is always ready.
88102
def ready?
89103
true
90104
end
91105

106+
# Read the next chunk from the buffered body.
107+
#
108+
# @returns [String | Nil] the next chunk or nil if there are no more chunks.
92109
def read
93110
return nil unless @chunks
94111

@@ -99,23 +116,30 @@ def read
99116
end
100117
end
101118

119+
# Discard the body. Invokes {#close}.
102120
def discard
103121
# It's safe to call close here because there is no underlying stream to close:
104122
self.close
105123
end
106124

125+
# Write a chunk to the buffered body.
107126
def write(chunk)
108127
@chunks << chunk
109128
end
110129

130+
# Close the body for writing. This is a no-op.
111131
def close_write(error)
112132
# Nothing to do.
113133
end
114134

135+
# Whether the body can be rewound.
136+
#
137+
# @returns [Boolean] if the body has chunks.
115138
def rewindable?
116139
@chunks != nil
117140
end
118141

142+
# Rewind the body to the beginning, causing a subsequent read to return the first chunk.
119143
def rewind
120144
return false unless @chunks
121145

@@ -124,6 +148,9 @@ def rewind
124148
return true
125149
end
126150

151+
# Inspect the buffered body.
152+
#
153+
# @returns [String] a string representation of the buffered body.
127154
def inspect
128155
if @chunks
129156
"\#<#{self.class} #{@chunks.size} chunks, #{self.length} bytes>"

lib/protocol/http/body/completable.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ module HTTP
1010
module Body
1111
# Invokes a callback once the body has completed, either successfully or due to an error.
1212
class Completable < Wrapper
13+
# Wrap a message body with a callback. If the body is empty, the callback is invoked immediately.
14+
#
15+
# @parameter message [Request | Response] the message body.
16+
# @parameter block [Proc] the callback to invoke when the body is closed.
1317
def self.wrap(message, &block)
1418
if body = message&.body and !body.empty?
1519
message.body = self.new(message.body, block)
@@ -18,20 +22,29 @@ def self.wrap(message, &block)
1822
end
1923
end
2024

25+
# Initialize the completable body with a callback.
26+
#
27+
# @parameter body [Readable] the body to wrap.
28+
# @parameter callback [Proc] the callback to invoke when the body is closed.
2129
def initialize(body, callback)
2230
super(body)
2331

2432
@callback = callback
2533
end
2634

35+
# @returns [Boolean] completable bodies are not rewindable.
2736
def rewindable?
2837
false
2938
end
3039

40+
# Rewind the body, is not supported.
3141
def rewind
3242
false
3343
end
3444

45+
# Close the body and invoke the callback. If an error is given, it is passed to the callback.
46+
#
47+
# The calback is only invoked once, and before `super` is invoked.
3548
def close(error = nil)
3649
if @callback
3750
@callback.call(error)

lib/protocol/http/body/deflate.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,27 @@
1010
module Protocol
1111
module HTTP
1212
module Body
13+
# A body which compresses or decompresses the contents using the DEFLATE or GZIP algorithm.
1314
class ZStream < Wrapper
15+
# The default compression level.
1416
DEFAULT_LEVEL = 7
1517

18+
# The DEFLATE window size.
1619
DEFLATE = -Zlib::MAX_WBITS
20+
21+
# The GZIP window size.
1722
GZIP = Zlib::MAX_WBITS | 16
1823

24+
# The supported encodings.
1925
ENCODINGS = {
2026
"deflate" => DEFLATE,
2127
"gzip" => GZIP,
2228
}
2329

30+
# Initialize the body with the given stream.
31+
#
32+
# @parameter body [Readable] the body to wrap.
33+
# @parameter stream [Zlib::Deflate | Zlib::Inflate] the stream to use for compression or decompression.
2434
def initialize(body, stream)
2535
super(body)
2636

@@ -30,6 +40,9 @@ def initialize(body, stream)
3040
@output_length = 0
3141
end
3242

43+
# Close the stream.
44+
#
45+
# @parameter error [Exception | Nil] the error that caused the stream to be closed.
3346
def close(error = nil)
3447
if stream = @stream
3548
@stream = nil
@@ -39,14 +52,21 @@ def close(error = nil)
3952
super
4053
end
4154

55+
# The length of the output, if known. Generally, this is not known due to the nature of compression.
4256
def length
4357
# We don't know the length of the output until after it's been compressed.
4458
nil
4559
end
4660

61+
# @attribute [Integer] input_length the total number of bytes read from the input.
4762
attr :input_length
63+
64+
# @attribute [Integer] output_length the total number of bytes written to the output.
4865
attr :output_length
4966

67+
# The compression ratio, according to the input and output lengths.
68+
#
69+
# @returns [Float] the compression ratio, e.g. 0.5 for 50% compression.
5070
def ratio
5171
if @input_length != 0
5272
@output_length.to_f / @input_length.to_f
@@ -55,16 +75,29 @@ def ratio
5575
end
5676
end
5777

78+
# Inspect the body, including the compression ratio.
79+
#
80+
# @returns [String] a string representation of the body.
5881
def inspect
5982
"#{super} | \#<#{self.class} #{(ratio*100).round(2)}%>"
6083
end
6184
end
6285

86+
# A body which compresses the contents using the DEFLATE or GZIP algorithm.
6387
class Deflate < ZStream
88+
# Create a new body which compresses the given body using the GZIP algorithm by default.
89+
#
90+
# @parameter body [Readable] the body to wrap.
91+
# @parameter window_size [Integer] the window size to use for compression.
92+
# @parameter level [Integer] the compression level to use.
93+
# @returns [Deflate] the wrapped body.
6494
def self.for(body, window_size = GZIP, level = DEFAULT_LEVEL)
6595
self.new(body, Zlib::Deflate.new(level, window_size))
6696
end
6797

98+
# Read a chunk from the underlying body and compress it. If the body is finished, the stream is flushed and finished, and the remaining data is returned.
99+
#
100+
# @returns [String | Nil] the compressed chunk or `nil` if the stream is closed.
68101
def read
69102
return if @stream.finished?
70103

lib/protocol/http/body/digestable.rb

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,36 @@ module HTTP
1212
module Body
1313
# Invokes a callback once the body has finished reading.
1414
class Digestable < Wrapper
15+
# Wrap a message body with a callback. If the body is empty, the callback is not invoked, as there is no data to digest.
16+
#
17+
# @parameter message [Request | Response] the message body.
18+
# @parameter digest [Digest] the digest to use.
19+
# @parameter block [Proc] the callback to invoke when the body is closed.
1520
def self.wrap(message, digest = Digest::SHA256.new, &block)
1621
if body = message&.body and !body.empty?
1722
message.body = self.new(message.body, digest, block)
1823
end
1924
end
2025

26+
# Initialize the digestable body with a callback.
27+
#
28+
# @parameter body [Readable] the body to wrap.
29+
# @parameter digest [Digest] the digest to use.
2130
# @parameter callback [Block] The callback is invoked when the digest is complete.
2231
def initialize(body, digest = Digest::SHA256.new, callback = nil)
2332
super(body)
2433

2534
@digest = digest
2635
@callback = callback
2736
end
37+
38+
# @attribute [Digest] digest the digest object.
39+
attr :digest
2840

29-
def digest
30-
@digest
31-
end
32-
41+
# Generate an appropriate ETag for the digest, assuming it is complete. If you call this method before the body is fully read, the ETag will be incorrect.
42+
#
43+
# @parameter weak [Boolean] If true, the ETag is marked as weak.
44+
# @returns [String] the ETag.
3345
def etag(weak: false)
3446
if weak
3547
"W/\"#{digest.hexdigest}\""
@@ -38,6 +50,9 @@ def etag(weak: false)
3850
end
3951
end
4052

53+
# Read the body and update the digest. When the body is fully read, the callback is invoked with `self` as the argument.
54+
#
55+
# @returns [String | Nil] the next chunk of data, or nil if the body is fully read.
4156
def read
4257
if chunk = super
4358
@digest.update(chunk)

0 commit comments

Comments
 (0)