httpx 0.12.0 → 0.14.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_10_1.md +1 -1
  3. data/doc/release_notes/0_13_0.md +58 -0
  4. data/doc/release_notes/0_13_1.md +5 -0
  5. data/doc/release_notes/0_13_2.md +9 -0
  6. data/doc/release_notes/0_14_0.md +79 -0
  7. data/doc/release_notes/0_14_1.md +7 -0
  8. data/lib/httpx.rb +1 -2
  9. data/lib/httpx/callbacks.rb +12 -3
  10. data/lib/httpx/chainable.rb +2 -2
  11. data/lib/httpx/connection.rb +29 -22
  12. data/lib/httpx/connection/http1.rb +35 -15
  13. data/lib/httpx/connection/http2.rb +61 -15
  14. data/lib/httpx/headers.rb +7 -3
  15. data/lib/httpx/io/ssl.rb +30 -17
  16. data/lib/httpx/io/tcp.rb +48 -27
  17. data/lib/httpx/io/udp.rb +31 -7
  18. data/lib/httpx/io/unix.rb +27 -12
  19. data/lib/httpx/options.rb +97 -74
  20. data/lib/httpx/plugins/aws_sdk_authentication.rb +5 -2
  21. data/lib/httpx/plugins/aws_sigv4.rb +5 -4
  22. data/lib/httpx/plugins/basic_authentication.rb +8 -3
  23. data/lib/httpx/plugins/compression.rb +24 -12
  24. data/lib/httpx/plugins/compression/brotli.rb +10 -7
  25. data/lib/httpx/plugins/compression/deflate.rb +6 -5
  26. data/lib/httpx/plugins/compression/gzip.rb +4 -3
  27. data/lib/httpx/plugins/cookies.rb +3 -7
  28. data/lib/httpx/plugins/digest_authentication.rb +5 -5
  29. data/lib/httpx/plugins/expect.rb +6 -6
  30. data/lib/httpx/plugins/follow_redirects.rb +4 -4
  31. data/lib/httpx/plugins/grpc.rb +247 -0
  32. data/lib/httpx/plugins/grpc/call.rb +62 -0
  33. data/lib/httpx/plugins/grpc/message.rb +85 -0
  34. data/lib/httpx/plugins/h2c.rb +43 -58
  35. data/lib/httpx/plugins/internal_telemetry.rb +1 -1
  36. data/lib/httpx/plugins/multipart/part.rb +2 -2
  37. data/lib/httpx/plugins/proxy.rb +3 -7
  38. data/lib/httpx/plugins/proxy/http.rb +5 -4
  39. data/lib/httpx/plugins/proxy/ssh.rb +3 -3
  40. data/lib/httpx/plugins/rate_limiter.rb +1 -1
  41. data/lib/httpx/plugins/retries.rb +14 -15
  42. data/lib/httpx/plugins/stream.rb +99 -75
  43. data/lib/httpx/plugins/upgrade.rb +84 -0
  44. data/lib/httpx/plugins/upgrade/h2.rb +54 -0
  45. data/lib/httpx/pool.rb +14 -5
  46. data/lib/httpx/request.rb +25 -2
  47. data/lib/httpx/resolver/native.rb +7 -3
  48. data/lib/httpx/response.rb +9 -5
  49. data/lib/httpx/session.rb +17 -7
  50. data/lib/httpx/transcoder/chunker.rb +1 -1
  51. data/lib/httpx/version.rb +1 -1
  52. data/sig/callbacks.rbs +2 -0
  53. data/sig/chainable.rbs +2 -1
  54. data/sig/connection/http1.rbs +6 -1
  55. data/sig/connection/http2.rbs +6 -2
  56. data/sig/headers.rbs +2 -2
  57. data/sig/options.rbs +16 -22
  58. data/sig/plugins/aws_sdk_authentication.rbs +2 -0
  59. data/sig/plugins/aws_sigv4.rbs +0 -1
  60. data/sig/plugins/basic_authentication.rbs +2 -0
  61. data/sig/plugins/compression.rbs +7 -5
  62. data/sig/plugins/compression/brotli.rbs +1 -1
  63. data/sig/plugins/compression/deflate.rbs +1 -1
  64. data/sig/plugins/compression/gzip.rbs +1 -1
  65. data/sig/plugins/cookies.rbs +0 -1
  66. data/sig/plugins/digest_authentication.rbs +0 -1
  67. data/sig/plugins/expect.rbs +0 -2
  68. data/sig/plugins/follow_redirects.rbs +0 -2
  69. data/sig/plugins/h2c.rbs +5 -10
  70. data/sig/plugins/persistent.rbs +0 -1
  71. data/sig/plugins/proxy.rbs +0 -1
  72. data/sig/plugins/retries.rbs +0 -4
  73. data/sig/plugins/stream.rbs +17 -16
  74. data/sig/plugins/upgrade.rbs +23 -0
  75. data/sig/request.rbs +7 -2
  76. data/sig/response.rbs +4 -1
  77. data/sig/session.rbs +4 -0
  78. metadata +21 -7
  79. data/lib/httpx/timeout.rb +0 -67
  80. data/sig/timeout.rbs +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5770fadc8d4604ccb0fb274c7fc157802315f68c749a83973ee5596fee8effb1
4
- data.tar.gz: 4f24cca053093be31636405dcb740f5c6b2599197d9ebdfb90cbf127e86a5ffc
3
+ metadata.gz: fbedc26fba4c0d9a8e0543b437ada103b42bbd89d537a50546590adb2f004d04
4
+ data.tar.gz: 6645eeb893ed1f07a8e7c7c86db73c0ff2d49f01c63e52888d7da868b925bcdc
5
5
  SHA512:
6
- metadata.gz: 48fe64207a82e2b8db91b73cc6252ff48ea624e57dcbcbfa30e5f6986e1936e61bbdc83cece34c470dd6e318f41845a306a57e2b5c8710084444b6193786a1eb
7
- data.tar.gz: 70b43fc624a8452187a730e39f55d9e92d16438babbd76aa2346c5b3dd0c2cad7f8e5e787f7c4ab1917c52ed750c7656f2f6c5e92a2ec0fce9cc0a76e5994d1e
6
+ metadata.gz: 1ea37e203901dbae07960c12023cbeb2b3ee62ea461a265ab3dff8687b8874e33cf3bf0bbf62df30243030ce9420317bf635e5df1102cfa9a8503ec09949ca3c
7
+ data.tar.gz: 3515498ac93ca1ef5f165754e8caa95dbddf8aebe079b0efbf8d5a7a78a778d76a4812e4beeddc723dd6fb81dafa4a47c7ae9cb2463834ebd5cf7812ef30a45c
@@ -26,7 +26,7 @@ From now on, both headers and the responnse payload will also appear, so expecte
26
26
  ## Bugfixes
27
27
 
28
28
  * HTTP/2 and HTTP/1.1 exhausted connections now get properly migrated into a new connection;
29
- * HTTP/2 421 responses will now correctly migrate the connection and pendign requests to HTTP/1.1 (a hanging loop was being caused);
29
+ * HTTP/2 421 responses will now correctly migrate the connection and pending requests to HTTP/1.1 (a hanging loop was being caused);
30
30
  * HTTP/2 connection failed with a GOAWAY settings timeout will now return error responses (instead of hanging indefinitely);
31
31
  * Non-IP proxy name-resolving errors will now move on to the next available proxy in the list (instead of hanging indefinitely);
32
32
  * Non-IP DNS resolve errors for `native` and `https` variants will now return the appropriate error response (instead of hanging indefinitely);
@@ -0,0 +1,58 @@
1
+ # 0.13.0
2
+
3
+ ## Features
4
+
5
+ ### Upgrade plugin
6
+
7
+ A new plugin, `:upgrade`, is now available. This plugin allows one to "hook" on HTTP/1.1's protocol upgrade mechanism (see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism), which is the mechanism that browsers use to initiate websockets (there is an example of how to use `httpx` to start a websocket client connection [in the tests](https://gitlab.com/honeyryderchuck/httpx/-/blob/master/test/support/requests/plugins/upgrade.rb))
8
+
9
+ You can read more about the `:upgrade` plugin in the [wiki](https://honeyryderchuck.gitlab.io/httpx/wiki/Connection-Upgrade).
10
+
11
+ It's the basis of two plugins:
12
+
13
+ #### `:h2c`
14
+
15
+ This plugin was been rewritten on top of the `:upgrade` plugin, and handles upgrading a plaintext (non-"https") HTTP/1.1 connection, into an HTTP/2 connection.
16
+
17
+ https://honeyryderchuck.gitlab.io/httpx/wiki/Connection-Upgrade#h2c
18
+
19
+ #### `:upgrade/h2`
20
+
21
+ This plugin handles when a server responds to a request with an `Upgrade: h2` header, does the following requests to the same origin via HTTP/2 prior knowledge (bypassing the necessity for ALPN negotiation, which is the whole point of the feature).
22
+
23
+ https://honeyryderchuck.gitlab.io/httpx/wiki/Connection-Upgrade#h2
24
+
25
+ ### `:addresses` option
26
+
27
+ The `:addresses` option is now available. You can use it to pass a list of IPs to connect to:
28
+
29
+ ```ruby
30
+ # will not resolve example.com, and instead connect to one of the IPs passed.
31
+ HTTPX.get("http://example.com", addresses: %w[172.5.3.1 172.5.3.2]))
32
+ ```
33
+
34
+ You should also use it to connect to HTTP servers bound to a UNIX socket, in which case you'll have to provide a path:
35
+
36
+ ```ruby
37
+ HTTPX.get("http://example.com", addresses: %w[/path/to/usocket]))
38
+ ```
39
+
40
+ The `:transport_options` are therefore deprecated, and will be moved in a major version.
41
+
42
+ ## Improvements
43
+
44
+ Some internal improvements that allow certain plugins not to "leak" globally, such as the `:compression` plugin, which used to enable compression for all the `httpx` sessions from the same process. It doesn't anymore.
45
+
46
+ Using exceptionless nonblocking connect calls in the supported rubies.
47
+
48
+ Removed unneeded APIs around the Options object (`with_` methods, or the defined options list).
49
+
50
+ ## Bugfixes
51
+
52
+ HTTP/1.1 persistent connections were closing after each request after the max requests was reached. It's fixed, and the new connection will also be persistent.
53
+
54
+ When passing open IO objects for origins (the `:io` option), `httpx` was still trying to resolve the origin's domain. This not only didn't make sense, it broke if the domain is unresolvable. It has been fixed.
55
+
56
+ Fixed usage of `:io` option when passed an "authority/io" hash.
57
+
58
+ Fixing some issues around trying to connnect to the next available IPAddress when the previous one was unreachable or ETIMEDOUT.
@@ -0,0 +1,5 @@
1
+ # 0.13.1
2
+
3
+ ## Bugfixes
4
+
5
+ Rescue `Errno::EALREADY` on calls to `connect_nonblock(exception: false)` (there are exceptions after all...).
@@ -0,0 +1,9 @@
1
+ # 0.13.1
2
+
3
+ ## Improvements
4
+
5
+ `UDPSocket#sendmsg_nonblock` is now used in the native resolver.
6
+
7
+ ## Bugfixes
8
+
9
+ Usage in Windows was buggy, resulting in `Errno::EINVAL` during DNS resolving, when using the native resolver. This was due to a discrepancy between `recvfrom` behaviour in WS Sockets and Linux Sockets. This was fixed by making we the UDP socket never tries to receive before a DNS query has been actually sent.
@@ -0,0 +1,79 @@
1
+ # 0.14.0
2
+
3
+ ## Features
4
+
5
+ ### GRPC plugin
6
+
7
+ A new plugin, `:grpc`, is now available. This plugin provides a simple DSL to build GRPC services and performing calls using `httpx` under the hood.
8
+
9
+ Example:
10
+
11
+ ```ruby
12
+ require "httpx"
13
+
14
+ grpc = HTTPX.plugin(:grpc)
15
+ helloworld_stub = grpc.build_stub("localhost:4545")
16
+ helloworld_svc = helloworld_stub.rpc(:SayHello, HelloRequest, HelloReply)
17
+ result = helloworld_svc.say_hello(HelloRequest.new(name: "Jack")) #=> HelloReply: "Hello Jack"
18
+ ```
19
+
20
+ You can read more about the `:grpc` plugin in the [wiki](https://honeyryderchuck.gitlab.io/httpx/wiki/GRPC).
21
+
22
+ ### :origin
23
+
24
+ A new `:origin` option is available. You can use it for setting a base URL for subsequent relative paths on that session:
25
+
26
+ ```ruby
27
+ HTTPX.get("/httpbin/get") #=> HTTPX::Error: invalid URI: /httpbin/get
28
+
29
+ httpbin = HTTPX.with(origin: "https://nghttp2.org")
30
+ httpbin.get("/httpbin/get") #=> #<Response:5420 HTTP/2.0 @status=200 ....
31
+ ```
32
+
33
+ **Note!** The origin is **not** for setting base paths, i.e. if you pass it a relative path, it'll be filtered out in subsequent requests (`HTTPX.with(origin: "https://nghttp2.org/httpbin")` will still use only `"https://nghttp2.org"`).
34
+
35
+ ## Improvements
36
+
37
+ * setting an unexpected option will now raise an `HTTPX::Error` with an helpful message, instead of a confusing `NoMethodError`:
38
+
39
+ ```ruby
40
+ HTTPX.with(foo: "bar")
41
+ # before
42
+ #=> NoMethodError
43
+ # after
44
+ #=> HTTPX::Error: unknown option: foo
45
+
46
+ ```
47
+
48
+ * `HTTPX::Options#def_option` (which can be used for setting custom plugin options) can now be passed a full body string (where the argument is `value`), although it still support the block form. This is the recommended approach, as the block form is based on `define_method`, which would make clients unusable inside ractors.
49
+
50
+ * Added support for `:wait_for_handshake` under the `http2_settings` option (`false` by default). HTTP/2 connections complete the protocol handshake before requests are sent. When this option is `true`, requests get send in the initial payload, before the HTTP/2 connection is fully acknowledged.
51
+
52
+ * 441716a5ac0f7707211ebe0048f568cf0b759c3f: The `:stream` plugin has been improved to start streaming the real response as methods are called (instead of a completely separate synchronous one, which is definitely not good):
53
+
54
+ ```ruby
55
+ session = HTTPX.plugin(:stream)
56
+ response = session.get(build_uri("/stream/3"), stream: true)
57
+
58
+ # before
59
+ response.status # this could block indefinitely, if the request truly streams infinitely.
60
+
61
+ # after
62
+ response.status # sends the request, and starts streaming the response until status is available.
63
+ response.each {|chunk|...} # and now you can start yielding the chunks...
64
+ ```
65
+
66
+
67
+ ## Bugfixes
68
+
69
+ * fixed usage of the `:multipart` if `pathname` isn't loaded.
70
+ * fixed HTTP/2 trailers.
71
+ * fixed connection merges with the same origin, which was causing them to be duplicated and breaking further usage. (#125)
72
+ * fixed repeated session callbacks on a connection, by ensure they're set only once.
73
+ * fixed calculation of `content-length` for streaming or chunked compressed requests.
74
+
75
+
76
+ ## Chore
77
+
78
+ * using ruby base container images in CI instead.
79
+ * using truffleruby official container image.
@@ -0,0 +1,7 @@
1
+ # 0.14.0
2
+
3
+
4
+ ## Bugfixes
5
+
6
+ * fixed: HTTP/2-specific headers were being reused on insecure redirects, thereby creating an invalid request (#128);
7
+ * fixed: multipart request parts weren't using explicity set `:content_type`, instead using file mime type or "text/plain";
data/lib/httpx.rb CHANGED
@@ -12,12 +12,11 @@ require "httpx/callbacks"
12
12
  require "httpx/loggable"
13
13
  require "httpx/registry"
14
14
  require "httpx/transcoder"
15
- require "httpx/options"
16
- require "httpx/timeout"
17
15
  require "httpx/pool"
18
16
  require "httpx/headers"
19
17
  require "httpx/request"
20
18
  require "httpx/response"
19
+ require "httpx/options"
21
20
  require "httpx/chainable"
22
21
 
23
22
  # Top-Level Namespace
@@ -6,19 +6,28 @@ module HTTPX
6
6
  callbacks(type) << action
7
7
  end
8
8
 
9
- def once(event, &block)
10
- on(event) do |*args, &callback|
9
+ def once(type, &block)
10
+ on(type) do |*args, &callback|
11
11
  block.call(*args, &callback)
12
12
  :delete
13
13
  end
14
14
  end
15
15
 
16
+ def only(type, &block)
17
+ callbacks(type).clear
18
+ on(type, &block)
19
+ end
20
+
16
21
  def emit(type, *args)
17
- callbacks(type).delete_if { |pr| pr[*args] == :delete }
22
+ callbacks(type).delete_if { |pr| :delete == pr[*args] } # rubocop:disable Style/YodaCondition
18
23
  end
19
24
 
20
25
  protected
21
26
 
27
+ def callbacks_for?(type)
28
+ @callbacks.key?(type) && !@callbacks[type].empty?
29
+ end
30
+
22
31
  def callbacks(type = nil)
23
32
  return @callbacks unless type
24
33
 
@@ -17,12 +17,12 @@ module HTTPX
17
17
  # :nocov:
18
18
  def timeout(**args)
19
19
  warn ":#{__method__} is deprecated, use :with_timeout instead"
20
- branch(default_options.with(timeout: args))
20
+ with(timeout: args)
21
21
  end
22
22
 
23
23
  def headers(headers)
24
24
  warn ":#{__method__} is deprecated, use :with_headers instead"
25
- branch(default_options.with(headers: headers))
25
+ with(headers: headers)
26
26
  end
27
27
  # :nocov:
28
28
 
@@ -69,8 +69,10 @@ module HTTPX
69
69
  end
70
70
 
71
71
  @inflight = 0
72
- @keep_alive_timeout = options.timeout.keep_alive_timeout
72
+ @keep_alive_timeout = options.timeout[:keep_alive_timeout]
73
73
  @keep_alive_timer = nil
74
+
75
+ self.addresses = options.addresses if options.addresses
74
76
  end
75
77
 
76
78
  # this is a semi-private method, to be used by the resolver
@@ -105,6 +107,8 @@ module HTTPX
105
107
 
106
108
  return false if exhausted?
107
109
 
110
+ return false unless connection.addresses
111
+
108
112
  !(@io.addresses & connection.addresses).empty? && @options == connection.options
109
113
  end
110
114
 
@@ -125,7 +129,7 @@ module HTTPX
125
129
  end
126
130
 
127
131
  def merge(connection)
128
- @origins += connection.instance_variable_get(:@origins)
132
+ @origins |= connection.instance_variable_get(:@origins)
129
133
  connection.purge_pending do |req|
130
134
  send(req)
131
135
  end
@@ -234,9 +238,9 @@ module HTTPX
234
238
  def timeout
235
239
  return @timeout if defined?(@timeout)
236
240
 
237
- return @options.timeout.connect_timeout if @state == :idle
241
+ return @options.timeout[:connect_timeout] if @state == :idle
238
242
 
239
- @options.timeout.operation_timeout
243
+ @options.timeout[:operation_timeout]
240
244
  end
241
245
 
242
246
  private
@@ -409,7 +413,7 @@ module HTTPX
409
413
  emit(:exhausted)
410
414
  end
411
415
  parser.on(:origin) do |origin|
412
- @origins << origin
416
+ @origins |= [origin]
413
417
  end
414
418
  parser.on(:close) do |force|
415
419
  transition(:closing)
@@ -439,6 +443,7 @@ module HTTPX
439
443
  emit(:misdirected, request)
440
444
  else
441
445
  response = ErrorResponse.new(request, ex, @options)
446
+ request.response = response
442
447
  request.emit(:response, response)
443
448
  end
444
449
  end
@@ -447,7 +452,7 @@ module HTTPX
447
452
  def transition(nextstate)
448
453
  case nextstate
449
454
  when :idle
450
- @timeout = @current_timeout = @options.timeout.connect_timeout
455
+ @timeout = @current_timeout = @options.timeout[:connect_timeout]
451
456
 
452
457
  when :open
453
458
  return if @state == :closed
@@ -459,7 +464,7 @@ module HTTPX
459
464
 
460
465
  send_pending
461
466
 
462
- @timeout = @current_timeout = @options.timeout.operation_timeout
467
+ @timeout = @current_timeout = @options.timeout[:operation_timeout]
463
468
  emit(:open)
464
469
  when :closing
465
470
  return unless @state == :open
@@ -472,26 +477,15 @@ module HTTPX
472
477
  remove_instance_variable(:@total_timeout)
473
478
  end
474
479
 
475
- @io.close if @io
476
- @read_buffer.clear
477
- if @keep_alive_timer
478
- @keep_alive_timer.cancel
479
- remove_instance_variable(:@keep_alive_timer)
480
- end
481
-
482
- remove_instance_variable(:@timeout) if defined?(@timeout)
480
+ purge_after_closed
483
481
  when :already_open
484
482
  nextstate = :open
485
483
  send_pending
486
484
  end
487
485
  @state = nextstate
488
- rescue Errno::EHOSTUNREACH
489
- # at this point, all addresses from the IO object have failed
490
- reset
491
- emit(:unreachable)
492
- throw(:jump_tick)
493
486
  rescue Errno::ECONNREFUSED,
494
487
  Errno::EADDRNOTAVAIL,
488
+ Errno::EHOSTUNREACH,
495
489
  TLSError => e
496
490
  # connect errors, exit gracefully
497
491
  handle_error(e)
@@ -499,6 +493,17 @@ module HTTPX
499
493
  emit(:close)
500
494
  end
501
495
 
496
+ def purge_after_closed
497
+ @io.close if @io
498
+ @read_buffer.clear
499
+ if @keep_alive_timer
500
+ @keep_alive_timer.cancel
501
+ remove_instance_variable(:@keep_alive_timer)
502
+ end
503
+
504
+ remove_instance_variable(:@timeout) if defined?(@timeout)
505
+ end
506
+
502
507
  def handle_response
503
508
  @inflight -= 1
504
509
  return unless @inflight.zero?
@@ -538,12 +543,14 @@ module HTTPX
538
543
  def handle_error(error)
539
544
  parser.handle_error(error) if @parser && parser.respond_to?(:handle_error)
540
545
  while (request = @pending.shift)
541
- request.emit(:response, ErrorResponse.new(request, error, @options))
546
+ response = ErrorResponse.new(request, error, @options)
547
+ request.response = response
548
+ request.emit(:response, response)
542
549
  end
543
550
  end
544
551
 
545
552
  def total_timeout
546
- total = @options.timeout.total_timeout
553
+ total = @options.timeout[:total_timeout]
547
554
 
548
555
  return unless total
549
556
 
@@ -10,7 +10,7 @@ module HTTPX
10
10
  MAX_REQUESTS = 100
11
11
  CRLF = "\r\n"
12
12
 
13
- attr_reader :pending
13
+ attr_reader :pending, :requests
14
14
 
15
15
  def initialize(buffer, options)
16
16
  @options = Options.new(options)
@@ -69,7 +69,6 @@ module HTTPX
69
69
 
70
70
  return if @requests.include?(request)
71
71
 
72
- request.once(:headers, &method(:set_protocol_headers))
73
72
  @requests << request
74
73
  @pipelining = true if @requests.size > 1
75
74
  end
@@ -236,6 +235,8 @@ module HTTPX
236
235
 
237
236
  def disable_pipelining
238
237
  return if @requests.empty?
238
+ # do not disable pipelining if already set to 1 request at a time
239
+ return if @max_concurrent_requests == 1
239
240
 
240
241
  @requests.each do |r|
241
242
  r.transition(:idle)
@@ -253,12 +254,15 @@ module HTTPX
253
254
  end
254
255
 
255
256
  def set_protocol_headers(request)
256
- request.headers["host"] ||= request.authority
257
- request.headers["connection"] ||= request.options.persistent ? "keep-alive" : "close"
258
257
  if !request.headers.key?("content-length") &&
259
258
  request.body.bytesize == Float::INFINITY
260
259
  request.chunk!
261
260
  end
261
+
262
+ {
263
+ "host" => (request.headers["host"] || request.authority),
264
+ "connection" => (request.headers["connection"] || (request.options.persistent ? "keep-alive" : "close")),
265
+ }
262
266
  end
263
267
 
264
268
  def headline_uri(request)
@@ -271,22 +275,18 @@ module HTTPX
271
275
  join_headers(request) if request.state == :headers
272
276
  request.transition(:body)
273
277
  join_body(request) if request.state == :body
278
+ request.transition(:trailers)
279
+ # HTTP/1.1 trailers should only work for chunked encoding
280
+ join_trailers(request) if request.body.chunked? && request.state == :trailers
274
281
  request.transition(:done)
275
282
  end
276
283
  end
277
284
 
278
285
  def join_headers(request)
279
- buffer = +""
280
- buffer << "#{request.verb.to_s.upcase} #{headline_uri(request)} HTTP/#{@version.join(".")}" << CRLF
281
- log(color: :yellow) { "<- HEADLINE: #{buffer.chomp.inspect}" }
282
- @buffer << buffer
283
- buffer.clear
284
- request.headers.each do |field, value|
285
- buffer << "#{capitalized(field)}: #{value}" << CRLF
286
- log(color: :yellow) { "<- HEADER: #{buffer.chomp}" }
287
- @buffer << buffer
288
- buffer.clear
289
- end
286
+ @buffer << "#{request.verb.to_s.upcase} #{headline_uri(request)} HTTP/#{@version.join(".")}" << CRLF
287
+ log(color: :yellow) { "<- HEADLINE: #{@buffer.to_s.chomp.inspect}" }
288
+ extra_headers = set_protocol_headers(request)
289
+ join_headers2(request.headers.each(extra_headers))
290
290
  log { "<- " }
291
291
  @buffer << CRLF
292
292
  end
@@ -300,6 +300,26 @@ module HTTPX
300
300
  @buffer << chunk
301
301
  throw(:buffer_full, request) if @buffer.full?
302
302
  end
303
+
304
+ raise request.drain_error if request.drain_error
305
+ end
306
+
307
+ def join_trailers(request)
308
+ return unless request.trailers? && request.callbacks_for?(:trailers)
309
+
310
+ join_headers2(request.trailers)
311
+ log { "<- " }
312
+ @buffer << CRLF
313
+ end
314
+
315
+ def join_headers2(headers)
316
+ buffer = "".b
317
+ headers.each do |field, value|
318
+ buffer << "#{capitalized(field)}: #{value}" << CRLF
319
+ log(color: :yellow) { "<- HEADER: #{buffer.chomp}" }
320
+ @buffer << buffer
321
+ buffer.clear
322
+ end
303
323
  end
304
324
 
305
325
  UPCASED = {