protocol-http 0.45.0 → 0.47.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/protocol/http/accept_encoding.rb +15 -3
  4. data/lib/protocol/http/body/buffered.rb +29 -2
  5. data/lib/protocol/http/body/completable.rb +13 -0
  6. data/lib/protocol/http/body/deflate.rb +33 -0
  7. data/lib/protocol/http/body/digestable.rb +19 -4
  8. data/lib/protocol/http/body/file.rb +37 -1
  9. data/lib/protocol/http/body/head.rb +8 -0
  10. data/lib/protocol/http/body/inflate.rb +10 -2
  11. data/lib/protocol/http/body/readable.rb +32 -11
  12. data/lib/protocol/http/body/reader.rb +17 -0
  13. data/lib/protocol/http/body/rewindable.rb +19 -1
  14. data/lib/protocol/http/body/stream.rb +34 -6
  15. data/lib/protocol/http/body/streamable.rb +46 -5
  16. data/lib/protocol/http/body/wrapper.rb +25 -3
  17. data/lib/protocol/http/body/writable.rb +48 -7
  18. data/lib/protocol/http/body.rb +16 -0
  19. data/lib/protocol/http/content_encoding.rb +13 -3
  20. data/lib/protocol/http/cookie.rb +23 -0
  21. data/lib/protocol/http/header/authorization.rb +10 -2
  22. data/lib/protocol/http/header/cache_control.rb +42 -10
  23. data/lib/protocol/http/header/connection.rb +18 -1
  24. data/lib/protocol/http/header/cookie.rb +10 -3
  25. data/lib/protocol/http/header/date.rb +9 -0
  26. data/lib/protocol/http/header/etag.rb +11 -0
  27. data/lib/protocol/http/header/etags.rb +35 -4
  28. data/lib/protocol/http/header/multiple.rb +9 -1
  29. data/lib/protocol/http/header/priority.rb +55 -0
  30. data/lib/protocol/http/header/split.rb +17 -3
  31. data/lib/protocol/http/header/vary.rb +10 -1
  32. data/lib/protocol/http/headers.rb +82 -19
  33. data/lib/protocol/http/methods.rb +5 -0
  34. data/lib/protocol/http/middleware/builder.rb +17 -0
  35. data/lib/protocol/http/middleware.rb +28 -0
  36. data/lib/protocol/http/peer.rb +9 -0
  37. data/lib/protocol/http/reference.rb +33 -6
  38. data/lib/protocol/http/request.rb +25 -0
  39. data/lib/protocol/http/response.rb +12 -0
  40. data/lib/protocol/http/url.rb +34 -8
  41. data/lib/protocol/http/version.rb +1 -1
  42. data/readme.md +4 -0
  43. data/releases.md +4 -0
  44. data.tar.gz.sig +0 -0
  45. metadata +4 -2
  46. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 989a6411106c9c491df6a8916877cc9d1abbd4509da3e859e5d627723edc8daa
4
- data.tar.gz: 20fc3651bf5241e7c3fb55d286ff3172a09363a747ac97f5608eb4a7533aece7
3
+ metadata.gz: f53b02520f9b41a2cf01a8e2637c7331dbfbd330ad13487036c2af363e948c2d
4
+ data.tar.gz: 803588a8a92cc588e027dd285c8d76705d4fb137e2a5fa9566b2897d1473be38
5
5
  SHA512:
6
- metadata.gz: dcc3a33c4b19b130df7825a44c4bdf6bc2c631fa9a073299180fd34256436e89ab012087a2adfebc37f1f6f39d4d5e4967fd14ef481d7c150328edf0ef95d088
7
- data.tar.gz: 59b1398b707f7c79ca6055a45461006f1cffd5d90c862e05c0924baaaa5144161193b48c5d357f9648431dd57f18da57854e6eb2af56f4b0924ad0772e62a349
6
+ metadata.gz: a6cb23fc1e47567145ad6d30ab12b4157e3f5962de8a32c38702bdb979c5f9c9388f6b480104655a77dd83c42c5e3230b471efe683acda76e0a2576f0fd654e4
7
+ data.tar.gz: e61af805d44b682ad9da0de0753b6009a96ace3b68799a597523ac6d74d847923d810e2cc20ded2df569a57505c7a2abb768714153f47a46586df6ce9e9dd086
checksums.yaml.gz.sig CHANGED
Binary file
@@ -10,11 +10,15 @@ require_relative "body/inflate"
10
10
 
11
11
  module Protocol
12
12
  module HTTP
13
- # Set a valid accept-encoding header and decode the response.
13
+ # A middleware that sets the accept-encoding header and decodes the response according to the content-encoding header.
14
14
  class AcceptEncoding < Middleware
15
+ # The header used to request encodings.
15
16
  ACCEPT_ENCODING = "accept-encoding".freeze
17
+
18
+ # The header used to specify encodings.
16
19
  CONTENT_ENCODING = "content-encoding".freeze
17
20
 
21
+ # The default wrappers to use for decoding content.
18
22
  DEFAULT_WRAPPERS = {
19
23
  "gzip" => Body::Inflate.method(:for),
20
24
 
@@ -22,13 +26,21 @@ module Protocol
22
26
  # 'identity' => ->(body){body},
23
27
  }
24
28
 
25
- def initialize(app, wrappers = DEFAULT_WRAPPERS)
26
- super(app)
29
+ # Initialize the middleware with the given delegate and wrappers.
30
+ #
31
+ # @parameter delegate [Protocol::HTTP::Middleware] The delegate middleware.
32
+ # @parameter wrappers [Hash] A hash of encoding names to wrapper functions.
33
+ def initialize(delegate, wrappers = DEFAULT_WRAPPERS)
34
+ super(delegate)
27
35
 
28
36
  @accept_encoding = wrappers.keys.join(", ")
29
37
  @wrappers = wrappers
30
38
  end
31
39
 
40
+ # Set the accept-encoding header and decode the response body.
41
+ #
42
+ # @parameter request [Protocol::HTTP::Request] The request to modify.
43
+ # @returns [Protocol::HTTP::Response] The response.
32
44
  def call(request)
33
45
  request.headers[ACCEPT_ENCODING] = @accept_encoding
34
46
 
@@ -43,6 +43,10 @@ module Protocol
43
43
  self.new(chunks)
44
44
  end
45
45
 
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.
46
50
  def initialize(chunks = [], length = nil)
47
51
  @chunks = chunks
48
52
  @length = length
@@ -50,6 +54,7 @@ module Protocol
50
54
  @index = 0
51
55
  end
52
56
 
57
+ # @attribute [Array(String)] chunks the buffered chunks.
53
58
  attr :chunks
54
59
 
55
60
  # 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 @@ module Protocol
59
64
  self.class.new(@chunks)
60
65
  end
61
66
 
67
+ # Finish the body, this is a no-op.
68
+ #
69
+ # @returns [Buffered] self.
62
70
  def finish
63
71
  self
64
72
  end
65
73
 
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.
67
77
  def close(error = nil)
68
78
  @index = @chunks.length
69
79
 
70
80
  return nil
71
81
  end
72
82
 
83
+ # Clear the buffered chunks.
73
84
  def clear
74
85
  @chunks = []
75
86
  @length = 0
76
87
  @index = 0
77
88
  end
78
89
 
90
+ # The length of the body. Will compute and cache the length of the body, if it was not provided.
79
91
  def length
80
92
  @length ||= @chunks.inject(0) {|sum, chunk| sum + chunk.bytesize}
81
93
  end
82
94
 
95
+ # @returns [Boolean] if the body is empty.
83
96
  def empty?
84
97
  @index >= @chunks.length
85
98
  end
86
99
 
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.
88
102
  def ready?
89
103
  true
90
104
  end
91
105
 
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.
92
109
  def read
93
110
  return nil unless @chunks
94
111
 
@@ -99,23 +116,30 @@ module Protocol
99
116
  end
100
117
  end
101
118
 
119
+ # Discard the body. Invokes {#close}.
102
120
  def discard
103
121
  # It's safe to call close here because there is no underlying stream to close:
104
122
  self.close
105
123
  end
106
124
 
125
+ # Write a chunk to the buffered body.
107
126
  def write(chunk)
108
127
  @chunks << chunk
109
128
  end
110
129
 
130
+ # Close the body for writing. This is a no-op.
111
131
  def close_write(error)
112
132
  # Nothing to do.
113
133
  end
114
134
 
135
+ # Whether the body can be rewound.
136
+ #
137
+ # @returns [Boolean] if the body has chunks.
115
138
  def rewindable?
116
139
  @chunks != nil
117
140
  end
118
141
 
142
+ # Rewind the body to the beginning, causing a subsequent read to return the first chunk.
119
143
  def rewind
120
144
  return false unless @chunks
121
145
 
@@ -124,6 +148,9 @@ module Protocol
124
148
  return true
125
149
  end
126
150
 
151
+ # Inspect the buffered body.
152
+ #
153
+ # @returns [String] a string representation of the buffered body.
127
154
  def inspect
128
155
  if @chunks
129
156
  "\#<#{self.class} #{@chunks.size} chunks, #{self.length} bytes>"
@@ -10,6 +10,10 @@ module Protocol
10
10
  module Body
11
11
  # Invokes a callback once the body has completed, either successfully or due to an error.
12
12
  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.
13
17
  def self.wrap(message, &block)
14
18
  if body = message&.body and !body.empty?
15
19
  message.body = self.new(message.body, block)
@@ -18,20 +22,29 @@ module Protocol
18
22
  end
19
23
  end
20
24
 
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.
21
29
  def initialize(body, callback)
22
30
  super(body)
23
31
 
24
32
  @callback = callback
25
33
  end
26
34
 
35
+ # @returns [Boolean] completable bodies are not rewindable.
27
36
  def rewindable?
28
37
  false
29
38
  end
30
39
 
40
+ # Rewind the body, is not supported.
31
41
  def rewind
32
42
  false
33
43
  end
34
44
 
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.
35
48
  def close(error = nil)
36
49
  if @callback
37
50
  @callback.call(error)
@@ -10,17 +10,27 @@ require "zlib"
10
10
  module Protocol
11
11
  module HTTP
12
12
  module Body
13
+ # A body which compresses or decompresses the contents using the DEFLATE or GZIP algorithm.
13
14
  class ZStream < Wrapper
15
+ # The default compression level.
14
16
  DEFAULT_LEVEL = 7
15
17
 
18
+ # The DEFLATE window size.
16
19
  DEFLATE = -Zlib::MAX_WBITS
20
+
21
+ # The GZIP window size.
17
22
  GZIP = Zlib::MAX_WBITS | 16
18
23
 
24
+ # The supported encodings.
19
25
  ENCODINGS = {
20
26
  "deflate" => DEFLATE,
21
27
  "gzip" => GZIP,
22
28
  }
23
29
 
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.
24
34
  def initialize(body, stream)
25
35
  super(body)
26
36
 
@@ -30,6 +40,9 @@ module Protocol
30
40
  @output_length = 0
31
41
  end
32
42
 
43
+ # Close the stream.
44
+ #
45
+ # @parameter error [Exception | Nil] the error that caused the stream to be closed.
33
46
  def close(error = nil)
34
47
  if stream = @stream
35
48
  @stream = nil
@@ -39,14 +52,21 @@ module Protocol
39
52
  super
40
53
  end
41
54
 
55
+ # The length of the output, if known. Generally, this is not known due to the nature of compression.
42
56
  def length
43
57
  # We don't know the length of the output until after it's been compressed.
44
58
  nil
45
59
  end
46
60
 
61
+ # @attribute [Integer] input_length the total number of bytes read from the input.
47
62
  attr :input_length
63
+
64
+ # @attribute [Integer] output_length the total number of bytes written to the output.
48
65
  attr :output_length
49
66
 
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.
50
70
  def ratio
51
71
  if @input_length != 0
52
72
  @output_length.to_f / @input_length.to_f
@@ -55,16 +75,29 @@ module Protocol
55
75
  end
56
76
  end
57
77
 
78
+ # Inspect the body, including the compression ratio.
79
+ #
80
+ # @returns [String] a string representation of the body.
58
81
  def inspect
59
82
  "#{super} | \#<#{self.class} #{(ratio*100).round(2)}%>"
60
83
  end
61
84
  end
62
85
 
86
+ # A body which compresses the contents using the DEFLATE or GZIP algorithm.
63
87
  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.
64
94
  def self.for(body, window_size = GZIP, level = DEFAULT_LEVEL)
65
95
  self.new(body, Zlib::Deflate.new(level, window_size))
66
96
  end
67
97
 
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.
68
101
  def read
69
102
  return if @stream.finished?
70
103
 
@@ -12,12 +12,21 @@ module Protocol
12
12
  module Body
13
13
  # Invokes a callback once the body has finished reading.
14
14
  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.
15
20
  def self.wrap(message, digest = Digest::SHA256.new, &block)
16
21
  if body = message&.body and !body.empty?
17
22
  message.body = self.new(message.body, digest, block)
18
23
  end
19
24
  end
20
25
 
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.
21
30
  # @parameter callback [Block] The callback is invoked when the digest is complete.
22
31
  def initialize(body, digest = Digest::SHA256.new, callback = nil)
23
32
  super(body)
@@ -25,11 +34,14 @@ module Protocol
25
34
  @digest = digest
26
35
  @callback = callback
27
36
  end
37
+
38
+ # @attribute [Digest] digest the digest object.
39
+ attr :digest
28
40
 
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.
33
45
  def etag(weak: false)
34
46
  if weak
35
47
  "W/\"#{digest.hexdigest}\""
@@ -38,6 +50,9 @@ module Protocol
38
50
  end
39
51
  end
40
52
 
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.
41
56
  def read
42
57
  if chunk = super
43
58
  @digest.update(chunk)
@@ -8,14 +8,27 @@ require_relative "readable"
8
8
  module Protocol
9
9
  module HTTP
10
10
  module Body
11
+ # A body which reads from a file.
11
12
  class File < Readable
12
- BLOCK_SIZE = 4096
13
+ # The default block size.
14
+ BLOCK_SIZE = 64*1024
15
+
16
+ # The default mode for opening files.
13
17
  MODE = ::File::RDONLY | ::File::BINARY
14
18
 
19
+ # Open a file at the given path.
20
+ #
21
+ # @parameter path [String] the path to the file.
15
22
  def self.open(path, *arguments, **options)
16
23
  self.new(::File.open(path, MODE), *arguments, **options)
17
24
  end
18
25
 
26
+ # Initialize the file body with the given file.
27
+ #
28
+ # @parameter file [::File] the file to read from.
29
+ # @parameter range [Range] the range of bytes to read from the file.
30
+ # @parameter size [Integer] the size of the file, if known.
31
+ # @parameter block_size [Integer] the block size to use when reading from the file.
19
32
  def initialize(file, range = nil, size: file.size, block_size: BLOCK_SIZE)
20
33
  @file = file
21
34
  @range = range
@@ -33,6 +46,9 @@ module Protocol
33
46
  end
34
47
  end
35
48
 
49
+ # Close the file.
50
+ #
51
+ # @parameter error [Exception | Nil] the error that caused the file to be closed.
36
52
  def close(error = nil)
37
53
  @file.close
38
54
  @remaining = 0
@@ -40,32 +56,46 @@ module Protocol
40
56
  super
41
57
  end
42
58
 
59
+ # @attribute [::File] file the file to read from.
43
60
  attr :file
44
61
 
62
+ # @attribute [Integer] the offset to read from.
45
63
  attr :offset
64
+
65
+ # @attribute [Integer] the number of bytes to read.
46
66
  attr :length
47
67
 
68
+ # @returns [Boolean] whether more data should be read.
48
69
  def empty?
49
70
  @remaining == 0
50
71
  end
51
72
 
73
+ # @returns [Boolean] whether the body is ready to be read, always true for files.
52
74
  def ready?
53
75
  true
54
76
  end
55
77
 
78
+ # Returns a copy of the body, by duplicating the file descriptor, including the same range if specified.
79
+ #
80
+ # @returns [File] the duplicated body.
56
81
  def buffered
57
82
  self.class.new(@file.dup, @range, block_size: @block_size)
58
83
  end
59
84
 
85
+ # Rewind the file to the beginning of the range.
60
86
  def rewind
61
87
  @file.seek(@offset)
62
88
  @remaining = @length
63
89
  end
64
90
 
91
+ # @returns [Boolean] whether the body is rewindable, generally always true for seekable files.
65
92
  def rewindable?
66
93
  true
67
94
  end
68
95
 
96
+ # Read the next chunk of data from the file.
97
+ #
98
+ # @returns [String | Nil] the next chunk of data, or nil if the file is fully read.
69
99
  def read
70
100
  if @remaining > 0
71
101
  amount = [@remaining, @block_size].min
@@ -88,6 +118,9 @@ module Protocol
88
118
  # stream.close
89
119
  # end
90
120
 
121
+ # Read all the remaining data from the file and return it as a single string.
122
+ #
123
+ # @returns [String] the remaining data.
91
124
  def join
92
125
  return "" if @remaining == 0
93
126
 
@@ -98,6 +131,9 @@ module Protocol
98
131
  return buffer
99
132
  end
100
133
 
134
+ # Inspect the file body.
135
+ #
136
+ # @returns [String] a string representation of the file body.
101
137
  def inspect
102
138
  "\#<#{self.class} file=#{@file.inspect} offset=#{@offset} remaining=#{@remaining}>"
103
139
  end
@@ -8,7 +8,9 @@ require_relative "readable"
8
8
  module Protocol
9
9
  module HTTP
10
10
  module Body
11
+ # Represents a body suitable for HEAD requests, in other words, a body that is empty and has a known length.
11
12
  class Head < Readable
13
+ # Create a head body for the given body, capturing it's length and then closing it.
12
14
  def self.for(body)
13
15
  head = self.new(body.length)
14
16
 
@@ -17,18 +19,24 @@ module Protocol
17
19
  return head
18
20
  end
19
21
 
22
+ # Initialize the head body with the given length.
23
+ #
24
+ # @parameter length [Integer] the length of the body.
20
25
  def initialize(length)
21
26
  @length = length
22
27
  end
23
28
 
29
+ # @returns [Boolean] the body is empty.
24
30
  def empty?
25
31
  true
26
32
  end
27
33
 
34
+ # @returns [Boolean] the body is ready.
28
35
  def ready?
29
36
  true
30
37
  end
31
38
 
39
+ # @returns [Integer] the length of the body, if known.
32
40
  def length
33
41
  @length
34
42
  end
@@ -10,11 +10,19 @@ require_relative "deflate"
10
10
  module Protocol
11
11
  module HTTP
12
12
  module Body
13
+ # A body which decompresses the contents using the DEFLATE or GZIP algorithm.
13
14
  class Inflate < ZStream
14
- def self.for(body, encoding = GZIP)
15
- self.new(body, Zlib::Inflate.new(encoding))
15
+ # Create a new body which decompresses the given body using the GZIP algorithm by default.
16
+ #
17
+ # @parameter body [Readable] the body to wrap.
18
+ # @parameter window_size [Integer] the window size to use for decompression.
19
+ def self.for(body, window_size = GZIP)
20
+ self.new(body, Zlib::Inflate.new(window_size))
16
21
  end
17
22
 
23
+ # Read from the underlying stream and inflate it.
24
+ #
25
+ # @returns [String | Nil] the inflated data, or nil if the stream is finished.
18
26
  def read
19
27
  if stream = @stream
20
28
  # Read from the underlying stream and inflate it:
@@ -9,42 +9,51 @@ module Protocol
9
9
  module Body
10
10
  # Represents a readable input streams.
11
11
  #
12
- # Typically, you'd override `#read` to return chunks of data.
12
+ # There are two major modes of operation:
13
13
  #
14
- # In general, you read chunks of data from a body until it is empty and returns `nil`. Upon reading `nil`, the body is considered consumed and should not be read from again.
14
+ # 1. Reading chunks using {read} (or {each}/{join}), until the body is empty, or
15
+ # 2. Streaming chunks using {call}, which writes chunks to a provided output stream.
15
16
  #
16
- # Reading can also fail, for example if the body represents a streaming upload, and the connection is lost. In this case, `#read` will raise some kind of error.
17
+ # In both cases, reading can fail, for example if the body represents a streaming upload, and the connection is lost. In this case, {read} will raise some kind of error, or the stream will be closed with an error.
17
18
  #
18
- # If you don't want to read from a stream, and instead want to close it immediately, you can call `close` on the body. If the body is already completely consumed, `close` will do nothing, but if there is still data to be read, it will cause the underlying stream to be reset (and possibly closed).
19
+ # At any point, you can use {close} to close the stream and release any resources, or {discard} to read all remaining data without processing it which may allow the underlying connection to be reused (but can be slower).
19
20
  class Readable
20
21
  # Close the stream immediately. After invoking this method, the stream should be considered closed, and all internal resources should be released.
21
22
  #
22
23
  # If an error occured while handling the output, it can be passed as an argument. This may be propagated to the client, for example the client may be informed that the stream was not fully read correctly.
23
24
  #
24
- # Invoking `#read` after `#close` will return `nil`.
25
+ # Invoking {read} after {close} will return `nil`.
26
+ #
27
+ # @parameter error [Exception | Nil] The error that caused this stream to be closed, if any.
25
28
  def close(error = nil)
26
29
  end
27
30
 
28
31
  # Optimistically determine whether read (may) return any data.
29
- # If this returns true, then calling read will definitely return nil.
30
- # If this returns false, then calling read may return nil.
32
+ #
33
+ # - If this returns true, then calling read will definitely return nil.
34
+ # - If this returns false, then calling read may return nil.
35
+ #
36
+ # @return [Boolean] Whether the stream is empty.
31
37
  def empty?
32
38
  false
33
39
  end
34
40
 
35
- # Whether calling read will return a chunk of data without blocking.
36
- # We prefer pessimistic implementation, and thus default to `false`.
37
- # @return [Boolean]
41
+ # Whether calling read will return a chunk of data without blocking. We prefer pessimistic implementation, and thus default to `false`.
42
+ #
43
+ # @return [Boolean] Whether the stream is ready (read will not block).
38
44
  def ready?
39
45
  false
40
46
  end
41
47
 
42
48
  # Whether the stream can be rewound using {rewind}.
49
+ #
50
+ # @return [Boolean] Whether the stream is rewindable.
43
51
  def rewindable?
44
52
  false
45
53
  end
46
54
 
47
55
  # Rewind the stream to the beginning.
56
+ #
48
57
  # @returns [Boolean] Whether the stream was successfully rewound.
49
58
  def rewind
50
59
  false
@@ -60,19 +69,21 @@ module Protocol
60
69
  end
61
70
 
62
71
  # The total length of the body, if known.
72
+ #
63
73
  # @returns [Integer | Nil] The total length of the body, or `nil` if the length is unknown.
64
74
  def length
65
75
  nil
66
76
  end
67
77
 
68
78
  # Read the next available chunk.
79
+ #
69
80
  # @returns [String | Nil] The chunk of data, or `nil` if the stream has finished.
70
81
  # @raises [StandardError] If an error occurs while reading.
71
82
  def read
72
83
  nil
73
84
  end
74
85
 
75
- # Enumerate all chunks until finished, then invoke `#close`.
86
+ # Enumerate all chunks until finished, then invoke {close}.
76
87
  #
77
88
  # Closes the stream when finished or if an error occurs.
78
89
  #
@@ -109,6 +120,9 @@ module Protocol
109
120
  end
110
121
  end
111
122
 
123
+ # Whether to prefer streaming the body using {call} rather than reading it using {read} or {each}.
124
+ #
125
+ # @returns [Boolean] Whether the body should be streamed.
112
126
  def stream?
113
127
  false
114
128
  end
@@ -131,6 +145,7 @@ module Protocol
131
145
  end
132
146
  end
133
147
  ensure
148
+ # TODO Should this invoke close_write(error) instead?
134
149
  stream.close
135
150
  end
136
151
 
@@ -152,6 +167,9 @@ module Protocol
152
167
  end
153
168
  end
154
169
 
170
+ # Convert the body to a hash suitable for serialization. This won't include the contents of the body, but will include metadata such as the length, streamability, and readiness, etc.
171
+ #
172
+ # @returns [Hash] The body as a hash.
155
173
  def as_json(...)
156
174
  {
157
175
  class: self.class.name,
@@ -162,6 +180,9 @@ module Protocol
162
180
  }
163
181
  end
164
182
 
183
+ # Convert the body to JSON.
184
+ #
185
+ # @returns [String] The body as JSON.
165
186
  def to_json(...)
166
187
  as_json.to_json(...)
167
188
  end