async-http 0.30.1 → 0.30.2
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/lib/async/http/body/buffered.rb +2 -2
- data/lib/async/http/body/reader.rb +5 -0
- data/lib/async/http/body/streamable.rb +14 -3
- data/lib/async/http/headers.rb +4 -0
- data/lib/async/http/pool.rb +45 -24
- data/lib/async/http/protocol/http11.rb +31 -3
- data/lib/async/http/protocol/http2.rb +3 -1
- data/lib/async/http/protocol/http2/request.rb +14 -8
- data/lib/async/http/protocol/http2/response.rb +4 -4
- data/lib/async/http/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a937910aa49ee7d30c1941ac83a39f4f5ba2c5c52beec347b510dffd0ac2b40e
|
4
|
+
data.tar.gz: 62e34957a999e3e83636f2c00e6f9b6227264f1a7c0d00e69048bf7ff9ceab88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5256304facd1f375f6d668b10361d89262d5c367f50eff8d0c9f5c3d8ab6374a70c51a28997357b45bad40d4bb569d7daf3d7fb2a527a88735a8a10083abe2dc
|
7
|
+
data.tar.gz: c91d8eef348500108f6186bd80ba79eaaac72af42153f7a00fbb4ea05b6db91196b7b421755fe754e8ae6d05b2c560d6e2d49c172447cd2cbeb8dcd84fc77db8
|
@@ -27,16 +27,21 @@ module Async
|
|
27
27
|
class Streamable < Wrapper
|
28
28
|
def self.wrap(message, &block)
|
29
29
|
if message and message.body
|
30
|
-
|
30
|
+
if remaining = message.headers['content-length']
|
31
|
+
remaining = Integer(remaining)
|
32
|
+
end
|
33
|
+
|
34
|
+
message.body = self.new(message.body, block, remaining)
|
31
35
|
else
|
32
36
|
yield
|
33
37
|
end
|
34
38
|
end
|
35
39
|
|
36
|
-
def initialize(body, callback)
|
40
|
+
def initialize(body, callback, remaining = nil)
|
37
41
|
super(body)
|
38
42
|
|
39
43
|
@callback = callback
|
44
|
+
@remaining = remaining
|
40
45
|
end
|
41
46
|
|
42
47
|
def stop(*)
|
@@ -46,7 +51,13 @@ module Async
|
|
46
51
|
end
|
47
52
|
|
48
53
|
def read
|
49
|
-
|
54
|
+
if chunk = super
|
55
|
+
@remaining -= chunk.bytesize if @remaining
|
56
|
+
else
|
57
|
+
if @remaining and @remaining > 0
|
58
|
+
raise EOFError, "Expected #{@remaining} more bytes!"
|
59
|
+
end
|
60
|
+
|
50
61
|
@callback.call
|
51
62
|
end
|
52
63
|
|
data/lib/async/http/headers.rb
CHANGED
data/lib/async/http/pool.rb
CHANGED
@@ -18,6 +18,8 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
+
require 'async/notification'
|
22
|
+
|
21
23
|
module Async
|
22
24
|
module HTTP
|
23
25
|
# Pool behaviours
|
@@ -36,22 +38,23 @@ module Async
|
|
36
38
|
#
|
37
39
|
class Pool
|
38
40
|
def initialize(limit = nil, &block)
|
39
|
-
@
|
40
|
-
@
|
41
|
+
@resources = {} # resource => count
|
42
|
+
@available = Async::Notification.new
|
41
43
|
|
42
44
|
@limit = limit
|
45
|
+
@active = 0
|
43
46
|
|
44
47
|
@constructor = block
|
45
48
|
end
|
46
49
|
|
47
|
-
attr :
|
50
|
+
attr :resources
|
48
51
|
|
49
52
|
def empty?
|
50
|
-
@
|
53
|
+
@resources.empty?
|
51
54
|
end
|
52
55
|
|
53
56
|
def acquire
|
54
|
-
resource =
|
57
|
+
resource = wait_for_resource
|
55
58
|
|
56
59
|
return resource unless block_given?
|
57
60
|
|
@@ -62,7 +65,7 @@ module Async
|
|
62
65
|
end
|
63
66
|
end
|
64
67
|
|
65
|
-
# Make the resource
|
68
|
+
# Make the resource resources and let waiting tasks know that there is something resources.
|
66
69
|
def release(resource)
|
67
70
|
# A resource that is not good should also not be reusable.
|
68
71
|
if resource.reusable?
|
@@ -73,55 +76,69 @@ module Async
|
|
73
76
|
end
|
74
77
|
|
75
78
|
def close
|
76
|
-
@
|
77
|
-
@
|
79
|
+
@resources.each_key(&:close)
|
80
|
+
@resources.clear
|
81
|
+
|
82
|
+
@active = 0
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_s
|
86
|
+
"\#<#{self.class} resources=#{availability_string} limit=#{@limit}>"
|
78
87
|
end
|
79
88
|
|
80
89
|
protected
|
81
90
|
|
91
|
+
def availability_string
|
92
|
+
@resources.collect{|resource,usage| "#{usage}/#{resource.multiplex}#{resource.good? ? '' : '*'}"}.join(";")
|
93
|
+
end
|
94
|
+
|
82
95
|
def reuse(resource)
|
83
96
|
Async.logger.debug(self) {"Reuse #{resource}"}
|
84
97
|
|
85
|
-
@
|
98
|
+
@resources[resource] -= 1
|
86
99
|
|
87
|
-
|
88
|
-
task.resume
|
89
|
-
end
|
100
|
+
@available.signal
|
90
101
|
end
|
91
102
|
|
92
103
|
def retire(resource)
|
93
104
|
Async.logger.debug(self) {"Retire #{resource}"}
|
94
105
|
|
95
|
-
@
|
106
|
+
@resources.delete(resource)
|
107
|
+
|
108
|
+
@active -= 1
|
96
109
|
|
97
110
|
resource.close
|
111
|
+
|
112
|
+
@available.signal
|
98
113
|
end
|
99
114
|
|
100
|
-
def
|
101
|
-
# If we fail to create a resource (below), we will end up waiting for one to become
|
102
|
-
until resource =
|
103
|
-
@
|
104
|
-
Task.yield
|
115
|
+
def wait_for_resource
|
116
|
+
# If we fail to create a resource (below), we will end up waiting for one to become resources.
|
117
|
+
until resource = available_resource
|
118
|
+
@available.wait
|
105
119
|
end
|
106
120
|
|
121
|
+
Async.logger.debug(self) {"Wait for resource #{resource}"}
|
122
|
+
|
107
123
|
return resource
|
108
124
|
end
|
109
125
|
|
110
126
|
def create
|
111
127
|
# This might return nil, which means creating the resource failed.
|
112
128
|
if resource = @constructor.call
|
113
|
-
@
|
129
|
+
@resources[resource] = 1
|
114
130
|
end
|
115
131
|
|
116
132
|
return resource
|
117
133
|
end
|
118
134
|
|
119
|
-
def
|
120
|
-
|
135
|
+
def available_resource
|
136
|
+
# This is a linear search... not idea, but simple for now.
|
137
|
+
@resources.each do |resource, count|
|
121
138
|
if count < resource.multiplex
|
122
139
|
# We want to use this resource... but is it good?
|
123
140
|
if resource.good?
|
124
|
-
@
|
141
|
+
@resources[resource] += 1
|
125
142
|
|
126
143
|
return resource
|
127
144
|
else
|
@@ -130,11 +147,15 @@ module Async
|
|
130
147
|
end
|
131
148
|
end
|
132
149
|
|
133
|
-
if !@limit or @
|
134
|
-
Async.logger.debug(self) {"No
|
150
|
+
if !@limit or @active < @limit
|
151
|
+
Async.logger.debug(self) {"No resources resources, allocating new one..."}
|
152
|
+
|
153
|
+
@active += 1
|
135
154
|
|
136
155
|
return create
|
137
156
|
end
|
157
|
+
|
158
|
+
return nil
|
138
159
|
end
|
139
160
|
end
|
140
161
|
end
|
@@ -136,7 +136,7 @@ module Async
|
|
136
136
|
return if @stream.closed?
|
137
137
|
|
138
138
|
if response
|
139
|
-
write_response(self.version, response.status, response.headers, response.body)
|
139
|
+
write_response(self.version, response.status, response.headers, response.body, request.head?)
|
140
140
|
else
|
141
141
|
# If the request failed to generate a response, it was an internal server error:
|
142
142
|
write_response(self.version, 500, {}, nil)
|
@@ -217,10 +217,15 @@ module Async
|
|
217
217
|
return headers.delete(HOST), method, path, version, headers, body
|
218
218
|
end
|
219
219
|
|
220
|
-
def write_response(version, status, headers, body)
|
220
|
+
def write_response(version, status, headers, body = nil, head = false)
|
221
221
|
@stream.write("#{version} #{status}\r\n")
|
222
222
|
write_headers(headers)
|
223
|
-
|
223
|
+
|
224
|
+
if head
|
225
|
+
write_body_head(body)
|
226
|
+
else
|
227
|
+
write_body(body)
|
228
|
+
end
|
224
229
|
|
225
230
|
@stream.flush
|
226
231
|
end
|
@@ -265,11 +270,22 @@ module Async
|
|
265
270
|
write_persistent_header
|
266
271
|
@stream.write("content-length: #{length}\r\n\r\n")
|
267
272
|
|
273
|
+
chunk_length = 0
|
268
274
|
body.each do |chunk|
|
275
|
+
chunk_length += chunk.bytesize
|
276
|
+
|
277
|
+
if chunk_length > length
|
278
|
+
raise ArgumentError, "Trying to write #{chunk_length} bytes, but content length was #{length} bytes!"
|
279
|
+
end
|
280
|
+
|
269
281
|
@stream.write(chunk)
|
270
282
|
end
|
271
283
|
|
272
284
|
@stream.flush
|
285
|
+
|
286
|
+
if chunk_length != length
|
287
|
+
raise ArgumentError, "Wrote #{chunk_length} bytes, but content length was #{length} bytes!"
|
288
|
+
end
|
273
289
|
end
|
274
290
|
|
275
291
|
def write_chunked_body(body)
|
@@ -316,6 +332,18 @@ module Async
|
|
316
332
|
end
|
317
333
|
end
|
318
334
|
|
335
|
+
def write_body_head(body)
|
336
|
+
write_persistent_header
|
337
|
+
|
338
|
+
if body.nil? or body.empty?
|
339
|
+
@stream.write("content-length: 0\r\n\r\n")
|
340
|
+
elsif length = body.length
|
341
|
+
@stream.write("content-length: #{length}\r\n\r\n")
|
342
|
+
else
|
343
|
+
@stream.write("\r\n")
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
319
347
|
def read_response_body(request, status, headers)
|
320
348
|
# RFC 7230 3.3.3
|
321
349
|
# 1. Any response to a HEAD request and any response with a 1xx
|
@@ -27,7 +27,9 @@ module Async
|
|
27
27
|
module HTTP2
|
28
28
|
DEFAULT_SETTINGS = {
|
29
29
|
::HTTP::Protocol::HTTP2::Settings::ENABLE_PUSH => 0,
|
30
|
-
::HTTP::Protocol::HTTP2::Settings::MAXIMUM_CONCURRENT_STREAMS => 256
|
30
|
+
::HTTP::Protocol::HTTP2::Settings::MAXIMUM_CONCURRENT_STREAMS => 256,
|
31
|
+
::HTTP::Protocol::HTTP2::Settings::MAXIMUM_FRAME_SIZE => 0x100000,
|
32
|
+
::HTTP::Protocol::HTTP2::Settings::INITIAL_WINDOW_SIZE => 0x7FFFFFFF,
|
31
33
|
}
|
32
34
|
|
33
35
|
def self.client(stream, settings = DEFAULT_SETTINGS)
|
@@ -85,19 +85,25 @@ module Async
|
|
85
85
|
def send_response(response)
|
86
86
|
if response.nil?
|
87
87
|
@stream.send_headers(nil, NO_RESPONSE, ::HTTP::Protocol::HTTP2::END_STREAM)
|
88
|
+
elsif response.body?
|
89
|
+
headers = Headers::Merged.new([
|
90
|
+
[STATUS, response.status],
|
91
|
+
])
|
92
|
+
|
93
|
+
if length = response.body.length
|
94
|
+
headers << [[CONTENT_LENGTH, length]]
|
95
|
+
end
|
96
|
+
|
97
|
+
headers << response.headers
|
98
|
+
|
99
|
+
@stream.send_headers(nil, headers)
|
100
|
+
@stream.send_body(response.body)
|
88
101
|
else
|
89
102
|
headers = Headers::Merged.new([
|
90
103
|
[STATUS, response.status],
|
91
|
-
# I'm not sure whether this is a good idea. Maybe it's okay for errors?
|
92
|
-
# [REASON, response.reason],
|
93
104
|
], response.headers)
|
94
105
|
|
95
|
-
|
96
|
-
@stream.send_headers(nil, headers, ::HTTP::Protocol::HTTP2::END_STREAM)
|
97
|
-
else
|
98
|
-
@stream.send_headers(nil, headers)
|
99
|
-
@stream.send_body(response.body)
|
100
|
-
end
|
106
|
+
@stream.send_headers(nil, headers, ::HTTP::Protocol::HTTP2::END_STREAM)
|
101
107
|
end
|
102
108
|
end
|
103
109
|
end
|
@@ -26,9 +26,9 @@ module Async
|
|
26
26
|
module HTTP2
|
27
27
|
class Response < Protocol::Response
|
28
28
|
def initialize(protocol, stream_id)
|
29
|
-
@input =
|
29
|
+
@input = nil
|
30
30
|
|
31
|
-
super(protocol.version, nil, nil, Headers.new,
|
31
|
+
super(protocol.version, nil, nil, Headers.new, nil)
|
32
32
|
|
33
33
|
@protocol = protocol
|
34
34
|
@stream = Stream.new(self, protocol, stream_id)
|
@@ -56,8 +56,8 @@ module Async
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
|
60
|
-
@input.
|
59
|
+
unless end_stream
|
60
|
+
@body = @input = Body::Writable.new
|
61
61
|
end
|
62
62
|
|
63
63
|
# We are ready for processing:
|
data/lib/async/http/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async-http
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.30.
|
4
|
+
version: 0.30.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-08-
|
11
|
+
date: 2018-08-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async
|