async-http 0.50.9 → 0.50.10

Sign up to get free protection for your applications and to get access to all the features.
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.