protocol-http 0.45.0 → 0.46.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 +65 -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
@@ -8,8 +8,11 @@ module Protocol
8
8
  module HTTP
9
9
  module Body
10
10
  # General operations for interacting with a request or response body.
11
+ #
12
+ # This module is included in both {Request} and {Response}.
11
13
  module Reader
12
14
  # Read chunks from the body.
15
+ #
13
16
  # @yields {|chunk| ...} chunks from the body.
14
17
  def each(&block)
15
18
  if @body
@@ -19,6 +22,7 @@ module Protocol
19
22
  end
20
23
 
21
24
  # Reads the entire request/response body.
25
+ #
22
26
  # @returns [String] the entire body as a string.
23
27
  def read
24
28
  if @body
@@ -30,6 +34,7 @@ module Protocol
30
34
  end
31
35
 
32
36
  # Gracefully finish reading the body. This will buffer the remainder of the body.
37
+ #
33
38
  # @returns [Buffered] buffers the entire body.
34
39
  def finish
35
40
  if @body
@@ -46,19 +51,27 @@ module Protocol
46
51
  @body = nil
47
52
  body.discard
48
53
  end
54
+
55
+ return nil
49
56
  end
50
57
 
51
58
  # Buffer the entire request/response body.
59
+ #
52
60
  # @returns [Reader] itself.
53
61
  def buffered!
54
62
  if @body
55
63
  @body = @body.finish
56
64
  end
57
65
 
66
+ # TODO Should this return @body instead? It seems more useful.
58
67
  return self
59
68
  end
60
69
 
61
70
  # Write the body of the response to the given file path.
71
+ #
72
+ # @parameter path [String] the path to write the body to.
73
+ # @parameter mode [Integer] the mode to open the file with.
74
+ # @parameter options [Hash] additional options to pass to `File.open`.
62
75
  def save(path, mode = ::File::WRONLY|::File::CREAT|::File::TRUNC, **options)
63
76
  if @body
64
77
  ::File.open(path, mode, **options) do |file|
@@ -70,6 +83,8 @@ module Protocol
70
83
  end
71
84
 
72
85
  # Close the connection as quickly as possible. Discards body. May close the underlying connection if necessary to terminate the stream.
86
+ #
87
+ # @parameter error [Exception | Nil] the error that caused the stream to be closed, if any.
73
88
  def close(error = nil)
74
89
  if @body
75
90
  @body.close(error)
@@ -78,6 +93,8 @@ module Protocol
78
93
  end
79
94
 
80
95
  # Whether there is a body?
96
+ #
97
+ # @returns [Boolean] whether there is a body.
81
98
  def body?
82
99
  @body and !@body.empty?
83
100
  end
@@ -9,8 +9,13 @@ require_relative "buffered"
9
9
  module Protocol
10
10
  module HTTP
11
11
  module Body
12
- # A body which buffers all it's contents as it is `#read`.
12
+ # A body which buffers all it's contents as it is read.
13
+ #
14
+ # As the body is buffered in memory, you may want to ensure your server has sufficient (virtual) memory available to buffer the entire body.
13
15
  class Rewindable < Wrapper
16
+ # Wrap the given message body in a rewindable body, if it is not already rewindable.
17
+ #
18
+ # @parameter message [Request | Response] the message to wrap.
14
19
  def self.wrap(message)
15
20
  if body = message.body
16
21
  if body.rewindable?
@@ -21,6 +26,9 @@ module Protocol
21
26
  end
22
27
  end
23
28
 
29
+ # Initialize the body with the given body.
30
+ #
31
+ # @parameter body [Readable] the body to wrap.
24
32
  def initialize(body)
25
33
  super(body)
26
34
 
@@ -28,10 +36,12 @@ module Protocol
28
36
  @index = 0
29
37
  end
30
38
 
39
+ # @returns [Boolean] Whether the body is empty.
31
40
  def empty?
32
41
  (@index >= @chunks.size) && super
33
42
  end
34
43
 
44
+ # @returns [Boolean] Whether the body is ready to be read.
35
45
  def ready?
36
46
  (@index < @chunks.size) || super
37
47
  end
@@ -43,6 +53,9 @@ module Protocol
43
53
  Buffered.new(@chunks)
44
54
  end
45
55
 
56
+ # Read the next available chunk. This may return a buffered chunk if the stream has been rewound, or a chunk from the underlying stream, if available.
57
+ #
58
+ # @returns [String | Nil] The chunk of data, or `nil` if the stream has finished.
46
59
  def read
47
60
  if @index < @chunks.size
48
61
  chunk = @chunks[@index]
@@ -58,14 +71,19 @@ module Protocol
58
71
  return chunk
59
72
  end
60
73
 
74
+ # Rewind the stream to the beginning.
61
75
  def rewind
62
76
  @index = 0
63
77
  end
64
78
 
79
+ # @returns [Boolean] Whether the stream is rewindable, which it is.
65
80
  def rewindable?
66
81
  true
67
82
  end
68
83
 
84
+ # Inspect the rewindable body.
85
+ #
86
+ # @returns [String] a string representation of the body.
69
87
  def inspect
70
88
  "\#<#{self.class} #{@index}/#{@chunks.size} chunks read>"
71
89
  end
@@ -11,8 +11,13 @@ module Protocol
11
11
  module Body
12
12
  # The input stream is an IO-like object which contains the raw HTTP POST data. When applicable, its external encoding must be “ASCII-8BIT” and it must be opened in binary mode, for Ruby 1.9 compatibility. The input stream must respond to gets, each, read and rewind.
13
13
  class Stream
14
+ # The default line separator, used by {gets}.
14
15
  NEWLINE = "\n"
15
16
 
17
+ # Initialize the stream with the given input and output.
18
+ #
19
+ # @parameter input [Readable] The input stream.
20
+ # @parameter output [Writable] The output stream.
16
21
  def initialize(input = nil, output = Buffered.new)
17
22
  @input = input
18
23
  @output = output
@@ -26,7 +31,10 @@ module Protocol
26
31
  @closed_read = false
27
32
  end
28
33
 
34
+ # @attribute [Readable] The input stream.
29
35
  attr :input
36
+
37
+ # @attribute [Writable] The output stream.
30
38
  attr :output
31
39
 
32
40
  # This provides a read-only interface for data, which is surprisingly tricky to implement correctly.
@@ -39,9 +47,9 @@ module Protocol
39
47
  #
40
48
  # If buffer is given, then the read data will be placed into buffer instead of a newly created String object.
41
49
  #
42
- # @param length [Integer] the amount of data to read
43
- # @param buffer [String] the buffer which will receive the data
44
- # @return a buffer containing the data
50
+ # @parameterlength [Integer] the amount of data to read
51
+ # @parameter buffer [String] the buffer which will receive the data
52
+ # @returns [String] a buffer containing the data
45
53
  def read(length = nil, buffer = nil)
46
54
  return "" if length == 0
47
55
 
@@ -125,11 +133,14 @@ module Protocol
125
133
  end
126
134
 
127
135
  # Similar to {read_partial} but raises an `EOFError` if the stream is at EOF.
136
+ #
137
+ # @parameter length [Integer] The maximum number of bytes to read.
138
+ # @parameter buffer [String] The buffer to read into.
128
139
  def readpartial(length, buffer = nil)
129
140
  read_partial(length, buffer) or raise EOFError, "End of file reached!"
130
141
  end
131
142
 
132
- # Iterate over each chunk of data in the stream.
143
+ # Iterate over each chunk of data from the input stream.
133
144
  #
134
145
  # @yields {|chunk| ...} Each chunk of data.
135
146
  def each(&block)
@@ -146,6 +157,9 @@ module Protocol
146
157
  end
147
158
 
148
159
  # Read data from the stream without blocking if possible.
160
+ #
161
+ # @parameter length [Integer] The maximum number of bytes to read.
162
+ # @parameter buffer [String | Nil] The buffer to read into.
149
163
  def read_nonblock(length, buffer = nil, exception: nil)
150
164
  @buffer ||= read_next
151
165
  chunk = nil
@@ -323,6 +337,10 @@ module Protocol
323
337
  end
324
338
 
325
339
  # Close the input body.
340
+ #
341
+ # If, while processing the data that was read from this stream, an error is encountered, it should be passed to this method.
342
+ #
343
+ # @parameter error [Exception | Nil] The error that was encountered, if any.
326
344
  def close_read(error = nil)
327
345
  if input = @input
328
346
  @input = nil
@@ -334,6 +352,10 @@ module Protocol
334
352
  end
335
353
 
336
354
  # Close the output body.
355
+ #
356
+ # If, while generating the data that is written to this stream, an error is encountered, it should be passed to this method.
357
+ #
358
+ # @parameter error [Exception | Nil] The error that was encountered, if any.
337
359
  def close_write(error = nil)
338
360
  if output = @output
339
361
  @output = nil
@@ -343,6 +365,8 @@ module Protocol
343
365
  end
344
366
 
345
367
  # Close the input and output bodies.
368
+ #
369
+ # @parameter error [Exception | Nil] The error that caused this stream to be closed, if any.
346
370
  def close(error = nil)
347
371
  self.close_read(error)
348
372
  self.close_write(error)
@@ -352,18 +376,22 @@ module Protocol
352
376
  @closed = true
353
377
  end
354
378
 
355
- # Whether the stream has been closed.
379
+ # @returns [Boolean] Whether the stream has been closed.
356
380
  def closed?
357
381
  @closed
358
382
  end
359
383
 
360
- # Whether there are any output chunks remaining?
384
+ # @returns [Boolean] Whether there are any output chunks remaining.
361
385
  def empty?
362
386
  @output.empty?
363
387
  end
364
388
 
365
389
  private
366
390
 
391
+ # Read the next chunk of data from the input stream.
392
+ #
393
+ # @returns [String] The next chunk of data.
394
+ # @raises [IOError] If the input stream was explicitly closed.
367
395
  def read_next
368
396
  if @input
369
397
  return @input.read
@@ -13,39 +13,65 @@ module Protocol
13
13
  module Body
14
14
  # A body that invokes a block that can read and write to a stream.
15
15
  #
16
- # In some cases, it's advantageous to directly read and write to the underlying stream if possible. For example, HTTP/1 upgrade requests, WebSockets, and similar. To handle that case, response bodies can implement `stream?` and return `true`. When `stream?` returns true, the body **should** be consumed by calling `call(stream)`. Server implementations may choose to always invoke `call(stream)` if it's efficient to do so. Bodies that don't support it will fall back to using `#each`.
16
+ # In some cases, it's advantageous to directly read and write to the underlying stream if possible. For example, HTTP/1 upgrade requests, WebSockets, and similar. To handle that case, response bodies can implement {stream?} and return `true`. When {stream?} returns true, the body **should** be consumed by calling `call(stream)`. Server implementations may choose to always invoke `call(stream)` if it's efficient to do so. Bodies that don't support it will fall back to using {each}.
17
17
  #
18
18
  # When invoking `call(stream)`, the stream can be read from and written to, and closed. However, the stream is only guaranteed to be open for the duration of the `call(stream)` call. Once the method returns, the stream **should** be closed by the server.
19
19
  module Streamable
20
+ # Generate a new streaming request body using the given block to generate the body.
21
+ #
22
+ # @parameter block [Proc] The block that generates the body.
23
+ # @returns [RequestBody] The streaming request body.
20
24
  def self.request(&block)
21
25
  RequestBody.new(block)
22
26
  end
23
27
 
28
+ # Generate a new streaming response body using the given block to generate the body.
29
+ #
30
+ # @parameter request [Request] The request.
31
+ # @parameter block [Proc] The block that generates the body.
32
+ # @returns [ResponseBody] The streaming response body.
24
33
  def self.response(request, &block)
25
34
  ResponseBody.new(block, request.body)
26
35
  end
27
36
 
37
+ # A output stream that can be written to by a block.
28
38
  class Output
39
+ # Schedule the block to be executed in a fiber.
40
+ #
41
+ # @parameter input [Readable] The input stream.
42
+ # @parameter block [Proc] The block that generates the output.
43
+ # @returns [Output] The output stream.
29
44
  def self.schedule(input, block)
30
45
  self.new(input, block).tap(&:schedule)
31
46
  end
32
47
 
48
+ # Initialize the output stream with the given input and block.
49
+ #
50
+ # @parameter input [Readable] The input stream.
51
+ # @parameter block [Proc] The block that generates the output.
33
52
  def initialize(input, block)
34
53
  @output = Writable.new
35
54
  @stream = Stream.new(input, @output)
36
55
  @block = block
37
56
  end
38
57
 
58
+ # Schedule the block to be executed in a fiber.
59
+ #
60
+ # @returns [Fiber] The fiber.
39
61
  def schedule
40
62
  @fiber ||= Fiber.schedule do
41
63
  @block.call(@stream)
42
64
  end
43
65
  end
44
66
 
67
+ # Read from the output stream (may block).
45
68
  def read
46
69
  @output.read
47
70
  end
48
71
 
72
+ # Close the output stream.
73
+ #
74
+ # @parameter error [Exception | Nil] The error that caused this stream to be closed, if any.
49
75
  def close(error = nil)
50
76
  @output.close_write(error)
51
77
  end
@@ -55,13 +81,19 @@ module Protocol
55
81
  class ConsumedError < StandardError
56
82
  end
57
83
 
84
+ # A streaming body that can be read from and written to.
58
85
  class Body < Readable
86
+ # Initialize the body with the given block and input.
87
+ #
88
+ # @parameter block [Proc] The block that generates the body.
89
+ # @parameter input [Readable] The input stream, if known.
59
90
  def initialize(block, input = nil)
60
91
  @block = block
61
92
  @input = input
62
93
  @output = nil
63
94
  end
64
95
 
96
+ # @returns [Boolean] Whether the body can be streamed, which is true.
65
97
  def stream?
66
98
  true
67
99
  end
@@ -81,9 +113,9 @@ module Protocol
81
113
  @output.read
82
114
  end
83
115
 
84
- # Invoke the block with the given stream.
116
+ # Invoke the block with the given stream. The block can read and write to the stream, and must close the stream when finishing.
85
117
  #
86
- # The block can read and write to the stream, and must close the stream when finishing.
118
+ # @parameter stream [Stream] The stream to read and write to.
87
119
  def call(stream)
88
120
  if @block.nil?
89
121
  raise ConsumedError, "Streaming block has already been consumed!"
@@ -101,6 +133,9 @@ module Protocol
101
133
  raise
102
134
  end
103
135
 
136
+ # Close the input. The streaming body will eventually read all the input.
137
+ #
138
+ # @parameter error [Exception | Nil] The error that caused this stream to be closed, if any.
104
139
  def close_input(error = nil)
105
140
  if input = @input
106
141
  @input = nil
@@ -108,6 +143,9 @@ module Protocol
108
143
  end
109
144
  end
110
145
 
146
+ # Close the output, the streaming body will be unable to write any more output.
147
+ #
148
+ # @parameter error [Exception | Nil] The error that caused this stream to be closed, if any.
111
149
  def close_output(error = nil)
112
150
  @output&.close(error)
113
151
  end
@@ -115,8 +153,8 @@ module Protocol
115
153
 
116
154
  # A response body is used on the server side to generate the response body using a block.
117
155
  class ResponseBody < Body
156
+ # Close will be invoked when all the output is written.
118
157
  def close(error = nil)
119
- # Close will be invoked when all the output is written.
120
158
  self.close_output(error)
121
159
  end
122
160
  end
@@ -125,12 +163,15 @@ module Protocol
125
163
  #
126
164
  # As the response body isn't available until the request is sent, the response body must be {stream}ed into the request body.
127
165
  class RequestBody < Body
166
+ # Initialize the request body with the given block.
167
+ #
168
+ # @parameter block [Proc] The block that generates the body.
128
169
  def initialize(block)
129
170
  super(block, Writable.new)
130
171
  end
131
172
 
173
+ # Close will be invoked when all the input is read.
132
174
  def close(error = nil)
133
- # Close will be invoked when all the input is read.
134
175
  self.close_input(error)
135
176
  end
136
177
 
@@ -13,20 +13,26 @@ module Protocol
13
13
  # Wrap the body of the given message in a new instance of this class.
14
14
  #
15
15
  # @parameter message [Request | Response] the message to wrap.
16
- # @returns [Wrapper | nil] the wrapped body or nil if the body was nil.
16
+ # @returns [Wrapper | Nil] the wrapped body or `nil`` if the body was `nil`.
17
17
  def self.wrap(message)
18
18
  if body = message.body
19
19
  message.body = self.new(body)
20
20
  end
21
21
  end
22
22
 
23
+ # Initialize the wrapper with the given body.
24
+ #
25
+ # @parameter body [Readable] The body to wrap.
23
26
  def initialize(body)
24
27
  @body = body
25
28
  end
26
29
 
27
- # The wrapped body.
30
+ # @attribute [Readable] The wrapped body.
28
31
  attr :body
29
32
 
33
+ # Close the body.
34
+ #
35
+ # @parameter error [Exception | Nil] The error that caused this stream to be closed, if any.
30
36
  def close(error = nil)
31
37
  @body.close(error)
32
38
 
@@ -34,39 +40,49 @@ module Protocol
34
40
  # super
35
41
  end
36
42
 
43
+ # Forwards to the wrapped body.
37
44
  def empty?
38
45
  @body.empty?
39
46
  end
40
47
 
48
+ # Forwards to the wrapped body.
41
49
  def ready?
42
50
  @body.ready?
43
51
  end
44
52
 
53
+ # Forwards to the wrapped body.
45
54
  def buffered
46
55
  @body.buffered
47
56
  end
48
57
 
58
+ # Forwards to the wrapped body.
49
59
  def rewind
50
60
  @body.rewind
51
61
  end
52
62
 
63
+ # Forwards to the wrapped body.
53
64
  def rewindable?
54
65
  @body.rewindable?
55
66
  end
56
67
 
68
+ # Forwards to the wrapped body.
57
69
  def length
58
70
  @body.length
59
71
  end
60
72
 
61
- # Read the next available chunk.
73
+ # Forwards to the wrapped body.
62
74
  def read
63
75
  @body.read
64
76
  end
65
77
 
78
+ # Forwards to the wrapped body.
66
79
  def discard
67
80
  @body.discard
68
81
  end
69
82
 
83
+ # Convert the body to a hash suitable for serialization.
84
+ #
85
+ # @returns [Hash] The body as a hash.
70
86
  def as_json(...)
71
87
  {
72
88
  class: self.class.name,
@@ -74,10 +90,16 @@ module Protocol
74
90
  }
75
91
  end
76
92
 
93
+ # Convert the body to JSON.
94
+ #
95
+ # @returns [String] The body as JSON.
77
96
  def to_json(...)
78
97
  as_json.to_json(...)
79
98
  end
80
99
 
100
+ # Inspect the wrapped body. The wrapper, by default, is transparent.
101
+ #
102
+ # @returns [String] a string representation of the wrapped body.
81
103
  def inspect
82
104
  @body.inspect
83
105
  end
@@ -10,11 +10,14 @@ module Protocol
10
10
  module Body
11
11
  # A dynamic body which you can write to and read from.
12
12
  class Writable < Readable
13
+ # An error indicating that the body has been closed and no further writes are allowed.
13
14
  class Closed < StandardError
14
15
  end
15
16
 
16
- # @param [Integer] length The length of the response body if known.
17
- # @param [Async::Queue] queue Specify a different queue implementation, e.g. `Async::LimitedQueue.new(8)` to enable back-pressure streaming.
17
+ # Initialize the writable body.
18
+ #
19
+ # @parameter length [Integer] The length of the response body if known.
20
+ # @parameter queue [Thread::Queue] Specify a different queue implementation, e.g. `Thread::SizedQueue` to enable back-pressure.
18
21
  def initialize(length = nil, queue: Thread::Queue.new)
19
22
  @length = length
20
23
  @queue = queue
@@ -22,11 +25,12 @@ module Protocol
22
25
  @error = nil
23
26
  end
24
27
 
25
- def length
26
- @length
27
- end
28
+ # @attribute [Integer] The length of the response body if known.
29
+ attr :length
28
30
 
29
31
  # Stop generating output; cause the next call to write to fail with the given error. Does not prevent existing chunks from being read. In other words, this indicates both that no more data will be or should be written to the body.
32
+ #
33
+ # @parameter error [Exception] The error that caused this body to be closed, if any. Will be raised on the next call to {read}.
30
34
  def close(error = nil)
31
35
  @error ||= error
32
36
 
@@ -36,20 +40,29 @@ module Protocol
36
40
  super
37
41
  end
38
42
 
43
+ # Whether the body is closed. A closed body can not be written to or read from.
44
+ #
45
+ # @returns [Boolean] Whether the body is closed.
39
46
  def closed?
40
47
  @queue.closed?
41
48
  end
42
49
 
50
+ # @returns [Boolean] Whether the body is ready to be read from, without blocking.
43
51
  def ready?
44
52
  !@queue.empty? || @queue.closed?
45
53
  end
46
54
 
47
- # Has the producer called #finish and has the reader consumed the nil token?
55
+ # Indicates whether the body is empty. This can occur if the body has been closed, or if the producer has invoked {close_write} and the reader has consumed all available chunks.
56
+ #
57
+ # @returns [Boolean] Whether the body is empty.
48
58
  def empty?
49
59
  @queue.empty? && @queue.closed?
50
60
  end
51
61
 
52
62
  # Read the next available chunk.
63
+ #
64
+ # @returns [String | Nil] The next chunk, or `nil` if the body is finished.
65
+ # @raises [Exception] If the body was closed due to an error.
53
66
  def read
54
67
  if @error
55
68
  raise @error
@@ -65,7 +78,11 @@ module Protocol
65
78
  return chunk
66
79
  end
67
80
 
68
- # Write a single chunk to the body. Signal completion by calling `#finish`.
81
+ # Write a single chunk to the body. Signal completion by calling {close_write}.
82
+ #
83
+ # @parameter chunk [String] The chunk to write.
84
+ # @raises [Closed] If the body has been closed without error.
85
+ # @raises [Exception] If the body has been closed due to an error.
69
86
  def write(chunk)
70
87
  if @queue.closed?
71
88
  raise(@error || Closed)
@@ -75,27 +92,41 @@ module Protocol
75
92
  @count += 1
76
93
  end
77
94
 
95
+ # Signal that no more data will be written to the body.
96
+ #
97
+ # @parameter error [Exception] The error that caused this body to be closed, if any.
78
98
  def close_write(error = nil)
79
99
  @error ||= error
80
100
  @queue.close
81
101
  end
82
102
 
103
+ # The output interface for writing chunks to the body.
83
104
  class Output
105
+ # Initialize the output with the given writable body.
106
+ #
107
+ # @parameter writable [Writable] The writable body.
84
108
  def initialize(writable)
85
109
  @writable = writable
86
110
  @closed = false
87
111
  end
88
112
 
113
+ # @returns [Boolean] Whether the output is closed for writing only.
89
114
  def closed?
90
115
  @closed || @writable.closed?
91
116
  end
92
117
 
118
+ # Write a chunk to the body.
93
119
  def write(chunk)
94
120
  @writable.write(chunk)
95
121
  end
96
122
 
97
123
  alias << write
98
124
 
125
+ # Close the output stream.
126
+ #
127
+ # If an error is given, the error will be used to close the body by invoking {close} with the error. Otherwise, only the write side of the body will be closed.
128
+ #
129
+ # @parameter error [Exception | Nil] The error that caused this stream to be closed, if any.
99
130
  def close(error = nil)
100
131
  @closed = true
101
132
 
@@ -108,6 +139,12 @@ module Protocol
108
139
  end
109
140
 
110
141
  # Create an output wrapper which can be used to write chunks to the body.
142
+ #
143
+ # If a block is given, and the block raises an error, the error will used to close the body by invoking {close} with the error.
144
+ #
145
+ # @yields {|output| ...} if a block is given.
146
+ # @parameter output [Output] The output wrapper.
147
+ # @returns [Output] The output wrapper.
111
148
  def output
112
149
  output = Output.new(self)
113
150
 
@@ -124,6 +161,9 @@ module Protocol
124
161
  end
125
162
  end
126
163
 
164
+ # Inspect the body.
165
+ #
166
+ # @returns [String] A string representation of the body.
127
167
  def inspect
128
168
  if @error
129
169
  "\#<#{self.class} #{@count} chunks written, #{status}, error=#{@error}>"
@@ -134,6 +174,7 @@ module Protocol
134
174
 
135
175
  private
136
176
 
177
+ # @returns [String] A string representation of the body's status.
137
178
  def status
138
179
  if @queue.empty?
139
180
  if @queue.closed?
@@ -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