protocol-http 0.31.0 → 0.33.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/completable.rb +0 -9
- data/lib/protocol/http/body/deflate.rb +0 -6
- data/lib/protocol/http/body/digestable.rb +0 -4
- data/lib/protocol/http/body/file.rb +8 -4
- data/lib/protocol/http/body/inflate.rb +0 -4
- data/lib/protocol/http/body/readable.rb +51 -35
- data/lib/protocol/http/body/rewindable.rb +1 -5
- data/lib/protocol/http/body/streamable.rb +113 -0
- data/lib/protocol/http/body/wrapper.rb +2 -14
- data/lib/protocol/http/body/writable.rb +103 -0
- data/lib/protocol/http/version.rb +1 -1
- data/readme.md +5 -0
- data/releases.md +5 -0
- data.tar.gz.sig +0 -0
- metadata +4 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1bf929d8d011f9c79cf7b92bdf8f5eaefcd4faab288b555a7889f33a995daf2e
|
4
|
+
data.tar.gz: 4de5349ae1e3cee6fb4857dbac1c78ef7e44249e1adc4526674803d0524af8f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2214c233b4543e929f23ef59e8ffaed14f597c9762596ffd8afc8d9a1657fc6c50ace4dc1859a10033e2bba68e36d8f10a8486a056444676939d73352ed374f
|
7
|
+
data.tar.gz: 27d9318755a9a2027e21d144a361287adf9cc6f2990bcae4ce93cbd9767c125e6811a1d19c20cf2da5ec67f729132271c9e1744c6ea1c82a88c3fff795e71502
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -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
|
|
@@ -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,14 @@ 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
|
+
end
|
78
|
+
|
75
79
|
def join
|
76
80
|
return "" if @remaining == 0
|
77
81
|
|
@@ -7,9 +7,15 @@
|
|
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
20
|
# Close the stream immediately.
|
15
21
|
def close(error = nil)
|
@@ -29,65 +35,46 @@ module Protocol
|
|
29
35
|
false
|
30
36
|
end
|
31
37
|
|
38
|
+
# Whether the stream can be rewound using {rewind}.
|
32
39
|
def rewindable?
|
33
40
|
false
|
34
41
|
end
|
35
42
|
|
43
|
+
# Rewind the stream to the beginning.
|
44
|
+
# @returns [Boolean] Whether the stream was successfully rewound.
|
36
45
|
def rewind
|
37
46
|
false
|
38
47
|
end
|
39
48
|
|
49
|
+
# The total length of the body, if known.
|
50
|
+
# @returns [Integer | Nil] The total length of the body, or `nil` if the length is unknown.
|
40
51
|
def length
|
41
52
|
nil
|
42
53
|
end
|
43
54
|
|
44
55
|
# Read the next available chunk.
|
45
56
|
# @returns [String | Nil] The chunk of data, or `nil` if the stream has finished.
|
57
|
+
# @raises [StandardError] If an error occurs while reading.
|
46
58
|
def read
|
47
59
|
nil
|
48
60
|
end
|
49
61
|
|
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
|
-
ensure
|
67
|
-
stream.close
|
68
|
-
end
|
69
|
-
|
70
|
-
# Read all remaining chunks into a buffered body and close the underlying input.
|
71
|
-
# @returns [Buffered] The buffered body.
|
72
|
-
def finish
|
73
|
-
# Internally, this invokes `self.each` which then invokes `self.close`.
|
74
|
-
Buffered.read(self)
|
75
|
-
end
|
76
|
-
|
77
62
|
# Enumerate all chunks until finished, then invoke `#close`.
|
78
63
|
#
|
64
|
+
# Closes the stream when finished or if an error occurs.
|
65
|
+
#
|
79
66
|
# @yields {|chunk| ...} The block to call with each chunk of data.
|
80
67
|
# @parameter chunk [String | Nil] The chunk of data, or `nil` if the stream has finished.
|
81
68
|
def each
|
82
|
-
return to_enum
|
69
|
+
return to_enum unless block_given?
|
83
70
|
|
84
|
-
|
85
|
-
|
86
|
-
yield chunk
|
87
|
-
end
|
88
|
-
ensure
|
89
|
-
self.close($!)
|
71
|
+
while chunk = self.read
|
72
|
+
yield chunk
|
90
73
|
end
|
74
|
+
rescue => error
|
75
|
+
raise
|
76
|
+
ensure
|
77
|
+
self.close(error)
|
91
78
|
end
|
92
79
|
|
93
80
|
# Read all remaining chunks into a single binary string using `#each`.
|
@@ -107,6 +94,35 @@ module Protocol
|
|
107
94
|
end
|
108
95
|
end
|
109
96
|
|
97
|
+
def stream?
|
98
|
+
false
|
99
|
+
end
|
100
|
+
|
101
|
+
# Write the body to the given stream.
|
102
|
+
#
|
103
|
+
# In some cases, the stream may also be readable, such as when hijacking an HTTP/1 connection. In that case, it may be acceptable to read and write to the stream directly.
|
104
|
+
#
|
105
|
+
# If the stream is not ready, it will be flushed after each chunk. Closes the stream when finished or if an error occurs.
|
106
|
+
#
|
107
|
+
def call(stream)
|
108
|
+
self.each do |chunk|
|
109
|
+
stream.write(chunk)
|
110
|
+
|
111
|
+
# Flush the stream unless we are immediately expecting more data:
|
112
|
+
unless self.ready?
|
113
|
+
stream.flush
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Read all remaining chunks into a buffered body and close the underlying input.
|
119
|
+
#
|
120
|
+
# @returns [Buffered] The buffered body.
|
121
|
+
def finish
|
122
|
+
# Internally, this invokes `self.each` which then invokes `self.close`.
|
123
|
+
Buffered.read(self)
|
124
|
+
end
|
125
|
+
|
110
126
|
def as_json(...)
|
111
127
|
{
|
112
128
|
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]
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2022, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative 'readable'
|
7
|
+
require_relative 'stream'
|
8
|
+
|
9
|
+
module Protocol
|
10
|
+
module HTTP
|
11
|
+
module Body
|
12
|
+
# A body that invokes a block that can read and write to a stream.
|
13
|
+
#
|
14
|
+
# 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`.
|
15
|
+
#
|
16
|
+
# 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.
|
17
|
+
class Streamable < Readable
|
18
|
+
class Closed < StandardError
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(block, input = nil)
|
22
|
+
@block = block
|
23
|
+
@input = input
|
24
|
+
@output = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# Closing a stream indicates we are no longer interested in reading from it.
|
28
|
+
def close(error = nil)
|
29
|
+
if @input
|
30
|
+
@input.close
|
31
|
+
@input = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
if @output
|
35
|
+
@output.close(error)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
attr :block
|
40
|
+
|
41
|
+
class Output
|
42
|
+
def initialize(input, block)
|
43
|
+
stream = Stream.new(input, self)
|
44
|
+
|
45
|
+
@from = nil
|
46
|
+
|
47
|
+
@fiber = Fiber.new do |from|
|
48
|
+
@from = from
|
49
|
+
block.call(stream)
|
50
|
+
rescue Closed
|
51
|
+
# Ignore.
|
52
|
+
ensure
|
53
|
+
@fiber = nil
|
54
|
+
|
55
|
+
# No more chunks will be generated:
|
56
|
+
if from = @from
|
57
|
+
@from = nil
|
58
|
+
from.transfer(nil)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Can be invoked by the block to write to the stream.
|
64
|
+
def write(chunk)
|
65
|
+
if from = @from
|
66
|
+
@from = nil
|
67
|
+
@from = from.transfer(chunk)
|
68
|
+
else
|
69
|
+
raise RuntimeError, "Stream is not being read!"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Can be invoked by the block to close the stream.
|
74
|
+
def close(error = nil)
|
75
|
+
if from = @from
|
76
|
+
@from = nil
|
77
|
+
from.transfer(nil)
|
78
|
+
elsif @fiber
|
79
|
+
@fiber.raise(error || Closed)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def read
|
84
|
+
raise RuntimeError, "Stream is already being read!" if @from
|
85
|
+
|
86
|
+
@fiber&.transfer(Fiber.current)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Invokes the block in a fiber which yields chunks when they are available.
|
91
|
+
def read
|
92
|
+
@output ||= Output.new(@input, @block)
|
93
|
+
|
94
|
+
return @output.read
|
95
|
+
end
|
96
|
+
|
97
|
+
def stream?
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
def call(stream)
|
102
|
+
raise "Streaming body has already been read!" if @output
|
103
|
+
|
104
|
+
@block.call(stream)
|
105
|
+
rescue => error
|
106
|
+
raise
|
107
|
+
ensure
|
108
|
+
self.close(error)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
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,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2018-2023, 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
|
+
@queue = queue
|
20
|
+
|
21
|
+
@length = length
|
22
|
+
|
23
|
+
@count = 0
|
24
|
+
|
25
|
+
@finished = false
|
26
|
+
|
27
|
+
@closed = false
|
28
|
+
@error = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def length
|
32
|
+
@length
|
33
|
+
end
|
34
|
+
|
35
|
+
# 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.
|
36
|
+
def close(error = nil)
|
37
|
+
unless @closed
|
38
|
+
@queue.close
|
39
|
+
|
40
|
+
@closed = true
|
41
|
+
@error = error
|
42
|
+
end
|
43
|
+
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
def closed?
|
48
|
+
@closed
|
49
|
+
end
|
50
|
+
|
51
|
+
def ready?
|
52
|
+
!@queue.empty? || @queue.closed?
|
53
|
+
end
|
54
|
+
|
55
|
+
# Has the producer called #finish and has the reader consumed the nil token?
|
56
|
+
def empty?
|
57
|
+
@queue.empty? && @queue.closed?
|
58
|
+
end
|
59
|
+
|
60
|
+
# Read the next available chunk.
|
61
|
+
def read
|
62
|
+
@queue.pop
|
63
|
+
end
|
64
|
+
|
65
|
+
# Write a single chunk to the body. Signal completion by calling `#finish`.
|
66
|
+
def write(chunk)
|
67
|
+
# If the reader breaks, the writer will break.
|
68
|
+
# The inverse of this is less obvious (*)
|
69
|
+
if @closed
|
70
|
+
raise(@error || Closed)
|
71
|
+
end
|
72
|
+
|
73
|
+
@count += 1
|
74
|
+
@queue.push(chunk)
|
75
|
+
end
|
76
|
+
|
77
|
+
alias << write
|
78
|
+
|
79
|
+
def inspect
|
80
|
+
"\#<#{self.class} #{@count} chunks written, #{status}>"
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def status
|
86
|
+
if @queue.empty?
|
87
|
+
if @queue.closed?
|
88
|
+
'closed'
|
89
|
+
else
|
90
|
+
'waiting'
|
91
|
+
end
|
92
|
+
else
|
93
|
+
if @queue.closed?
|
94
|
+
'closing'
|
95
|
+
else
|
96
|
+
'ready'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/readme.md
CHANGED
@@ -22,6 +22,11 @@ Please see the [project documentation](https://socketry.github.io/protocol-http/
|
|
22
22
|
|
23
23
|
Please see the [project releases](https://socketry.github.io/protocol-http/releases/index) for all releases.
|
24
24
|
|
25
|
+
### v0.33.0
|
26
|
+
|
27
|
+
- Clarify behaviour of streaming bodies and copy `Protocol::Rack::Body::Streaming` to `Protocol::HTTP::Body::Streamable`.
|
28
|
+
- Copy `Async::HTTP::Body::Writable` to `Protocol::HTTP::Body::Writable`.
|
29
|
+
|
25
30
|
### v0.31.0
|
26
31
|
|
27
32
|
- 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.33.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-05 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
Binary file
|