async-http 0.50.9 → 0.50.10
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/protocol/http2/input.rb +61 -0
- data/lib/async/http/protocol/http2/output.rb +132 -0
- data/lib/async/http/protocol/http2/request.rb +1 -1
- data/lib/async/http/protocol/http2/response.rb +1 -1
- data/lib/async/http/protocol/http2/stream.rb +21 -146
- data/lib/async/http/version.rb +1 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fde1aeafebf3bc36dc76d397da2b9b229abe58c97afe7c0ea938f671ea653765
|
4
|
+
data.tar.gz: d5c26ad0fbeddf88e3456148879cc89084e2a19fe1a0efa5d5effe403bdef1e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ea998353b0bc33a8cf03f7b11ea719512306453b7be9f98faf4c2a378d78b3986505027dc812dd4dc9ea27e5d966def5150d34183d5e86762178a711e02c9ef
|
7
|
+
data.tar.gz: f9a5a6a6705590a1445f26e6897a1ba59dda0719023f970deb826195675616e46dc227b49160ae0c26c4548dda3cb8c9fc0151304955fea1aeb3ccf7bab7cf0d
|
data/async-http.gemspec
CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
|
24
24
|
spec.add_dependency("protocol-http", "~> 0.15.1")
|
25
25
|
spec.add_dependency("protocol-http1", "~> 0.10.0")
|
26
|
-
spec.add_dependency("protocol-http2", "~> 0.
|
26
|
+
spec.add_dependency("protocol-http2", "~> 0.13.0")
|
27
27
|
|
28
28
|
# spec.add_dependency("openssl")
|
29
29
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative '../../body/writable'
|
24
|
+
|
25
|
+
module Async
|
26
|
+
module HTTP
|
27
|
+
module Protocol
|
28
|
+
module HTTP2
|
29
|
+
# A writable body which requests window updates when data is read from it.
|
30
|
+
class Input < Body::Writable
|
31
|
+
def initialize(stream, length)
|
32
|
+
super(length)
|
33
|
+
|
34
|
+
@stream = stream
|
35
|
+
@remaining = length
|
36
|
+
end
|
37
|
+
|
38
|
+
def read
|
39
|
+
if chunk = super
|
40
|
+
# If we read a chunk fron the stream, we want to extend the window if required so more data will be provided.
|
41
|
+
@stream.request_window_update
|
42
|
+
end
|
43
|
+
|
44
|
+
# We track the expected length and check we got what we were expecting.
|
45
|
+
if @remaining
|
46
|
+
if chunk
|
47
|
+
@remaining -= chunk.bytesize
|
48
|
+
elsif @remaining > 0
|
49
|
+
raise EOFError, "Expected #{self.length} bytes, #{@remaining} bytes short!"
|
50
|
+
elsif @remaining < 0
|
51
|
+
raise EOFError, "Expected #{self.length} bytes, #{@remaining} bytes over!"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
return chunk
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative '../../body/stream'
|
24
|
+
|
25
|
+
module Async
|
26
|
+
module HTTP
|
27
|
+
module Protocol
|
28
|
+
module HTTP2
|
29
|
+
class Output
|
30
|
+
def self.for(stream, body)
|
31
|
+
output = self.new(stream, body)
|
32
|
+
|
33
|
+
output.start
|
34
|
+
|
35
|
+
return output
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(stream, body)
|
39
|
+
@stream = stream
|
40
|
+
@body = body
|
41
|
+
@task = nil
|
42
|
+
|
43
|
+
@window_updated = Async::Condition.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def start(parent: Task.current)
|
47
|
+
raise "Task already started!" if @task
|
48
|
+
|
49
|
+
if @body.respond_to?(:call)
|
50
|
+
@task = parent.async(&self.method(:stream))
|
51
|
+
else
|
52
|
+
@task = parent.async(&self.method(:passthrough))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def window_updated(size)
|
57
|
+
@window_updated.signal
|
58
|
+
end
|
59
|
+
|
60
|
+
def write(chunk)
|
61
|
+
until chunk.empty?
|
62
|
+
maximum_size = @stream.available_frame_size
|
63
|
+
|
64
|
+
while maximum_size <= 0
|
65
|
+
@window_updated.wait
|
66
|
+
|
67
|
+
maximum_size = @stream.available_frame_size
|
68
|
+
end
|
69
|
+
|
70
|
+
break unless chunk = send_data(chunk, maximum_size)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def close(error = nil)
|
75
|
+
@stream.finish_output(error)
|
76
|
+
end
|
77
|
+
|
78
|
+
def stop(error)
|
79
|
+
@task&.stop
|
80
|
+
@task = nil
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def stream(task)
|
86
|
+
task.annotate("Streaming #{@body} to #{@stream}.")
|
87
|
+
|
88
|
+
input = @stream.wait_for_input
|
89
|
+
|
90
|
+
@body.call(Body::Stream.new(input, self))
|
91
|
+
rescue Async::Stop
|
92
|
+
# Ignore.
|
93
|
+
end
|
94
|
+
|
95
|
+
# Reads chunks from the given body and writes them to the stream as fast as possible.
|
96
|
+
def passthrough(task)
|
97
|
+
task.annotate("Writing #{@body} to #{@stream}.")
|
98
|
+
|
99
|
+
while chunk = @body&.read
|
100
|
+
self.write(chunk)
|
101
|
+
# TODO this reduces memory usage?
|
102
|
+
# chunk.clear unless chunk.frozen?
|
103
|
+
# GC.start
|
104
|
+
end
|
105
|
+
|
106
|
+
@stream.finish_output
|
107
|
+
ensure
|
108
|
+
@body&.close($!)
|
109
|
+
@body = nil
|
110
|
+
end
|
111
|
+
|
112
|
+
# Send `maximum_size` bytes of data using the specified `stream`. If the buffer has no more chunks, `END_STREAM` will be sent on the final chunk.
|
113
|
+
# @param maximum_size [Integer] send up to this many bytes of data.
|
114
|
+
# @param stream [Stream] the stream to use for sending data frames.
|
115
|
+
# @return [String, nil] any data that could not be written.
|
116
|
+
def send_data(chunk, maximum_size)
|
117
|
+
if chunk.bytesize <= maximum_size
|
118
|
+
@stream.send_data(chunk, maximum_size: maximum_size)
|
119
|
+
else
|
120
|
+
@stream.send_data(chunk.byteslice(0, maximum_size), maximum_size: maximum_size)
|
121
|
+
|
122
|
+
# The window was not big enough to send all the data, so we save it for next time:
|
123
|
+
return chunk.byteslice(maximum_size, chunk.bytesize - maximum_size)
|
124
|
+
end
|
125
|
+
|
126
|
+
return nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -21,157 +21,15 @@
|
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
23
|
require 'protocol/http2/stream'
|
24
|
-
|
24
|
+
|
25
|
+
require_relative 'input'
|
26
|
+
require_relative 'output'
|
25
27
|
|
26
28
|
module Async
|
27
29
|
module HTTP
|
28
30
|
module Protocol
|
29
31
|
module HTTP2
|
30
32
|
class Stream < ::Protocol::HTTP2::Stream
|
31
|
-
# A writable body which requests window updates when data is read from it.
|
32
|
-
class Input < Body::Writable
|
33
|
-
def initialize(stream, length)
|
34
|
-
super(length)
|
35
|
-
|
36
|
-
@stream = stream
|
37
|
-
@remaining = length
|
38
|
-
end
|
39
|
-
|
40
|
-
def read
|
41
|
-
if chunk = super
|
42
|
-
# If we read a chunk fron the stream, we want to extend the window if required so more data will be provided.
|
43
|
-
@stream.request_window_update
|
44
|
-
end
|
45
|
-
|
46
|
-
# We track the expected length and check we got what we were expecting.
|
47
|
-
if @remaining
|
48
|
-
if chunk
|
49
|
-
@remaining -= chunk.bytesize
|
50
|
-
elsif @remaining > 0
|
51
|
-
raise EOFError, "Expected #{self.length} bytes, #{@remaining} bytes short!"
|
52
|
-
elsif @remaining < 0
|
53
|
-
raise EOFError, "Expected #{self.length} bytes, #{@remaining} bytes over!"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
return chunk
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
class Output
|
62
|
-
def self.for(stream, body)
|
63
|
-
output = self.new(stream, body)
|
64
|
-
|
65
|
-
output.start
|
66
|
-
|
67
|
-
return output
|
68
|
-
end
|
69
|
-
|
70
|
-
def initialize(stream, body)
|
71
|
-
@stream = stream
|
72
|
-
@body = body
|
73
|
-
|
74
|
-
@window_updated = Async::Condition.new
|
75
|
-
end
|
76
|
-
|
77
|
-
def start(parent: Task.current)
|
78
|
-
if @body.respond_to?(:call)
|
79
|
-
@task = parent.async(&self.method(:stream))
|
80
|
-
else
|
81
|
-
@task = parent.async(&self.method(:passthrough))
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def stop(error)
|
86
|
-
# Ensure that invoking #close doesn't try to close the stream.
|
87
|
-
@stream = nil
|
88
|
-
|
89
|
-
@task&.stop
|
90
|
-
end
|
91
|
-
|
92
|
-
def write(chunk)
|
93
|
-
until chunk.empty?
|
94
|
-
maximum_size = @stream.available_frame_size
|
95
|
-
|
96
|
-
while maximum_size <= 0
|
97
|
-
@window_updated.wait
|
98
|
-
|
99
|
-
maximum_size = @stream.available_frame_size
|
100
|
-
end
|
101
|
-
|
102
|
-
break unless chunk = send_data(chunk, maximum_size)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def window_updated(size)
|
107
|
-
@window_updated.signal
|
108
|
-
end
|
109
|
-
|
110
|
-
def close(error = nil)
|
111
|
-
if @stream
|
112
|
-
if error
|
113
|
-
@stream.close(error)
|
114
|
-
else
|
115
|
-
self.close_write
|
116
|
-
end
|
117
|
-
|
118
|
-
@stream = nil
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
def close_write
|
123
|
-
@stream.send_data(nil, ::Protocol::HTTP2::END_STREAM)
|
124
|
-
end
|
125
|
-
|
126
|
-
private
|
127
|
-
|
128
|
-
def stream(task)
|
129
|
-
task.annotate("Streaming #{@body} to #{@stream}.")
|
130
|
-
|
131
|
-
input = @stream.wait_for_input
|
132
|
-
|
133
|
-
@body.call(Body::Stream.new(input, self))
|
134
|
-
rescue Async::Stop
|
135
|
-
# Ignore.
|
136
|
-
end
|
137
|
-
|
138
|
-
# Reads chunks from the given body and writes them to the stream as fast as possible.
|
139
|
-
def passthrough(task)
|
140
|
-
task.annotate("Writing #{@body} to #{@stream}.")
|
141
|
-
|
142
|
-
while chunk = @body&.read
|
143
|
-
self.write(chunk)
|
144
|
-
# TODO this reduces memory usage?
|
145
|
-
# chunk.clear unless chunk.frozen?
|
146
|
-
# GC.start
|
147
|
-
end
|
148
|
-
|
149
|
-
self.close_write
|
150
|
-
rescue Async::Stop
|
151
|
-
# Ignore.
|
152
|
-
ensure
|
153
|
-
@body&.close($!)
|
154
|
-
@body = nil
|
155
|
-
end
|
156
|
-
|
157
|
-
# Send `maximum_size` bytes of data using the specified `stream`. If the buffer has no more chunks, `END_STREAM` will be sent on the final chunk.
|
158
|
-
# @param maximum_size [Integer] send up to this many bytes of data.
|
159
|
-
# @param stream [Stream] the stream to use for sending data frames.
|
160
|
-
# @return [String, nil] any data that could not be written.
|
161
|
-
def send_data(chunk, maximum_size)
|
162
|
-
if chunk.bytesize <= maximum_size
|
163
|
-
@stream.send_data(chunk, maximum_size: maximum_size)
|
164
|
-
else
|
165
|
-
@stream.send_data(chunk.byteslice(0, maximum_size), maximum_size: maximum_size)
|
166
|
-
|
167
|
-
# The window was not big enough to send all the data, so we save it for next time:
|
168
|
-
return chunk.byteslice(maximum_size, chunk.bytesize - maximum_size)
|
169
|
-
end
|
170
|
-
|
171
|
-
return nil
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
33
|
def initialize(*)
|
176
34
|
super
|
177
35
|
|
@@ -279,13 +137,28 @@ module Async
|
|
279
137
|
@output = Output.for(self, body)
|
280
138
|
end
|
281
139
|
|
140
|
+
# Called when the output terminates normally.
|
141
|
+
def finish_output(error = nil)
|
142
|
+
@output = nil
|
143
|
+
|
144
|
+
if error
|
145
|
+
send_reset_stream(::Protocol::HTTP2::Error::INTERNAL_ERROR)
|
146
|
+
else
|
147
|
+
send_data(nil, ::Protocol::HTTP2::END_STREAM)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
282
151
|
def window_updated(size)
|
283
152
|
super
|
284
153
|
|
285
154
|
@output&.window_updated(size)
|
286
155
|
end
|
287
156
|
|
288
|
-
|
157
|
+
# When the stream transitions to the closed state, this method is called. There are roughly two ways this can happen:
|
158
|
+
# - A frame is received which causes this stream to enter the closed state. This method will be invoked from the background reader task.
|
159
|
+
# - A frame is sent which causes this stream to enter the closed state. This method will be invoked from that task.
|
160
|
+
# While the input stream is relatively straight forward, the output stream can trigger the second case above
|
161
|
+
def closed(error)
|
289
162
|
super
|
290
163
|
|
291
164
|
if @input
|
@@ -297,6 +170,8 @@ module Async
|
|
297
170
|
@output.stop(error)
|
298
171
|
@output = nil
|
299
172
|
end
|
173
|
+
|
174
|
+
return self
|
300
175
|
end
|
301
176
|
end
|
302
177
|
end
|
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.50.
|
4
|
+
version: 0.50.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.
|
89
|
+
version: 0.13.0
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.
|
96
|
+
version: 0.13.0
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: async-rspec
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -250,6 +250,8 @@ files:
|
|
250
250
|
- lib/async/http/protocol/http2.rb
|
251
251
|
- lib/async/http/protocol/http2/client.rb
|
252
252
|
- lib/async/http/protocol/http2/connection.rb
|
253
|
+
- lib/async/http/protocol/http2/input.rb
|
254
|
+
- lib/async/http/protocol/http2/output.rb
|
253
255
|
- lib/async/http/protocol/http2/request.rb
|
254
256
|
- lib/async/http/protocol/http2/response.rb
|
255
257
|
- lib/async/http/protocol/http2/server.rb
|
@@ -282,7 +284,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
282
284
|
- !ruby/object:Gem::Version
|
283
285
|
version: '0'
|
284
286
|
requirements: []
|
285
|
-
rubygems_version: 3.
|
287
|
+
rubygems_version: 3.0.6
|
286
288
|
signing_key:
|
287
289
|
specification_version: 4
|
288
290
|
summary: A HTTP client and server library.
|