async-http 0.30.1 → 0.30.2

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: 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