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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: adaf496a96262a69c126558cde8463995949b75dc3f663ab068ca47e982a80e8
4
- data.tar.gz: d4872a7d08166ac5924d984c7883a9ccbd87b923f1d5ba593899d7917f5564a9
3
+ metadata.gz: fde1aeafebf3bc36dc76d397da2b9b229abe58c97afe7c0ea938f671ea653765
4
+ data.tar.gz: d5c26ad0fbeddf88e3456148879cc89084e2a19fe1a0efa5d5effe403bdef1e3
5
5
  SHA512:
6
- metadata.gz: 7fd5488f4658ae99753d09af39c5a1aabd4a1b2edd24837e5f023fea812c198e3b4e8a0879df5f5f823ff864a6095c5f6d20adda9ca4e61237272544bdee16c6
7
- data.tar.gz: d0f64c4635d26f825fba0cc710388a5857469d68771335672a29bba55c0f454e35e3c9c7694aec9d101b2d4243a3e7ef3295b690e293dcbc8622b3be44f4c0f6
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.12.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
@@ -106,7 +106,7 @@ module Async
106
106
  return headers
107
107
  end
108
108
 
109
- def close(error)
109
+ def closed(error)
110
110
  @request = nil
111
111
 
112
112
  super
@@ -109,7 +109,7 @@ module Async
109
109
  end
110
110
  end
111
111
 
112
- def close(error)
112
+ def closed(error)
113
113
  super
114
114
 
115
115
  if @response
@@ -21,157 +21,15 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
  require 'protocol/http2/stream'
24
- require_relative '../../body/writable'
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
- def close(error = nil)
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
@@ -22,6 +22,6 @@
22
22
 
23
23
  module Async
24
24
  module HTTP
25
- VERSION = "0.50.9"
25
+ VERSION = "0.50.10"
26
26
  end
27
27
  end
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.9
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-03-31 00:00:00.000000000 Z
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.12.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.12.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.1.2
287
+ rubygems_version: 3.0.6
286
288
  signing_key:
287
289
  specification_version: 4
288
290
  summary: A HTTP client and server library.