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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/protocol/http/accept_encoding.rb +15 -3
- data/lib/protocol/http/body/buffered.rb +29 -2
- data/lib/protocol/http/body/completable.rb +13 -0
- data/lib/protocol/http/body/deflate.rb +33 -0
- data/lib/protocol/http/body/digestable.rb +19 -4
- data/lib/protocol/http/body/file.rb +37 -1
- data/lib/protocol/http/body/head.rb +8 -0
- data/lib/protocol/http/body/inflate.rb +10 -2
- data/lib/protocol/http/body/readable.rb +32 -11
- data/lib/protocol/http/body/reader.rb +17 -0
- data/lib/protocol/http/body/rewindable.rb +19 -1
- data/lib/protocol/http/body/stream.rb +34 -6
- data/lib/protocol/http/body/streamable.rb +46 -5
- data/lib/protocol/http/body/wrapper.rb +25 -3
- data/lib/protocol/http/body/writable.rb +48 -7
- data/lib/protocol/http/body.rb +16 -0
- data/lib/protocol/http/content_encoding.rb +13 -3
- data/lib/protocol/http/cookie.rb +23 -0
- data/lib/protocol/http/header/authorization.rb +10 -2
- data/lib/protocol/http/header/cache_control.rb +42 -10
- data/lib/protocol/http/header/connection.rb +18 -1
- data/lib/protocol/http/header/cookie.rb +10 -3
- data/lib/protocol/http/header/date.rb +9 -0
- data/lib/protocol/http/header/etag.rb +11 -0
- data/lib/protocol/http/header/etags.rb +35 -4
- data/lib/protocol/http/header/multiple.rb +9 -1
- data/lib/protocol/http/header/priority.rb +65 -0
- data/lib/protocol/http/header/split.rb +17 -3
- data/lib/protocol/http/header/vary.rb +10 -1
- data/lib/protocol/http/headers.rb +82 -19
- data/lib/protocol/http/methods.rb +5 -0
- data/lib/protocol/http/middleware/builder.rb +17 -0
- data/lib/protocol/http/middleware.rb +28 -0
- data/lib/protocol/http/peer.rb +9 -0
- data/lib/protocol/http/reference.rb +33 -6
- data/lib/protocol/http/request.rb +25 -0
- data/lib/protocol/http/response.rb +12 -0
- data/lib/protocol/http/url.rb +34 -8
- data/lib/protocol/http/version.rb +1 -1
- data/readme.md +4 -0
- data/releases.md +4 -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: 753df89051cc0eeff1cab5100a4ebc2a98e11bd3dce4a3fbcba890406c198c32
|
4
|
+
data.tar.gz: dbc6b80efe3fc8a430839868f160151a9490336cc39423e31cfd851076b72947
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 104b449c9a3beaaf7f3c50e28eb21df3f2bef95b89ec954bd20a808a09b508f69d8e0387d6b41ed8e7dc02e41a85bc1116ff8f775694c4e0b26c3899b248f310
|
7
|
+
data.tar.gz: 3ea2e87c3375055aa89f8d9b73b549854cb53ecb90bf6ee3f75ffc4c4ae8b7fbb61b10119fbdb0d125287aea03c894c7327b79ee410d28a771faab24e2fcafa5
|
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
|
-
#
|
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
|
-
|
26
|
-
|
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
|
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
|
-
#
|
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
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
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
|
-
|
15
|
-
|
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
|
-
#
|
12
|
+
# There are two major modes of operation:
|
13
13
|
#
|
14
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
30
|
-
# If this returns
|
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
|
-
#
|
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
|
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
|