httpx 0.24.6 → 1.0.0

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 (131) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +0 -48
  3. data/README.md +4 -13
  4. data/doc/release_notes/0_24_4.md +3 -3
  5. data/doc/release_notes/1_0_0.md +60 -0
  6. data/lib/httpx/adapters/datadog.rb +28 -97
  7. data/lib/httpx/adapters/faraday.rb +9 -52
  8. data/lib/httpx/adapters/webmock.rb +2 -7
  9. data/lib/httpx/altsvc.rb +4 -22
  10. data/lib/httpx/base64.rb +27 -0
  11. data/lib/httpx/chainable.rb +0 -23
  12. data/lib/httpx/connection.rb +11 -32
  13. data/lib/httpx/domain_name.rb +5 -12
  14. data/lib/httpx/errors.rb +0 -2
  15. data/lib/httpx/extensions.rb +0 -124
  16. data/lib/httpx/io/ssl.rb +26 -59
  17. data/lib/httpx/io/tcp.rb +27 -68
  18. data/lib/httpx/io/udp.rb +13 -48
  19. data/lib/httpx/io/unix.rb +1 -8
  20. data/lib/httpx/loggable.rb +4 -19
  21. data/lib/httpx/options.rb +24 -84
  22. data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
  23. data/lib/httpx/plugins/{authentication → auth}/digest.rb +2 -5
  24. data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
  25. data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
  26. data/lib/httpx/plugins/auth.rb +25 -0
  27. data/lib/httpx/plugins/aws_sigv4.rb +0 -1
  28. data/lib/httpx/plugins/{basic_authentication.rb → basic_auth.rb} +5 -6
  29. data/lib/httpx/plugins/brotli.rb +50 -0
  30. data/lib/httpx/plugins/circuit_breaker/circuit.rb +40 -16
  31. data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +16 -5
  32. data/lib/httpx/plugins/circuit_breaker.rb +11 -4
  33. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
  34. data/lib/httpx/plugins/cookies.rb +1 -1
  35. data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +5 -5
  36. data/lib/httpx/plugins/follow_redirects.rb +21 -24
  37. data/lib/httpx/plugins/grpc/grpc_encoding.rb +82 -0
  38. data/lib/httpx/plugins/grpc/message.rb +7 -39
  39. data/lib/httpx/plugins/grpc.rb +15 -21
  40. data/lib/httpx/plugins/h2c.rb +0 -1
  41. data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +5 -5
  42. data/lib/httpx/plugins/oauth.rb +2 -2
  43. data/lib/httpx/plugins/proxy/http.rb +0 -2
  44. data/lib/httpx/plugins/proxy/socks4.rb +0 -4
  45. data/lib/httpx/plugins/proxy/socks5.rb +1 -5
  46. data/lib/httpx/plugins/proxy.rb +3 -32
  47. data/lib/httpx/plugins/retries.rb +3 -4
  48. data/lib/httpx/plugins/stream.rb +4 -6
  49. data/lib/httpx/punycode.rb +9 -291
  50. data/lib/httpx/request/body.rb +145 -0
  51. data/lib/httpx/request.rb +2 -119
  52. data/lib/httpx/resolver/https.rb +1 -1
  53. data/lib/httpx/resolver/native.rb +6 -14
  54. data/lib/httpx/resolver/resolver.rb +1 -1
  55. data/lib/httpx/resolver/system.rb +11 -9
  56. data/lib/httpx/response/body.rb +206 -0
  57. data/lib/httpx/response/buffer.rb +90 -0
  58. data/lib/httpx/response.rb +5 -208
  59. data/lib/httpx/selector.rb +0 -2
  60. data/lib/httpx/session.rb +0 -10
  61. data/lib/httpx/transcoder/body.rb +0 -1
  62. data/lib/httpx/transcoder/deflate.rb +37 -0
  63. data/lib/httpx/transcoder/form.rb +52 -32
  64. data/lib/httpx/transcoder/gzip.rb +74 -0
  65. data/lib/httpx/transcoder/json.rb +2 -4
  66. data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
  67. data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +3 -3
  68. data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
  69. data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
  70. data/lib/httpx/transcoder/multipart.rb +17 -0
  71. data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
  72. data/lib/httpx/transcoder/utils/deflater.rb +72 -0
  73. data/lib/httpx/transcoder/xml.rb +0 -2
  74. data/lib/httpx/transcoder.rb +2 -2
  75. data/lib/httpx/utils.rb +10 -20
  76. data/lib/httpx/version.rb +1 -1
  77. data/lib/httpx.rb +0 -8
  78. data/sig/chainable.rbs +5 -6
  79. data/sig/connection.rbs +0 -1
  80. data/sig/errors.rbs +0 -3
  81. data/sig/httpx.rbs +2 -1
  82. data/sig/io/unix.rbs +1 -1
  83. data/sig/options.rbs +12 -8
  84. data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
  85. data/sig/plugins/auth.rbs +13 -0
  86. data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
  87. data/sig/plugins/brotli.rbs +22 -0
  88. data/sig/plugins/circuit_breaker.rbs +7 -3
  89. data/sig/plugins/compression.rbs +0 -2
  90. data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
  91. data/sig/plugins/follow_redirects.rbs +0 -1
  92. data/sig/plugins/grpc/call.rbs +19 -0
  93. data/sig/plugins/grpc/grpc_encoding.rbs +33 -0
  94. data/sig/plugins/grpc/message.rbs +17 -0
  95. data/sig/plugins/grpc.rbs +2 -32
  96. data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
  97. data/sig/plugins/oauth.rbs +1 -1
  98. data/sig/plugins/proxy/socks4.rbs +2 -3
  99. data/sig/plugins/proxy/socks5.rbs +0 -1
  100. data/sig/plugins/proxy/ssh.rbs +1 -1
  101. data/sig/plugins/response_cache.rbs +5 -2
  102. data/sig/request/body.rbs +42 -0
  103. data/sig/request.rbs +1 -27
  104. data/sig/resolver/resolver.rbs +1 -1
  105. data/sig/response/body.rbs +52 -0
  106. data/sig/response/buffer.rbs +24 -0
  107. data/sig/response.rbs +0 -39
  108. data/sig/transcoder/body.rbs +4 -3
  109. data/sig/transcoder/deflate.rbs +11 -0
  110. data/sig/transcoder/form.rbs +5 -3
  111. data/sig/transcoder/gzip.rbs +24 -0
  112. data/sig/transcoder/json.rbs +4 -2
  113. data/sig/{plugins → transcoder}/multipart.rbs +3 -10
  114. data/sig/transcoder/utils/body_reader.rbs +15 -0
  115. data/sig/transcoder/utils/deflater.rbs +29 -0
  116. data/sig/transcoder.rbs +18 -2
  117. metadata +50 -34
  118. data/lib/httpx/plugins/authentication.rb +0 -24
  119. data/lib/httpx/plugins/compression/brotli.rb +0 -54
  120. data/lib/httpx/plugins/compression/deflate.rb +0 -54
  121. data/lib/httpx/plugins/compression/gzip.rb +0 -90
  122. data/lib/httpx/plugins/compression.rb +0 -165
  123. data/lib/httpx/plugins/multipart/decoder.rb +0 -137
  124. data/lib/httpx/plugins/multipart.rb +0 -96
  125. data/sig/plugins/authentication.rbs +0 -13
  126. data/sig/plugins/compression/brotli.rbs +0 -21
  127. data/sig/plugins/compression/deflate.rbs +0 -17
  128. data/sig/plugins/compression/gzip.rbs +0 -29
  129. /data/sig/plugins/{authentication → auth}/digest.rbs +0 -0
  130. /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
  131. /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ class Response::Body
5
+ attr_reader :encoding, :encodings
6
+
7
+ def initialize(response, options)
8
+ @response = response
9
+ @headers = response.headers
10
+ @options = options
11
+ @threshold_size = options.body_threshold_size
12
+ @window_size = options.window_size
13
+ @encoding = response.content_type.charset || Encoding::BINARY
14
+ @encodings = []
15
+ @length = 0
16
+ @buffer = nil
17
+ @state = :idle
18
+ initialize_inflaters
19
+ end
20
+
21
+ def initialize_dup(other)
22
+ super
23
+
24
+ @buffer = other.instance_variable_get(:@buffer).dup
25
+ end
26
+
27
+ def closed?
28
+ @state == :closed
29
+ end
30
+
31
+ def write(chunk)
32
+ return if @state == :closed
33
+
34
+ @inflaters.reverse_each do |inflater|
35
+ chunk = inflater.call(chunk)
36
+ end if @inflaters
37
+
38
+ size = chunk.bytesize
39
+ @length += size
40
+ transition(:open)
41
+ @buffer.write(chunk)
42
+
43
+ @response.emit(:chunk_received, chunk)
44
+ size
45
+ end
46
+
47
+ def read(*args)
48
+ return unless @buffer
49
+
50
+ unless @reader
51
+ rewind
52
+ @reader = @buffer
53
+ end
54
+
55
+ @reader.read(*args)
56
+ end
57
+
58
+ def bytesize
59
+ @length
60
+ end
61
+
62
+ def each
63
+ return enum_for(__method__) unless block_given?
64
+
65
+ begin
66
+ if @buffer
67
+ rewind
68
+ while (chunk = @buffer.read(@window_size))
69
+ yield(chunk.force_encoding(@encoding))
70
+ end
71
+ end
72
+ ensure
73
+ close
74
+ end
75
+ end
76
+
77
+ def filename
78
+ return unless @headers.key?("content-disposition")
79
+
80
+ Utils.get_filename(@headers["content-disposition"])
81
+ end
82
+
83
+ def to_s
84
+ return "".b unless @buffer
85
+
86
+ @buffer.to_s
87
+ end
88
+
89
+ alias_method :to_str, :to_s
90
+
91
+ def empty?
92
+ @length.zero?
93
+ end
94
+
95
+ def copy_to(dest)
96
+ return unless @buffer
97
+
98
+ rewind
99
+
100
+ if dest.respond_to?(:path) && @buffer.respond_to?(:path)
101
+ FileUtils.mv(@buffer.path, dest.path)
102
+ else
103
+ ::IO.copy_stream(@buffer, dest)
104
+ end
105
+ end
106
+
107
+ # closes/cleans the buffer, resets everything
108
+ def close
109
+ if @buffer
110
+ @buffer.close
111
+ @buffer = nil
112
+ end
113
+ @length = 0
114
+ transition(:closed)
115
+ end
116
+
117
+ def ==(other)
118
+ object_id == other.object_id || begin
119
+ if other.respond_to?(:read)
120
+ _with_same_buffer_pos { FileUtils.compare_stream(@buffer, other) }
121
+ else
122
+ to_s == other.to_s
123
+ end
124
+ end
125
+ end
126
+
127
+ # :nocov:
128
+ def inspect
129
+ "#<HTTPX::Response::Body:#{object_id} " \
130
+ "@state=#{@state} " \
131
+ "@length=#{@length}>"
132
+ end
133
+ # :nocov:
134
+
135
+ def rewind
136
+ return unless @buffer
137
+
138
+ # in case there's some reading going on
139
+ @reader = nil
140
+
141
+ @buffer.rewind
142
+ end
143
+
144
+ private
145
+
146
+ def initialize_inflaters
147
+ return unless @headers.key?("content-encoding")
148
+
149
+ return unless @options.decompress_response_body
150
+
151
+ @inflaters = @headers.get("content-encoding").filter_map do |encoding|
152
+ next if encoding == "identity"
153
+
154
+ inflater = self.class.initialize_inflater_by_encoding(encoding, @response)
155
+
156
+ # do not uncompress if there is no decoder available. In fact, we can't reliably
157
+ # continue decompressing beyond that, so ignore.
158
+ break unless inflater
159
+
160
+ @encodings << encoding
161
+ inflater
162
+ end
163
+ end
164
+
165
+ def transition(nextstate)
166
+ case nextstate
167
+ when :open
168
+ return unless @state == :idle
169
+
170
+ @buffer = Response::Buffer.new(
171
+ threshold_size: @threshold_size,
172
+ bytesize: @length,
173
+ encoding: @encoding
174
+ )
175
+ when :closed
176
+ return if @state == :closed
177
+ end
178
+
179
+ @state = nextstate
180
+ end
181
+
182
+ def _with_same_buffer_pos
183
+ return yield unless @buffer && @buffer.respond_to?(:pos)
184
+
185
+ # @type ivar @buffer: StringIO | Tempfile
186
+ current_pos = @buffer.pos
187
+ @buffer.rewind
188
+ begin
189
+ yield
190
+ ensure
191
+ @buffer.pos = current_pos
192
+ end
193
+ end
194
+
195
+ class << self
196
+ def initialize_inflater_by_encoding(encoding, response, **kwargs)
197
+ case encoding
198
+ when "gzip"
199
+ Transcoder::GZIP.decode(response, **kwargs)
200
+ when "deflate"
201
+ Transcoder::Deflate.decode(response, **kwargs)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require "stringio"
5
+ require "tempfile"
6
+
7
+ module HTTPX
8
+ class Response::Buffer < SimpleDelegator
9
+ def initialize(threshold_size:, bytesize: 0, encoding: Encoding::BINARY)
10
+ @threshold_size = threshold_size
11
+ @bytesize = bytesize
12
+ @encoding = encoding
13
+ try_upgrade_buffer
14
+ super(@buffer)
15
+ end
16
+
17
+ def initialize_dup(other)
18
+ super
19
+
20
+ @buffer = other.instance_variable_get(:@buffer).dup
21
+ end
22
+
23
+ def size
24
+ @bytesize
25
+ end
26
+
27
+ def write(chunk)
28
+ @bytesize += chunk.bytesize
29
+ try_upgrade_buffer
30
+ @buffer.write(chunk)
31
+ end
32
+
33
+ def to_s
34
+ case @buffer
35
+ when StringIO
36
+ begin
37
+ @buffer.string.force_encoding(@encoding)
38
+ rescue ArgumentError
39
+ @buffer.string
40
+ end
41
+ when Tempfile
42
+ rewind
43
+ content = _with_same_buffer_pos { @buffer.read }
44
+ begin
45
+ content.force_encoding(@encoding)
46
+ rescue ArgumentError # ex: unknown encoding name - utf
47
+ content
48
+ end
49
+ end
50
+ end
51
+
52
+ def close
53
+ @buffer.close
54
+ @buffer.unlink if @buffer.respond_to?(:unlink)
55
+ end
56
+
57
+ private
58
+
59
+ def try_upgrade_buffer
60
+ if !@buffer.is_a?(Tempfile) && @bytesize > @threshold_size
61
+ aux = @buffer
62
+
63
+ @buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
64
+
65
+ if aux
66
+ aux.rewind
67
+ ::IO.copy_stream(aux, @buffer)
68
+ aux.close
69
+ end
70
+
71
+ else
72
+ return if @buffer
73
+
74
+ @buffer = StringIO.new("".b)
75
+
76
+ end
77
+ __setobj__(@buffer)
78
+ end
79
+
80
+ def _with_same_buffer_pos
81
+ current_pos = @buffer.pos
82
+ @buffer.rewind
83
+ begin
84
+ yield
85
+ ensure
86
+ @buffer.pos = current_pos
87
+ end
88
+ end
89
+ end
90
+ end
@@ -124,199 +124,6 @@ module HTTPX
124
124
  content_length == "0"
125
125
  end
126
126
  end
127
-
128
- class Body
129
- attr_reader :encoding
130
-
131
- def initialize(response, options)
132
- @response = response
133
- @headers = response.headers
134
- @options = options
135
- @threshold_size = options.body_threshold_size
136
- @window_size = options.window_size
137
- @encoding = response.content_type.charset || Encoding::BINARY
138
- @length = 0
139
- @buffer = nil
140
- @state = :idle
141
- end
142
-
143
- def initialize_dup(other)
144
- super
145
-
146
- @buffer = other.instance_variable_get(:@buffer).dup
147
- end
148
-
149
- def closed?
150
- @state == :closed
151
- end
152
-
153
- def write(chunk)
154
- return if @state == :closed
155
-
156
- size = chunk.bytesize
157
- @length += size
158
- transition
159
- @buffer.write(chunk)
160
-
161
- @response.emit(:chunk_received, chunk)
162
- size
163
- end
164
-
165
- def read(*args)
166
- return unless @buffer
167
-
168
- unless @reader
169
- rewind
170
- @reader = @buffer
171
- end
172
-
173
- @reader.read(*args)
174
- end
175
-
176
- def bytesize
177
- @length
178
- end
179
-
180
- def each
181
- return enum_for(__method__) unless block_given?
182
-
183
- begin
184
- if @buffer
185
- rewind
186
- while (chunk = @buffer.read(@window_size))
187
- yield(chunk.force_encoding(@encoding))
188
- end
189
- end
190
- ensure
191
- close
192
- end
193
- end
194
-
195
- def filename
196
- return unless @headers.key?("content-disposition")
197
-
198
- Utils.get_filename(@headers["content-disposition"])
199
- end
200
-
201
- def to_s
202
- case @buffer
203
- when StringIO
204
- begin
205
- @buffer.string.force_encoding(@encoding)
206
- rescue ArgumentError
207
- @buffer.string
208
- end
209
- when Tempfile
210
- rewind
211
- content = _with_same_buffer_pos { @buffer.read }
212
- begin
213
- content.force_encoding(@encoding)
214
- rescue ArgumentError # ex: unknown encoding name - utf
215
- content
216
- end
217
- else
218
- "".b
219
- end
220
- end
221
- alias_method :to_str, :to_s
222
-
223
- def empty?
224
- @length.zero?
225
- end
226
-
227
- def copy_to(dest)
228
- return unless @buffer
229
-
230
- rewind
231
-
232
- if dest.respond_to?(:path) && @buffer.respond_to?(:path)
233
- FileUtils.mv(@buffer.path, dest.path)
234
- else
235
- ::IO.copy_stream(@buffer, dest)
236
- end
237
- end
238
-
239
- # closes/cleans the buffer, resets everything
240
- def close
241
- if @buffer
242
- @buffer.close
243
- @buffer.unlink if @buffer.respond_to?(:unlink)
244
- @buffer = nil
245
- end
246
- @length = 0
247
- @state = :closed
248
- end
249
-
250
- def ==(other)
251
- object_id == other.object_id || begin
252
- if other.respond_to?(:read)
253
- _with_same_buffer_pos { FileUtils.compare_stream(@buffer, other) }
254
- else
255
- to_s == other.to_s
256
- end
257
- end
258
- end
259
-
260
- # :nocov:
261
- def inspect
262
- "#<HTTPX::Response::Body:#{object_id} " \
263
- "@state=#{@state} " \
264
- "@length=#{@length}>"
265
- end
266
- # :nocov:
267
-
268
- def rewind
269
- return unless @buffer
270
-
271
- # in case there's some reading going on
272
- @reader = nil
273
-
274
- @buffer.rewind
275
- end
276
-
277
- private
278
-
279
- def transition
280
- case @state
281
- when :idle
282
- if @length > @threshold_size
283
- @state = :buffer
284
- @buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
285
- else
286
- @state = :memory
287
- @buffer = StringIO.new("".b)
288
- end
289
- when :memory
290
- # @type ivar @buffer: StringIO | Tempfile
291
- if @length > @threshold_size
292
- aux = @buffer
293
- @buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
294
- aux.rewind
295
- ::IO.copy_stream(aux, @buffer)
296
- # (this looks like a bug from Ruby < 2.3
297
- @buffer.pos = aux.pos ##################
298
- ########################################
299
- aux.close
300
- @state = :buffer
301
- end
302
- end
303
-
304
- nil unless %i[memory buffer].include?(@state)
305
- end
306
-
307
- def _with_same_buffer_pos
308
- return yield unless @buffer && @buffer.respond_to?(:pos)
309
-
310
- # @type ivar @buffer: StringIO | Tempfile
311
- current_pos = @buffer.pos
312
- @buffer.rewind
313
- begin
314
- yield
315
- ensure
316
- @buffer.pos = current_pos
317
- end
318
- end
319
- end
320
127
  end
321
128
 
322
129
  class ContentType
@@ -358,20 +165,8 @@ module HTTPX
358
165
  log_exception(@error)
359
166
  end
360
167
 
361
- def status
362
- warn ":#{__method__} is deprecated, use :error.message instead"
363
- @error.message
364
- end
365
-
366
- if Exception.method_defined?(:full_message)
367
- def to_s
368
- @error.full_message(highlight: false)
369
- end
370
- else
371
- def to_s
372
- "#{@error.message} (#{@error.class})\n" \
373
- "#{@error.backtrace.join("\n") if @error.backtrace}"
374
- end
168
+ def to_s
169
+ @error.full_message(highlight: false)
375
170
  end
376
171
 
377
172
  def close
@@ -388,4 +183,6 @@ module HTTPX
388
183
  end
389
184
  end
390
185
 
391
- require "httpx/pmatch_extensions" if RUBY_VERSION >= "3.0.0"
186
+ require_relative "response/body"
187
+ require_relative "response/buffer"
188
+ require_relative "pmatch_extensions" if RUBY_VERSION >= "3.0.0"
@@ -9,8 +9,6 @@ class HTTPX::Selector
9
9
  private_constant :READABLE
10
10
  private_constant :WRITABLE
11
11
 
12
- using HTTPX::IOExtensions
13
-
14
12
  def initialize
15
13
  @selectables = []
16
14
  end
data/lib/httpx/session.rb CHANGED
@@ -339,16 +339,6 @@ module HTTPX
339
339
  end
340
340
  self
341
341
  end
342
-
343
- # :nocov:
344
- def plugins(pls)
345
- warn ":#{__method__} is deprecated, use :plugin instead"
346
- pls.each do |pl|
347
- plugin(pl)
348
- end
349
- self
350
- end
351
- # :nocov:
352
342
  end
353
343
  end
354
344
 
@@ -9,7 +9,6 @@ module HTTPX::Transcoder
9
9
  module_function
10
10
 
11
11
  class Encoder
12
- using HTTPX::ArrayExtensions::Sum
13
12
  extend Forwardable
14
13
 
15
14
  def_delegator :@raw, :to_s
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+ require_relative "utils/deflater"
5
+
6
+ module HTTPX
7
+ module Transcoder
8
+ module Deflate
9
+ class Deflater < Transcoder::Deflater
10
+ def deflate(chunk)
11
+ @deflater ||= Zlib::Deflate.new
12
+
13
+ if chunk.nil?
14
+ unless @deflater.closed?
15
+ last = @deflater.finish
16
+ @deflater.close
17
+ last.empty? ? nil : last
18
+ end
19
+ else
20
+ @deflater.deflate(chunk)
21
+ end
22
+ end
23
+ end
24
+
25
+ module_function
26
+
27
+ def encode(body)
28
+ Deflater.new(body)
29
+ end
30
+
31
+ def decode(response, bytesize: nil)
32
+ bytesize ||= response.headers.key?("content-length") ? response.headers["content-length"].to_i : Float::INFINITY
33
+ GZIP::Inflater.new(bytesize)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -2,57 +2,77 @@
2
2
 
3
3
  require "forwardable"
4
4
  require "uri"
5
+ require_relative "multipart"
5
6
 
6
- module HTTPX::Transcoder
7
- module Form
8
- module_function
7
+ module HTTPX
8
+ module Transcoder
9
+ module Form
10
+ module_function
9
11
 
10
- PARAM_DEPTH_LIMIT = 32
12
+ PARAM_DEPTH_LIMIT = 32
11
13
 
12
- class Encoder
13
- extend Forwardable
14
+ class Encoder
15
+ extend Forwardable
14
16
 
15
- def_delegator :@raw, :to_s
17
+ def_delegator :@raw, :to_s
16
18
 
17
- def_delegator :@raw, :to_str
19
+ def_delegator :@raw, :to_str
18
20
 
19
- def_delegator :@raw, :bytesize
21
+ def_delegator :@raw, :bytesize
20
22
 
21
- def initialize(form)
22
- @raw = form.each_with_object("".b) do |(key, val), buf|
23
- HTTPX::Transcoder.normalize_keys(key, val) do |k, v|
24
- buf << "&" unless buf.empty?
25
- buf << URI.encode_www_form_component(k)
26
- buf << "=#{URI.encode_www_form_component(v.to_s)}" unless v.nil?
23
+ def initialize(form)
24
+ @raw = form.each_with_object("".b) do |(key, val), buf|
25
+ HTTPX::Transcoder.normalize_keys(key, val) do |k, v|
26
+ buf << "&" unless buf.empty?
27
+ buf << URI.encode_www_form_component(k)
28
+ buf << "=#{URI.encode_www_form_component(v.to_s)}" unless v.nil?
29
+ end
27
30
  end
28
31
  end
29
- end
30
32
 
31
- def content_type
32
- "application/x-www-form-urlencoded"
33
+ def content_type
34
+ "application/x-www-form-urlencoded"
35
+ end
33
36
  end
34
- end
35
37
 
36
- module Decoder
37
- module_function
38
+ module Decoder
39
+ module_function
38
40
 
39
- def call(response, *)
40
- URI.decode_www_form(response.to_s).each_with_object({}) do |(field, value), params|
41
- HTTPX::Transcoder.normalize_query(params, field, value, PARAM_DEPTH_LIMIT)
41
+ def call(response, *)
42
+ URI.decode_www_form(response.to_s).each_with_object({}) do |(field, value), params|
43
+ HTTPX::Transcoder.normalize_query(params, field, value, PARAM_DEPTH_LIMIT)
44
+ end
42
45
  end
43
46
  end
44
- end
45
47
 
46
- def encode(form)
47
- Encoder.new(form)
48
- end
48
+ def encode(form)
49
+ if multipart?(form)
50
+ Multipart::Encoder.new(form)
51
+ else
52
+ Encoder.new(form)
53
+ end
54
+ end
49
55
 
50
- def decode(response)
51
- content_type = response.content_type.mime_type
56
+ def decode(response)
57
+ content_type = response.content_type.mime_type
52
58
 
53
- raise HTTPX::Error, "invalid form mime type (#{content_type})" unless content_type == "application/x-www-form-urlencoded"
59
+ case content_type
60
+ when "application/x-www-form-urlencoded"
61
+ Decoder
62
+ when "multipart/form-data"
63
+ Multipart::Decoder.new(response)
64
+ else
65
+ raise Error, "invalid form mime type (#{content_type})"
66
+ end
67
+ end
54
68
 
55
- Decoder
69
+ def multipart?(data)
70
+ data.any? do |_, v|
71
+ Multipart::MULTIPART_VALUE_COND.call(v) ||
72
+ (v.respond_to?(:to_ary) && v.to_ary.any?(&Multipart::MULTIPART_VALUE_COND)) ||
73
+ (v.respond_to?(:to_hash) && v.to_hash.any? { |_, e| Multipart::MULTIPART_VALUE_COND.call(e) })
74
+ end
75
+ end
56
76
  end
57
77
  end
58
78
  end