async-http 0.23.3 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/async-http.gemspec +1 -1
- data/lib/async/http/accept_encoding.rb +1 -3
- data/lib/async/http/body/file.rb +76 -0
- data/lib/async/http/body/fixed.rb +3 -0
- data/lib/async/http/body/readable.rb +4 -0
- data/lib/async/http/client.rb +1 -1
- data/lib/async/http/headers.rb +108 -12
- data/lib/async/http/protocol/http10.rb +5 -1
- data/lib/async/http/protocol/http11.rb +28 -5
- data/lib/async/http/protocol/http2.rb +12 -11
- data/lib/async/http/url_endpoint.rb +5 -1
- data/lib/async/http/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2e35c6673eda6e9dd937957b447502b7d6ad4e715508185ead1d9ef2f03db7b3
|
|
4
|
+
data.tar.gz: 597da90259cdb9611a5b70701d6eefc2ede3deaee51f905bdbbc78cd14feffd8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 03ab49f9a1363ca6b2ee627a4ea437ead245103f7d6ec0feec290ea25a3ef7601f5afa19417c8fe0656c9b4bdde89868d051babec2234c35c1908ebacf41ccb6
|
|
7
|
+
data.tar.gz: a7e24ce9ba42e98bd8ebcfeaf043a1b7e3748b8e50400365a8a1ec6214dc20f33b46ad553e3156bb320fa376d0fb1c0a030d50b7fbee3bc4ef69187f01af8dab
|
data/async-http.gemspec
CHANGED
|
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
|
|
|
17
17
|
spec.require_paths = ["lib"]
|
|
18
18
|
|
|
19
19
|
spec.add_dependency("async", "~> 1.6")
|
|
20
|
-
spec.add_dependency("async-io", "~> 1.
|
|
20
|
+
spec.add_dependency("async-io", "~> 1.12")
|
|
21
21
|
|
|
22
22
|
spec.add_dependency("http-2", "~> 0.9.0")
|
|
23
23
|
# spec.add_dependency("openssl")
|
|
@@ -44,12 +44,10 @@ module Async
|
|
|
44
44
|
response = super
|
|
45
45
|
|
|
46
46
|
if !response.body.empty? and content_encoding = response.headers['content-encoding']
|
|
47
|
-
encodings = content_encoding.split(/\s*,\s*/)
|
|
48
|
-
|
|
49
47
|
body = response.body
|
|
50
48
|
|
|
51
49
|
# We want to unwrap all encodings
|
|
52
|
-
|
|
50
|
+
content_encoding.reverse_each do |name|
|
|
53
51
|
if wrapper = @wrappers[name]
|
|
54
52
|
body = wrapper.call(body)
|
|
55
53
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
|
11
|
+
# all copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
# THE SOFTWARE.
|
|
20
|
+
|
|
21
|
+
require_relative 'readable'
|
|
22
|
+
|
|
23
|
+
module Async
|
|
24
|
+
module HTTP
|
|
25
|
+
module Body
|
|
26
|
+
class File < Readable
|
|
27
|
+
def initialize(path, range = nil, block_size: Async::IO::Stream::BLOCK_SIZE)
|
|
28
|
+
@path = path
|
|
29
|
+
@file = File.open(path)
|
|
30
|
+
|
|
31
|
+
@block_size = block_size
|
|
32
|
+
|
|
33
|
+
if range
|
|
34
|
+
@offset = range.min
|
|
35
|
+
@length = @remaining = range.size
|
|
36
|
+
else
|
|
37
|
+
@offset = 0
|
|
38
|
+
@length = @remaining = @file.size
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
attr :length
|
|
43
|
+
|
|
44
|
+
def empty?
|
|
45
|
+
@remaining == 0
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def read
|
|
49
|
+
if @remaining > 0
|
|
50
|
+
amount = [@remaining, @block_size].min
|
|
51
|
+
|
|
52
|
+
if chunk = @file.read(amount)
|
|
53
|
+
@remaining -= chunk.bytesize
|
|
54
|
+
|
|
55
|
+
return chunk
|
|
56
|
+
else
|
|
57
|
+
@file.close
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def join
|
|
63
|
+
buffer = @file.read(@remaining)
|
|
64
|
+
|
|
65
|
+
@remaining = 0
|
|
66
|
+
|
|
67
|
+
return buffer
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def inspect
|
|
71
|
+
"\#<#{self.class} path=#{@path} offset=#{@offset} remaining=#{@remaining}>"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
data/lib/async/http/client.rb
CHANGED
|
@@ -63,7 +63,7 @@ module Async
|
|
|
63
63
|
request.authority ||= @authority
|
|
64
64
|
attempt = 0
|
|
65
65
|
|
|
66
|
-
#
|
|
66
|
+
# We may retry the request if it is possible to do so. https://tools.ietf.org/html/draft-nottingham-httpbis-retry-01 is a good guide for how retrying requests should work.
|
|
67
67
|
begin
|
|
68
68
|
attempt += 1
|
|
69
69
|
|
data/lib/async/http/headers.rb
CHANGED
|
@@ -21,6 +21,38 @@
|
|
|
21
21
|
module Async
|
|
22
22
|
module HTTP
|
|
23
23
|
class Headers
|
|
24
|
+
class Split < Array
|
|
25
|
+
COMMA = /\s*,\s*/
|
|
26
|
+
|
|
27
|
+
def initialize(value)
|
|
28
|
+
super(value.split(COMMA))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def << value
|
|
32
|
+
super value.split(COMMA)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_s
|
|
36
|
+
join(", ")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class Multiple < Array
|
|
41
|
+
def initialize(value)
|
|
42
|
+
super()
|
|
43
|
+
|
|
44
|
+
self << value
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_s
|
|
48
|
+
join("\n")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.[] hash
|
|
53
|
+
self.new(hash.to_a)
|
|
54
|
+
end
|
|
55
|
+
|
|
24
56
|
def initialize(fields = [])
|
|
25
57
|
@fields = fields
|
|
26
58
|
@indexed = to_h
|
|
@@ -36,6 +68,10 @@ module Async
|
|
|
36
68
|
super
|
|
37
69
|
end
|
|
38
70
|
|
|
71
|
+
def empty?
|
|
72
|
+
@fields.empty?
|
|
73
|
+
end
|
|
74
|
+
|
|
39
75
|
def each(&block)
|
|
40
76
|
@fields.each(&block)
|
|
41
77
|
end
|
|
@@ -59,17 +95,69 @@ module Async
|
|
|
59
95
|
end
|
|
60
96
|
end
|
|
61
97
|
|
|
98
|
+
def slice!(keys)
|
|
99
|
+
values, @fields = @fields.partition do |field|
|
|
100
|
+
keys.include?(field.first.downcase)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
if @indexed
|
|
104
|
+
keys.each do |key|
|
|
105
|
+
@indexed.delete(key)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def add(key, value)
|
|
111
|
+
self[key] = value
|
|
112
|
+
end
|
|
113
|
+
|
|
62
114
|
def []= key, value
|
|
63
115
|
@fields << [key, value]
|
|
64
116
|
|
|
65
117
|
if @indexed
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
118
|
+
# It would be good to do some kind of validation here.
|
|
119
|
+
merge(@indexed, key.downcase, value)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
MERGE_POLICY = {
|
|
124
|
+
# Headers which may only be specified once.
|
|
125
|
+
'content-type' => false,
|
|
126
|
+
'content-disposition' => false,
|
|
127
|
+
'content-length' => false,
|
|
128
|
+
'user-agent' => false,
|
|
129
|
+
'referer' => false,
|
|
130
|
+
'host' => false,
|
|
131
|
+
'authorization' => false,
|
|
132
|
+
'proxy-authorization' => false,
|
|
133
|
+
'if-modified-since' => false,
|
|
134
|
+
'if-unmodified-since' => false,
|
|
135
|
+
'from' => false,
|
|
136
|
+
'location' => false,
|
|
137
|
+
'max-forwards' => false,
|
|
138
|
+
|
|
139
|
+
'connection' => Split,
|
|
140
|
+
|
|
141
|
+
# Headers specifically for proxies:
|
|
142
|
+
'via' => Split,
|
|
143
|
+
'x-forwarded-for' => Split,
|
|
144
|
+
|
|
145
|
+
# Headers which may be specified multiple times, but which can't be concatenated.
|
|
146
|
+
'set-cookie' => Multiple,
|
|
147
|
+
'www-authenticate' => Multiple,
|
|
148
|
+
'proxy-authenticate' => Multiple
|
|
149
|
+
}.tap{|hash| hash.default = Split}
|
|
150
|
+
|
|
151
|
+
def merge(hash, key, value)
|
|
152
|
+
if policy = MERGE_POLICY[key]
|
|
153
|
+
if current_value = hash[key]
|
|
154
|
+
current_value << value
|
|
70
155
|
else
|
|
71
|
-
|
|
156
|
+
hash[key] = policy.new(value)
|
|
72
157
|
end
|
|
158
|
+
else
|
|
159
|
+
# We can't merge these, we only expose the last one set.
|
|
160
|
+
hash[key] = value
|
|
73
161
|
end
|
|
74
162
|
end
|
|
75
163
|
|
|
@@ -81,13 +169,7 @@ module Async
|
|
|
81
169
|
|
|
82
170
|
def to_h
|
|
83
171
|
@fields.inject({}) do |hash, (key, value)|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if current_value = hash[key]
|
|
87
|
-
hash[key] = Array(current_value) << value
|
|
88
|
-
else
|
|
89
|
-
hash[key] = value
|
|
90
|
-
end
|
|
172
|
+
merge(hash, key.downcase, value)
|
|
91
173
|
|
|
92
174
|
hash
|
|
93
175
|
end
|
|
@@ -100,6 +182,20 @@ module Async
|
|
|
100
182
|
@fields == other.fields
|
|
101
183
|
end
|
|
102
184
|
end
|
|
185
|
+
|
|
186
|
+
class Merged
|
|
187
|
+
def initialize(*all)
|
|
188
|
+
@all = all
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def each(&block)
|
|
192
|
+
@all.each do |headers|
|
|
193
|
+
headers.each do |key, value|
|
|
194
|
+
yield key, value.to_s
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
103
199
|
end
|
|
104
200
|
end
|
|
105
201
|
end
|
|
@@ -38,7 +38,6 @@ module Async
|
|
|
38
38
|
CONNECTION = 'connection'.freeze
|
|
39
39
|
HOST = 'host'.freeze
|
|
40
40
|
CLOSE = 'close'.freeze
|
|
41
|
-
|
|
42
41
|
VERSION = "HTTP/1.1".freeze
|
|
43
42
|
|
|
44
43
|
def initialize(stream)
|
|
@@ -74,7 +73,11 @@ module Async
|
|
|
74
73
|
end
|
|
75
74
|
|
|
76
75
|
def persistent?(headers)
|
|
77
|
-
headers
|
|
76
|
+
if connection = headers[CONNECTION]
|
|
77
|
+
return !connection.include?(CLOSE)
|
|
78
|
+
else
|
|
79
|
+
return true
|
|
80
|
+
end
|
|
78
81
|
end
|
|
79
82
|
|
|
80
83
|
# Server loop.
|
|
@@ -193,6 +196,12 @@ module Async
|
|
|
193
196
|
if body.nil? or body.empty?
|
|
194
197
|
@stream.write("Content-Length: 0\r\n\r\n")
|
|
195
198
|
body.read if body
|
|
199
|
+
elsif length = body.length
|
|
200
|
+
@stream.write("Content-Length: #{length}\r\n\r\n")
|
|
201
|
+
|
|
202
|
+
body.each do |chunk|
|
|
203
|
+
@stream.write(chunk)
|
|
204
|
+
end
|
|
196
205
|
elsif chunked
|
|
197
206
|
@stream.write("Transfer-Encoding: chunked\r\n\r\n")
|
|
198
207
|
|
|
@@ -219,11 +228,25 @@ module Async
|
|
|
219
228
|
@stream.flush
|
|
220
229
|
end
|
|
221
230
|
|
|
231
|
+
TRANSFER_ENCODING = 'transfer-encoding'.freeze
|
|
232
|
+
CONTENT_LENGTH = 'content-length'.freeze
|
|
233
|
+
CHUNKED = 'chunked'.freeze
|
|
234
|
+
|
|
235
|
+
def chunked?(headers)
|
|
236
|
+
if transfer_encoding = headers[TRANSFER_ENCODING]
|
|
237
|
+
if transfer_encoding.count == 1
|
|
238
|
+
return transfer_encoding.first == CHUNKED
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
222
243
|
def read_body(headers)
|
|
223
|
-
if headers
|
|
244
|
+
if chunked?(headers)
|
|
224
245
|
return Body::Chunked.new(self)
|
|
225
|
-
elsif content_length = headers
|
|
226
|
-
|
|
246
|
+
elsif content_length = headers[CONTENT_LENGTH]
|
|
247
|
+
if content_length != 0
|
|
248
|
+
return Body::Fixed.new(@stream, Integer(content_length))
|
|
249
|
+
end
|
|
227
250
|
end
|
|
228
251
|
end
|
|
229
252
|
end
|
|
@@ -66,11 +66,12 @@ module Async
|
|
|
66
66
|
Async.logger.debug(self) {"Received frame: #{frame.inspect}"}
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
@goaway = false
|
|
70
|
+
|
|
69
71
|
@controller.on(:goaway) do |payload|
|
|
70
72
|
Async.logger.error(self) {"goaway: #{payload.inspect}"}
|
|
71
73
|
|
|
72
|
-
@
|
|
73
|
-
@stream.close
|
|
74
|
+
@goaway = true
|
|
74
75
|
end
|
|
75
76
|
|
|
76
77
|
@count = 0
|
|
@@ -89,7 +90,7 @@ module Async
|
|
|
89
90
|
end
|
|
90
91
|
|
|
91
92
|
def reusable?
|
|
92
|
-
!@stream.closed?
|
|
93
|
+
!@goaway || !@stream.closed?
|
|
93
94
|
end
|
|
94
95
|
|
|
95
96
|
def version
|
|
@@ -197,19 +198,19 @@ module Async
|
|
|
197
198
|
stream = @controller.new_stream
|
|
198
199
|
@count += 1
|
|
199
200
|
|
|
200
|
-
headers = {
|
|
201
|
+
headers = Headers::Merged.new({
|
|
201
202
|
SCHEME => HTTPS,
|
|
202
|
-
METHOD => request.method
|
|
203
|
-
PATH => request.path
|
|
204
|
-
AUTHORITY => request.authority
|
|
205
|
-
}
|
|
203
|
+
METHOD => request.method,
|
|
204
|
+
PATH => request.path,
|
|
205
|
+
AUTHORITY => request.authority,
|
|
206
|
+
}, request.headers)
|
|
206
207
|
|
|
207
208
|
finished = Async::Notification.new
|
|
208
209
|
|
|
209
210
|
exception = nil
|
|
210
211
|
response = Response.new
|
|
211
212
|
response.version = self.version
|
|
212
|
-
response.headers =
|
|
213
|
+
response.headers = Headers.new
|
|
213
214
|
body = Body::Writable.new
|
|
214
215
|
response.body = body
|
|
215
216
|
|
|
@@ -254,7 +255,7 @@ module Async
|
|
|
254
255
|
request.body.read if request.body
|
|
255
256
|
else
|
|
256
257
|
begin
|
|
257
|
-
stream.headers(headers
|
|
258
|
+
stream.headers(headers)
|
|
258
259
|
rescue
|
|
259
260
|
raise RequestFailed.new
|
|
260
261
|
end
|
|
@@ -263,7 +264,7 @@ module Async
|
|
|
263
264
|
stream.data(chunk, end_stream: false)
|
|
264
265
|
end
|
|
265
266
|
|
|
266
|
-
stream.data(""
|
|
267
|
+
stream.data("")
|
|
267
268
|
end
|
|
268
269
|
|
|
269
270
|
start_connection
|
|
@@ -94,9 +94,13 @@ module Async
|
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
+
def tcp_options
|
|
98
|
+
{reuse_port: @options[:reuse_port] ? true : false}
|
|
99
|
+
end
|
|
100
|
+
|
|
97
101
|
def endpoint
|
|
98
102
|
unless @endpoint
|
|
99
|
-
@endpoint = Async::IO::Endpoint.tcp(hostname, port)
|
|
103
|
+
@endpoint = Async::IO::Endpoint.tcp(hostname, port, tcp_options)
|
|
100
104
|
|
|
101
105
|
if secure?
|
|
102
106
|
# Wrap it in SSL:
|
data/lib/async/http/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: async-http
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.24.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -30,14 +30,14 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '1.
|
|
33
|
+
version: '1.12'
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '1.
|
|
40
|
+
version: '1.12'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: http-2
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -128,6 +128,7 @@ files:
|
|
|
128
128
|
- lib/async/http/body/buffered.rb
|
|
129
129
|
- lib/async/http/body/chunked.rb
|
|
130
130
|
- lib/async/http/body/deflate.rb
|
|
131
|
+
- lib/async/http/body/file.rb
|
|
131
132
|
- lib/async/http/body/fixed.rb
|
|
132
133
|
- lib/async/http/body/inflate.rb
|
|
133
134
|
- lib/async/http/body/readable.rb
|