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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/protocol/http/body/buffered.rb +23 -2
- data/lib/protocol/http/body/completable.rb +0 -9
- data/lib/protocol/http/body/deflate.rb +1 -7
- data/lib/protocol/http/body/digestable.rb +1 -5
- data/lib/protocol/http/body/file.rb +11 -5
- data/lib/protocol/http/body/inflate.rb +1 -5
- data/lib/protocol/http/body/readable.rb +57 -29
- data/lib/protocol/http/body/rewindable.rb +1 -5
- data/lib/protocol/http/body/stream.rb +9 -7
- data/lib/protocol/http/body/streamable.rb +154 -0
- data/lib/protocol/http/body/wrapper.rb +2 -14
- data/lib/protocol/http/body/writable.rb +144 -0
- data/lib/protocol/http/version.rb +1 -1
- data/readme.md +7 -0
- data/releases.md +5 -0
- data.tar.gz.sig +0 -0
- metadata +4 -2
- metadata.gz.sig +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb1197435097557cab74b1b61b92fe0a15a938892dd07486ee9f9db06360decd
|
4
|
+
data.tar.gz: 78bcbbd73817b03ce63b55b2bbd4408afeaeaa127e3c19f9dfd5d6ddb120f05d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
114
|
+
if @chunks
|
115
|
+
"\#<#{self.class} #{@chunks.size} chunks, #{self.length} bytes>"
|
116
|
+
end
|
96
117
|
end
|
97
118
|
end
|
98
119
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
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-
|
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-
|
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-
|
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
|
-
#
|
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
|
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-
|
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
|
-
|
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
|
-
|
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
|
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.
|
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-
|
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
|
-
��
|
1
|
+
ik� �ܨ�9"Z�L�����A�NA�J���ƾM��b����|(U�U�Lj�%�c��o�o���j+�V����T@� f2I�w���C�4�a~�ф���1�S�>�_���{��U������Cw��4�+�>�!��g����MK/�h"��H
|