protocol-http 0.32.0 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0e3d21463ec1b9dc0e08f2215f51135dc40342b5d922d4d827f2e43bbd0cc19
4
- data.tar.gz: b6372b9064511569270558da4fc43dc792a5fe11d56caad820ba52cc997bcba1
3
+ metadata.gz: cb1197435097557cab74b1b61b92fe0a15a938892dd07486ee9f9db06360decd
4
+ data.tar.gz: 78bcbbd73817b03ce63b55b2bbd4408afeaeaa127e3c19f9dfd5d6ddb120f05d
5
5
  SHA512:
6
- metadata.gz: 0d26f9ee12048761f8a921f6f9d9b921a3baae9f4d10a00dae7800a4d1549da4b86c51cf9dd7b699e6e862273bd76528ea77401ad92f79367e141d3d45307798
7
- data.tar.gz: f8fe391ecdcb104fc5886cf6f2e1ad86a5f9599df6a532218201623188972f0b6249ff75c72663afe172677feb14d111b70a419cced7ad4fe3f6fcd4abf58d3d
6
+ metadata.gz: 34db4e5940f59773708692bc080706be457f9f41445b6858e01da474f0ba36aba31d8d8c2f3ccd329f2120f412d9c4bcc42c568c057db3e0cef816c83ab34db1
7
+ data.tar.gz: 23baab35d26b5b367fa556d4a302f81680b944fef07b2ca0a581fab7b36ac8320cb3c4a62cdc50f8cfd21efb408eee01e348c06b4c538a5cfbcf8cd6ce99b28f
checksums.yaml.gz.sig CHANGED
Binary file
@@ -56,6 +56,17 @@ module Protocol
56
56
  self
57
57
  end
58
58
 
59
+ # Ensure that future reads return nil, but allow for rewinding.
60
+ def close(error = nil)
61
+ @index = @chunks.length
62
+ end
63
+
64
+ def clear
65
+ @chunks.clear
66
+ @length = 0
67
+ @index = 0
68
+ end
69
+
59
70
  def length
60
71
  @length ||= @chunks.inject(0) {|sum, chunk| sum + chunk.bytesize}
61
72
  end
@@ -70,6 +81,8 @@ module Protocol
70
81
  end
71
82
 
72
83
  def read
84
+ return nil unless @chunks
85
+
73
86
  if chunk = @chunks[@index]
74
87
  @index += 1
75
88
 
@@ -81,18 +94,26 @@ module Protocol
81
94
  @chunks << chunk
82
95
  end
83
96
 
97
+ def close_write(error)
98
+ # Nothing to do.
99
+ end
100
+
84
101
  def rewindable?
85
- true
102
+ @chunks != nil
86
103
  end
87
104
 
88
105
  def rewind
106
+ return false unless @chunks
107
+
89
108
  @index = 0
90
109
 
91
110
  return true
92
111
  end
93
112
 
94
113
  def inspect
95
- "\#<#{self.class} #{@chunks.size} chunks, #{self.length} bytes>"
114
+ if @chunks
115
+ "\#<#{self.class} #{@chunks.size} chunks, #{self.length} bytes>"
116
+ end
96
117
  end
97
118
  end
98
119
  end
@@ -32,15 +32,6 @@ module Protocol
32
32
  false
33
33
  end
34
34
 
35
- def finish
36
- super.tap do
37
- if @callback
38
- @callback.call
39
- @callback = nil
40
- end
41
- end
42
- end
43
-
44
35
  def close(error = nil)
45
36
  super.tap do
46
37
  if @callback
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2023, by Samuel Williams.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'wrapper'
7
7
 
@@ -62,12 +62,6 @@ module Protocol
62
62
  self.new(body, Zlib::Deflate.new(level, window_size))
63
63
  end
64
64
 
65
- def stream?
66
- # We might want to revisit this design choice.
67
- # We could wrap the streaming body in a Deflate stream, but that would require an extra stream wrapper which we don't have right now. See also `Digestable#stream?`.
68
- false
69
- end
70
-
71
65
  def read
72
66
  return if @stream.finished?
73
67
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2020-2023, by Samuel Williams.
4
+ # Copyright, 2020-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'wrapper'
7
7
 
@@ -38,10 +38,6 @@ module Protocol
38
38
  end
39
39
  end
40
40
 
41
- def stream?
42
- false
43
- end
44
-
45
41
  def read
46
42
  if chunk = super
47
43
  @digest.update(chunk)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2023, by Samuel Williams.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'readable'
7
7
 
@@ -56,10 +56,6 @@ module Protocol
56
56
  @remaining = @length
57
57
  end
58
58
 
59
- def stream?
60
- false
61
- end
62
-
63
59
  def read
64
60
  if @remaining > 0
65
61
  amount = [@remaining, @block_size].min
@@ -72,6 +68,16 @@ module Protocol
72
68
  end
73
69
  end
74
70
 
71
+ # def stream?
72
+ # true
73
+ # end
74
+
75
+ # def call(stream)
76
+ # IO.copy_stream(@file, stream, @remaining)
77
+ # ensure
78
+ # stream.close
79
+ # end
80
+
75
81
  def join
76
82
  return "" if @remaining == 0
77
83
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2023, by Samuel Williams.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
5
 
6
6
  require 'zlib'
7
7
 
@@ -15,10 +15,6 @@ module Protocol
15
15
  self.new(body, Zlib::Inflate.new(encoding))
16
16
  end
17
17
 
18
- def stream?
19
- false
20
- end
21
-
22
18
  def read
23
19
  return if @stream.finished?
24
20
 
@@ -7,11 +7,21 @@
7
7
  module Protocol
8
8
  module HTTP
9
9
  module Body
10
- # An interface for reading data from a body.
10
+ # Represents a readable input streams.
11
11
  #
12
12
  # Typically, you'd override `#read` to return chunks of data.
13
+ #
14
+ # I n 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.
15
+ #
16
+ # Reading can also fail, for example if the body represents a streaming upload, and the connection is lost. In this case, the body will raise some kind of error.
17
+ #
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).
13
19
  class Readable
14
- # Close the stream immediately.
20
+ # Close the stream immediately. After invoking this method, the stream should be considered closed, and all internal resources should be released.
21
+ #
22
+ # 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
+ # Invoking `#read` after `#close` will return `nil`.
15
25
  def close(error = nil)
16
26
  end
17
27
 
@@ -29,62 +39,47 @@ module Protocol
29
39
  false
30
40
  end
31
41
 
42
+ # Whether the stream can be rewound using {rewind}.
32
43
  def rewindable?
33
44
  false
34
45
  end
35
46
 
47
+ # Rewind the stream to the beginning.
48
+ # @returns [Boolean] Whether the stream was successfully rewound.
36
49
  def rewind
37
50
  false
38
51
  end
39
52
 
53
+ # The total length of the body, if known.
54
+ # @returns [Integer | Nil] The total length of the body, or `nil` if the length is unknown.
40
55
  def length
41
56
  nil
42
57
  end
43
58
 
44
59
  # Read the next available chunk.
45
60
  # @returns [String | Nil] The chunk of data, or `nil` if the stream has finished.
61
+ # @raises [StandardError] If an error occurs while reading.
46
62
  def read
47
63
  nil
48
64
  end
49
65
 
50
- # Should the internal mechanism prefer to use {call}?
51
- # @returns [Boolean]
52
- def stream?
53
- false
54
- end
55
-
56
- # Write the body to the given stream.
57
- def call(stream)
58
- while chunk = self.read
59
- stream.write(chunk)
60
-
61
- # Flush the stream unless we are immediately expecting more data:
62
- unless self.ready?
63
- stream.flush
64
- end
65
- end
66
- end
67
-
68
- # Read all remaining chunks into a buffered body and close the underlying input.
69
- # @returns [Buffered] The buffered body.
70
- def finish
71
- # Internally, this invokes `self.each` which then invokes `self.close`.
72
- Buffered.read(self)
73
- end
74
-
75
66
  # Enumerate all chunks until finished, then invoke `#close`.
76
67
  #
68
+ # Closes the stream when finished or if an error occurs.
69
+ #
77
70
  # @yields {|chunk| ...} The block to call with each chunk of data.
78
71
  # @parameter chunk [String | Nil] The chunk of data, or `nil` if the stream has finished.
79
72
  def each
80
- return to_enum(:each) unless block_given?
73
+ return to_enum unless block_given?
81
74
 
82
75
  begin
83
76
  while chunk = self.read
84
77
  yield chunk
85
78
  end
79
+ rescue => error
80
+ raise
86
81
  ensure
87
- self.close($!)
82
+ self.close(error)
88
83
  end
89
84
  end
90
85
 
@@ -105,6 +100,39 @@ module Protocol
105
100
  end
106
101
  end
107
102
 
103
+ def stream?
104
+ false
105
+ end
106
+
107
+ # Invoke the body with the given stream.
108
+ #
109
+ # The default implementation simply writes each chunk to the stream. If the body is not ready, it will be flushed after each chunk. Closes the stream when finished or if an error occurs.
110
+ #
111
+ # Write the body to the given stream.
112
+ #
113
+ # @parameter stream [IO | Object] An `IO`-like object that responds to `#read`, `#write` and `#flush`.
114
+ # @returns [Boolean] Whether the ownership of the stream was transferred.
115
+ def call(stream)
116
+ self.each do |chunk|
117
+ stream.write(chunk)
118
+
119
+ # Flush the stream unless we are immediately expecting more data:
120
+ unless self.ready?
121
+ stream.flush
122
+ end
123
+ end
124
+ ensure
125
+ stream.close
126
+ end
127
+
128
+ # Read all remaining chunks into a buffered body and close the underlying input.
129
+ #
130
+ # @returns [Buffered] The buffered body.
131
+ def finish
132
+ # Internally, this invokes `self.each` which then invokes `self.close`.
133
+ Buffered.read(self)
134
+ end
135
+
108
136
  def as_json(...)
109
137
  {
110
138
  class: self.class.name,
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2023, by Samuel Williams.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'wrapper'
7
7
  require_relative 'buffered'
@@ -43,10 +43,6 @@ module Protocol
43
43
  Buffered.new(@chunks)
44
44
  end
45
45
 
46
- def stream?
47
- false
48
- end
49
-
50
46
  def read
51
47
  if @index < @chunks.size
52
48
  chunk = @chunks[@index]
@@ -21,6 +21,7 @@ module Protocol
21
21
 
22
22
  # Will hold remaining data in `#read`.
23
23
  @buffer = nil
24
+
24
25
  @closed = false
25
26
  @closed_read = false
26
27
  end
@@ -251,21 +252,22 @@ module Protocol
251
252
  end
252
253
 
253
254
  # Close the input body.
254
- def close_read
255
- if @input
255
+ def close_read(error = nil)
256
+ if input = @input
257
+ @input = nil
256
258
  @closed_read = true
257
259
  @buffer = nil
258
260
 
259
- @input&.close
260
- @input = nil
261
+ input.close(error)
261
262
  end
262
263
  end
263
264
 
264
265
  # Close the output body.
265
- def close_write
266
- if @output
267
- @output&.close
266
+ def close_write(error = nil)
267
+ if output = @output
268
268
  @output = nil
269
+
270
+ output.close_write(error)
269
271
  end
270
272
  end
271
273
 
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
+
6
+ require_relative 'readable'
7
+ require_relative 'writable'
8
+
9
+ require_relative 'stream'
10
+
11
+ module Protocol
12
+ module HTTP
13
+ module Body
14
+ # A body that invokes a block that can read and write to a stream.
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`.
17
+ #
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
+ module Streamable
20
+ def self.new(*arguments)
21
+ if arguments.size == 1
22
+ DeferredBody.new(*arguments)
23
+ else
24
+ Body.new(*arguments)
25
+ end
26
+ end
27
+
28
+ def self.request(&block)
29
+ DeferredBody.new(block)
30
+ end
31
+
32
+ def self.response(request, &block)
33
+ Body.new(block, request.body)
34
+ end
35
+
36
+ class Output
37
+ def self.schedule(input, block)
38
+ self.new(input, block).tap(&:schedule)
39
+ end
40
+
41
+ def initialize(input, block)
42
+ @output = Writable.new
43
+ @stream = Stream.new(input, @output)
44
+ @block = block
45
+ end
46
+
47
+ def schedule
48
+ @fiber ||= Fiber.schedule do
49
+ @block.call(@stream)
50
+ end
51
+ end
52
+
53
+ def read
54
+ @output.read
55
+ end
56
+
57
+ def close(error = nil)
58
+ @output.close_write(error)
59
+ end
60
+ end
61
+
62
+ # Raised when a streaming body is consumed more than once.
63
+ class ConsumedError < StandardError
64
+ end
65
+
66
+ class Body < Readable
67
+ def initialize(block, input = nil)
68
+ @block = block
69
+ @input = input
70
+ @output = nil
71
+ end
72
+
73
+ def stream?
74
+ true
75
+ end
76
+
77
+ # Invokes the block in a fiber which yields chunks when they are available.
78
+ def read
79
+ # We are reading chunk by chunk, allocate an output stream and execute the block to generate the chunks:
80
+ if @output.nil?
81
+ if @block.nil?
82
+ raise ConsumedError, "Streaming body has already been consumed!"
83
+ end
84
+
85
+ @output = Output.schedule(@input, @block)
86
+ @block = nil
87
+ end
88
+
89
+ @output.read
90
+ end
91
+
92
+ # Invoke the block with the given stream.
93
+ #
94
+ # The block can read and write to the stream, and must close the stream when finishing.
95
+ def call(stream)
96
+ if @block.nil?
97
+ raise ConsumedError, "Streaming block has already been consumed!"
98
+ end
99
+
100
+ block = @block
101
+
102
+ @input = @output = @block = nil
103
+
104
+ # Ownership of the stream is passed into the block, in other words, the block is responsible for closing the stream.
105
+ block.call(stream)
106
+ rescue => error
107
+ # If, for some reason, the block raises an error, we assume it may not have closed the stream, so we close it here:
108
+ stream.close
109
+ raise
110
+ end
111
+
112
+ # Closing a stream indicates we are no longer interested in reading from it.
113
+ def close(error = nil)
114
+ if output = @output
115
+ @output = nil
116
+ # Closing the output here may take some time, as it may need to finish handling the stream:
117
+ output.close(error)
118
+ end
119
+
120
+ if input = @input
121
+ @input = nil
122
+ input.close(error)
123
+ end
124
+ end
125
+ end
126
+
127
+ # A deferred body has an extra `stream` method which can be used to stream data into the body, as the response body won't be available until the request has been sent.
128
+ class DeferredBody < Body
129
+ def initialize(block)
130
+ super(block, Writable.new)
131
+ end
132
+
133
+ # Closing a stream indicates we are no longer interested in reading from it, but in this case that does not mean that the output block is finished generating data.
134
+ def close(error = nil)
135
+ if error
136
+ super
137
+ end
138
+ end
139
+
140
+ # Stream the response body into the block's input.
141
+ def stream(body)
142
+ body&.each do |chunk|
143
+ @input.write(chunk)
144
+ end
145
+ rescue => error
146
+ raise
147
+ ensure
148
+ @input.close_write(error)
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -27,15 +27,11 @@ module Protocol
27
27
  # The wrapped body.
28
28
  attr :body
29
29
 
30
- # Buffer any remaining body.
31
- def finish
32
- @body.finish
33
- end
34
-
35
30
  def close(error = nil)
36
31
  @body.close(error)
37
32
 
38
- super
33
+ # It's a no-op:
34
+ # super
39
35
  end
40
36
 
41
37
  def empty?
@@ -77,14 +73,6 @@ module Protocol
77
73
  def inspect
78
74
  @body.inspect
79
75
  end
80
-
81
- def stream?
82
- @body.stream?
83
- end
84
-
85
- def call(stream)
86
- @body.call(stream)
87
- end
88
76
  end
89
77
  end
90
78
  end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require_relative 'readable'
7
+
8
+ module Protocol
9
+ module HTTP
10
+ module Body
11
+ # A dynamic body which you can write to and read from.
12
+ class Writable < Readable
13
+ class Closed < StandardError
14
+ end
15
+
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.
18
+ def initialize(length = nil, queue: Thread::Queue.new)
19
+ @length = length
20
+ @queue = queue
21
+ @count = 0
22
+ @error = nil
23
+ end
24
+
25
+ def length
26
+ @length
27
+ end
28
+
29
+ # 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.
30
+ def close(error = nil)
31
+ @error ||= error
32
+
33
+ @queue.clear
34
+ @queue.close
35
+
36
+ super
37
+ end
38
+
39
+ def closed?
40
+ @queue.closed?
41
+ end
42
+
43
+ def ready?
44
+ !@queue.empty? || @queue.closed?
45
+ end
46
+
47
+ # Has the producer called #finish and has the reader consumed the nil token?
48
+ def empty?
49
+ @queue.empty? && @queue.closed?
50
+ end
51
+
52
+ # Read the next available chunk.
53
+ def read
54
+ if @error
55
+ raise @error
56
+ end
57
+
58
+ @queue.pop
59
+ end
60
+
61
+ # Write a single chunk to the body. Signal completion by calling `#finish`.
62
+ def write(chunk)
63
+ if @queue.closed?
64
+ raise(@error || Closed)
65
+ end
66
+
67
+ @queue.push(chunk)
68
+ @count += 1
69
+ end
70
+
71
+ def close_write(error = nil)
72
+ @error ||= error
73
+ @queue.close
74
+ end
75
+
76
+ class Output
77
+ def initialize(writable)
78
+ @writable = writable
79
+ @closed = false
80
+ end
81
+
82
+ def closed?
83
+ @closed || @writable.closed?
84
+ end
85
+
86
+ def write(chunk)
87
+ @writable.write(chunk)
88
+ end
89
+
90
+ alias << write
91
+
92
+ def close(error = nil)
93
+ @closed = true
94
+
95
+ if error
96
+ @writable.close(error)
97
+ else
98
+ @writable.close_write
99
+ end
100
+ end
101
+ end
102
+
103
+ # Create an output wrapper which can be used to write chunks to the body.
104
+ def output
105
+ output = Output.new(self)
106
+
107
+ unless block_given?
108
+ return output
109
+ end
110
+
111
+ begin
112
+ yield output
113
+ rescue => error
114
+ raise error
115
+ ensure
116
+ output.close(error)
117
+ end
118
+ end
119
+
120
+ def inspect
121
+ "\#<#{self.class} #{@count} chunks written, #{status}>"
122
+ end
123
+
124
+ private
125
+
126
+ def status
127
+ if @queue.empty?
128
+ if @queue.closed?
129
+ 'closed'
130
+ else
131
+ 'waiting'
132
+ end
133
+ else
134
+ if @queue.closed?
135
+ 'closing'
136
+ else
137
+ 'ready'
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Protocol
7
7
  module HTTP
8
- VERSION = "0.32.0"
8
+ VERSION = "0.34.0"
9
9
  end
10
10
  end
data/readme.md CHANGED
@@ -14,6 +14,8 @@ Provides abstractions for working with the HTTP protocol.
14
14
 
15
15
  Please see the [project documentation](https://socketry.github.io/protocol-http/) for more details.
16
16
 
17
+ - [Streaming](https://socketry.github.io/protocol-http/guides/streaming/index) - This guide gives an overview of how to implement streaming requests and responses.
18
+
17
19
  - [Getting Started](https://socketry.github.io/protocol-http/guides/getting-started/index) - This guide explains how to use `protocol-http` for building abstract HTTP interfaces.
18
20
 
19
21
  - [Design Overview](https://socketry.github.io/protocol-http/guides/design-overview/index) - This guide explains the high level design of `protocol-http` in the context of wider design patterns that can be used to implement HTTP clients and servers.
@@ -22,6 +24,11 @@ Please see the [project documentation](https://socketry.github.io/protocol-http/
22
24
 
23
25
  Please see the [project releases](https://socketry.github.io/protocol-http/releases/index) for all releases.
24
26
 
27
+ ### v0.33.0
28
+
29
+ - Clarify behaviour of streaming bodies and copy `Protocol::Rack::Body::Streaming` to `Protocol::HTTP::Body::Streamable`.
30
+ - Copy `Async::HTTP::Body::Writable` to `Protocol::HTTP::Body::Writable`.
31
+
25
32
  ### v0.31.0
26
33
 
27
34
  - Ensure chunks are flushed if required, when streaming.
data/releases.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Releases
2
2
 
3
+ ## v0.33.0
4
+
5
+ - Clarify behaviour of streaming bodies and copy `Protocol::Rack::Body::Streaming` to `Protocol::HTTP::Body::Streamable`.
6
+ - Copy `Async::HTTP::Body::Writable` to `Protocol::HTTP::Body::Writable`.
7
+
3
8
  ## v0.31.0
4
9
 
5
10
  - Ensure chunks are flushed if required, when streaming.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.32.0
4
+ version: 0.34.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -47,7 +47,7 @@ cert_chain:
47
47
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
48
48
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
49
49
  -----END CERTIFICATE-----
50
- date: 2024-09-03 00:00:00.000000000 Z
50
+ date: 2024-09-10 00:00:00.000000000 Z
51
51
  dependencies: []
52
52
  description:
53
53
  email:
@@ -68,7 +68,9 @@ files:
68
68
  - lib/protocol/http/body/reader.rb
69
69
  - lib/protocol/http/body/rewindable.rb
70
70
  - lib/protocol/http/body/stream.rb
71
+ - lib/protocol/http/body/streamable.rb
71
72
  - lib/protocol/http/body/wrapper.rb
73
+ - lib/protocol/http/body/writable.rb
72
74
  - lib/protocol/http/content_encoding.rb
73
75
  - lib/protocol/http/cookie.rb
74
76
  - lib/protocol/http/error.rb
metadata.gz.sig CHANGED
@@ -1 +1 @@
1
- ��gEEs|� �s�ʍ8�@6��*�������`��lo��� ï��W~E��E �|q ��u����(j�o�� r�;��*�A���Yώ�ts��1U_ģ���*��M#�ˊ��)���ČS`�D���<ʱxe�26_��/��C�|\OA��6/oH�����L h1��������;7������B��H)�5����Q�o�C�֠�`!a�:��2 �y>$��q��•���;��Tf�wlH^� nf�O��E6)3)�ѿƆJ���ˉ�ji���6��/�w�Av\){���%����#�P��R��M�Lݢ�d�|��2���?��� �H�Pf�Lx�,��������B��d��Vd�y�@��j�(�=
1
+ ik� �ܨ�9"Z�L�����A�NA�J���ƾM��b����|(UU�Lj�%�c��o�o���j+�V����T@� f2Iw���C4a~�ф���1�S�>�_���{��U������Cw��4�+�>�!��g����MK/�h"��H