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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e4879744da01c80a5eabb09a27baa3d24d69850ce077f4aa4e434d22c5ab3e7
4
- data.tar.gz: c877962aff587aa7966f5c7a94b9f594f6d303225adb35f70267ff9818be6bf3
3
+ metadata.gz: a937910aa49ee7d30c1941ac83a39f4f5ba2c5c52beec347b510dffd0ac2b40e
4
+ data.tar.gz: 62e34957a999e3e83636f2c00e6f9b6227264f1a7c0d00e69048bf7ff9ceab88
5
5
  SHA512:
6
- metadata.gz: 9bca1257fabc99664358645840f0fc0cb7934e07a5697ffdeff00078302bd16fb251a569922c50d1753998f04b5c3008adb376c72f0a166e43a2781e29a50ce7
7
- data.tar.gz: '0282c23bb974961e316ccc99dc6188c041b0bb1855eaba7bdaeb117646e758f8e3d9ef3d5000f610482f153d374d63c0cac70ad872a4bf56d46a2832b7b2c264'
6
+ metadata.gz: 5256304facd1f375f6d668b10361d89262d5c367f50eff8d0c9f5c3d8ab6374a70c51a28997357b45bad40d4bb569d7daf3d7fb2a527a88735a8a10083abe2dc
7
+ data.tar.gz: c91d8eef348500108f6186bd80ba79eaaac72af42153f7a00fbb4ea05b6db91196b7b421755fe754e8ae6d05b2c560d6e2d49c172447cd2cbeb8dcd84fc77db8
@@ -47,9 +47,9 @@ module Async
47
47
  self.new(chunks)
48
48
  end
49
49
 
50
- def initialize(chunks)
50
+ def initialize(chunks, length = nil)
51
51
  @chunks = chunks
52
- @length = nil
52
+ @length = length
53
53
 
54
54
  @index = 0
55
55
  end
@@ -53,6 +53,11 @@ module Async
53
53
  def close
54
54
  self.stop
55
55
  end
56
+
57
+ # Whether there is a body?
58
+ def body?
59
+ @body and !@body.empty?
60
+ end
56
61
  end
57
62
  end
58
63
  end
@@ -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
- message.body = self.new(message.body, block)
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
- unless chunk = super
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
 
@@ -190,6 +190,10 @@ module Async
190
190
  @all = all
191
191
  end
192
192
 
193
+ def << headers
194
+ @all << headers
195
+ end
196
+
193
197
  def each(&block)
194
198
  @all.each do |headers|
195
199
  headers.each do |key, value|
@@ -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
- @available = {} # resource => count
40
- @waiting = []
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 :available
50
+ attr :resources
48
51
 
49
52
  def empty?
50
- @available.empty?
53
+ @resources.empty?
51
54
  end
52
55
 
53
56
  def acquire
54
- resource = wait_for_next_available
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 available and let waiting tasks know that there is something available.
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
- @available.each_key(&:close)
77
- @available.clear
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
- @available[resource] -= 1
98
+ @resources[resource] -= 1
86
99
 
87
- if task = @waiting.pop
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
- @available.delete(resource)
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 wait_for_next_available
101
- # If we fail to create a resource (below), we will end up waiting for one to become available.
102
- until resource = next_available
103
- @waiting << Fiber.current
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
- @available[resource] = 1
129
+ @resources[resource] = 1
114
130
  end
115
131
 
116
132
  return resource
117
133
  end
118
134
 
119
- def next_available
120
- @available.each do |resource, count|
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
- @available[resource] += 1
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 @available.count < @limit
134
- Async.logger.debug(self) {"No available resources, allocating new one..."}
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
- write_body(body)
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
- if response.body.nil? or response.body.empty?
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 = Body::Writable.new
29
+ @input = nil
30
30
 
31
- super(protocol.version, nil, nil, Headers.new, @input)
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
- if end_stream
60
- @input.finish
59
+ unless end_stream
60
+ @body = @input = Body::Writable.new
61
61
  end
62
62
 
63
63
  # We are ready for processing:
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module HTTP
23
- VERSION = "0.30.1"
23
+ VERSION = "0.30.2"
24
24
  end
25
25
  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.30.1
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-07 00:00:00.000000000 Z
11
+ date: 2018-08-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async