httparty 0.15.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.editorconfig +18 -0
- data/.github/workflows/ci.yml +26 -0
- data/.gitignore +3 -0
- data/.rubocop_todo.yml +1 -1
- data/{History.md → Changelog.md} +137 -0
- data/Gemfile +8 -1
- data/Guardfile +3 -2
- data/README.md +6 -6
- data/docs/README.md +120 -38
- data/examples/README.md +28 -11
- data/examples/aaws.rb +6 -2
- data/examples/body_stream.rb +14 -0
- data/examples/custom_parsers.rb +4 -0
- data/examples/headers_and_user_agents.rb +7 -3
- data/examples/idn.rb +10 -0
- data/examples/logging.rb +3 -3
- data/examples/microsoft_graph.rb +52 -0
- data/examples/multipart.rb +22 -0
- data/examples/peer_cert.rb +9 -0
- data/examples/stream_download.rb +8 -2
- data/httparty.gemspec +7 -4
- data/lib/httparty/connection_adapter.rb +59 -16
- data/lib/httparty/cookie_hash.rb +10 -8
- data/lib/httparty/decompressor.rb +102 -0
- data/lib/httparty/exceptions.rb +4 -1
- data/lib/httparty/hash_conversions.rb +28 -12
- data/lib/httparty/headers_processor.rb +32 -0
- data/lib/httparty/logger/apache_formatter.rb +31 -6
- data/lib/httparty/logger/curl_formatter.rb +9 -7
- 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 +15 -15
- data/lib/httparty/parser.rb +25 -16
- data/lib/httparty/request/body.rb +105 -0
- data/lib/httparty/request/multipart_boundary.rb +13 -0
- data/lib/httparty/request.rb +96 -105
- data/lib/httparty/response/headers.rb +6 -2
- data/lib/httparty/response.rb +59 -8
- 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 +70 -25
- data/website/css/common.css +1 -1
- metadata +38 -106
- data/.simplecov +0 -1
- data/.travis.yml +0 -8
- 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 -56
- data/features/steps/httparty_steps.rb +0 -43
- data/features/steps/mongrel_helper.rb +0 -127
- data/features/steps/remote_service_steps.rb +0 -92
- 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 -1
- 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 -495
- data/spec/httparty/cookie_hash_spec.rb +0 -100
- data/spec/httparty/exception_spec.rb +0 -45
- data/spec/httparty/hash_conversions_spec.rb +0 -49
- data/spec/httparty/logger/apache_formatter_spec.rb +0 -41
- data/spec/httparty/logger/curl_formatter_spec.rb +0 -119
- data/spec/httparty/logger/logger_spec.rb +0 -38
- data/spec/httparty/net_digest_auth_spec.rb +0 -268
- data/spec/httparty/parser_spec.rb +0 -178
- data/spec/httparty/request_spec.rb +0 -1244
- data/spec/httparty/response_spec.rb +0 -347
- data/spec/httparty/ssl_spec.rb +0 -74
- data/spec/httparty_spec.rb +0 -877
- 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,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
|
-
|
13
|
-
|
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)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'digest/md5'
|
2
4
|
require 'net/http'
|
3
5
|
|
@@ -14,11 +16,11 @@ module Net
|
|
14
16
|
|
15
17
|
authenticator.authorization_header.each do |v|
|
16
18
|
add_field('Authorization', v)
|
17
|
-
end
|
19
|
+
end
|
18
20
|
|
19
21
|
authenticator.cookie_header.each do |v|
|
20
22
|
add_field('Cookie', v)
|
21
|
-
end
|
23
|
+
end
|
22
24
|
end
|
23
25
|
|
24
26
|
class DigestAuthenticator
|
@@ -44,12 +46,9 @@ module Net
|
|
44
46
|
header << %(algorithm="#{@response['algorithm']}") if algorithm_present?
|
45
47
|
|
46
48
|
if qop_present?
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
"nc=00000001"
|
51
|
-
]
|
52
|
-
fields.each { |field| header << field }
|
49
|
+
header << %(cnonce="#{@cnonce}")
|
50
|
+
header << %(qop="#{@response['qop']}")
|
51
|
+
header << 'nc=00000001'
|
53
52
|
end
|
54
53
|
|
55
54
|
header << %(opaque="#{@response['opaque']}") if opaque_present?
|
@@ -64,7 +63,8 @@ module Net
|
|
64
63
|
|
65
64
|
def parse(response_header)
|
66
65
|
header = response_header['www-authenticate']
|
67
|
-
|
66
|
+
|
67
|
+
header = header.gsub(/qop=(auth(?:-int)?)/, 'qop="\\1"')
|
68
68
|
|
69
69
|
header =~ /Digest (.*)/
|
70
70
|
params = {}
|
@@ -97,13 +97,13 @@ module Net
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def random
|
100
|
-
format
|
100
|
+
format '%x', (Time.now.to_i + rand(65535))
|
101
101
|
end
|
102
102
|
|
103
103
|
def request_digest
|
104
104
|
a = [md5(a1), @response['nonce'], md5(a2)]
|
105
|
-
a.insert(2,
|
106
|
-
md5(a.join(
|
105
|
+
a.insert(2, '00000001', @cnonce, @response['qop']) if qop_present?
|
106
|
+
md5(a.join(':'))
|
107
107
|
end
|
108
108
|
|
109
109
|
def md5(str)
|
@@ -113,11 +113,11 @@ module Net
|
|
113
113
|
def algorithm_present?
|
114
114
|
@response.key?('algorithm') && !@response['algorithm'].empty?
|
115
115
|
end
|
116
|
-
|
116
|
+
|
117
117
|
def use_md5_sess?
|
118
118
|
algorithm_present? && @response['algorithm'] == 'MD5-sess'
|
119
119
|
end
|
120
|
-
|
120
|
+
|
121
121
|
def a1
|
122
122
|
a1_user_realm_pwd = [@username, @response['realm'], @password].join(':')
|
123
123
|
if use_md5_sess?
|
@@ -128,7 +128,7 @@ module Net
|
|
128
128
|
end
|
129
129
|
|
130
130
|
def a2
|
131
|
-
[@method, @path].join(
|
131
|
+
[@method, @path].join(':')
|
132
132
|
end
|
133
133
|
end
|
134
134
|
end
|
data/lib/httparty/parser.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module HTTParty
|
2
4
|
# The default parser used by HTTParty, supports xml, json, html, csv and
|
3
5
|
# plain text.
|
@@ -38,16 +40,18 @@ module HTTParty
|
|
38
40
|
# @abstract Read the Custom Parsers section for more information.
|
39
41
|
class Parser
|
40
42
|
SupportedFormats = {
|
41
|
-
'text/xml'
|
42
|
-
'application/xml'
|
43
|
-
'application/json'
|
44
|
-
'
|
45
|
-
'application/
|
46
|
-
'text/
|
47
|
-
'
|
48
|
-
'text/
|
49
|
-
'text/
|
50
|
-
'
|
43
|
+
'text/xml' => :xml,
|
44
|
+
'application/xml' => :xml,
|
45
|
+
'application/json' => :json,
|
46
|
+
'application/vnd.api+json' => :json,
|
47
|
+
'application/hal+json' => :json,
|
48
|
+
'text/json' => :json,
|
49
|
+
'application/javascript' => :plain,
|
50
|
+
'text/javascript' => :plain,
|
51
|
+
'text/html' => :html,
|
52
|
+
'text/plain' => :plain,
|
53
|
+
'text/csv' => :csv,
|
54
|
+
'application/csv' => :csv,
|
51
55
|
'text/comma-separated-values' => :csv
|
52
56
|
}
|
53
57
|
|
@@ -99,8 +103,11 @@ module HTTParty
|
|
99
103
|
# @return [nil] when the response body is nil, an empty string, spaces only or "null"
|
100
104
|
def parse
|
101
105
|
return nil if body.nil?
|
102
|
-
return nil if body ==
|
106
|
+
return nil if body == 'null'
|
103
107
|
return nil if body.valid_encoding? && body.strip.empty?
|
108
|
+
if body.valid_encoding? && body.encoding == Encoding::UTF_8
|
109
|
+
@body = body.gsub(/\A#{UTF8_BOM}/, '')
|
110
|
+
end
|
104
111
|
if supports_format?
|
105
112
|
parse_supported_format
|
106
113
|
else
|
@@ -114,10 +121,10 @@ module HTTParty
|
|
114
121
|
MultiXml.parse(body)
|
115
122
|
end
|
116
123
|
|
117
|
-
UTF8_BOM = "\xEF\xBB\xBF"
|
124
|
+
UTF8_BOM = "\xEF\xBB\xBF"
|
118
125
|
|
119
126
|
def json
|
120
|
-
JSON.parse(body
|
127
|
+
JSON.parse(body, :quirks_mode => true, :allow_nan => true)
|
121
128
|
end
|
122
129
|
|
123
130
|
def csv
|
@@ -137,9 +144,11 @@ module HTTParty
|
|
137
144
|
end
|
138
145
|
|
139
146
|
def parse_supported_format
|
140
|
-
|
141
|
-
|
142
|
-
|
147
|
+
if respond_to?(format, true)
|
148
|
+
send(format)
|
149
|
+
else
|
150
|
+
raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format."
|
151
|
+
end
|
143
152
|
end
|
144
153
|
end
|
145
154
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'multipart_boundary'
|
4
|
+
|
5
|
+
module HTTParty
|
6
|
+
class Request
|
7
|
+
class Body
|
8
|
+
NEWLINE = "\r\n"
|
9
|
+
private_constant :NEWLINE
|
10
|
+
|
11
|
+
def initialize(params, query_string_normalizer: nil, force_multipart: false)
|
12
|
+
@params = params
|
13
|
+
@query_string_normalizer = query_string_normalizer
|
14
|
+
@force_multipart = force_multipart
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
if params.respond_to?(:to_hash)
|
19
|
+
multipart? ? generate_multipart : normalize_query(params)
|
20
|
+
else
|
21
|
+
params
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def boundary
|
26
|
+
@boundary ||= MultipartBoundary.generate
|
27
|
+
end
|
28
|
+
|
29
|
+
def multipart?
|
30
|
+
params.respond_to?(:to_hash) && (force_multipart || has_file?(params))
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# https://html.spec.whatwg.org/#multipart-form-data
|
36
|
+
MULTIPART_FORM_DATA_REPLACEMENT_TABLE = {
|
37
|
+
'"' => '%22',
|
38
|
+
"\r" => '%0D',
|
39
|
+
"\n" => '%0A'
|
40
|
+
}.freeze
|
41
|
+
|
42
|
+
def generate_multipart
|
43
|
+
normalized_params = params.flat_map { |key, value| HashConversions.normalize_keys(key, value) }
|
44
|
+
|
45
|
+
multipart = normalized_params.inject(''.dup) do |memo, (key, value)|
|
46
|
+
memo << "--#{boundary}#{NEWLINE}"
|
47
|
+
memo << %(Content-Disposition: form-data; name="#{key}")
|
48
|
+
# value.path is used to support ActionDispatch::Http::UploadedFile
|
49
|
+
# https://github.com/jnunemaker/httparty/pull/585
|
50
|
+
memo << %(; filename="#{file_name(value).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE)}") if file?(value)
|
51
|
+
memo << NEWLINE
|
52
|
+
memo << "Content-Type: #{content_type(value)}#{NEWLINE}" if file?(value)
|
53
|
+
memo << NEWLINE
|
54
|
+
memo << content_body(value)
|
55
|
+
memo << NEWLINE
|
56
|
+
end
|
57
|
+
|
58
|
+
multipart << "--#{boundary}--#{NEWLINE}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def has_file?(value)
|
62
|
+
if value.respond_to?(:to_hash)
|
63
|
+
value.to_hash.any? { |_, v| has_file?(v) }
|
64
|
+
elsif value.respond_to?(:to_ary)
|
65
|
+
value.to_ary.any? { |v| has_file?(v) }
|
66
|
+
else
|
67
|
+
file?(value)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def file?(object)
|
72
|
+
object.respond_to?(:path) && object.respond_to?(:read)
|
73
|
+
end
|
74
|
+
|
75
|
+
def normalize_query(query)
|
76
|
+
if query_string_normalizer
|
77
|
+
query_string_normalizer.call(query)
|
78
|
+
else
|
79
|
+
HashConversions.to_params(query)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def content_body(object)
|
84
|
+
if file?(object)
|
85
|
+
object = (file = object).read
|
86
|
+
file.rewind if file.respond_to?(:rewind)
|
87
|
+
end
|
88
|
+
|
89
|
+
object.to_s
|
90
|
+
end
|
91
|
+
|
92
|
+
def content_type(object)
|
93
|
+
return object.content_type if object.respond_to?(:content_type)
|
94
|
+
mime = MiniMime.lookup_by_filename(object.path)
|
95
|
+
mime ? mime.content_type : 'application/octet-stream'
|
96
|
+
end
|
97
|
+
|
98
|
+
def file_name(object)
|
99
|
+
object.respond_to?(:original_filename) ? object.original_filename : File.basename(object.path)
|
100
|
+
end
|
101
|
+
|
102
|
+
attr_reader :params, :query_string_normalizer, :force_multipart
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|