async-http 0.19.0 → 0.20.0

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: dd03fb46876ed4121fa2232b18de6ec00e7176a6c224adf8d5001ec53cf89f1c
4
- data.tar.gz: c42bf8f085ad74ee4fbc6b15c47bcb06915a41f42d3a48d58d39a43f695b267d
3
+ metadata.gz: b40fa9e658f1c38dbd0a8c6ecdfb201af8e6bc504dfb30697b776d6fdf001eac
4
+ data.tar.gz: 714670c4acf8f6b961558d3ea336e81e1d596db29ecbe4df66dbe8349d257975
5
5
  SHA512:
6
- metadata.gz: 7be71154d37d575a6c7b19c7d2c5b81b98e513d260a5d15c9d4c8e0a7fbaf3285d99ca7dc4030e9567af159ff73f4dc25c6144dd55e1745ae307898d0f6c8c9f
7
- data.tar.gz: bb5748a2d385ce8528b218246a5a701d1c8fb1b45d1b40c2d9ce8e559b023c9c032c1ef396cc930c101278e56ddd2a57280e9e16d77df4eca1bfbc09c99e4caf
6
+ metadata.gz: 740c053ba517dd857b7b612d9e10054edc0e63f3a16062cecde65f042a1fc8036e9c9c299c258f894de91fbb2fa1ab8ad1f27b4ecacce7527fb166c76774b5f8
7
+ data.tar.gz: fed266d04a67864ab917d61162b577bd2a24a8567eea6cbc59df5535558d735b0f5e5ef5e902a6b27bac7343ecfc0eec9654d105f346b2492f4d0edb3da5b55b
@@ -17,9 +17,9 @@ Gem::Specification.new do |spec|
17
17
  spec.require_paths = ["lib"]
18
18
 
19
19
  spec.add_dependency("async", "~> 1.6")
20
- spec.add_dependency("async-io", "~> 1.7")
20
+ spec.add_dependency("async-io", "~> 1.8")
21
21
 
22
- spec.add_dependency("http-2", "~> 0.8")
22
+ spec.add_dependency("http-2", "~> 0.9.0")
23
23
  # spec.add_dependency("openssl")
24
24
 
25
25
  spec.add_development_dependency "async-rspec", "~> 1.1"
@@ -36,7 +36,7 @@ module Async
36
36
 
37
37
  def read
38
38
  if @remaining > 0
39
- if chunk = @stream.read(@remaining)
39
+ if chunk = @stream.read_partial(@remaining)
40
40
  @remaining -= chunk.bytesize
41
41
 
42
42
  return chunk
@@ -51,6 +51,7 @@ module Async
51
51
  return chunk
52
52
  end
53
53
 
54
+ # Cause the next call to write to fail with the given error.
54
55
  def stop(error)
55
56
  @stopped = error
56
57
  end
@@ -67,13 +68,15 @@ module Async
67
68
  @queue.enqueue(chunk)
68
69
  end
69
70
 
71
+ alias << write
72
+
70
73
  # Signal that output has finished.
71
74
  def finish
72
75
  @queue.enqueue(nil)
73
76
  end
74
77
 
75
78
  def inspect
76
- "\#<#{self.class} #{@count} chunks written>"
79
+ "\#<#{self.class} #{@count} chunks written#{@finished ? ', finished' : ''}>"
77
80
  end
78
81
  end
79
82
  end
@@ -59,17 +59,24 @@ module Async
59
59
  include Verbs
60
60
 
61
61
  def call(request)
62
- connection = @connections.acquire
63
-
64
62
  request.authority ||= @authority
65
- response = connection.call(request)
66
63
 
67
- # The connection won't be released until the body is completely read/released.
68
- Body::Streamable.wrap(response) do
69
- @connections.release(connection)
64
+ # As we cache connections, it's possible these connections go bad (e.g. closed by remote host). In this case, we need to try again. It's up to the caller to impose a timeout on this.
65
+ while true
66
+ connection = @connections.acquire
67
+
68
+ if response = connection.call(request)
69
+ # The connection won't be released until the body is completely read/released.
70
+ Body::Streamable.wrap(response) do
71
+ @connections.release(connection)
72
+ end
73
+
74
+ return response
75
+ else
76
+ # The connection failed for some reason, we close it.
77
+ connection.close
78
+ end
70
79
  end
71
-
72
- return response
73
80
  end
74
81
 
75
82
  protected
@@ -78,15 +85,10 @@ module Async
78
85
  Pool.new(connection_limit) do
79
86
  Async.logger.debug(self) {"Making connection to #{@endpoint.inspect}"}
80
87
 
81
- @endpoint.each do |endpoint|
82
- peer = endpoint.connect
83
-
84
- peer.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
85
-
86
- stream = IO::Stream.new(peer)
87
-
88
- break @protocol.client(stream)
89
- end
88
+ peer = @endpoint.connect
89
+ peer.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
90
+
91
+ @protocol.client(IO::Stream.new(peer))
90
92
  end
91
93
  end
92
94
  end
@@ -31,7 +31,7 @@ module Async
31
31
  VERSION
32
32
  end
33
33
 
34
- def keep_alive?(headers)
34
+ def persistent?(headers)
35
35
  headers['connection'] == KEEP_ALIVE
36
36
  end
37
37
 
@@ -44,7 +44,7 @@ module Async
44
44
 
45
45
  write_response(response.version, response.status, response.headers, response.body)
46
46
 
47
- unless keep_alive?(request.headers) && keep_alive?(headers)
47
+ unless persistent?(request.headers) && persistent?(headers)
48
48
  @keep_alive = false
49
49
 
50
50
  break
@@ -54,9 +54,9 @@ module Async
54
54
  return false
55
55
  end
56
56
 
57
- def write_body(body, chunked = true)
57
+ def write_body(body, chunked = false)
58
58
  # We don't support chunked encoding.
59
- super(body, false)
59
+ super(body, chunked)
60
60
  end
61
61
 
62
62
  def read_body(headers)
@@ -65,7 +65,7 @@ module Async
65
65
  end
66
66
 
67
67
  # Technically, with HTTP/1.0, if no content-length is specified, we just need to read everything until the connection is closed.
68
- if !keep_alive?(headers)
68
+ if !persistent?(headers)
69
69
  return Body::Remainder.new(@stream)
70
70
  end
71
71
  end
@@ -36,16 +36,19 @@ module Async
36
36
  def initialize(stream)
37
37
  super(stream, CRLF)
38
38
 
39
- @keep_alive = true
39
+ @persistent = true
40
+ @count = 0
40
41
  end
41
42
 
43
+ attr :count
44
+
42
45
  # Only one simultaneous connection at a time.
43
46
  def multiplex
44
47
  1
45
48
  end
46
49
 
47
50
  def reusable?
48
- @keep_alive
51
+ @persistent
49
52
  end
50
53
 
51
54
  class << self
@@ -62,7 +65,7 @@ module Async
62
65
  VERSION
63
66
  end
64
67
 
65
- def keep_alive?(headers)
68
+ def persistent?(headers)
66
69
  headers['connection'] != CLOSE
67
70
  end
68
71
 
@@ -70,6 +73,7 @@ module Async
70
73
  def receive_requests(task: Task.current)
71
74
  while true
72
75
  request = Request.new(*read_request)
76
+ @count += 1
73
77
 
74
78
  response = yield request
75
79
 
@@ -79,8 +83,8 @@ module Async
79
83
 
80
84
  request.finish
81
85
 
82
- unless keep_alive?(request.headers) and keep_alive?(response.headers)
83
- @keep_alive = false
86
+ unless persistent?(request.headers) and persistent?(response.headers)
87
+ @persistent = false
84
88
 
85
89
  break
86
90
  end
@@ -91,6 +95,8 @@ module Async
91
95
  end
92
96
 
93
97
  def call(request)
98
+ @count += 1
99
+
94
100
  request.version ||= self.version
95
101
 
96
102
  Async.logger.debug(self) {"#{request.method} #{request.path} #{request.headers.inspect}"}
@@ -98,6 +104,7 @@ module Async
98
104
 
99
105
  return Response.new(*read_response)
100
106
  rescue EOFError
107
+ Async.logger.debug(self) {"Connection failed with EOFError after #{@count} requests."}
101
108
  return nil
102
109
  end
103
110
 
@@ -118,7 +125,7 @@ module Async
118
125
  headers = read_headers
119
126
  body = read_body(headers)
120
127
 
121
- @keep_alive = keep_alive?(headers)
128
+ @keep_alive = persistent?(headers)
122
129
 
123
130
  return version, Integer(status), reason, headers, body
124
131
  end
@@ -163,9 +170,9 @@ module Async
163
170
  end
164
171
 
165
172
  def write_body(body, chunked = true)
166
- if body.empty?
173
+ if body.nil? or body.empty?
167
174
  @stream.write("Content-Length: 0\r\n\r\n")
168
- body.read
175
+ body.read if body
169
176
  elsif chunked
170
177
  @stream.write("Transfer-Encoding: chunked\r\n\r\n")
171
178
 
@@ -64,8 +64,12 @@ module Async
64
64
  @controller.on(:frame_received) do |frame|
65
65
  Async.logger.debug(self) {"Received frame: #{frame.inspect}"}
66
66
  end
67
+
68
+ @count = 0
67
69
  end
68
70
 
71
+ attr :count
72
+
69
73
  # Multiple requests can be processed at the same time.
70
74
  def multiplex
71
75
  @controller.remote_settings[:settings_max_concurrent_streams]
@@ -85,10 +89,10 @@ module Async
85
89
 
86
90
  def read_in_background(task: Task.current)
87
91
  task.async do |nested_task|
88
- buffer = Async::IO::BinaryString.new
92
+ nested_task.annotate("#{version} reading data")
89
93
 
90
- while data = @stream.io.read(1024*8, buffer)
91
- @controller << data
94
+ while buffer = @stream.read_partial
95
+ @controller << buffer
92
96
  end
93
97
 
94
98
  Async.logger.debug(self) {"Connection reset by peer!"}
@@ -104,6 +108,8 @@ module Async
104
108
  def receive_requests(task: Task.current, &block)
105
109
  # emits new streams opened by the client
106
110
  @controller.on(:stream) do |stream|
111
+ @count += 1
112
+
107
113
  request = Request.new
108
114
  request.version = self.version
109
115
  request.headers = {}
@@ -143,9 +149,9 @@ module Async
143
149
  headers.update(response.headers)
144
150
 
145
151
  # puts "Sending headers #{headers}"
146
- if response.body.empty?
152
+ if response.body.nil? or response.body.empty?
147
153
  stream.headers(headers, end_stream: true)
148
- response.body.read
154
+ response.body.read if response.body
149
155
  else
150
156
  stream.headers(headers, end_stream: false)
151
157
 
@@ -166,6 +172,8 @@ module Async
166
172
  end
167
173
 
168
174
  def call(request)
175
+ @count += 1
176
+
169
177
  request.version ||= self.version
170
178
 
171
179
  stream = @controller.new_stream
@@ -207,8 +215,9 @@ module Async
207
215
  body.finish
208
216
  end
209
217
 
210
- if request.body.empty?
218
+ if request.body.nil? or request.body.empty?
211
219
  stream.headers(headers, end_stream: true)
220
+ request.body.read if request.body
212
221
  else
213
222
  stream.headers(headers, end_stream: false)
214
223
 
@@ -42,8 +42,9 @@ module Async
42
42
 
43
43
  def accept(peer, address, task: Task.current)
44
44
  peer.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
45
+ block_size = peer.getsockopt(Socket::SOL_SOCKET, Socket::SO_RCVBUF).int
45
46
 
46
- stream = Async::IO::Stream.new(peer)
47
+ stream = Async::IO::Stream.new(peer, block_size: block_size)
47
48
  protocol = @protocol_class.server(stream)
48
49
 
49
50
  # Async.logger.debug(self) {"Incoming connnection from #{address.inspect}"}
@@ -63,8 +64,6 @@ module Async
63
64
  end
64
65
  rescue EOFError, Errno::ECONNRESET, Errno::EPIPE
65
66
  # Sometimes client will disconnect without completing a result or reading the entire buffer.
66
- ensure
67
- peer.close
68
67
  end
69
68
 
70
69
  def run
@@ -34,7 +34,9 @@ module Async
34
34
  end
35
35
 
36
36
  def wrap(response, &block)
37
- response.body = Body::Statistics.new(@start_time, response.body, block)
37
+ if response and response.body
38
+ response.body = Body::Statistics.new(@start_time, response.body, block)
39
+ end
38
40
  end
39
41
  end
40
42
 
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module HTTP
23
- VERSION = "0.19.0"
23
+ VERSION = "0.20.0"
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.19.0
4
+ version: 0.20.0
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-04-17 00:00:00.000000000 Z
11
+ date: 2018-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.7'
33
+ version: '1.8'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.7'
40
+ version: '1.8'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: http-2
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.8'
47
+ version: 0.9.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0.8'
54
+ version: 0.9.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: async-rspec
57
57
  requirement: !ruby/object:Gem::Requirement