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.

Files changed (101) hide show
  1. checksums.yaml +5 -5
  2. data/.editorconfig +18 -0
  3. data/.github/workflows/ci.yml +23 -0
  4. data/.gitignore +2 -0
  5. data/.rubocop_todo.yml +1 -1
  6. data/{History → Changelog.md} +220 -59
  7. data/Gemfile +8 -3
  8. data/README.md +8 -7
  9. data/bin/httparty +3 -1
  10. data/docs/README.md +171 -0
  11. data/examples/README.md +34 -12
  12. data/examples/aaws.rb +7 -3
  13. data/examples/body_stream.rb +14 -0
  14. data/examples/crack.rb +1 -1
  15. data/examples/custom_parsers.rb +5 -1
  16. data/examples/delicious.rb +4 -4
  17. data/examples/headers_and_user_agents.rb +7 -3
  18. data/examples/idn.rb +10 -0
  19. data/examples/logging.rb +4 -4
  20. data/examples/microsoft_graph.rb +52 -0
  21. data/examples/multipart.rb +22 -0
  22. data/examples/peer_cert.rb +9 -0
  23. data/examples/stackexchange.rb +1 -1
  24. data/examples/stream_download.rb +26 -0
  25. data/examples/tripit_sign_in.rb +17 -6
  26. data/examples/twitter.rb +2 -2
  27. data/examples/whoismyrep.rb +1 -1
  28. data/httparty.gemspec +7 -5
  29. data/lib/httparty/connection_adapter.rb +86 -20
  30. data/lib/httparty/cookie_hash.rb +10 -8
  31. data/lib/httparty/decompressor.rb +92 -0
  32. data/lib/httparty/exceptions.rb +8 -2
  33. data/lib/httparty/hash_conversions.rb +30 -8
  34. data/lib/httparty/headers_processor.rb +32 -0
  35. data/lib/httparty/logger/apache_formatter.rb +31 -6
  36. data/lib/httparty/logger/curl_formatter.rb +68 -23
  37. data/lib/httparty/logger/logger.rb +5 -1
  38. data/lib/httparty/logger/logstash_formatter.rb +61 -0
  39. data/lib/httparty/module_inheritable_attributes.rb +6 -4
  40. data/lib/httparty/net_digest_auth.rb +23 -21
  41. data/lib/httparty/parser.rb +25 -14
  42. data/lib/httparty/request/body.rb +98 -0
  43. data/lib/httparty/request/multipart_boundary.rb +13 -0
  44. data/lib/httparty/request.rb +156 -106
  45. data/lib/httparty/response/headers.rb +23 -19
  46. data/lib/httparty/response.rb +92 -13
  47. data/lib/httparty/response_fragment.rb +21 -0
  48. data/lib/httparty/text_encoder.rb +72 -0
  49. data/lib/httparty/utils.rb +13 -0
  50. data/lib/httparty/version.rb +3 -1
  51. data/lib/httparty.rb +98 -34
  52. data/website/css/common.css +1 -1
  53. metadata +34 -113
  54. data/.travis.yml +0 -7
  55. data/features/basic_authentication.feature +0 -20
  56. data/features/command_line.feature +0 -95
  57. data/features/deals_with_http_error_codes.feature +0 -26
  58. data/features/digest_authentication.feature +0 -30
  59. data/features/handles_compressed_responses.feature +0 -27
  60. data/features/handles_multiple_formats.feature +0 -57
  61. data/features/steps/env.rb +0 -27
  62. data/features/steps/httparty_response_steps.rb +0 -52
  63. data/features/steps/httparty_steps.rb +0 -43
  64. data/features/steps/mongrel_helper.rb +0 -127
  65. data/features/steps/remote_service_steps.rb +0 -90
  66. data/features/supports_read_timeout_option.feature +0 -13
  67. data/features/supports_redirection.feature +0 -22
  68. data/features/supports_timeout_option.feature +0 -13
  69. data/spec/fixtures/delicious.xml +0 -23
  70. data/spec/fixtures/empty.xml +0 -0
  71. data/spec/fixtures/google.html +0 -3
  72. data/spec/fixtures/ssl/generate.sh +0 -29
  73. data/spec/fixtures/ssl/generated/1fe462c2.0 +0 -16
  74. data/spec/fixtures/ssl/generated/bogushost.crt +0 -13
  75. data/spec/fixtures/ssl/generated/ca.crt +0 -16
  76. data/spec/fixtures/ssl/generated/ca.key +0 -15
  77. data/spec/fixtures/ssl/generated/selfsigned.crt +0 -14
  78. data/spec/fixtures/ssl/generated/server.crt +0 -13
  79. data/spec/fixtures/ssl/generated/server.key +0 -15
  80. data/spec/fixtures/ssl/openssl-exts.cnf +0 -9
  81. data/spec/fixtures/twitter.csv +0 -2
  82. data/spec/fixtures/twitter.json +0 -1
  83. data/spec/fixtures/twitter.xml +0 -403
  84. data/spec/fixtures/undefined_method_add_node_for_nil.xml +0 -2
  85. data/spec/httparty/connection_adapter_spec.rb +0 -468
  86. data/spec/httparty/cookie_hash_spec.rb +0 -83
  87. data/spec/httparty/exception_spec.rb +0 -38
  88. data/spec/httparty/hash_conversions_spec.rb +0 -41
  89. data/spec/httparty/logger/apache_formatter_spec.rb +0 -41
  90. data/spec/httparty/logger/curl_formatter_spec.rb +0 -18
  91. data/spec/httparty/logger/logger_spec.rb +0 -38
  92. data/spec/httparty/net_digest_auth_spec.rb +0 -230
  93. data/spec/httparty/parser_spec.rb +0 -173
  94. data/spec/httparty/request_spec.rb +0 -1073
  95. data/spec/httparty/response_spec.rb +0 -241
  96. data/spec/httparty/ssl_spec.rb +0 -74
  97. data/spec/httparty_spec.rb +0 -850
  98. data/spec/spec_helper.rb +0 -59
  99. data/spec/support/ssl_test_helper.rb +0 -47
  100. data/spec/support/ssl_test_server.rb +0 -80
  101. 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
- # HTTPParty::ConnectionAdapter will make it easier. Just override
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
- # Some things that are probably interesting are as follows:
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
- # * :+pem+: contains pem data. see HTTParty::ClassMethods.pem.
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[:http_proxyaddr]
75
- http = Net::HTTP.new(host, port, options[:http_proxyaddr], options[:http_proxyport], options[:http_proxyuser], options[:http_proxypass])
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 options[:timeout] && (options[:timeout].is_a?(Integer) || options[:timeout].is_a?(Float))
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 options[:read_timeout] && (options[:read_timeout].is_a?(Integer) || options[:read_timeout].is_a?(Float))
128
+ if add_timeout?(options[:read_timeout])
90
129
  http.read_timeout = options[:read_timeout]
91
130
  end
92
131
 
93
- if options[:open_timeout] && (options[:open_timeout].is_a?(Integer) || options[:open_timeout].is_a?(Float))
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
- if RUBY_VERSION >= "2.0.0"
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
- if RUBY_VERSION >= "2.0.0"
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 = OpenSSL::X509::Store.new
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::RSA.new(options[:pem], options[:pem_password])
161
- http.verify_mode = options[:verify_peer] == false ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
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 = options[:verify_peer] == false ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
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
@@ -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(value)
5
- case value
6
+ def add_cookies(data)
7
+ case data
6
8
  when Hash
7
- merge!(value)
9
+ merge!(data)
8
10
  when String
9
- value.split('; ').each do |cookie|
10
- array = cookie.split('=', 2)
11
- self[array[0].to_sym] = array[1]
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
- delete_if { |k, v| CLIENT_COOKIES.include?(k.to_s.downcase) }.collect { |k, v| "#{k}=#{v}" }.join("; ")
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
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
- # @abstact Exceptions raised by HTTParty inherit from Error
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-existant format
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
- param = ''
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
- param << value.to_ary.map { |element| normalize_param("#{key}[]", element) }.join
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
- param << "#{key}=#{ERB::Util.url_encode(value.to_s)}&"
51
+ normalized_keys << [key.to_s, value]
34
52
  end
35
53
 
36
54
  stack.each do |parent, hash|
37
- hash.each do |k, v|
38
- if v.respond_to?(:to_hash)
39
- stack << ["#{parent}[#{k}]", v.to_hash]
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
- param << normalize_param("#{parent}[#{k}]", v)
63
+ normalized_keys << normalize_keys("#{parent}[#{child_key}]", child_value).flatten
42
64
  end
43
65
  end
44
66
  end
45
67
 
46
- param
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, :current_time
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
- current_time = Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
15
- http_method = request.http_method.name.split("::").last.upcase
16
- path = request.path.to_s
17
- content_length = response.respond_to?(:headers) ? response.headers['Content-Length'] : response['Content-Length']
18
- @logger.send @level, "[#{TAG_NAME}] [#{current_time}] #{response.code} \"#{http_method} #{path}\" #{content_length || '-'} "
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, :current_time
10
+ attr_accessor :level, :logger
9
11
 
10
12
  def initialize(logger, level)
11
- @logger = logger
12
- @level = level.to_sym
13
+ @logger = logger
14
+ @level = level.to_sym
15
+ @messages = []
13
16
  end
14
17
 
15
18
  def format(request, response)
16
- messages = []
17
- time = Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
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
- messages << print(time, OUT, "#{http_method} #{path}")
22
+ log_request
23
+ log_response
22
24
 
23
- if request.options[:headers] && request.options[:headers].size > 0
24
- request.options[:headers].each do |k, v|
25
- messages << print(time, OUT, "#{k}: #{v}")
26
- end
27
- end
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
- messages << print(time, OUT, request.raw_body)
30
- messages << print(time, OUT, "")
31
- messages << print(time, IN, "HTTP/#{response.http_version} #{response.code}")
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
- messages << print(time, IN, "#{response_header.capitalize}: #{headers[response_header]}")
76
+ log IN, "#{response_header.capitalize}: #{headers[response_header]}"
36
77
  end
78
+ end
37
79
 
38
- messages << print(time, IN, "\n#{response.body}")
80
+ def log_hash(hash)
81
+ hash.each { |k, v| log(OUT, "#{k}: #{v}") }
82
+ end
39
83
 
40
- @logger.send @level, messages.join("\n")
84
+ def log(direction, line = '')
85
+ messages << "[#{TAG_NAME}] [#{current_time}] #{direction} #{line}"
41
86
  end
42
87
 
43
- def print(time, direction, line)
44
- "[#{TAG_NAME}] [#{time}] #{direction} #{line}"
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