httparty 0.13.7 → 0.20.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of httparty might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.editorconfig +18 -0
- data/.github/workflows/ci.yml +23 -0
- data/.gitignore +2 -0
- data/.rubocop_todo.yml +1 -1
- data/{History → Changelog.md} +220 -59
- data/Gemfile +8 -3
- data/README.md +8 -7
- data/bin/httparty +3 -1
- data/docs/README.md +171 -0
- data/examples/README.md +34 -12
- data/examples/aaws.rb +7 -3
- data/examples/body_stream.rb +14 -0
- data/examples/crack.rb +1 -1
- data/examples/custom_parsers.rb +5 -1
- data/examples/delicious.rb +4 -4
- data/examples/headers_and_user_agents.rb +7 -3
- data/examples/idn.rb +10 -0
- data/examples/logging.rb +4 -4
- data/examples/microsoft_graph.rb +52 -0
- data/examples/multipart.rb +22 -0
- data/examples/peer_cert.rb +9 -0
- data/examples/stackexchange.rb +1 -1
- data/examples/stream_download.rb +26 -0
- data/examples/tripit_sign_in.rb +17 -6
- data/examples/twitter.rb +2 -2
- data/examples/whoismyrep.rb +1 -1
- data/httparty.gemspec +7 -5
- data/lib/httparty/connection_adapter.rb +86 -20
- data/lib/httparty/cookie_hash.rb +10 -8
- data/lib/httparty/decompressor.rb +92 -0
- data/lib/httparty/exceptions.rb +8 -2
- data/lib/httparty/hash_conversions.rb +30 -8
- data/lib/httparty/headers_processor.rb +32 -0
- data/lib/httparty/logger/apache_formatter.rb +31 -6
- data/lib/httparty/logger/curl_formatter.rb +68 -23
- data/lib/httparty/logger/logger.rb +5 -1
- data/lib/httparty/logger/logstash_formatter.rb +61 -0
- data/lib/httparty/module_inheritable_attributes.rb +6 -4
- data/lib/httparty/net_digest_auth.rb +23 -21
- data/lib/httparty/parser.rb +25 -14
- data/lib/httparty/request/body.rb +98 -0
- data/lib/httparty/request/multipart_boundary.rb +13 -0
- data/lib/httparty/request.rb +156 -106
- data/lib/httparty/response/headers.rb +23 -19
- data/lib/httparty/response.rb +92 -13
- data/lib/httparty/response_fragment.rb +21 -0
- data/lib/httparty/text_encoder.rb +72 -0
- data/lib/httparty/utils.rb +13 -0
- data/lib/httparty/version.rb +3 -1
- data/lib/httparty.rb +98 -34
- data/website/css/common.css +1 -1
- metadata +34 -113
- data/.travis.yml +0 -7
- data/features/basic_authentication.feature +0 -20
- data/features/command_line.feature +0 -95
- data/features/deals_with_http_error_codes.feature +0 -26
- data/features/digest_authentication.feature +0 -30
- data/features/handles_compressed_responses.feature +0 -27
- data/features/handles_multiple_formats.feature +0 -57
- data/features/steps/env.rb +0 -27
- data/features/steps/httparty_response_steps.rb +0 -52
- data/features/steps/httparty_steps.rb +0 -43
- data/features/steps/mongrel_helper.rb +0 -127
- data/features/steps/remote_service_steps.rb +0 -90
- data/features/supports_read_timeout_option.feature +0 -13
- data/features/supports_redirection.feature +0 -22
- data/features/supports_timeout_option.feature +0 -13
- data/spec/fixtures/delicious.xml +0 -23
- data/spec/fixtures/empty.xml +0 -0
- data/spec/fixtures/google.html +0 -3
- data/spec/fixtures/ssl/generate.sh +0 -29
- data/spec/fixtures/ssl/generated/1fe462c2.0 +0 -16
- data/spec/fixtures/ssl/generated/bogushost.crt +0 -13
- data/spec/fixtures/ssl/generated/ca.crt +0 -16
- data/spec/fixtures/ssl/generated/ca.key +0 -15
- data/spec/fixtures/ssl/generated/selfsigned.crt +0 -14
- data/spec/fixtures/ssl/generated/server.crt +0 -13
- data/spec/fixtures/ssl/generated/server.key +0 -15
- data/spec/fixtures/ssl/openssl-exts.cnf +0 -9
- data/spec/fixtures/twitter.csv +0 -2
- data/spec/fixtures/twitter.json +0 -1
- data/spec/fixtures/twitter.xml +0 -403
- data/spec/fixtures/undefined_method_add_node_for_nil.xml +0 -2
- data/spec/httparty/connection_adapter_spec.rb +0 -468
- data/spec/httparty/cookie_hash_spec.rb +0 -83
- data/spec/httparty/exception_spec.rb +0 -38
- data/spec/httparty/hash_conversions_spec.rb +0 -41
- data/spec/httparty/logger/apache_formatter_spec.rb +0 -41
- data/spec/httparty/logger/curl_formatter_spec.rb +0 -18
- data/spec/httparty/logger/logger_spec.rb +0 -38
- data/spec/httparty/net_digest_auth_spec.rb +0 -230
- data/spec/httparty/parser_spec.rb +0 -173
- data/spec/httparty/request_spec.rb +0 -1073
- data/spec/httparty/response_spec.rb +0 -241
- data/spec/httparty/ssl_spec.rb +0 -74
- data/spec/httparty_spec.rb +0 -850
- data/spec/spec_helper.rb +0 -59
- data/spec/support/ssl_test_helper.rb +0 -47
- data/spec/support/ssl_test_server.rb +0 -80
- data/spec/support/stub_response.rb +0 -49
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTParty
|
2
4
|
# Default connection adapter that returns a new Net::HTTP each time
|
3
5
|
#
|
4
6
|
# == Custom Connection Factories
|
5
7
|
#
|
6
8
|
# If you like to implement your own connection adapter, subclassing
|
7
|
-
#
|
9
|
+
# HTTParty::ConnectionAdapter will make it easier. Just override
|
8
10
|
# the #connection method. The uri and options attributes will have
|
9
11
|
# all the info you need to construct your http connection. Whatever
|
10
12
|
# you return from your connection method needs to adhere to the
|
@@ -38,26 +40,52 @@ module HTTParty
|
|
38
40
|
# in the #options attribute. It is up to you to interpret them within your
|
39
41
|
# connection adapter. Take a look at the implementation of
|
40
42
|
# HTTParty::ConnectionAdapter#connection for examples of how they are used.
|
41
|
-
#
|
43
|
+
# The keys used in options are
|
42
44
|
# * :+timeout+: timeout in seconds
|
43
45
|
# * :+open_timeout+: http connection open_timeout in seconds, overrides timeout if set
|
44
46
|
# * :+read_timeout+: http connection read_timeout in seconds, overrides timeout if set
|
47
|
+
# * :+write_timeout+: http connection write_timeout in seconds, overrides timeout if set (Ruby >= 2.6.0 required)
|
45
48
|
# * :+debug_output+: see HTTParty::ClassMethods.debug_output.
|
46
|
-
# * :+
|
49
|
+
# * :+cert_store+: contains certificate data. see method 'attach_ssl_certificates'
|
50
|
+
# * :+pem+: contains pem client certificate data. see method 'attach_ssl_certificates'
|
51
|
+
# * :+p12+: contains PKCS12 client client certificate data. see method 'attach_ssl_certificates'
|
47
52
|
# * :+verify+: verify the server’s certificate against the ca certificate.
|
48
53
|
# * :+verify_peer+: set to false to turn off server verification but still send client certificate
|
49
54
|
# * :+ssl_ca_file+: see HTTParty::ClassMethods.ssl_ca_file.
|
50
55
|
# * :+ssl_ca_path+: see HTTParty::ClassMethods.ssl_ca_path.
|
56
|
+
# * :+ssl_version+: SSL versions to allow. see method 'attach_ssl_certificates'
|
57
|
+
# * :+ciphers+: The list of SSL ciphers to support
|
51
58
|
# * :+connection_adapter_options+: contains the hash you passed to HTTParty.connection_adapter when you configured your connection adapter
|
59
|
+
# * :+local_host+: The local address to bind to
|
60
|
+
# * :+local_port+: The local port to bind to
|
61
|
+
# * :+http_proxyaddr+: HTTP Proxy address
|
62
|
+
# * :+http_proxyport+: HTTP Proxy port
|
63
|
+
# * :+http_proxyuser+: HTTP Proxy user
|
64
|
+
# * :+http_proxypass+: HTTP Proxy password
|
65
|
+
#
|
66
|
+
# === Inherited methods
|
67
|
+
# * :+clean_host+: Method used to sanitize host names
|
68
|
+
|
52
69
|
class ConnectionAdapter
|
53
70
|
# Private: Regex used to strip brackets from IPv6 URIs.
|
54
71
|
StripIpv6BracketsRegex = /\A\[(.*)\]\z/
|
55
72
|
|
73
|
+
OPTION_DEFAULTS = {
|
74
|
+
verify: true,
|
75
|
+
verify_peer: true
|
76
|
+
}
|
77
|
+
|
56
78
|
# Public
|
57
79
|
def self.call(uri, options)
|
58
80
|
new(uri, options).connection
|
59
81
|
end
|
60
82
|
|
83
|
+
def self.default_cert_store
|
84
|
+
@default_cert_store ||= OpenSSL::X509::Store.new.tap do |cert_store|
|
85
|
+
cert_store.set_default_paths
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
61
89
|
attr_reader :uri, :options
|
62
90
|
|
63
91
|
def initialize(uri, options = {})
|
@@ -65,14 +93,21 @@ module HTTParty
|
|
65
93
|
raise ArgumentError, "uri must be a #{uri_adapter}, not a #{uri.class}" unless uri.is_a? uri_adapter
|
66
94
|
|
67
95
|
@uri = uri
|
68
|
-
@options = options
|
96
|
+
@options = OPTION_DEFAULTS.merge(options)
|
69
97
|
end
|
70
98
|
|
71
99
|
def connection
|
72
100
|
host = clean_host(uri.host)
|
73
101
|
port = uri.port || (uri.scheme == 'https' ? 443 : 80)
|
74
|
-
if options
|
75
|
-
http = Net::HTTP.new(
|
102
|
+
if options.key?(:http_proxyaddr)
|
103
|
+
http = Net::HTTP.new(
|
104
|
+
host,
|
105
|
+
port,
|
106
|
+
options[:http_proxyaddr],
|
107
|
+
options[:http_proxyport],
|
108
|
+
options[:http_proxyuser],
|
109
|
+
options[:http_proxypass]
|
110
|
+
)
|
76
111
|
else
|
77
112
|
http = Net::HTTP.new(host, port)
|
78
113
|
end
|
@@ -81,19 +116,35 @@ module HTTParty
|
|
81
116
|
|
82
117
|
attach_ssl_certificates(http, options)
|
83
118
|
|
84
|
-
if
|
119
|
+
if add_timeout?(options[:timeout])
|
85
120
|
http.open_timeout = options[:timeout]
|
86
121
|
http.read_timeout = options[:timeout]
|
122
|
+
|
123
|
+
from_ruby_version('2.6.0', option: :write_timeout, warn: false) do
|
124
|
+
http.write_timeout = options[:timeout]
|
125
|
+
end
|
87
126
|
end
|
88
127
|
|
89
|
-
if
|
128
|
+
if add_timeout?(options[:read_timeout])
|
90
129
|
http.read_timeout = options[:read_timeout]
|
91
130
|
end
|
92
131
|
|
93
|
-
if
|
132
|
+
if add_timeout?(options[:open_timeout])
|
94
133
|
http.open_timeout = options[:open_timeout]
|
95
134
|
end
|
96
135
|
|
136
|
+
if add_timeout?(options[:write_timeout])
|
137
|
+
from_ruby_version('2.6.0', option: :write_timeout) do
|
138
|
+
http.write_timeout = options[:write_timeout]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
if add_max_retries?(options[:max_retries])
|
143
|
+
from_ruby_version('2.5.0', option: :max_retries) do
|
144
|
+
http.max_retries = options[:max_retries]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
97
148
|
if options[:debug_output]
|
98
149
|
http.set_debug_output(options[:debug_output])
|
99
150
|
end
|
@@ -106,18 +157,14 @@ module HTTParty
|
|
106
157
|
#
|
107
158
|
# @see https://bugs.ruby-lang.org/issues/6617
|
108
159
|
if options[:local_host]
|
109
|
-
|
160
|
+
from_ruby_version('2.0.0', option: :local_host) do
|
110
161
|
http.local_host = options[:local_host]
|
111
|
-
else
|
112
|
-
Kernel.warn("Warning: option :local_host requires Ruby version 2.0 or later")
|
113
162
|
end
|
114
163
|
end
|
115
164
|
|
116
165
|
if options[:local_port]
|
117
|
-
|
166
|
+
from_ruby_version('2.0.0', option: :local_port) do
|
118
167
|
http.local_port = options[:local_port]
|
119
|
-
else
|
120
|
-
Kernel.warn("Warning: option :local_port requires Ruby version 2.0 or later")
|
121
168
|
end
|
122
169
|
end
|
123
170
|
|
@@ -126,6 +173,22 @@ module HTTParty
|
|
126
173
|
|
127
174
|
private
|
128
175
|
|
176
|
+
def from_ruby_version(ruby_version, option: nil, warn: true)
|
177
|
+
if RUBY_VERSION >= ruby_version
|
178
|
+
yield
|
179
|
+
elsif warn
|
180
|
+
Kernel.warn("Warning: option #{ option } requires Ruby version #{ ruby_version } or later")
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def add_timeout?(timeout)
|
185
|
+
timeout && (timeout.is_a?(Integer) || timeout.is_a?(Float))
|
186
|
+
end
|
187
|
+
|
188
|
+
def add_max_retries?(max_retries)
|
189
|
+
max_retries && max_retries.is_a?(Integer) && max_retries >= 0
|
190
|
+
end
|
191
|
+
|
129
192
|
def clean_host(host)
|
130
193
|
strip_ipv6_brackets(host)
|
131
194
|
end
|
@@ -138,6 +201,10 @@ module HTTParty
|
|
138
201
|
uri.port == 443 || uri.scheme == 'https'
|
139
202
|
end
|
140
203
|
|
204
|
+
def verify_ssl_certificate?
|
205
|
+
!(options[:verify] == false || options[:verify_peer] == false)
|
206
|
+
end
|
207
|
+
|
141
208
|
def attach_ssl_certificates(http, options)
|
142
209
|
if http.use_ssl?
|
143
210
|
if options.fetch(:verify, true)
|
@@ -146,8 +213,7 @@ module HTTParty
|
|
146
213
|
http.cert_store = options[:cert_store]
|
147
214
|
else
|
148
215
|
# Use the default cert store by default, i.e. system ca certs
|
149
|
-
http.cert_store =
|
150
|
-
http.cert_store.set_default_paths
|
216
|
+
http.cert_store = self.class.default_cert_store
|
151
217
|
end
|
152
218
|
else
|
153
219
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
@@ -157,8 +223,8 @@ module HTTParty
|
|
157
223
|
# Note: options[:pem] must contain the content of a PEM file having the private key appended
|
158
224
|
if options[:pem]
|
159
225
|
http.cert = OpenSSL::X509::Certificate.new(options[:pem])
|
160
|
-
http.key = OpenSSL::PKey
|
161
|
-
http.verify_mode =
|
226
|
+
http.key = OpenSSL::PKey.read(options[:pem], options[:pem_password])
|
227
|
+
http.verify_mode = verify_ssl_certificate? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
162
228
|
end
|
163
229
|
|
164
230
|
# PKCS12 client certificate authentication
|
@@ -166,7 +232,7 @@ module HTTParty
|
|
166
232
|
p12 = OpenSSL::PKCS12.new(options[:p12], options[:p12_password])
|
167
233
|
http.cert = p12.certificate
|
168
234
|
http.key = p12.key
|
169
|
-
http.verify_mode =
|
235
|
+
http.verify_mode = verify_ssl_certificate? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
170
236
|
end
|
171
237
|
|
172
238
|
# SSL certificate authority file and/or directory
|
data/lib/httparty/cookie_hash.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class HTTParty::CookieHash < Hash #:nodoc:
|
2
|
-
CLIENT_COOKIES = %w(path expires domain path secure httponly)
|
4
|
+
CLIENT_COOKIES = %w(path expires domain path secure httponly samesite)
|
3
5
|
|
4
|
-
def add_cookies(
|
5
|
-
case
|
6
|
+
def add_cookies(data)
|
7
|
+
case data
|
6
8
|
when Hash
|
7
|
-
merge!(
|
9
|
+
merge!(data)
|
8
10
|
when String
|
9
|
-
|
10
|
-
|
11
|
-
self[
|
11
|
+
data.split('; ').each do |cookie|
|
12
|
+
key, value = cookie.split('=', 2)
|
13
|
+
self[key.to_sym] = value if key
|
12
14
|
end
|
13
15
|
else
|
14
16
|
raise "add_cookies only takes a Hash or a String"
|
@@ -16,6 +18,6 @@ class HTTParty::CookieHash < Hash #:nodoc:
|
|
16
18
|
end
|
17
19
|
|
18
20
|
def to_cookie_string
|
19
|
-
|
21
|
+
select { |k, v| !CLIENT_COOKIES.include?(k.to_s.downcase) }.collect { |k, v| "#{k}=#{v}" }.join('; ')
|
20
22
|
end
|
21
23
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTParty
|
4
|
+
# Decompresses the response body based on the Content-Encoding header.
|
5
|
+
#
|
6
|
+
# Net::HTTP automatically decompresses Content-Encoding values "gzip" and "deflate".
|
7
|
+
# This class will handle "br" (Brotli) and "compress" (LZW) if the requisite
|
8
|
+
# gems are installed. Otherwise, it returns nil if the body data cannot be
|
9
|
+
# decompressed.
|
10
|
+
#
|
11
|
+
# @abstract Read the HTTP Compression section for more information.
|
12
|
+
class Decompressor
|
13
|
+
|
14
|
+
# "gzip" and "deflate" are handled by Net::HTTP
|
15
|
+
# hence they do not need to be handled by HTTParty
|
16
|
+
SupportedEncodings = {
|
17
|
+
'none' => :none,
|
18
|
+
'identity' => :none,
|
19
|
+
'br' => :brotli,
|
20
|
+
'compress' => :lzw
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
# The response body of the request
|
24
|
+
# @return [String]
|
25
|
+
attr_reader :body
|
26
|
+
|
27
|
+
# The Content-Encoding algorithm used to encode the body
|
28
|
+
# @return [Symbol] e.g. :gzip
|
29
|
+
attr_reader :encoding
|
30
|
+
|
31
|
+
# @param [String] body - the response body of the request
|
32
|
+
# @param [Symbol] encoding - the Content-Encoding algorithm used to encode the body
|
33
|
+
def initialize(body, encoding)
|
34
|
+
@body = body
|
35
|
+
@encoding = encoding
|
36
|
+
end
|
37
|
+
|
38
|
+
# Perform decompression on the response body
|
39
|
+
# @return [String] the decompressed body
|
40
|
+
# @return [nil] when the response body is nil or cannot decompressed
|
41
|
+
def decompress
|
42
|
+
return nil if body.nil?
|
43
|
+
return body if encoding.nil? || encoding.strip.empty?
|
44
|
+
|
45
|
+
if supports_encoding?
|
46
|
+
decompress_supported_encoding
|
47
|
+
else
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def supports_encoding?
|
55
|
+
SupportedEncodings.keys.include?(encoding)
|
56
|
+
end
|
57
|
+
|
58
|
+
def decompress_supported_encoding
|
59
|
+
method = SupportedEncodings[encoding]
|
60
|
+
if respond_to?(method, true)
|
61
|
+
send(method)
|
62
|
+
else
|
63
|
+
raise NotImplementedError, "#{self.class.name} has not implemented a decompression method for #{encoding.inspect} encoding."
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def none
|
68
|
+
body
|
69
|
+
end
|
70
|
+
|
71
|
+
def brotli
|
72
|
+
return nil unless defined?(::Brotli)
|
73
|
+
begin
|
74
|
+
::Brotli.inflate(body)
|
75
|
+
rescue StandardError
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def lzw
|
81
|
+
begin
|
82
|
+
if defined?(::LZWS::String)
|
83
|
+
::LZWS::String.decompress(body)
|
84
|
+
elsif defined?(::LZW::Simple)
|
85
|
+
::LZW::Simple.new.decompress(body)
|
86
|
+
end
|
87
|
+
rescue StandardError
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/httparty/exceptions.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTParty
|
2
|
-
# @
|
4
|
+
# @abstract Exceptions raised by HTTParty inherit from Error
|
3
5
|
class Error < StandardError; end
|
4
6
|
|
5
|
-
# Exception raised when you attempt to set a non-
|
7
|
+
# Exception raised when you attempt to set a non-existent format
|
6
8
|
class UnsupportedFormat < Error; end
|
7
9
|
|
8
10
|
# Exception raised when using a URI scheme other than HTTP or HTTPS
|
@@ -20,10 +22,14 @@ module HTTParty
|
|
20
22
|
# @param [Net::HTTPResponse]
|
21
23
|
def initialize(response)
|
22
24
|
@response = response
|
25
|
+
super(response)
|
23
26
|
end
|
24
27
|
end
|
25
28
|
|
26
29
|
# Exception that is raised when request has redirected too many times.
|
27
30
|
# Calling {#response} returns the Net:HTTP response object.
|
28
31
|
class RedirectionTooDeep < ResponseError; end
|
32
|
+
|
33
|
+
# Exception that is raised when request redirects and location header is present more than once
|
34
|
+
class DuplicateLocationHeader < ResponseError; end
|
29
35
|
end
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
|
1
5
|
module HTTParty
|
2
6
|
module HashConversions
|
3
7
|
# @return <String> This hash as a query string
|
@@ -22,28 +26,46 @@ module HTTParty
|
|
22
26
|
#
|
23
27
|
# @example normalize_param(:name, "Bob Jones") #=> "name=Bob%20Jones&"
|
24
28
|
def self.normalize_param(key, value)
|
25
|
-
|
29
|
+
normalized_keys = normalize_keys(key, value)
|
30
|
+
|
31
|
+
normalized_keys.flatten.each_slice(2).inject(''.dup) do |string, (k, v)|
|
32
|
+
string << "#{ERB::Util.url_encode(k)}=#{ERB::Util.url_encode(v.to_s)}&"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.normalize_keys(key, value)
|
26
37
|
stack = []
|
38
|
+
normalized_keys = []
|
27
39
|
|
28
40
|
if value.respond_to?(:to_ary)
|
29
|
-
|
41
|
+
if value.empty?
|
42
|
+
normalized_keys << ["#{key}[]", '']
|
43
|
+
else
|
44
|
+
normalized_keys = value.to_ary.flat_map do |element|
|
45
|
+
normalize_keys("#{key}[]", element)
|
46
|
+
end
|
47
|
+
end
|
30
48
|
elsif value.respond_to?(:to_hash)
|
31
49
|
stack << [key, value.to_hash]
|
32
50
|
else
|
33
|
-
|
51
|
+
normalized_keys << [key.to_s, value]
|
34
52
|
end
|
35
53
|
|
36
54
|
stack.each do |parent, hash|
|
37
|
-
hash.each do |
|
38
|
-
if
|
39
|
-
stack << ["#{parent}[#{
|
55
|
+
hash.each do |child_key, child_value|
|
56
|
+
if child_value.respond_to?(:to_hash)
|
57
|
+
stack << ["#{parent}[#{child_key}]", child_value.to_hash]
|
58
|
+
elsif child_value.respond_to?(:to_ary)
|
59
|
+
child_value.to_ary.each do |v|
|
60
|
+
normalized_keys << normalize_keys("#{parent}[#{child_key}][]", v).flatten
|
61
|
+
end
|
40
62
|
else
|
41
|
-
|
63
|
+
normalized_keys << normalize_keys("#{parent}[#{child_key}]", child_value).flatten
|
42
64
|
end
|
43
65
|
end
|
44
66
|
end
|
45
67
|
|
46
|
-
|
68
|
+
normalized_keys
|
47
69
|
end
|
48
70
|
end
|
49
71
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTParty
|
4
|
+
class HeadersProcessor
|
5
|
+
attr_reader :headers, :options
|
6
|
+
|
7
|
+
def initialize(headers, options)
|
8
|
+
@headers = headers
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
return unless options[:headers]
|
14
|
+
|
15
|
+
options[:headers] = headers.merge(options[:headers]) if headers.any?
|
16
|
+
options[:headers] = Utils.stringify_keys(process_dynamic_headers)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def process_dynamic_headers
|
22
|
+
options[:headers].each_with_object({}) do |header, processed_headers|
|
23
|
+
key, value = header
|
24
|
+
processed_headers[key] = if value.respond_to?(:call)
|
25
|
+
value.arity == 0 ? value.call : value.call(options)
|
26
|
+
else
|
27
|
+
value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTParty
|
2
4
|
module Logger
|
3
5
|
class ApacheFormatter #:nodoc:
|
4
6
|
TAG_NAME = HTTParty.name
|
5
7
|
|
6
|
-
attr_accessor :level, :logger
|
8
|
+
attr_accessor :level, :logger
|
7
9
|
|
8
10
|
def initialize(logger, level)
|
9
11
|
@logger = logger
|
@@ -11,11 +13,34 @@ module HTTParty
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def format(request, response)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
@request = request
|
17
|
+
@response = response
|
18
|
+
|
19
|
+
logger.public_send level, message
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :request, :response
|
25
|
+
|
26
|
+
def message
|
27
|
+
"[#{TAG_NAME}] [#{current_time}] #{response.code} \"#{http_method} #{path}\" #{content_length || '-'} "
|
28
|
+
end
|
29
|
+
|
30
|
+
def current_time
|
31
|
+
Time.now.strftime('%Y-%m-%d %H:%M:%S %z')
|
32
|
+
end
|
33
|
+
|
34
|
+
def http_method
|
35
|
+
request.http_method.name.split('::').last.upcase
|
36
|
+
end
|
37
|
+
|
38
|
+
def path
|
39
|
+
request.path.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def content_length
|
43
|
+
response.respond_to?(:headers) ? response.headers['Content-Length'] : response['Content-Length']
|
19
44
|
end
|
20
45
|
end
|
21
46
|
end
|
@@ -1,47 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTParty
|
2
4
|
module Logger
|
3
5
|
class CurlFormatter #:nodoc:
|
4
6
|
TAG_NAME = HTTParty.name
|
5
|
-
OUT
|
6
|
-
IN
|
7
|
+
OUT = '>'
|
8
|
+
IN = '<'
|
7
9
|
|
8
|
-
attr_accessor :level, :logger
|
10
|
+
attr_accessor :level, :logger
|
9
11
|
|
10
12
|
def initialize(logger, level)
|
11
|
-
@logger
|
12
|
-
@level
|
13
|
+
@logger = logger
|
14
|
+
@level = level.to_sym
|
15
|
+
@messages = []
|
13
16
|
end
|
14
17
|
|
15
18
|
def format(request, response)
|
16
|
-
|
17
|
-
|
18
|
-
http_method = request.http_method.name.split("::").last.upcase
|
19
|
-
path = request.path.to_s
|
19
|
+
@request = request
|
20
|
+
@response = response
|
20
21
|
|
21
|
-
|
22
|
+
log_request
|
23
|
+
log_response
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
logger.public_send level, messages.join('\n')
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :request, :response
|
31
|
+
attr_accessor :messages
|
28
32
|
|
29
|
-
|
30
|
-
|
31
|
-
|
33
|
+
def log_request
|
34
|
+
log_url
|
35
|
+
log_headers
|
36
|
+
log_query
|
37
|
+
log OUT, request.raw_body if request.raw_body
|
38
|
+
log OUT
|
39
|
+
end
|
40
|
+
|
41
|
+
def log_response
|
42
|
+
log IN, "HTTP/#{response.http_version} #{response.code}"
|
43
|
+
log_response_headers
|
44
|
+
log IN, "\n#{response.body}"
|
45
|
+
log IN
|
46
|
+
end
|
32
47
|
|
48
|
+
def log_url
|
49
|
+
http_method = request.http_method.name.split('::').last.upcase
|
50
|
+
uri = if request.options[:base_uri]
|
51
|
+
request.options[:base_uri] + request.path.path
|
52
|
+
else
|
53
|
+
request.path.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
log OUT, "#{http_method} #{uri}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def log_headers
|
60
|
+
return unless request.options[:headers] && request.options[:headers].size > 0
|
61
|
+
|
62
|
+
log OUT, 'Headers: '
|
63
|
+
log_hash request.options[:headers]
|
64
|
+
end
|
65
|
+
|
66
|
+
def log_query
|
67
|
+
return unless request.options[:query]
|
68
|
+
|
69
|
+
log OUT, 'Query: '
|
70
|
+
log_hash request.options[:query]
|
71
|
+
end
|
72
|
+
|
73
|
+
def log_response_headers
|
33
74
|
headers = response.respond_to?(:headers) ? response.headers : response
|
34
75
|
response.each_header do |response_header|
|
35
|
-
|
76
|
+
log IN, "#{response_header.capitalize}: #{headers[response_header]}"
|
36
77
|
end
|
78
|
+
end
|
37
79
|
|
38
|
-
|
80
|
+
def log_hash(hash)
|
81
|
+
hash.each { |k, v| log(OUT, "#{k}: #{v}") }
|
82
|
+
end
|
39
83
|
|
40
|
-
|
84
|
+
def log(direction, line = '')
|
85
|
+
messages << "[#{TAG_NAME}] [#{current_time}] #{direction} #{line}"
|
41
86
|
end
|
42
87
|
|
43
|
-
def
|
44
|
-
"
|
88
|
+
def current_time
|
89
|
+
Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
|
45
90
|
end
|
46
91
|
end
|
47
92
|
end
|
@@ -1,12 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'httparty/logger/apache_formatter'
|
2
4
|
require 'httparty/logger/curl_formatter'
|
5
|
+
require 'httparty/logger/logstash_formatter'
|
3
6
|
|
4
7
|
module HTTParty
|
5
8
|
module Logger
|
6
9
|
def self.formatters
|
7
10
|
@formatters ||= {
|
8
11
|
:curl => Logger::CurlFormatter,
|
9
|
-
:apache => Logger::ApacheFormatter
|
12
|
+
:apache => Logger::ApacheFormatter,
|
13
|
+
:logstash => Logger::LogstashFormatter,
|
10
14
|
}
|
11
15
|
end
|
12
16
|
|