httparty 0.15.4 → 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 (93) 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/Changelog.md +105 -0
  7. data/Gemfile +6 -1
  8. data/README.md +6 -6
  9. data/docs/README.md +100 -38
  10. data/examples/README.md +28 -11
  11. data/examples/aaws.rb +6 -2
  12. data/examples/body_stream.rb +14 -0
  13. data/examples/custom_parsers.rb +4 -0
  14. data/examples/headers_and_user_agents.rb +7 -3
  15. data/examples/idn.rb +10 -0
  16. data/examples/logging.rb +3 -3
  17. data/examples/microsoft_graph.rb +52 -0
  18. data/examples/multipart.rb +22 -0
  19. data/examples/peer_cert.rb +9 -0
  20. data/examples/stream_download.rb +8 -2
  21. data/httparty.gemspec +3 -3
  22. data/lib/httparty/connection_adapter.rb +59 -16
  23. data/lib/httparty/cookie_hash.rb +10 -8
  24. data/lib/httparty/decompressor.rb +92 -0
  25. data/lib/httparty/exceptions.rb +4 -1
  26. data/lib/httparty/hash_conversions.rb +28 -12
  27. data/lib/httparty/headers_processor.rb +32 -0
  28. data/lib/httparty/logger/apache_formatter.rb +31 -6
  29. data/lib/httparty/logger/curl_formatter.rb +9 -7
  30. data/lib/httparty/logger/logger.rb +5 -1
  31. data/lib/httparty/logger/logstash_formatter.rb +61 -0
  32. data/lib/httparty/module_inheritable_attributes.rb +6 -4
  33. data/lib/httparty/net_digest_auth.rb +15 -15
  34. data/lib/httparty/parser.rb +22 -16
  35. data/lib/httparty/request/body.rb +98 -0
  36. data/lib/httparty/request/multipart_boundary.rb +13 -0
  37. data/lib/httparty/request.rb +82 -95
  38. data/lib/httparty/response/headers.rb +4 -2
  39. data/lib/httparty/response.rb +59 -8
  40. data/lib/httparty/response_fragment.rb +21 -0
  41. data/lib/httparty/text_encoder.rb +72 -0
  42. data/lib/httparty/utils.rb +13 -0
  43. data/lib/httparty/version.rb +3 -1
  44. data/lib/httparty.rb +70 -24
  45. data/website/css/common.css +1 -1
  46. metadata +35 -100
  47. data/.travis.yml +0 -8
  48. data/features/basic_authentication.feature +0 -20
  49. data/features/command_line.feature +0 -95
  50. data/features/deals_with_http_error_codes.feature +0 -26
  51. data/features/digest_authentication.feature +0 -30
  52. data/features/handles_compressed_responses.feature +0 -27
  53. data/features/handles_multiple_formats.feature +0 -57
  54. data/features/steps/env.rb +0 -27
  55. data/features/steps/httparty_response_steps.rb +0 -56
  56. data/features/steps/httparty_steps.rb +0 -43
  57. data/features/steps/mongrel_helper.rb +0 -127
  58. data/features/steps/remote_service_steps.rb +0 -92
  59. data/features/supports_read_timeout_option.feature +0 -13
  60. data/features/supports_redirection.feature +0 -22
  61. data/features/supports_timeout_option.feature +0 -13
  62. data/spec/fixtures/delicious.xml +0 -23
  63. data/spec/fixtures/empty.xml +0 -0
  64. data/spec/fixtures/google.html +0 -3
  65. data/spec/fixtures/ssl/generate.sh +0 -29
  66. data/spec/fixtures/ssl/generated/bogushost.crt +0 -13
  67. data/spec/fixtures/ssl/generated/ca.crt +0 -16
  68. data/spec/fixtures/ssl/generated/ca.key +0 -15
  69. data/spec/fixtures/ssl/generated/selfsigned.crt +0 -14
  70. data/spec/fixtures/ssl/generated/server.crt +0 -13
  71. data/spec/fixtures/ssl/generated/server.key +0 -15
  72. data/spec/fixtures/ssl/openssl-exts.cnf +0 -9
  73. data/spec/fixtures/twitter.csv +0 -2
  74. data/spec/fixtures/twitter.json +0 -1
  75. data/spec/fixtures/twitter.xml +0 -403
  76. data/spec/fixtures/undefined_method_add_node_for_nil.xml +0 -2
  77. data/spec/httparty/connection_adapter_spec.rb +0 -495
  78. data/spec/httparty/cookie_hash_spec.rb +0 -100
  79. data/spec/httparty/exception_spec.rb +0 -45
  80. data/spec/httparty/hash_conversions_spec.rb +0 -49
  81. data/spec/httparty/logger/apache_formatter_spec.rb +0 -41
  82. data/spec/httparty/logger/curl_formatter_spec.rb +0 -119
  83. data/spec/httparty/logger/logger_spec.rb +0 -38
  84. data/spec/httparty/net_digest_auth_spec.rb +0 -268
  85. data/spec/httparty/parser_spec.rb +0 -185
  86. data/spec/httparty/request_spec.rb +0 -1251
  87. data/spec/httparty/response_spec.rb +0 -347
  88. data/spec/httparty/ssl_spec.rb +0 -74
  89. data/spec/httparty_spec.rb +0 -877
  90. data/spec/spec_helper.rb +0 -59
  91. data/spec/support/ssl_test_helper.rb +0 -47
  92. data/spec/support/ssl_test_server.rb +0 -80
  93. data/spec/support/stub_response.rb +0 -49
@@ -0,0 +1,22 @@
1
+ # If you are uploading file in params, multipart will used as content-type automatically
2
+
3
+ HTTParty.post(
4
+ 'http://localhost:3000/user',
5
+ body: {
6
+ name: 'Foo Bar',
7
+ email: 'example@email.com',
8
+ avatar: File.open('/full/path/to/avatar.jpg')
9
+ }
10
+ )
11
+
12
+
13
+ # However, you can force it yourself
14
+
15
+ HTTParty.post(
16
+ 'http://localhost:3000/user',
17
+ multipart: true,
18
+ body: {
19
+ name: 'Foo Bar',
20
+ email: 'example@email.com'
21
+ }
22
+ )
@@ -0,0 +1,9 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require File.join(dir, 'httparty')
3
+
4
+ peer_cert = nil
5
+ HTTParty.get("https://www.example.com") do |fragment|
6
+ peer_cert ||= fragment.connection.peer_cert
7
+ end
8
+
9
+ puts "The server's certificate expires #{peer_cert.not_after}"
@@ -9,8 +9,14 @@ url = "https://cdn.kernel.org/pub/linux/kernel/v4.x/#{filename}"
9
9
 
10
10
  File.open(filename, "w") do |file|
11
11
  response = HTTParty.get(url, stream_body: true) do |fragment|
12
- print "."
13
- file.write(fragment)
12
+ if [301, 302].include?(fragment.code)
13
+ print "skip writing for redirect"
14
+ elsif fragment.code == 200
15
+ print "."
16
+ file.write(fragment)
17
+ else
18
+ raise StandardError, "Non-success status code while streaming #{fragment.code}"
19
+ end
14
20
  end
15
21
  end
16
22
  puts
data/httparty.gemspec CHANGED
@@ -9,13 +9,14 @@ Gem::Specification.new do |s|
9
9
  s.licenses = ['MIT']
10
10
  s.authors = ["John Nunemaker", "Sandro Turriate"]
11
11
  s.email = ["nunemaker@gmail.com"]
12
- s.homepage = "http://jnunemaker.github.com/httparty"
12
+ s.homepage = "https://github.com/jnunemaker/httparty"
13
13
  s.summary = 'Makes http fun! Also, makes consuming restful web services dead easy.'
14
14
  s.description = 'Makes http fun! Also, makes consuming restful web services dead easy.'
15
15
 
16
- s.required_ruby_version = '>= 2.0.0'
16
+ s.required_ruby_version = '>= 2.3.0'
17
17
 
18
18
  s.add_dependency 'multi_xml', ">= 0.5.2"
19
+ s.add_dependency('mime-types', "~> 3.0")
19
20
 
20
21
  # If this line is removed, all hard partying will cease.
21
22
  s.post_install_message = "When you HTTParty, you must party hard!"
@@ -24,7 +25,6 @@ Gem::Specification.new do |s|
24
25
  test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
26
 
26
27
  s.files = all_files - test_files
27
- s.test_files = test_files
28
28
  s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
29
29
  s.require_paths = ["lib"]
30
30
  end
@@ -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,12 +40,13 @@ 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
- # The keys used in options are
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
- # * :+cert_store+: contains certificate data. see method 'attach_ssl_certificates'
49
+ # * :+cert_store+: contains certificate data. see method 'attach_ssl_certificates'
47
50
  # * :+pem+: contains pem client certificate data. see method 'attach_ssl_certificates'
48
51
  # * :+p12+: contains PKCS12 client client certificate data. see method 'attach_ssl_certificates'
49
52
  # * :+verify+: verify the server’s certificate against the ca certificate.
@@ -77,6 +80,12 @@ module HTTParty
77
80
  new(uri, options).connection
78
81
  end
79
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
+
80
89
  attr_reader :uri, :options
81
90
 
82
91
  def initialize(uri, options = {})
@@ -91,7 +100,14 @@ module HTTParty
91
100
  host = clean_host(uri.host)
92
101
  port = uri.port || (uri.scheme == 'https' ? 443 : 80)
93
102
  if options.key?(:http_proxyaddr)
94
- http = Net::HTTP.new(host, port, options[:http_proxyaddr], options[:http_proxyport], options[:http_proxyuser], options[:http_proxypass])
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
+ )
95
111
  else
96
112
  http = Net::HTTP.new(host, port)
97
113
  end
@@ -100,19 +116,35 @@ module HTTParty
100
116
 
101
117
  attach_ssl_certificates(http, options)
102
118
 
103
- if options[:timeout] && (options[:timeout].is_a?(Integer) || options[:timeout].is_a?(Float))
119
+ if add_timeout?(options[:timeout])
104
120
  http.open_timeout = options[:timeout]
105
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
106
126
  end
107
127
 
108
- if options[:read_timeout] && (options[:read_timeout].is_a?(Integer) || options[:read_timeout].is_a?(Float))
128
+ if add_timeout?(options[:read_timeout])
109
129
  http.read_timeout = options[:read_timeout]
110
130
  end
111
131
 
112
- if options[:open_timeout] && (options[:open_timeout].is_a?(Integer) || options[:open_timeout].is_a?(Float))
132
+ if add_timeout?(options[:open_timeout])
113
133
  http.open_timeout = options[:open_timeout]
114
134
  end
115
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
+
116
148
  if options[:debug_output]
117
149
  http.set_debug_output(options[:debug_output])
118
150
  end
@@ -125,18 +157,14 @@ module HTTParty
125
157
  #
126
158
  # @see https://bugs.ruby-lang.org/issues/6617
127
159
  if options[:local_host]
128
- if RUBY_VERSION >= "2.0.0"
160
+ from_ruby_version('2.0.0', option: :local_host) do
129
161
  http.local_host = options[:local_host]
130
- else
131
- Kernel.warn("Warning: option :local_host requires Ruby version 2.0 or later")
132
162
  end
133
163
  end
134
164
 
135
165
  if options[:local_port]
136
- if RUBY_VERSION >= "2.0.0"
166
+ from_ruby_version('2.0.0', option: :local_port) do
137
167
  http.local_port = options[:local_port]
138
- else
139
- Kernel.warn("Warning: option :local_port requires Ruby version 2.0 or later")
140
168
  end
141
169
  end
142
170
 
@@ -145,6 +173,22 @@ module HTTParty
145
173
 
146
174
  private
147
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
+
148
192
  def clean_host(host)
149
193
  strip_ipv6_brackets(host)
150
194
  end
@@ -169,8 +213,7 @@ module HTTParty
169
213
  http.cert_store = options[:cert_store]
170
214
  else
171
215
  # Use the default cert store by default, i.e. system ca certs
172
- http.cert_store = OpenSSL::X509::Store.new
173
- http.cert_store.set_default_paths
216
+ http.cert_store = self.class.default_cert_store
174
217
  end
175
218
  else
176
219
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
@@ -180,7 +223,7 @@ module HTTParty
180
223
  # Note: options[:pem] must contain the content of a PEM file having the private key appended
181
224
  if options[:pem]
182
225
  http.cert = OpenSSL::X509::Certificate.new(options[:pem])
183
- http.key = OpenSSL::PKey::RSA.new(options[:pem], options[:pem_password])
226
+ http.key = OpenSSL::PKey.read(options[:pem], options[:pem_password])
184
227
  http.verify_mode = verify_ssl_certificate? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
185
228
  end
186
229
 
@@ -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
- reject { |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,5 +1,7 @@
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
7
  # Exception raised when you attempt to set a non-existent format
@@ -20,6 +22,7 @@ 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
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erb'
2
4
 
3
5
  module HTTParty
@@ -24,32 +26,46 @@ module HTTParty
24
26
  #
25
27
  # @example normalize_param(:name, "Bob Jones") #=> "name=Bob%20Jones&"
26
28
  def self.normalize_param(key, value)
27
- 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)
28
37
  stack = []
38
+ normalized_keys = []
29
39
 
30
40
  if value.respond_to?(:to_ary)
31
- param << if value.empty?
32
- "#{key}[]=&"
33
- else
34
- value.to_ary.map { |element| normalize_param("#{key}[]", element) }.join
35
- end
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
36
48
  elsif value.respond_to?(:to_hash)
37
49
  stack << [key, value.to_hash]
38
50
  else
39
- param << "#{key}=#{ERB::Util.url_encode(value.to_s)}&"
51
+ normalized_keys << [key.to_s, value]
40
52
  end
41
53
 
42
54
  stack.each do |parent, hash|
43
- hash.each do |k, v|
44
- if v.respond_to?(:to_hash)
45
- 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
46
62
  else
47
- param << normalize_param("#{parent}[#{k}]", v)
63
+ normalized_keys << normalize_keys("#{parent}[#{child_key}]", child_value).flatten
48
64
  end
49
65
  end
50
66
  end
51
67
 
52
- param
68
+ normalized_keys
53
69
  end
54
70
  end
55
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,9 +1,11 @@
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 = '>'.freeze
6
- IN = '<'.freeze
7
+ OUT = '>'
8
+ IN = '<'
7
9
 
8
10
  attr_accessor :level, :logger
9
11
 
@@ -20,7 +22,7 @@ module HTTParty
20
22
  log_request
21
23
  log_response
22
24
 
23
- logger.send level, messages.join("\n")
25
+ logger.public_send level, messages.join('\n')
24
26
  end
25
27
 
26
28
  private
@@ -44,7 +46,7 @@ module HTTParty
44
46
  end
45
47
 
46
48
  def log_url
47
- http_method = request.http_method.name.split("::").last.upcase
49
+ http_method = request.http_method.name.split('::').last.upcase
48
50
  uri = if request.options[:base_uri]
49
51
  request.options[:base_uri] + request.path.path
50
52
  else
@@ -80,11 +82,11 @@ module HTTParty
80
82
  end
81
83
 
82
84
  def log(direction, line = '')
83
- messages << "[#{TAG_NAME}] [#{time}] #{direction} #{line}"
85
+ messages << "[#{TAG_NAME}] [#{current_time}] #{direction} #{line}"
84
86
  end
85
87
 
86
- def time
87
- @time ||= Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
88
+ def current_time
89
+ Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
88
90
  end
89
91
  end
90
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
 
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTParty
4
+ module Logger
5
+ class LogstashFormatter #:nodoc:
6
+ TAG_NAME = HTTParty.name
7
+
8
+ attr_accessor :level, :logger
9
+
10
+ def initialize(logger, level)
11
+ @logger = logger
12
+ @level = level.to_sym
13
+ end
14
+
15
+ def format(request, response)
16
+ @request = request
17
+ @response = response
18
+
19
+ logger.public_send level, logstash_message
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :request, :response
25
+
26
+ def logstash_message
27
+ {
28
+ '@timestamp' => current_time,
29
+ '@version' => 1,
30
+ 'content_length' => content_length || '-',
31
+ 'http_method' => http_method,
32
+ 'message' => message,
33
+ 'path' => path,
34
+ 'response_code' => response.code,
35
+ 'severity' => level,
36
+ 'tags' => [TAG_NAME],
37
+ }.to_json
38
+ end
39
+
40
+ def message
41
+ "[#{TAG_NAME}] #{response.code} \"#{http_method} #{path}\" #{content_length || '-'} "
42
+ end
43
+
44
+ def current_time
45
+ Time.now.strftime('%Y-%m-%d %H:%M:%S %z')
46
+ end
47
+
48
+ def http_method
49
+ @http_method ||= request.http_method.name.split('::').last.upcase
50
+ end
51
+
52
+ def path
53
+ @path ||= request.path.to_s
54
+ end
55
+
56
+ def content_length
57
+ @content_length ||= response.respond_to?(:headers) ? response.headers['Content-Length'] : response['Content-Length']
58
+ end
59
+ end
60
+ end
61
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTParty
2
4
  module ModuleInheritableAttributes #:nodoc:
3
5
  def self.included(base)
@@ -9,12 +11,12 @@ module HTTParty
9
11
  duplicate = hash.dup
10
12
 
11
13
  duplicate.each_pair do |key, value|
12
- duplicate[key] = if value.is_a?(Hash)
13
- hash_deep_dup(value)
14
+ if value.is_a?(Hash)
15
+ duplicate[key] = hash_deep_dup(value)
14
16
  elsif value.is_a?(Proc)
15
17
  duplicate[key] = value.dup
16
18
  else
17
- value
19
+ duplicate[key] = value
18
20
  end
19
21
  end
20
22
 
@@ -36,7 +38,7 @@ module HTTParty
36
38
  def inherited(subclass)
37
39
  super
38
40
  @mattr_inheritable_attrs.each do |inheritable_attribute|
39
- ivar = "@#{inheritable_attribute}"
41
+ ivar = :"@#{inheritable_attribute}"
40
42
  subclass.instance_variable_set(ivar, instance_variable_get(ivar).clone)
41
43
 
42
44
  if instance_variable_get(ivar).respond_to?(:merge)