httpx 0.8.0 → 0.10.1

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