httpx 0.21.0 → 1.2.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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +0 -48
  3. data/README.md +54 -45
  4. data/doc/release_notes/0_10_0.md +2 -2
  5. data/doc/release_notes/0_11_0.md +3 -5
  6. data/doc/release_notes/0_12_0.md +5 -5
  7. data/doc/release_notes/0_13_0.md +4 -4
  8. data/doc/release_notes/0_14_0.md +2 -2
  9. data/doc/release_notes/0_16_0.md +3 -3
  10. data/doc/release_notes/0_17_0.md +1 -1
  11. data/doc/release_notes/0_18_0.md +4 -4
  12. data/doc/release_notes/0_18_2.md +1 -1
  13. data/doc/release_notes/0_19_0.md +1 -1
  14. data/doc/release_notes/0_20_0.md +1 -1
  15. data/doc/release_notes/0_21_0.md +7 -5
  16. data/doc/release_notes/0_21_1.md +12 -0
  17. data/doc/release_notes/0_22_0.md +13 -0
  18. data/doc/release_notes/0_22_1.md +11 -0
  19. data/doc/release_notes/0_22_2.md +5 -0
  20. data/doc/release_notes/0_22_3.md +55 -0
  21. data/doc/release_notes/0_22_4.md +6 -0
  22. data/doc/release_notes/0_22_5.md +6 -0
  23. data/doc/release_notes/0_23_0.md +42 -0
  24. data/doc/release_notes/0_23_1.md +5 -0
  25. data/doc/release_notes/0_23_2.md +5 -0
  26. data/doc/release_notes/0_23_3.md +6 -0
  27. data/doc/release_notes/0_23_4.md +5 -0
  28. data/doc/release_notes/0_24_0.md +48 -0
  29. data/doc/release_notes/0_24_1.md +12 -0
  30. data/doc/release_notes/0_24_2.md +12 -0
  31. data/doc/release_notes/0_24_3.md +12 -0
  32. data/doc/release_notes/0_24_4.md +18 -0
  33. data/doc/release_notes/0_24_5.md +6 -0
  34. data/doc/release_notes/0_24_6.md +5 -0
  35. data/doc/release_notes/0_24_7.md +10 -0
  36. data/doc/release_notes/1_0_0.md +60 -0
  37. data/doc/release_notes/1_0_1.md +5 -0
  38. data/doc/release_notes/1_0_2.md +7 -0
  39. data/doc/release_notes/1_1_0.md +32 -0
  40. data/doc/release_notes/1_1_1.md +17 -0
  41. data/doc/release_notes/1_1_2.md +12 -0
  42. data/doc/release_notes/1_1_3.md +18 -0
  43. data/doc/release_notes/1_1_4.md +6 -0
  44. data/doc/release_notes/1_1_5.md +12 -0
  45. data/doc/release_notes/1_2_0.md +49 -0
  46. data/doc/release_notes/1_2_1.md +6 -0
  47. data/lib/httpx/adapters/datadog.rb +100 -106
  48. data/lib/httpx/adapters/faraday.rb +143 -107
  49. data/lib/httpx/adapters/sentry.rb +26 -7
  50. data/lib/httpx/adapters/webmock.rb +33 -17
  51. data/lib/httpx/altsvc.rb +61 -24
  52. data/lib/httpx/base64.rb +27 -0
  53. data/lib/httpx/buffer.rb +12 -0
  54. data/lib/httpx/callbacks.rb +5 -3
  55. data/lib/httpx/chainable.rb +54 -39
  56. data/lib/httpx/connection/http1.rb +62 -37
  57. data/lib/httpx/connection/http2.rb +16 -27
  58. data/lib/httpx/connection.rb +213 -120
  59. data/lib/httpx/domain_name.rb +10 -13
  60. data/lib/httpx/errors.rb +34 -2
  61. data/lib/httpx/extensions.rb +4 -134
  62. data/lib/httpx/io/ssl.rb +77 -71
  63. data/lib/httpx/io/tcp.rb +46 -70
  64. data/lib/httpx/io/udp.rb +18 -52
  65. data/lib/httpx/io/unix.rb +6 -13
  66. data/lib/httpx/io.rb +3 -9
  67. data/lib/httpx/loggable.rb +4 -19
  68. data/lib/httpx/options.rb +168 -110
  69. data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
  70. data/lib/httpx/plugins/{authentication → auth}/digest.rb +13 -14
  71. data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
  72. data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
  73. data/lib/httpx/plugins/auth.rb +25 -0
  74. data/lib/httpx/plugins/aws_sdk_authentication.rb +1 -3
  75. data/lib/httpx/plugins/aws_sigv4.rb +5 -6
  76. data/lib/httpx/plugins/basic_auth.rb +29 -0
  77. data/lib/httpx/plugins/brotli.rb +50 -0
  78. data/lib/httpx/plugins/callbacks.rb +91 -0
  79. data/lib/httpx/plugins/circuit_breaker/circuit.rb +40 -16
  80. data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +14 -5
  81. data/lib/httpx/plugins/circuit_breaker.rb +30 -7
  82. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
  83. data/lib/httpx/plugins/cookies.rb +20 -10
  84. data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +11 -12
  85. data/lib/httpx/plugins/expect.rb +15 -13
  86. data/lib/httpx/plugins/follow_redirects.rb +71 -29
  87. data/lib/httpx/plugins/grpc/call.rb +2 -3
  88. data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
  89. data/lib/httpx/plugins/grpc/message.rb +7 -37
  90. data/lib/httpx/plugins/grpc.rb +35 -29
  91. data/lib/httpx/plugins/h2c.rb +25 -18
  92. data/lib/httpx/plugins/internal_telemetry.rb +16 -0
  93. data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
  94. data/lib/httpx/plugins/oauth.rb +170 -0
  95. data/lib/httpx/plugins/persistent.rb +1 -1
  96. data/lib/httpx/plugins/proxy/http.rb +15 -10
  97. data/lib/httpx/plugins/proxy/socks4.rb +8 -6
  98. data/lib/httpx/plugins/proxy/socks5.rb +10 -8
  99. data/lib/httpx/plugins/proxy.rb +69 -67
  100. data/lib/httpx/plugins/push_promise.rb +1 -1
  101. data/lib/httpx/plugins/rate_limiter.rb +3 -1
  102. data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
  103. data/lib/httpx/plugins/response_cache/store.rb +34 -17
  104. data/lib/httpx/plugins/response_cache.rb +6 -6
  105. data/lib/httpx/plugins/retries.rb +61 -12
  106. data/lib/httpx/plugins/ssrf_filter.rb +142 -0
  107. data/lib/httpx/plugins/stream.rb +27 -32
  108. data/lib/httpx/plugins/upgrade/h2.rb +4 -4
  109. data/lib/httpx/plugins/upgrade.rb +8 -10
  110. data/lib/httpx/plugins/webdav.rb +10 -8
  111. data/lib/httpx/pool.rb +85 -23
  112. data/lib/httpx/punycode.rb +9 -291
  113. data/lib/httpx/request/body.rb +158 -0
  114. data/lib/httpx/request.rb +86 -121
  115. data/lib/httpx/resolver/https.rb +54 -17
  116. data/lib/httpx/resolver/multi.rb +8 -12
  117. data/lib/httpx/resolver/native.rb +163 -70
  118. data/lib/httpx/resolver/resolver.rb +28 -13
  119. data/lib/httpx/resolver/system.rb +15 -10
  120. data/lib/httpx/resolver.rb +38 -16
  121. data/lib/httpx/response/body.rb +242 -0
  122. data/lib/httpx/response/buffer.rb +96 -0
  123. data/lib/httpx/response.rb +113 -211
  124. data/lib/httpx/selector.rb +2 -4
  125. data/lib/httpx/session.rb +91 -64
  126. data/lib/httpx/session_extensions.rb +4 -1
  127. data/lib/httpx/timers.rb +28 -8
  128. data/lib/httpx/transcoder/body.rb +0 -2
  129. data/lib/httpx/transcoder/chunker.rb +0 -1
  130. data/lib/httpx/transcoder/deflate.rb +37 -0
  131. data/lib/httpx/transcoder/form.rb +52 -33
  132. data/lib/httpx/transcoder/gzip.rb +74 -0
  133. data/lib/httpx/transcoder/json.rb +2 -5
  134. data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
  135. data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +3 -3
  136. data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
  137. data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
  138. data/lib/httpx/transcoder/multipart.rb +17 -0
  139. data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
  140. data/lib/httpx/transcoder/utils/deflater.rb +72 -0
  141. data/lib/httpx/transcoder/utils/inflater.rb +19 -0
  142. data/lib/httpx/transcoder/xml.rb +0 -5
  143. data/lib/httpx/transcoder.rb +4 -6
  144. data/lib/httpx/utils.rb +36 -16
  145. data/lib/httpx/version.rb +1 -1
  146. data/lib/httpx.rb +12 -14
  147. data/sig/altsvc.rbs +33 -0
  148. data/sig/buffer.rbs +1 -0
  149. data/sig/callbacks.rbs +3 -3
  150. data/sig/chainable.rbs +10 -9
  151. data/sig/connection/http1.rbs +5 -4
  152. data/sig/connection/http2.rbs +1 -1
  153. data/sig/connection.rbs +46 -24
  154. data/sig/errors.rbs +9 -3
  155. data/sig/httpx.rbs +5 -4
  156. data/sig/io/ssl.rbs +26 -0
  157. data/sig/io/tcp.rbs +60 -0
  158. data/sig/io/udp.rbs +20 -0
  159. data/sig/io/unix.rbs +10 -0
  160. data/sig/options.rbs +28 -12
  161. data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
  162. data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
  163. data/sig/plugins/auth.rbs +13 -0
  164. data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
  165. data/sig/plugins/brotli.rbs +22 -0
  166. data/sig/plugins/callbacks.rbs +38 -0
  167. data/sig/plugins/circuit_breaker.rbs +13 -3
  168. data/sig/plugins/compression.rbs +6 -4
  169. data/sig/plugins/cookies/jar.rbs +2 -2
  170. data/sig/plugins/cookies.rbs +2 -0
  171. data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
  172. data/sig/plugins/follow_redirects.rbs +11 -2
  173. data/sig/plugins/grpc/call.rbs +19 -0
  174. data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
  175. data/sig/plugins/grpc/message.rbs +17 -0
  176. data/sig/plugins/grpc.rbs +2 -32
  177. data/sig/plugins/h2c.rbs +1 -1
  178. data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
  179. data/sig/plugins/oauth.rbs +54 -0
  180. data/sig/plugins/proxy/socks4.rbs +4 -4
  181. data/sig/plugins/proxy/socks5.rbs +2 -2
  182. data/sig/plugins/proxy/ssh.rbs +1 -1
  183. data/sig/plugins/proxy.rbs +10 -4
  184. data/sig/plugins/response_cache.rbs +12 -3
  185. data/sig/plugins/retries.rbs +28 -8
  186. data/sig/plugins/stream.rbs +24 -17
  187. data/sig/plugins/upgrade.rbs +5 -3
  188. data/sig/pool.rbs +5 -4
  189. data/sig/request/body.rbs +40 -0
  190. data/sig/request.rbs +12 -28
  191. data/sig/resolver/https.rbs +7 -2
  192. data/sig/resolver/native.rbs +10 -4
  193. data/sig/resolver/resolver.rbs +6 -4
  194. data/sig/resolver/system.rbs +2 -0
  195. data/sig/resolver.rbs +9 -5
  196. data/sig/response/body.rbs +53 -0
  197. data/sig/response/buffer.rbs +24 -0
  198. data/sig/response.rbs +17 -38
  199. data/sig/session.rbs +24 -18
  200. data/sig/timers.rbs +17 -7
  201. data/sig/transcoder/body.rbs +4 -3
  202. data/sig/transcoder/deflate.rbs +11 -0
  203. data/sig/transcoder/form.rbs +5 -3
  204. data/sig/transcoder/gzip.rbs +24 -0
  205. data/sig/transcoder/json.rbs +4 -2
  206. data/sig/{plugins → transcoder}/multipart.rbs +3 -12
  207. data/sig/transcoder/utils/body_reader.rbs +15 -0
  208. data/sig/transcoder/utils/deflater.rbs +29 -0
  209. data/sig/transcoder/utils/inflater.rbs +12 -0
  210. data/sig/transcoder/xml.rbs +1 -1
  211. data/sig/transcoder.rbs +22 -7
  212. data/sig/utils.rbs +2 -0
  213. metadata +127 -40
  214. data/lib/httpx/plugins/authentication.rb +0 -20
  215. data/lib/httpx/plugins/basic_authentication.rb +0 -30
  216. data/lib/httpx/plugins/compression/brotli.rb +0 -54
  217. data/lib/httpx/plugins/compression/deflate.rb +0 -49
  218. data/lib/httpx/plugins/compression/gzip.rb +0 -88
  219. data/lib/httpx/plugins/compression.rb +0 -164
  220. data/lib/httpx/plugins/multipart/decoder.rb +0 -187
  221. data/lib/httpx/plugins/multipart.rb +0 -84
  222. data/lib/httpx/registry.rb +0 -85
  223. data/sig/plugins/authentication.rbs +0 -11
  224. data/sig/plugins/compression/brotli.rbs +0 -21
  225. data/sig/plugins/compression/deflate.rbs +0 -17
  226. data/sig/plugins/compression/gzip.rbs +0 -29
  227. data/sig/registry.rbs +0 -13
  228. /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
  229. /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tempfile"
4
+ require "delegate"
5
+
6
+ module HTTPX
7
+ module Transcoder
8
+ module Multipart
9
+ class FilePart < SimpleDelegator
10
+ attr_reader :original_filename, :content_type
11
+
12
+ def initialize(filename, content_type)
13
+ @original_filename = filename
14
+ @content_type = content_type
15
+ @file = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
16
+ super(@file)
17
+ end
18
+ end
19
+
20
+ class Decoder
21
+ include HTTPX::Utils
22
+
23
+ CRLF = "\r\n"
24
+ BOUNDARY_RE = /;\s*boundary=([^;]+)/i.freeze
25
+ MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{CRLF}/ni.freeze
26
+ MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni.freeze
27
+ MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{CRLF}]*)/ni.freeze
28
+ WINDOW_SIZE = 2 << 14
29
+
30
+ def initialize(response)
31
+ @boundary = begin
32
+ m = response.headers["content-type"].to_s[BOUNDARY_RE, 1]
33
+ raise Error, "no boundary declared in content-type header" unless m
34
+
35
+ m.strip
36
+ end
37
+ @buffer = "".b
38
+ @parts = {}
39
+ @intermediate_boundary = "--#{@boundary}"
40
+ @state = :idle
41
+ end
42
+
43
+ def call(response, *)
44
+ response.body.each do |chunk|
45
+ @buffer << chunk
46
+
47
+ parse
48
+ end
49
+
50
+ raise Error, "invalid or unsupported multipart format" unless @buffer.empty?
51
+
52
+ @parts
53
+ end
54
+
55
+ private
56
+
57
+ def parse
58
+ case @state
59
+ when :idle
60
+ raise Error, "payload does not start with boundary" unless @buffer.start_with?("#{@intermediate_boundary}#{CRLF}")
61
+
62
+ @buffer = @buffer.byteslice(@intermediate_boundary.bytesize + 2..-1)
63
+
64
+ @state = :part_header
65
+ when :part_header
66
+ idx = @buffer.index("#{CRLF}#{CRLF}")
67
+
68
+ # raise Error, "couldn't parse part headers" unless idx
69
+ return unless idx
70
+
71
+ head = @buffer.byteslice(0..idx + 4 - 1)
72
+
73
+ @buffer = @buffer.byteslice(head.bytesize..-1)
74
+
75
+ content_type = head[MULTIPART_CONTENT_TYPE, 1]
76
+ if (name = head[MULTIPART_CONTENT_DISPOSITION, 1])
77
+ name = /\A"(.*)"\Z/ =~ name ? Regexp.last_match(1) : name.dup
78
+ name.gsub!(/\\(.)/, "\\1")
79
+ name
80
+ else
81
+ name = head[MULTIPART_CONTENT_ID, 1]
82
+ end
83
+
84
+ filename = HTTPX::Utils.get_filename(head)
85
+
86
+ name = filename || +"#{content_type || "text/plain"}[]" if name.nil? || name.empty?
87
+
88
+ @current = name
89
+
90
+ @parts[name] = if filename
91
+ FilePart.new(filename, content_type)
92
+ else
93
+ "".b
94
+ end
95
+
96
+ @state = :part_body
97
+ when :part_body
98
+ part = @parts[@current]
99
+
100
+ body_separator = if part.is_a?(FilePart)
101
+ "#{CRLF}#{CRLF}"
102
+ else
103
+ CRLF
104
+ end
105
+ idx = @buffer.index(body_separator)
106
+
107
+ if idx
108
+ payload = @buffer.byteslice(0..idx - 1)
109
+ @buffer = @buffer.byteslice(idx + body_separator.bytesize..-1)
110
+ part << payload
111
+ part.rewind if part.respond_to?(:rewind)
112
+ @state = :parse_boundary
113
+ else
114
+ part << @buffer
115
+ @buffer.clear
116
+ end
117
+ when :parse_boundary
118
+ raise Error, "payload does not start with boundary" unless @buffer.start_with?(@intermediate_boundary)
119
+
120
+ @buffer = @buffer.byteslice(@intermediate_boundary.bytesize..-1)
121
+
122
+ if @buffer == "--"
123
+ @buffer.clear
124
+ @state = :done
125
+ return
126
+ elsif @buffer.start_with?(CRLF)
127
+ @buffer = @buffer.byteslice(2..-1)
128
+ @state = :part_header
129
+ else
130
+ return
131
+ end
132
+ when :done
133
+ raise Error, "parsing should have been over by now"
134
+ end until @buffer.empty?
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module HTTPX::Plugins
4
- module Multipart
3
+ module HTTPX
4
+ module Transcoder::Multipart
5
5
  class Encoder
6
6
  attr_reader :bytesize
7
7
 
@@ -43,7 +43,7 @@ module HTTPX::Plugins
43
43
  def to_parts(form)
44
44
  @bytesize = 0
45
45
  params = form.each_with_object([]) do |(key, val), aux|
46
- Multipart.normalize_keys(key, val) do |k, v|
46
+ Transcoder.normalize_keys(key, val, MULTIPART_VALUE_COND) do |k, v|
47
47
  next if v.nil?
48
48
 
49
49
  value, content_type, filename = Part.call(v)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- module Plugins::Multipart
4
+ module Transcoder::Multipart
5
5
  module MimeTypeDetector
6
6
  module_function
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- module Plugins::Multipart
4
+ module Transcoder::Multipart
5
5
  module Part
6
6
  module_function
7
7
 
@@ -21,7 +21,8 @@ module HTTPX
21
21
 
22
22
  value = value.open(File::RDONLY) if Object.const_defined?(:Pathname) && value.is_a?(Pathname)
23
23
 
24
- if value.is_a?(File)
24
+ if value.respond_to?(:path) && value.respond_to?(:read)
25
+ # either a File, a Tempfile, or something else which has to quack like a file
25
26
  filename ||= File.basename(value.path)
26
27
  content_type ||= MimeTypeDetector.call(value, filename) || "application/octet-stream"
27
28
  [value, content_type, filename]
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "multipart/encoder"
4
+ require_relative "multipart/decoder"
5
+ require_relative "multipart/part"
6
+ require_relative "multipart/mime_type_detector"
7
+
8
+ module HTTPX::Transcoder
9
+ module Multipart
10
+ MULTIPART_VALUE_COND = lambda do |value|
11
+ value.respond_to?(:read) ||
12
+ (value.respond_to?(:to_hash) &&
13
+ value.key?(:body) &&
14
+ (value.key?(:filename) || value.key?(:content_type)))
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+
5
+ module HTTPX
6
+ module Transcoder
7
+ class BodyReader
8
+ def initialize(body)
9
+ @body = if body.respond_to?(:read)
10
+ body.rewind if body.respond_to?(:rewind)
11
+ body
12
+ elsif body.respond_to?(:each)
13
+ body.enum_for(:each)
14
+ else
15
+ StringIO.new(body.to_s)
16
+ end
17
+ end
18
+
19
+ def bytesize
20
+ return @body.bytesize if @body.respond_to?(:bytesize)
21
+
22
+ Float::INFINITY
23
+ end
24
+
25
+ def read(length = nil, outbuf = nil)
26
+ return @body.read(length, outbuf) if @body.respond_to?(:read)
27
+
28
+ begin
29
+ chunk = @body.next
30
+ if outbuf
31
+ outbuf.clear.force_encoding(Encoding::BINARY)
32
+ outbuf << chunk
33
+ else
34
+ outbuf = chunk
35
+ end
36
+ outbuf unless length && outbuf.empty?
37
+ rescue StopIteration
38
+ end
39
+ end
40
+
41
+ def close
42
+ @body.close if @body.respond_to?(:close)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require_relative "body_reader"
5
+
6
+ module HTTPX
7
+ module Transcoder
8
+ class Deflater
9
+ extend Forwardable
10
+
11
+ attr_reader :content_type
12
+
13
+ def initialize(body)
14
+ @content_type = body.content_type
15
+ @body = BodyReader.new(body)
16
+ @closed = false
17
+ end
18
+
19
+ def bytesize
20
+ buffer_deflate!
21
+
22
+ @buffer.size
23
+ end
24
+
25
+ def read(length = nil, outbuf = nil)
26
+ return @buffer.read(length, outbuf) if @buffer
27
+
28
+ return if @closed
29
+
30
+ chunk = @body.read(length)
31
+
32
+ compressed_chunk = deflate(chunk)
33
+
34
+ return unless compressed_chunk
35
+
36
+ if outbuf
37
+ outbuf.clear.force_encoding(Encoding::BINARY)
38
+ outbuf << compressed_chunk
39
+ else
40
+ compressed_chunk
41
+ end
42
+ end
43
+
44
+ def close
45
+ return if @closed
46
+
47
+ @buffer.close if @buffer
48
+
49
+ @body.close
50
+
51
+ @closed = true
52
+ end
53
+
54
+ private
55
+
56
+ # rubocop:disable Naming/MemoizedInstanceVariableName
57
+ def buffer_deflate!
58
+ return @buffer if defined?(@buffer)
59
+
60
+ buffer = Response::Buffer.new(
61
+ threshold_size: Options::MAX_BODY_THRESHOLD_SIZE
62
+ )
63
+ ::IO.copy_stream(self, buffer)
64
+
65
+ buffer.rewind
66
+
67
+ @buffer = buffer
68
+ end
69
+ # rubocop:enable Naming/MemoizedInstanceVariableName
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,19 @@
1
+ module HTTPX
2
+ module Transcoder
3
+ class Inflater
4
+ def initialize(bytesize)
5
+ @bytesize = bytesize
6
+ end
7
+
8
+ def call(chunk)
9
+ buffer = @inflater.inflate(chunk)
10
+ @bytesize -= chunk.bytesize
11
+ if @bytesize <= 0
12
+ buffer << @inflater.finish
13
+ @inflater.close
14
+ end
15
+ buffer
16
+ end
17
+ end
18
+ end
19
+ end
@@ -6,8 +6,6 @@ require "uri"
6
6
 
7
7
  module HTTPX::Transcoder
8
8
  module Xml
9
- using HTTPX::RegexpExtensions
10
-
11
9
  module_function
12
10
 
13
11
  MIME_TYPES = %r{\b(application|text)/(.+\+)?xml\b}.freeze
@@ -38,7 +36,6 @@ module HTTPX::Transcoder
38
36
  begin
39
37
  require "nokogiri"
40
38
 
41
- # rubocop:disable Lint/DuplicateMethods
42
39
  def decode(response)
43
40
  content_type = response.content_type.mime_type
44
41
 
@@ -51,7 +48,5 @@ module HTTPX::Transcoder
51
48
  raise HTTPX::Error, "\"nokogiri\" is required in order to decode XML"
52
49
  end
53
50
  end
54
- # rubocop:enable Lint/DuplicateMethods
55
51
  end
56
- register "xml", Xml
57
52
  end
@@ -2,14 +2,10 @@
2
2
 
3
3
  module HTTPX
4
4
  module Transcoder
5
- extend Registry
6
-
7
- using RegexpExtensions unless Regexp.method_defined?(:match?)
8
-
9
5
  module_function
10
6
 
11
7
  def normalize_keys(key, value, cond = nil, &block)
12
- if (cond && cond.call(value))
8
+ if cond && cond.call(value)
13
9
  block.call(key.to_s, value)
14
10
  elsif value.respond_to?(:to_ary)
15
11
  if value.empty?
@@ -73,7 +69,7 @@ module HTTPX
73
69
  end
74
70
 
75
71
  def params_hash_has_key?(hash, key)
76
- return false if /\[\]/.match?(key)
72
+ return false if key.include?("[]")
77
73
 
78
74
  key.split(/[\[\]]+/).inject(hash) do |h, part|
79
75
  next h if part == ""
@@ -92,3 +88,5 @@ require "httpx/transcoder/form"
92
88
  require "httpx/transcoder/json"
93
89
  require "httpx/transcoder/xml"
94
90
  require "httpx/transcoder/chunker"
91
+ require "httpx/transcoder/deflate"
92
+ require "httpx/transcoder/gzip"
data/lib/httpx/utils.rb CHANGED
@@ -4,6 +4,11 @@ module HTTPX
4
4
  module Utils
5
5
  using URIExtensions
6
6
 
7
+ TOKEN = %r{[^\s()<>,;:\\"/\[\]?=]+}.freeze
8
+ VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/.freeze
9
+ FILENAME_REGEX = /\s*filename=(#{VALUE})/.freeze
10
+ FILENAME_EXTENSION_REGEX = /\s*filename\*=(#{VALUE})/.freeze
11
+
7
12
  module_function
8
13
 
9
14
  def now
@@ -25,31 +30,46 @@ module HTTPX
25
30
  time - Time.now
26
31
  end
27
32
 
28
- if RUBY_VERSION < "2.3"
29
-
30
- def to_uri(uri)
31
- URI(uri)
33
+ def get_filename(header, _prefix_regex = nil)
34
+ filename = nil
35
+ case header
36
+ when FILENAME_REGEX
37
+ filename = Regexp.last_match(1)
38
+ filename = Regexp.last_match(1) if filename =~ /^"(.*)"$/
39
+ when FILENAME_EXTENSION_REGEX
40
+ filename = Regexp.last_match(1)
41
+ encoding, _, filename = filename.split("'", 3)
32
42
  end
33
43
 
34
- else
44
+ return unless filename
35
45
 
36
- URIParser = URI::RFC2396_Parser.new
46
+ filename = URI::DEFAULT_PARSER.unescape(filename) if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
37
47
 
38
- def to_uri(uri)
39
- return URI(uri) unless uri.is_a?(String) && !uri.ascii_only?
48
+ filename.scrub!
40
49
 
41
- uri = URI(URIParser.escape(uri))
50
+ filename = filename.gsub(/\\(.)/, '\1') unless /\\[^\\"]/.match?(filename)
42
51
 
43
- non_ascii_hostname = URIParser.unescape(uri.host)
52
+ filename.force_encoding ::Encoding.find(encoding) if encoding
44
53
 
45
- non_ascii_hostname.force_encoding(Encoding::UTF_8)
54
+ filename
55
+ end
46
56
 
47
- idna_hostname = Punycode.encode_hostname(non_ascii_hostname)
57
+ URIParser = URI::RFC2396_Parser.new
48
58
 
49
- uri.host = idna_hostname
50
- uri.non_ascii_hostname = non_ascii_hostname
51
- uri
52
- end
59
+ def to_uri(uri)
60
+ return URI(uri) unless uri.is_a?(String) && !uri.ascii_only?
61
+
62
+ uri = URI(URIParser.escape(uri))
63
+
64
+ non_ascii_hostname = URIParser.unescape(uri.host)
65
+
66
+ non_ascii_hostname.force_encoding(Encoding::UTF_8)
67
+
68
+ idna_hostname = Punycode.encode_hostname(non_ascii_hostname)
69
+
70
+ uri.host = idna_hostname
71
+ uri.non_ascii_hostname = non_ascii_hostname
72
+ uri
53
73
  end
54
74
  end
55
75
  end
data/lib/httpx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "0.21.0"
4
+ VERSION = "1.2.1"
5
5
  end
data/lib/httpx.rb CHANGED
@@ -11,7 +11,6 @@ require "httpx/domain_name"
11
11
  require "httpx/altsvc"
12
12
  require "httpx/callbacks"
13
13
  require "httpx/loggable"
14
- require "httpx/registry"
15
14
  require "httpx/transcoder"
16
15
  require "httpx/timers"
17
16
  require "httpx/pool"
@@ -21,7 +20,6 @@ require "httpx/response"
21
20
  require "httpx/options"
22
21
  require "httpx/chainable"
23
22
 
24
- require "mutex_m"
25
23
  # Top-Level Namespace
26
24
  #
27
25
  module HTTPX
@@ -32,16 +30,17 @@ module HTTPX
32
30
  #
33
31
  module Plugins
34
32
  @plugins = {}
35
- @plugins.extend(Mutex_m)
33
+ @plugins_mutex = Thread::Mutex.new
36
34
 
37
35
  # Loads a plugin based on a name. If the plugin hasn't been loaded, tries to load
38
36
  # it from the load path under "httpx/plugins/" directory.
39
37
  #
40
38
  def self.load_plugin(name)
41
39
  h = @plugins
42
- unless (plugin = h.synchronize { h[name] })
40
+ m = @plugins_mutex
41
+ unless (plugin = m.synchronize { h[name] })
43
42
  require "httpx/plugins/#{name}"
44
- raise "Plugin #{name} hasn't been registered" unless (plugin = h.synchronize { h[name] })
43
+ raise "Plugin #{name} hasn't been registered" unless (plugin = m.synchronize { h[name] })
45
44
  end
46
45
  plugin
47
46
  end
@@ -50,20 +49,19 @@ module HTTPX
50
49
  #
51
50
  def self.register_plugin(name, mod)
52
51
  h = @plugins
53
- h.synchronize { h[name] = mod }
52
+ m = @plugins_mutex
53
+ m.synchronize { h[name] = mod }
54
54
  end
55
55
  end
56
56
 
57
- # :nocov:
58
- def self.const_missing(const_name)
59
- super unless const_name == :Client
60
- warn "DEPRECATION WARNING: the class #{self}::Client is deprecated. Use #{self}::Session instead."
61
- Session
62
- end
63
- # :nocov:
64
-
65
57
  extend Chainable
66
58
  end
67
59
 
68
60
  require "httpx/session"
69
61
  require "httpx/session_extensions"
62
+
63
+ # load integrations when possible
64
+
65
+ require "httpx/adapters/datadog" if defined?(DDTrace) || defined?(Datadog)
66
+ require "httpx/adapters/sentry" if defined?(Sentry)
67
+ require "httpx/adapters/webmock" if defined?(WebMock)
data/sig/altsvc.rbs ADDED
@@ -0,0 +1,33 @@
1
+ module HTTPX
2
+ module AltSvc
3
+ module ConnectionMixin
4
+
5
+ def send: (Request request) -> void
6
+
7
+ def match?: (URI::Generic uri, Options options) -> bool
8
+
9
+ private
10
+
11
+ def match_altsvcs?: (URI::Generic uri) -> bool
12
+
13
+ def match_altsvc_options?: (URI::Generic uri, Options options) -> bool
14
+ end
15
+
16
+ type altsvc_params = Hash[String, untyped]
17
+
18
+ def self?.cached_altsvc: (String origin) -> Array[altsvc_params]
19
+
20
+ def self?.cached_altsvc_set: (String origin, altsvc_params) -> void
21
+
22
+ def self?.lookup: (String origin, Integer | Float ttl) -> Array[altsvc_params]
23
+
24
+ def self?.emit: (Request request, response response) { (http_uri alt_origin, String origin, altsvc_params alt_params) -> void } -> void
25
+
26
+ def self?.parse: (String altsvc) { (http_uri alt_origin, altsvc_params alt_params) -> void } -> void
27
+ | (String altsvc) -> Enumerable[[http_uri, altsvc_params]]
28
+
29
+ def self?.parse_altsvc_scheme: (String alt_proto) -> String?
30
+
31
+ def self.parse_altsvc_origin: (string alt_proto, String alt_origin) -> http_uri?
32
+ end
33
+ end
data/sig/buffer.rbs CHANGED
@@ -11,6 +11,7 @@ module HTTPX
11
11
 
12
12
  def full?: () -> bool
13
13
  def shift!: (Integer) -> void
14
+ def capacity: () -> Integer
14
15
 
15
16
  # delegated
16
17
  def <<: (string data) -> String
data/sig/callbacks.rbs CHANGED
@@ -4,9 +4,9 @@ module HTTPX
4
4
  end
5
5
 
6
6
  module Callbacks
7
- def on: (Symbol) { (*untyped) -> void } -> void
8
- def once: (Symbol) { (*untyped) -> void } -> void
9
- def only: (Symbol) { (*untyped) -> void } -> void
7
+ def on: (Symbol) { (*untyped) -> void } -> self
8
+ def once: (Symbol) { (*untyped) -> void } -> self
9
+ def only: (Symbol) { (*untyped) -> void } -> self
10
10
  def emit: (Symbol, *untyped) -> void
11
11
 
12
12
  def callbacks_for?: (Symbol) -> bool
data/sig/chainable.rbs CHANGED
@@ -2,9 +2,9 @@ module HTTPX
2
2
  module Chainable
3
3
  def request: (*Request, **untyped) -> Array[response]
4
4
  | (Request, **untyped) -> response
5
- | (verb | string, uri | [uri], **untyped) -> response
6
- | (Array[[verb | string, uri] | [verb | string, uri, options]], **untyped) -> Array[response]
7
- | (verb | string, _Each[uri | [uri, options]], **untyped) -> Array[response]
5
+ | (verb, uri | [uri], **untyped) -> response
6
+ | (Array[[verb, uri] | [verb, uri, options]], **untyped) -> Array[response]
7
+ | (verb, _Each[uri | [uri, options]], **untyped) -> Array[response]
8
8
 
9
9
  def accept: (String) -> Session
10
10
  def wrap: () { (Session) -> void } -> void
@@ -12,18 +12,17 @@ module HTTPX
12
12
  def with: (options) -> Session
13
13
  | (options) { (Session) -> void } -> void
14
14
 
15
- def plugin: (:authentication, ?options) -> Plugins::sessionAuthentication
16
- | (:basic_authentication, ?options) -> Plugins::sessionBasicAuth
17
- | (:digest_authentication, ?options) -> Plugins::sessionDigestAuth
18
- | (:ntlm_authentication, ?options) -> Plugins::sessionNTLMAuth
15
+ def plugin: (:auth, ?options) -> Plugins::sessionAuthorization
16
+ | (:basic_auth, ?options) -> Plugins::sessionBasicAuth
17
+ | (:digest_auth, ?options) -> Plugins::sessionDigestAuth
18
+ | (:ntlm_auth, ?options) -> Plugins::sessionNTLMAuth
19
19
  | (:aws_sdk_authentication, ?options) -> Plugins::sessionAwsSdkAuthentication
20
- | (:compression, ?options) -> Session
20
+ | (:brotli, ?options) -> Session
21
21
  | (:cookies, ?options) -> Plugins::sessionCookies
22
22
  | (:expect, ?options) -> Session
23
23
  | (:follow_redirects, ?options) -> Plugins::sessionFollowRedirects
24
24
  | (:upgrade, ?options) -> Session
25
25
  | (:h2c, ?options) -> Session
26
- | (:multipart, ?options) -> Session
27
26
  | (:persistent, ?options) -> Plugins::sessionPersistent
28
27
  | (:proxy, ?options) -> (Plugins::sessionProxy & Plugins::httpProxy)
29
28
  | (:push_promise, ?options) -> Plugins::sessionPushPromise
@@ -34,6 +33,8 @@ module HTTPX
34
33
  | (:grpc, ?options) -> Plugins::grpcSession
35
34
  | (:response_cache, ?options) -> Plugins::sessionResponseCache
36
35
  | (:circuit_breaker, ?options) -> Plugins::sessionCircuitBreaker
36
+ | (:oauth, ?options) -> Plugins::sessionOAuth
37
+ | (:callbacks, ?options) -> Plugins::sessionCallbacks
37
38
  | (Symbol | Module, ?options) { (Class) -> void } -> Session
38
39
  | (Symbol | Module, ?options) -> Session
39
40
 
@@ -10,8 +10,9 @@ module HTTPX
10
10
  attr_reader pending: Array[Request]
11
11
  attr_reader requests: Array[Request]
12
12
 
13
+ attr_accessor max_concurrent_requests: Integer
14
+
13
15
  @options: Options
14
- @max_concurrent_requests: Integer
15
16
  @max_requests: Integer
16
17
  @parser: Parser::HTTP1
17
18
  @buffer: Buffer
@@ -53,19 +54,19 @@ module HTTPX
53
54
 
54
55
  def ping: () -> void
55
56
 
56
- def timeout: () -> Numeric
57
+ def timeout: () -> Numeric?
57
58
 
58
59
  private
59
60
 
60
61
  def initialize: (Buffer, options) -> untyped
61
62
 
62
- def manage_connection: (Response) -> void
63
+ def manage_connection: (Request request, Response response) -> void
63
64
 
64
65
  def disable: () -> void
65
66
 
66
67
  def disable_pipelining: () -> void
67
68
 
68
- def set_protocol_headers: (Request) -> _Each[[String, String]]
69
+ def set_protocol_headers: (Request request) -> _Each[[String, String]]
69
70
 
70
71
  def handle: (Request request) -> void
71
72