httpx 0.12.0 → 0.14.1

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