httpx 0.24.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) 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/0_24_6.md +5 -0
  6. data/doc/release_notes/1_0_0.md +60 -0
  7. data/lib/httpx/adapters/datadog.rb +28 -97
  8. data/lib/httpx/adapters/faraday.rb +9 -52
  9. data/lib/httpx/adapters/webmock.rb +2 -7
  10. data/lib/httpx/altsvc.rb +4 -22
  11. data/lib/httpx/base64.rb +27 -0
  12. data/lib/httpx/chainable.rb +2 -25
  13. data/lib/httpx/connection.rb +11 -32
  14. data/lib/httpx/domain_name.rb +5 -12
  15. data/lib/httpx/errors.rb +0 -2
  16. data/lib/httpx/extensions.rb +0 -124
  17. data/lib/httpx/io/ssl.rb +26 -59
  18. data/lib/httpx/io/tcp.rb +27 -68
  19. data/lib/httpx/io/udp.rb +13 -48
  20. data/lib/httpx/io/unix.rb +1 -8
  21. data/lib/httpx/loggable.rb +4 -19
  22. data/lib/httpx/options.rb +24 -84
  23. data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
  24. data/lib/httpx/plugins/{authentication → auth}/digest.rb +2 -5
  25. data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
  26. data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
  27. data/lib/httpx/plugins/auth.rb +25 -0
  28. data/lib/httpx/plugins/aws_sigv4.rb +0 -1
  29. data/lib/httpx/plugins/{basic_authentication.rb → basic_auth.rb} +5 -6
  30. data/lib/httpx/plugins/brotli.rb +50 -0
  31. data/lib/httpx/plugins/circuit_breaker/circuit.rb +40 -16
  32. data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +16 -5
  33. data/lib/httpx/plugins/circuit_breaker.rb +11 -4
  34. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
  35. data/lib/httpx/plugins/cookies.rb +1 -1
  36. data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +5 -5
  37. data/lib/httpx/plugins/follow_redirects.rb +21 -24
  38. data/lib/httpx/plugins/grpc/grpc_encoding.rb +82 -0
  39. data/lib/httpx/plugins/grpc/message.rb +7 -39
  40. data/lib/httpx/plugins/grpc.rb +15 -21
  41. data/lib/httpx/plugins/h2c.rb +0 -1
  42. data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +5 -5
  43. data/lib/httpx/plugins/oauth.rb +2 -2
  44. data/lib/httpx/plugins/proxy/http.rb +0 -2
  45. data/lib/httpx/plugins/proxy/socks4.rb +0 -4
  46. data/lib/httpx/plugins/proxy/socks5.rb +1 -5
  47. data/lib/httpx/plugins/proxy.rb +3 -32
  48. data/lib/httpx/plugins/retries.rb +3 -4
  49. data/lib/httpx/plugins/stream.rb +4 -6
  50. data/lib/httpx/punycode.rb +9 -291
  51. data/lib/httpx/request/body.rb +145 -0
  52. data/lib/httpx/request.rb +2 -119
  53. data/lib/httpx/resolver/https.rb +1 -1
  54. data/lib/httpx/resolver/native.rb +6 -14
  55. data/lib/httpx/resolver/resolver.rb +1 -1
  56. data/lib/httpx/resolver/system.rb +11 -9
  57. data/lib/httpx/response/body.rb +206 -0
  58. data/lib/httpx/response/buffer.rb +90 -0
  59. data/lib/httpx/response.rb +5 -208
  60. data/lib/httpx/selector.rb +0 -2
  61. data/lib/httpx/session.rb +3 -10
  62. data/lib/httpx/transcoder/body.rb +0 -1
  63. data/lib/httpx/transcoder/deflate.rb +37 -0
  64. data/lib/httpx/transcoder/form.rb +52 -32
  65. data/lib/httpx/transcoder/gzip.rb +74 -0
  66. data/lib/httpx/transcoder/json.rb +2 -4
  67. data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
  68. data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +3 -3
  69. data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
  70. data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
  71. data/lib/httpx/transcoder/multipart.rb +17 -0
  72. data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
  73. data/lib/httpx/transcoder/utils/deflater.rb +72 -0
  74. data/lib/httpx/transcoder/xml.rb +0 -2
  75. data/lib/httpx/transcoder.rb +2 -2
  76. data/lib/httpx/utils.rb +10 -20
  77. data/lib/httpx/version.rb +1 -1
  78. data/lib/httpx.rb +0 -8
  79. data/sig/chainable.rbs +5 -6
  80. data/sig/connection.rbs +0 -1
  81. data/sig/errors.rbs +0 -3
  82. data/sig/httpx.rbs +2 -1
  83. data/sig/io/unix.rbs +1 -1
  84. data/sig/options.rbs +12 -8
  85. data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
  86. data/sig/plugins/auth.rbs +13 -0
  87. data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
  88. data/sig/plugins/brotli.rbs +22 -0
  89. data/sig/plugins/circuit_breaker.rbs +7 -3
  90. data/sig/plugins/compression.rbs +0 -2
  91. data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
  92. data/sig/plugins/follow_redirects.rbs +0 -1
  93. data/sig/plugins/grpc/call.rbs +19 -0
  94. data/sig/plugins/grpc/grpc_encoding.rbs +33 -0
  95. data/sig/plugins/grpc/message.rbs +17 -0
  96. data/sig/plugins/grpc.rbs +2 -32
  97. data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
  98. data/sig/plugins/oauth.rbs +1 -1
  99. data/sig/plugins/proxy/socks4.rbs +2 -3
  100. data/sig/plugins/proxy/socks5.rbs +0 -1
  101. data/sig/plugins/proxy/ssh.rbs +1 -1
  102. data/sig/plugins/response_cache.rbs +5 -2
  103. data/sig/request/body.rbs +42 -0
  104. data/sig/request.rbs +1 -27
  105. data/sig/resolver/resolver.rbs +1 -1
  106. data/sig/response/body.rbs +52 -0
  107. data/sig/response/buffer.rbs +24 -0
  108. data/sig/response.rbs +0 -39
  109. data/sig/session.rbs +2 -0
  110. data/sig/transcoder/body.rbs +4 -3
  111. data/sig/transcoder/deflate.rbs +11 -0
  112. data/sig/transcoder/form.rbs +5 -3
  113. data/sig/transcoder/gzip.rbs +24 -0
  114. data/sig/transcoder/json.rbs +4 -2
  115. data/sig/{plugins → transcoder}/multipart.rbs +3 -10
  116. data/sig/transcoder/utils/body_reader.rbs +15 -0
  117. data/sig/transcoder/utils/deflater.rbs +29 -0
  118. data/sig/transcoder.rbs +18 -2
  119. metadata +52 -34
  120. data/lib/httpx/plugins/authentication.rb +0 -24
  121. data/lib/httpx/plugins/compression/brotli.rb +0 -54
  122. data/lib/httpx/plugins/compression/deflate.rb +0 -54
  123. data/lib/httpx/plugins/compression/gzip.rb +0 -90
  124. data/lib/httpx/plugins/compression.rb +0 -165
  125. data/lib/httpx/plugins/multipart/decoder.rb +0 -137
  126. data/lib/httpx/plugins/multipart.rb +0 -96
  127. data/sig/plugins/authentication.rbs +0 -13
  128. data/sig/plugins/compression/brotli.rbs +0 -21
  129. data/sig/plugins/compression/deflate.rbs +0 -17
  130. data/sig/plugins/compression/gzip.rbs +0 -29
  131. /data/sig/plugins/{authentication → auth}/digest.rbs +0 -0
  132. /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
  133. /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,9 @@ 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
344
+
345
+ # session may be overridden by certain adapters.
346
+ S = Session
354
347
  end
@@ -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