httpx 0.8.0 → 0.10.1

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.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +48 -0
  3. data/README.md +9 -5
  4. data/doc/release_notes/0_0_1.md +7 -0
  5. data/doc/release_notes/0_0_2.md +9 -0
  6. data/doc/release_notes/0_0_3.md +9 -0
  7. data/doc/release_notes/0_0_4.md +7 -0
  8. data/doc/release_notes/0_0_5.md +5 -0
  9. data/doc/release_notes/0_10_0.md +66 -0
  10. data/doc/release_notes/0_10_1.md +39 -0
  11. data/doc/release_notes/0_1_0.md +9 -0
  12. data/doc/release_notes/0_2_0.md +5 -0
  13. data/doc/release_notes/0_2_1.md +16 -0
  14. data/doc/release_notes/0_3_0.md +12 -0
  15. data/doc/release_notes/0_3_1.md +6 -0
  16. data/doc/release_notes/0_4_0.md +51 -0
  17. data/doc/release_notes/0_4_1.md +3 -0
  18. data/doc/release_notes/0_5_0.md +15 -0
  19. data/doc/release_notes/0_5_1.md +14 -0
  20. data/doc/release_notes/0_6_0.md +5 -0
  21. data/doc/release_notes/0_6_1.md +6 -0
  22. data/doc/release_notes/0_6_2.md +6 -0
  23. data/doc/release_notes/0_6_3.md +13 -0
  24. data/doc/release_notes/0_6_4.md +21 -0
  25. data/doc/release_notes/0_6_5.md +22 -0
  26. data/doc/release_notes/0_6_6.md +19 -0
  27. data/doc/release_notes/0_6_7.md +5 -0
  28. data/doc/release_notes/0_7_0.md +46 -0
  29. data/doc/release_notes/0_8_0.md +27 -0
  30. data/doc/release_notes/0_8_1.md +8 -0
  31. data/doc/release_notes/0_8_2.md +7 -0
  32. data/doc/release_notes/0_9_0.md +38 -0
  33. data/lib/httpx.rb +2 -0
  34. data/lib/httpx/adapters/faraday.rb +1 -1
  35. data/lib/httpx/chainable.rb +11 -11
  36. data/lib/httpx/connection.rb +23 -31
  37. data/lib/httpx/connection/http1.rb +30 -4
  38. data/lib/httpx/connection/http2.rb +29 -10
  39. data/lib/httpx/domain_name.rb +440 -0
  40. data/lib/httpx/errors.rb +2 -1
  41. data/lib/httpx/extensions.rb +22 -2
  42. data/lib/httpx/headers.rb +2 -2
  43. data/lib/httpx/io/ssl.rb +0 -1
  44. data/lib/httpx/io/tcp.rb +6 -5
  45. data/lib/httpx/io/udp.rb +4 -1
  46. data/lib/httpx/options.rb +5 -1
  47. data/lib/httpx/parser/http1.rb +14 -17
  48. data/lib/httpx/plugins/compression.rb +46 -65
  49. data/lib/httpx/plugins/compression/brotli.rb +10 -14
  50. data/lib/httpx/plugins/compression/deflate.rb +7 -6
  51. data/lib/httpx/plugins/compression/gzip.rb +23 -5
  52. data/lib/httpx/plugins/cookies.rb +21 -60
  53. data/lib/httpx/plugins/cookies/cookie.rb +173 -0
  54. data/lib/httpx/plugins/cookies/jar.rb +74 -0
  55. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +142 -0
  56. data/lib/httpx/plugins/expect.rb +12 -1
  57. data/lib/httpx/plugins/follow_redirects.rb +20 -2
  58. data/lib/httpx/plugins/h2c.rb +1 -1
  59. data/lib/httpx/plugins/multipart.rb +12 -6
  60. data/lib/httpx/plugins/persistent.rb +6 -1
  61. data/lib/httpx/plugins/proxy.rb +16 -2
  62. data/lib/httpx/plugins/proxy/socks4.rb +14 -14
  63. data/lib/httpx/plugins/rate_limiter.rb +51 -0
  64. data/lib/httpx/plugins/retries.rb +3 -2
  65. data/lib/httpx/plugins/stream.rb +109 -13
  66. data/lib/httpx/pool.rb +14 -17
  67. data/lib/httpx/request.rb +8 -20
  68. data/lib/httpx/resolver.rb +7 -10
  69. data/lib/httpx/resolver/https.rb +22 -24
  70. data/lib/httpx/resolver/native.rb +19 -16
  71. data/lib/httpx/resolver/resolver_mixin.rb +4 -2
  72. data/lib/httpx/resolver/system.rb +2 -2
  73. data/lib/httpx/response.rb +16 -25
  74. data/lib/httpx/selector.rb +11 -18
  75. data/lib/httpx/session.rb +40 -26
  76. data/lib/httpx/transcoder.rb +18 -0
  77. data/lib/httpx/transcoder/chunker.rb +0 -2
  78. data/lib/httpx/transcoder/form.rb +9 -7
  79. data/lib/httpx/transcoder/json.rb +0 -4
  80. data/lib/httpx/utils.rb +45 -0
  81. data/lib/httpx/version.rb +1 -1
  82. data/sig/buffer.rbs +24 -0
  83. data/sig/callbacks.rbs +14 -0
  84. data/sig/chainable.rbs +37 -0
  85. data/sig/connection.rbs +85 -0
  86. data/sig/connection/http1.rbs +66 -0
  87. data/sig/connection/http2.rbs +78 -0
  88. data/sig/domain_name.rbs +17 -0
  89. data/sig/errors.rbs +3 -0
  90. data/sig/headers.rbs +42 -0
  91. data/sig/httpx.rbs +15 -0
  92. data/sig/loggable.rbs +11 -0
  93. data/sig/missing.rbs +12 -0
  94. data/sig/options.rbs +118 -0
  95. data/sig/parser/http1.rbs +50 -0
  96. data/sig/plugins/authentication.rbs +11 -0
  97. data/sig/plugins/basic_authentication.rbs +13 -0
  98. data/sig/plugins/compression.rbs +55 -0
  99. data/sig/plugins/compression/brotli.rbs +21 -0
  100. data/sig/plugins/compression/deflate.rbs +17 -0
  101. data/sig/plugins/compression/gzip.rbs +29 -0
  102. data/sig/plugins/cookies.rbs +26 -0
  103. data/sig/plugins/cookies/cookie.rbs +50 -0
  104. data/sig/plugins/cookies/jar.rbs +27 -0
  105. data/sig/plugins/digest_authentication.rbs +33 -0
  106. data/sig/plugins/expect.rbs +19 -0
  107. data/sig/plugins/follow_redirects.rbs +37 -0
  108. data/sig/plugins/h2c.rbs +26 -0
  109. data/sig/plugins/multipart.rbs +21 -0
  110. data/sig/plugins/persistent.rbs +17 -0
  111. data/sig/plugins/proxy.rbs +47 -0
  112. data/sig/plugins/proxy/http.rbs +14 -0
  113. data/sig/plugins/proxy/socks4.rbs +33 -0
  114. data/sig/plugins/proxy/socks5.rbs +36 -0
  115. data/sig/plugins/proxy/ssh.rbs +18 -0
  116. data/sig/plugins/push_promise.rbs +22 -0
  117. data/sig/plugins/rate_limiter.rbs +11 -0
  118. data/sig/plugins/retries.rbs +48 -0
  119. data/sig/plugins/stream.rbs +39 -0
  120. data/sig/pool.rbs +36 -0
  121. data/sig/registry.rbs +9 -0
  122. data/sig/request.rbs +61 -0
  123. data/sig/resolver.rbs +26 -0
  124. data/sig/resolver/https.rbs +49 -0
  125. data/sig/resolver/native.rbs +60 -0
  126. data/sig/resolver/resolver_mixin.rbs +27 -0
  127. data/sig/resolver/system.rbs +17 -0
  128. data/sig/response.rbs +87 -0
  129. data/sig/selector.rbs +20 -0
  130. data/sig/session.rbs +49 -0
  131. data/sig/timeout.rbs +29 -0
  132. data/sig/transcoder.rbs +18 -0
  133. data/sig/transcoder/body.rbs +18 -0
  134. data/sig/transcoder/chunker.rbs +32 -0
  135. data/sig/transcoder/form.rbs +16 -0
  136. data/sig/transcoder/json.rbs +14 -0
  137. metadata +128 -22
  138. data/lib/httpx/resolver/options.rb +0 -25
@@ -0,0 +1,22 @@
1
+ # 0.6.5
2
+
3
+ This release fixes important bugs, and automates the PKI for the test suite.
4
+
5
+ ## Features
6
+
7
+ * `resolver_options` can now receive a `:cache` flag (default: `true`). This bypasses caching and forces the lookup;
8
+
9
+ ## Improvements
10
+
11
+ * Building the TLS certs necessary for the test suite has been scripted, after the initial certs expired and brought the CI to a halt;
12
+ * All DNS resolvers have a functional test, both for the happy as well as the error case;
13
+ * Added functional tests for HTTP and HTTPS proxy with authentication, making all proxy options now tested with authentication;
14
+
15
+
16
+ ## Bugfixes
17
+
18
+ * native and https DNS resolvers weren't usable after a resolving error;
19
+ * system DNS resolver could halt the system after a dns resolving error;
20
+ * fixed system halt on HTTP proxy authentication error;
21
+
22
+
@@ -0,0 +1,19 @@
1
+ # 0.6.6
2
+
3
+ ## Features
4
+
5
+ * The `retries` plugin receives two new options:
6
+ * `retry_on`: a callable that receives the failed response as an argument; the return value will determine whether there'll be a retried request.
7
+ * `retry_after`: time (in seconds) after which there request will be retried. Can be an integer or a callable that receives the request and returns an integer (one can do exponential back-off like that, for example).
8
+ * Added support for DNS-over-HTTPS GET requests as per the latest spec.
9
+
10
+ ## Improvements
11
+
12
+ * `HTTPX.plugins` got deprecated; basically, it's great until you have to pass options to a plugin, and then it just works (not). The recommended way to load multiple plugins is `HTTPX.plugin(...).plugin(...)`.
13
+
14
+
15
+ ## Bugfixes
16
+
17
+ * fixed a proxy bug where an `Alt-Svc` response header would make the client try to connect. Just like connection coalescing and the ORIGIN frame, it ignores it when going through a proxy.
18
+
19
+
@@ -0,0 +1,5 @@
1
+ # 0.6.7
2
+
3
+ ## Bugfixes
4
+
5
+ * An error was reported when using the follow plugin allowing insecure redirects: if the insecure redirect would be for the same host, and the original request was performed with HTTP/2, the library would try to coalesce the request, and blocks the reactor. A check was made to ensure that connection only coalesces if both are https connections.
@@ -0,0 +1,46 @@
1
+ # 0.7.0
2
+
3
+
4
+ ## Features
5
+
6
+ New option: `:max_requests`. This is a connection-level option signalizing how many requests can be performed on a connection. Although the HTTP/1 parser defined this well, in HTTP/2 this wasn't very clear, so: by definition, the remote MAX_CONCURRENT_STREAMS setting will be used to define it, unless the user explicitly passed the option. You can also pass `:max_requests => Float::INFINITY` if you know that the server allows more requests than that on a connection.
7
+
8
+ New plugin: `:expect`.
9
+
10
+ Although there was support for `expect: 100-continue` header already when passed, this plugin can:
11
+
12
+ * automatically set the header on requests with body;
13
+ * execute the flow;
14
+ * recover from 417 status errors (i.e. try again without it);
15
+ * send body after X seconds if no 100 response came;
16
+
17
+ Suport for `with_` methods for the session. As long as the suffix is a valid attribute, it's just like that:
18
+
19
+ ```ruby
20
+ HTTPX.with_timeout(...).with_ssl(...)
21
+ # same as:
22
+ # HTTPX.with(timeout: ..., ssl: ...)
23
+ ```
24
+
25
+ ## Improvements
26
+
27
+ ### Connections
28
+
29
+ The following improvements make the `persistent` plugin way more resilient:
30
+
31
+ * Better balancing of HTTP/2 connections by distributing requests among X connections depending of how many requests they can process.
32
+ * Exhausted connections can off-load to a new same-origin connection (such as, when the server negotiates less `MAX_CONCURRENT_STREAMS` than what's expected).
33
+
34
+ ### Timeouts
35
+
36
+ (Timeouts will be one of the main improvements from the 0.7.x series)
37
+
38
+ `:total_timeout` is now a connection-level directive, which means that this feature will actually make more sense and account for all requests in a block at the same time, instead of one-by-one.
39
+
40
+ ### Options
41
+
42
+ Option setters were being bypassed, therefore a lot of the type-checks defined there weren't effectively being picked upon, which could have led to weird user errors.
43
+
44
+ ## Bugfixes
45
+
46
+ * fixed the `push_promise` plugin integration (wasn't working well since `http-2-next` was adopted);
@@ -0,0 +1,27 @@
1
+ # 0.8.0
2
+
3
+
4
+ ## Features
5
+
6
+ * `keep_alive_timeout`: for persistent connections, the keep alive timeout will set the connection to be closed if not reused for a request **after** the last received response;
7
+
8
+ ## Improvements
9
+
10
+ * using `max_requests` for HTTP/1 pipelining as well;
11
+ * `retries` plugin now works with plain HTTP responses (not just error responses);
12
+ * reduced the number of string allocations from log labels;
13
+ * performance: a lot of improvements were made to optimize the "waiting for IO events" phase, which dramatically reduced the CPU usage and make the performance of the library more on-par with other ruby HTTP gems for the 1-shot request scenario.
14
+
15
+
16
+ ## Bugfixes
17
+
18
+ * fixed `HTTPX::Response#copy_to`;
19
+ * fixed `compression` plugin not properly compressing request bodies using `gzip`;
20
+ * fixed `compression` plugin not handling `content-encoding: identity` payloads;
21
+ * do not overwrite user-defined `max_requests`on HTTP2 connection handshake;
22
+ * `retries` plugin: connection was blocking when a request with body was retried;
23
+ * `alt-svc: clear` response header was causing the process to hang;
24
+
25
+ ## Tests
26
+
27
+ * Code coverage improved to 91%;
@@ -0,0 +1,8 @@
1
+ # 0.8.1
2
+
3
+
4
+ ## Bugfixes
5
+
6
+ * fixing HTTP/2 handshake IO interests calculation;
7
+ * fixed the double ctrl+f issue when terminating an ongoing HTTP/2 request;
8
+ * fixed connection comparison when passing headers;
@@ -0,0 +1,7 @@
1
+ # 0.8.2
2
+
3
+ ## Features
4
+
5
+ * `:expect` plugin now supports a new option, `:expect_threshold_size`, meaning: the byte size threshold below which no `expect` header will be sent with requests with payload.
6
+ * `:compression` plugin now supports a new option, `:compression_threshold_size`, meaning: the bytesize threshold below which request payload won't be compressed before being sent.
7
+ * for HTTP/2 connections, when `keep_alive_timeout` expires, a `PING` frame is used to check connection availability; if successful, the connection will be reused.
@@ -0,0 +1,38 @@
1
+ # 0.9.0
2
+
3
+ ## Features
4
+
5
+ ### Multiple requests with specific options
6
+
7
+ You can now pass a third element to the "request element" of an array to `.request`.
8
+
9
+ ```ruby
10
+ requests = [
11
+ [:post, "https://url/post", { form: { foo: "bar" } }],
12
+ [:post, "https://url/post", { form: { foo: "bar2" } }]
13
+ ]
14
+ HTTPX.request(requests)
15
+ # or, if you want to pass options common to all requests
16
+ HTTPX.request(requests, max_concurrent_requests: 1)
17
+ ```
18
+
19
+
20
+ ### HTTPX::Session#build_request
21
+
22
+ `HTTPX::Session::build_request` is now public API from a session. You can now build requests before you send them. These request objects are still considered somewhat "internal", so consider them immutable and **do not rely on its API**. Just pass them forward.
23
+
24
+ Note: this API is only available for instantiated session, so there is no `HTTPX.build_request`.
25
+
26
+
27
+ ```ruby
28
+
29
+ HTTPX.wrap do |http|
30
+ requests = [
31
+ http.build_request(:post, "https://url/post", { form: { foo: "bar" } }),
32
+ http.build_request(:post, "https://url/post", { form: { foo: "bar2" } })
33
+ ]
34
+ http.request(requests)
35
+ # or, if you want to pass options common to all requests
36
+ http.request(requests, max_concurrent_requests: 1)
37
+ end
38
+ ```
@@ -5,6 +5,8 @@ require "httpx/version"
5
5
  require "httpx/extensions"
6
6
 
7
7
  require "httpx/errors"
8
+ require "httpx/utils"
9
+ require "httpx/domain_name"
8
10
  require "httpx/altsvc"
9
11
  require "httpx/callbacks"
10
12
  require "httpx/loggable"
@@ -121,7 +121,7 @@ module Faraday
121
121
  end
122
122
 
123
123
  def respond_to_missing?(meth)
124
- @env.respond_to?(meth)
124
+ @env.respond_to?(meth) || super
125
125
  end
126
126
 
127
127
  def method_missing(meth, *args, &blk)
@@ -10,8 +10,8 @@ module HTTPX
10
10
  MOD
11
11
  end
12
12
 
13
- def request(verb, uri, **options)
14
- branch(default_options).request(verb, uri, **options)
13
+ def request(*args, **options)
14
+ branch(default_options).request(*args, **options)
15
15
  end
16
16
 
17
17
  # :nocov:
@@ -34,20 +34,23 @@ module HTTPX
34
34
  branch(default_options).wrap(&blk)
35
35
  end
36
36
 
37
- def plugin(*args, **opts)
37
+ def plugin(*args, **opts, &blk)
38
38
  klass = is_a?(Session) ? self.class : Session
39
39
  klass = Class.new(klass)
40
40
  klass.instance_variable_set(:@default_options, klass.default_options.merge(default_options))
41
- klass.plugin(*args, **opts).new
41
+ klass.plugin(*args, **opts, &blk).new
42
42
  end
43
43
 
44
44
  # deprecated
45
+ # :nocov:
45
46
  def plugins(*args, **opts)
47
+ warn ":#{__method__} is deprecated, use :plugin instead"
46
48
  klass = is_a?(Session) ? self.class : Session
47
49
  klass = Class.new(klass)
48
50
  klass.instance_variable_set(:@default_options, klass.default_options.merge(default_options))
49
51
  klass.plugins(*args, **opts).new
50
52
  end
53
+ # :nocov:
51
54
 
52
55
  def with(options, &blk)
53
56
  branch(default_options.merge(options), &blk)
@@ -59,7 +62,6 @@ module HTTPX
59
62
  @options || Options.new
60
63
  end
61
64
 
62
- # :nodoc:
63
65
  def branch(options, &blk)
64
66
  return self.class.new(options, &blk) if is_a?(Session)
65
67
 
@@ -67,12 +69,10 @@ module HTTPX
67
69
  end
68
70
 
69
71
  def method_missing(meth, *args, **options)
70
- if meth =~ /\Awith_(.+)/
71
- option = Regexp.last_match(1).to_sym
72
- with(option => (args.first || options))
73
- else
74
- super
75
- end
72
+ return super unless meth =~ /\Awith_(.+)/
73
+
74
+ option = Regexp.last_match(1).to_sym
75
+ with(option => (args.first || options))
76
76
  end
77
77
 
78
78
  def respond_to_missing?(meth, *args)
@@ -51,7 +51,7 @@ module HTTPX
51
51
  def initialize(type, uri, options)
52
52
  @type = type
53
53
  @origins = [uri.origin]
54
- @origin = URI(uri.origin)
54
+ @origin = Utils.uri(uri.origin)
55
55
  @options = Options.new(options)
56
56
  @window_size = @options.window_size
57
57
  @read_buffer = Buffer.new(BUFFER_SIZE)
@@ -88,8 +88,6 @@ module HTTPX
88
88
 
89
89
  return false if exhausted?
90
90
 
91
- return false if @keep_alive_timer && @keep_alive_timer.fires_in.negative?
92
-
93
91
  (
94
92
  (
95
93
  @origins.include?(uri.origin) &&
@@ -107,8 +105,6 @@ module HTTPX
107
105
 
108
106
  return false if exhausted?
109
107
 
110
- return false if @keep_alive_timer && @keep_alive_timer.fires_in.negative?
111
-
112
108
  !(@io.addresses & connection.addresses).empty? && @options == connection.options
113
109
  end
114
110
 
@@ -124,8 +120,8 @@ module HTTPX
124
120
  end
125
121
  end
126
122
 
127
- def create_idle
128
- self.class.new(@type, @origin, @options)
123
+ def create_idle(options = {})
124
+ self.class.new(@type, @origin, @options.merge(options))
129
125
  end
130
126
 
131
127
  def merge(connection)
@@ -135,18 +131,7 @@ module HTTPX
135
131
  end
136
132
  end
137
133
 
138
- def unmerge(connection)
139
- @origins -= connection.instance_variable_get(:@origins)
140
- purge_pending do |request|
141
- request.uri.origin == connection.origin && begin
142
- request.transition(:idle)
143
- connection.send(request)
144
- true
145
- end
146
- end
147
- end
148
-
149
- def purge_pending
134
+ def purge_pending(&block)
150
135
  pendings = []
151
136
  if @parser
152
137
  @inflight -= @parser.pending.size
@@ -154,9 +139,7 @@ module HTTPX
154
139
  end
155
140
  pendings << @pending
156
141
  pendings.each do |pending|
157
- pending.reject! do |request|
158
- yield request
159
- end
142
+ pending.reject!(&block)
160
143
  end
161
144
  end
162
145
 
@@ -229,8 +212,19 @@ module HTTPX
229
212
  def send(request)
230
213
  if @parser && !@write_buffer.full?
231
214
  request.headers["alt-used"] = @origin.authority if match_altsvcs?(request.uri)
215
+ if @keep_alive_timer
216
+ # when pushing a request into an existing connection, we have to check whether there
217
+ # is the possibility that the connection might have extended the keep alive timeout.
218
+ # for such cases, we want to ping for availability before deciding to shovel requests.
219
+ if @keep_alive_timer.fires_in.negative?
220
+ @pending << request
221
+ parser.ping
222
+ return
223
+ end
224
+
225
+ @keep_alive_timer.pause
226
+ end
232
227
  @inflight += 1
233
- @keep_alive_timer.pause if @keep_alive_timer
234
228
  parser.send(request)
235
229
  else
236
230
  @pending << request
@@ -281,8 +275,6 @@ module HTTPX
281
275
  return
282
276
  end
283
277
 
284
- log { "READ: #{siz} bytes..." }
285
-
286
278
  if siz.zero?
287
279
  read_drained = @read_buffer.empty?
288
280
  break
@@ -310,7 +302,6 @@ module HTTPX
310
302
  on_error(ex)
311
303
  return
312
304
  end
313
- log { "WRITE: #{siz} bytes..." }
314
305
 
315
306
  if siz.zero?
316
307
  write_drained = !@write_buffer.empty?
@@ -361,6 +352,8 @@ module HTTPX
361
352
  emit(:altsvc, alt_origin, origin, alt_params)
362
353
  end
363
354
 
355
+ parser.on(:pong, &method(:send_pending))
356
+
364
357
  parser.on(:promise) do |request, stream|
365
358
  request.emit(:promise, parser, stream)
366
359
  end
@@ -395,7 +388,7 @@ module HTTPX
395
388
  parser.on(:error) do |request, ex|
396
389
  case ex
397
390
  when MisdirectedRequestError
398
- emit(:uncoalesce, request.uri)
391
+ emit(:misdirected, request)
399
392
  else
400
393
  response = ErrorResponse.new(request, ex, @options)
401
394
  request.emit(:response, response)
@@ -431,7 +424,7 @@ module HTTPX
431
424
  remove_instance_variable(:@total_timeout)
432
425
  end
433
426
 
434
- @io.close
427
+ @io.close if @io
435
428
  @read_buffer.clear
436
429
  if @keep_alive_timer
437
430
  @keep_alive_timer.cancel
@@ -451,7 +444,6 @@ module HTTPX
451
444
  throw(:jump_tick)
452
445
  rescue Errno::ECONNREFUSED,
453
446
  Errno::EADDRNOTAVAIL,
454
- Errno::EHOSTUNREACH,
455
447
  OpenSSL::SSL::SSLError => e
456
448
  # connect errors, exit gracefully
457
449
  handle_error(e)
@@ -469,8 +461,8 @@ module HTTPX
469
461
  else
470
462
  @keep_alive_timer = @timers.after(@keep_alive_timeout) do
471
463
  unless @inflight.zero?
472
- log { "(#{object_id})) keep alive timeout expired, closing..." }
473
- reset
464
+ log { "(#{@origin}): keep alive timeout expired" }
465
+ parser.ping
474
466
  end
475
467
  end
476
468
  end
@@ -21,6 +21,7 @@ module HTTPX
21
21
  @version = [1, 1]
22
22
  @pending = []
23
23
  @requests = []
24
+ @handshake_completed = false
24
25
  end
25
26
 
26
27
  def interests
@@ -78,6 +79,7 @@ module HTTPX
78
79
  break if idx >= requests_limit
79
80
  next if request.state == :done
80
81
 
82
+ request.headers["connection"] ||= request.options.persistent || idx < requests_limit - 1 ? "keep-alive" : "close"
81
83
  handle(request)
82
84
  end
83
85
  end
@@ -154,6 +156,7 @@ module HTTPX
154
156
  @parser.reset!
155
157
  @max_requests -= 1
156
158
  manage_connection(response)
159
+
157
160
  send(@pending.shift) unless @pending.empty?
158
161
  end
159
162
 
@@ -170,23 +173,39 @@ module HTTPX
170
173
  end
171
174
  end
172
175
 
176
+ def ping
177
+ emit(:reset)
178
+ emit(:exhausted)
179
+ end
180
+
173
181
  private
174
182
 
175
183
  def manage_connection(response)
176
184
  connection = response.headers["connection"]
177
185
  case connection
178
- when /keep\-alive/i
186
+ when /keep-alive/i
187
+ if @handshake_completed
188
+ if @max_requests.zero?
189
+ @pending.concat(@requests)
190
+ @requests.clear
191
+ emit(:exhausted)
192
+ end
193
+ return
194
+ end
195
+
179
196
  keep_alive = response.headers["keep-alive"]
180
197
  return unless keep_alive
181
198
 
182
199
  parameters = Hash[keep_alive.split(/ *, */).map do |pair|
183
200
  pair.split(/ *= */)
184
201
  end]
185
- @max_requests = parameters["max"].to_i if parameters.key?("max")
202
+ @max_requests = parameters["max"].to_i - 1 if parameters.key?("max")
203
+
186
204
  if parameters.key?("timeout")
187
205
  keep_alive_timeout = parameters["timeout"].to_i
188
206
  emit(:timeout, keep_alive_timeout)
189
207
  end
208
+ @handshake_completed = true
190
209
  when /close/i
191
210
  disable
192
211
  when nil
@@ -206,7 +225,14 @@ module HTTPX
206
225
  def disable_pipelining
207
226
  return if @requests.empty?
208
227
 
209
- @requests.each { |r| r.transition(:idle) }
228
+ @requests.each do |r|
229
+ r.transition(:idle)
230
+
231
+ # when we disable pipelining, we still want to try keep-alive.
232
+ # only when keep-alive with one request fails, do we fallback to
233
+ # connection: close.
234
+ r.headers["connection"] = "close" if @max_concurrent_requests == 1
235
+ end
210
236
  # server doesn't handle pipelining, and probably
211
237
  # doesn't support keep-alive. Fallback to send only
212
238
  # 1 keep alive request.
@@ -216,7 +242,7 @@ module HTTPX
216
242
 
217
243
  def set_request_headers(request)
218
244
  request.headers["host"] ||= request.authority
219
- request.headers["connection"] ||= "keep-alive"
245
+ request.headers["connection"] ||= request.options.persistent ? "keep-alive" : "close"
220
246
  if !request.headers.key?("content-length") &&
221
247
  request.body.bytesize == Float::INFINITY
222
248
  request.chunk!