savon 2.15.1 → 3.0.0.rc2
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 +4 -4
- data/CHANGELOG.md +26 -0
- data/README.md +6 -1
- data/lib/savon/builder.rb +29 -12
- data/lib/savon/http_error.rb +3 -3
- data/lib/savon/mock/expectation.rb +4 -4
- data/lib/savon/operation.rb +49 -29
- data/lib/savon/options.rb +54 -8
- data/lib/savon/request.rb +95 -45
- data/lib/savon/request_logger.rb +9 -6
- data/lib/savon/response.rb +9 -13
- data/lib/savon/soap_fault.rb +9 -2
- data/lib/savon/version.rb +1 -1
- data/lib/savon.rb +8 -0
- metadata +108 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 217170b4a884638cf811c1b5334c9acbbfa7e5b88b1554f29c3e043557be5195
|
4
|
+
data.tar.gz: c36c7221ec92faa8df1064d4776c21269885d8db5d8d047db69dc3f598f05220
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8da94da891a4d4db8e6a8ee2fbf08d2d230e2fcd7ea709f474649c4a06161c301a989a2174e58b73d27ca05a7141d8f1690e86526555e9f17d78665cca54e645
|
7
|
+
data.tar.gz: 1cb489bdd51b813057ad4b5b6c9f81739b89fa08276cc5ddca096271daf530b5c63307723d78b2202970a1f8f23270d71acf9f11daea5f43ca25c523067f7bbc
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,32 @@
|
|
3
3
|
## Unreleased
|
4
4
|
* Add your PR changelog line here
|
5
5
|
|
6
|
+
## 3.0.0.rc2 (2025-08-12)
|
7
|
+
* MTOM support with tests by @pcai in https://github.com/savonrb/savon/pull/1012
|
8
|
+
* Upgrade notes on ssl_verify_mode by @ehutzelman in https://github.com/savonrb/savon/pull/1013
|
9
|
+
* Pass the provided Savon/custom logger to Faraday by @larskanis in https://github.com/savonrb/savon/pull/1017
|
10
|
+
* Add ruby 3.4 to CI by @doconnor-clintel in https://github.com/savonrb/savon/pull/1024
|
11
|
+
* Restore support for SSL Ciphers by @doconnor-clintel in https://github.com/savonrb/savon/pull/1020
|
12
|
+
* Drop ruby 3.0 from CI by @doconnor-clintel in https://github.com/savonrb/savon/pull/1025
|
13
|
+
* Don't block minor updates to faraday by @larskanis in https://github.com/savonrb/savon/pull/1028
|
14
|
+
* Add gzip middleware when Accept-Encoding includes gzip by @kjeldahl in https://github.com/savonrb/savon/pull/1030
|
15
|
+
* Add option to provide connection middlewares. by @amartinfraguas in https://github.com/savonrb/savon/pull/1026
|
16
|
+
|
17
|
+
## 3.0.0.rc1 (2024-07-15)
|
18
|
+
|
19
|
+
* Use Faraday instead of HTTPI
|
20
|
+
* BC BREAKING Cookies are handled differently now
|
21
|
+
* BC BREAKING Multiple pieces of functionality will rely on faraday libraries to be provided by the consuming codebase
|
22
|
+
* BC BREAKING Adapter overrides now utilize the faraday model
|
23
|
+
* BC BREAKING Multiple hard deprecations due to a lack of feature parity between Faraday and HTTPI
|
24
|
+
* Deprecates digest auth
|
25
|
+
* Deprecates ssl_cert_key_file auth, upgrade path is to read the key
|
26
|
+
in and provide it
|
27
|
+
* Deprecates encrypted ssl keys, upgrade path is to
|
28
|
+
decrypt the key and pass it to faraday in code
|
29
|
+
* Deprecates providing a ca cert, upgrade path is to provide a ca cert file
|
30
|
+
* deprecates overriding ssl ciphers, as faraday does not support this
|
31
|
+
|
6
32
|
## 2.15.1 (2024-07-08)
|
7
33
|
|
8
34
|
* Ruby 3.0+ is required in the gemspec.
|
data/README.md
CHANGED
@@ -10,6 +10,7 @@ Heavy metal SOAP client
|
|
10
10
|
[](https://codeclimate.com/github/savonrb/savon)
|
11
11
|
[](https://coveralls.io/r/savonrb/savon)
|
12
12
|
|
13
|
+
If you're reading this on GitHub, note that this README is for the main branch and that features/changes described here might not correspond to your version. You can find the documentation for your release [at rubydoc.info](https://www.rubydoc.info/find/gems?q=savon).
|
13
14
|
|
14
15
|
## Installation
|
15
16
|
|
@@ -22,7 +23,7 @@ $ gem install savon
|
|
22
23
|
or add it to your Gemfile like this:
|
23
24
|
|
24
25
|
```
|
25
|
-
gem 'savon', '~>
|
26
|
+
gem 'savon', '~> 3.0.0'
|
26
27
|
```
|
27
28
|
|
28
29
|
## Usage example
|
@@ -52,6 +53,10 @@ response.body
|
|
52
53
|
For more examples, you should check out the
|
53
54
|
[integration tests](https://github.com/savonrb/savon/tree/version2/spec/integration).
|
54
55
|
|
56
|
+
## Upgrading from v2.x to v3.x
|
57
|
+
|
58
|
+
See [UPGRADING.md](UPGRADING.md) for more information.
|
59
|
+
|
55
60
|
## Ruby version support
|
56
61
|
|
57
62
|
Every savon release is tested with contemporary supported versions of ruby. Historical compatibility information:
|
data/lib/savon/builder.rb
CHANGED
@@ -38,18 +38,23 @@ module Savon
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def build_document
|
41
|
-
|
41
|
+
# check if xml was already provided
|
42
|
+
if @locals.include? :xml
|
43
|
+
xml_result = @locals[:xml]
|
44
|
+
else
|
45
|
+
xml_result = build_xml
|
42
46
|
|
43
|
-
|
44
|
-
|
45
|
-
|
47
|
+
# if we have a signature sign the document
|
48
|
+
if @signature
|
49
|
+
@signature.document = xml_result
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
+
2.times do
|
52
|
+
@header = nil
|
53
|
+
@signature.document = build_xml
|
54
|
+
end
|
51
55
|
|
52
|
-
|
56
|
+
xml_result = @signature.document
|
57
|
+
end
|
53
58
|
end
|
54
59
|
|
55
60
|
# if there are attachments for the request, we should build a multipart message according to
|
@@ -70,7 +75,6 @@ module Savon
|
|
70
75
|
end
|
71
76
|
|
72
77
|
def to_s
|
73
|
-
return @locals[:xml] if @locals.include? :xml
|
74
78
|
build_document
|
75
79
|
end
|
76
80
|
|
@@ -254,15 +258,28 @@ module Savon
|
|
254
258
|
|
255
259
|
# the mail.body.encoded algorithm reorders the parts, default order is [ "text/plain", "text/enriched", "text/html" ]
|
256
260
|
# should redefine the sort order, because the soap request xml should be the first
|
257
|
-
multipart_message.body.set_sort_order [
|
261
|
+
multipart_message.body.set_sort_order ['application/xop+xml', 'text/xml']
|
258
262
|
|
259
263
|
multipart_message.body.encoded(multipart_message.content_transfer_encoding)
|
260
264
|
end
|
261
265
|
|
262
266
|
def init_multipart_message(message_xml)
|
263
267
|
multipart_message = Mail.new
|
268
|
+
|
269
|
+
# MTOM differs from general SOAP attachments:
|
270
|
+
# 1. binary encoding
|
271
|
+
# 2. application/xop+xml mime type
|
272
|
+
if @locals[:mtom]
|
273
|
+
type = "application/xop+xml; charset=#{@globals[:encoding]}; type=\"text/xml\""
|
274
|
+
|
275
|
+
multipart_message.transport_encoding = 'binary'
|
276
|
+
message_xml.force_encoding('BINARY')
|
277
|
+
else
|
278
|
+
type = 'text/xml'
|
279
|
+
end
|
280
|
+
|
264
281
|
xml_part = Mail::Part.new do
|
265
|
-
content_type
|
282
|
+
content_type type
|
266
283
|
body message_xml
|
267
284
|
# in Content-Type the start parameter is recommended (RFC 2387)
|
268
285
|
content_id '<soap-request-body@soap>'
|
data/lib/savon/http_error.rb
CHANGED
@@ -4,7 +4,7 @@ module Savon
|
|
4
4
|
class HTTPError < Error
|
5
5
|
|
6
6
|
def self.present?(http)
|
7
|
-
http.
|
7
|
+
!http.success?
|
8
8
|
end
|
9
9
|
|
10
10
|
def initialize(http)
|
@@ -14,13 +14,13 @@ module Savon
|
|
14
14
|
attr_reader :http
|
15
15
|
|
16
16
|
def to_s
|
17
|
-
String.new("HTTP error (#{@http.
|
17
|
+
String.new("HTTP error (#{@http.status})").tap do |str_error|
|
18
18
|
str_error << ": #{@http.body}" unless @http.body.empty?
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
def to_hash
|
23
|
-
{ :code => @http.
|
23
|
+
{ :code => @http.status, :headers => @http.headers, :body => @http.body }
|
24
24
|
end
|
25
25
|
|
26
26
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require "
|
2
|
+
require "faraday"
|
3
3
|
|
4
4
|
module Savon
|
5
5
|
class MockExpectation
|
@@ -41,8 +41,8 @@ module Savon
|
|
41
41
|
unless @response
|
42
42
|
raise ExpectationError, "This expectation was not set up with a response."
|
43
43
|
end
|
44
|
-
|
45
|
-
|
44
|
+
env = Faraday::Env.from(status: @response[:code], response_headers: @response[:headers], response_body: @response[:body])
|
45
|
+
Faraday::Response.new(env)
|
46
46
|
end
|
47
47
|
|
48
48
|
private
|
@@ -75,7 +75,7 @@ module Savon
|
|
75
75
|
next if (expected_value == :any && msg_real.include?(key))
|
76
76
|
return false if expected_value != msg_real[key]
|
77
77
|
end
|
78
|
-
|
78
|
+
true
|
79
79
|
end
|
80
80
|
end
|
81
81
|
end
|
data/lib/savon/operation.rb
CHANGED
@@ -7,6 +7,8 @@ require "savon/response"
|
|
7
7
|
require "savon/request_logger"
|
8
8
|
require "savon/http_error"
|
9
9
|
require "mail"
|
10
|
+
require 'faraday/gzip'
|
11
|
+
|
10
12
|
|
11
13
|
module Savon
|
12
14
|
class Operation
|
@@ -15,6 +17,7 @@ module Savon
|
|
15
17
|
1 => "text/xml",
|
16
18
|
2 => "application/soap+xml"
|
17
19
|
}
|
20
|
+
SOAP_REQUEST_TYPE_MTOM = "application/xop+xml"
|
18
21
|
|
19
22
|
def self.create(operation_name, wsdl, globals)
|
20
23
|
if wsdl.document?
|
@@ -58,16 +61,20 @@ module Savon
|
|
58
61
|
builder = build(locals, &block)
|
59
62
|
|
60
63
|
response = Savon.notify_observers(@name, builder, @globals, @locals)
|
61
|
-
response ||= call_with_logging
|
64
|
+
response ||= call_with_logging build_connection(builder)
|
62
65
|
|
63
|
-
|
66
|
+
raise_expected_faraday_response! unless response.kind_of?(Faraday::Response)
|
64
67
|
|
65
68
|
create_response(response)
|
66
69
|
end
|
67
70
|
|
68
71
|
def request(locals = {}, &block)
|
69
72
|
builder = build(locals, &block)
|
70
|
-
|
73
|
+
connection = build_connection(builder)
|
74
|
+
connection.build_request(:post) do |req|
|
75
|
+
req.url(@globals[:endpoint])
|
76
|
+
req.body = @locals[:body]
|
77
|
+
end
|
71
78
|
end
|
72
79
|
|
73
80
|
private
|
@@ -83,37 +90,50 @@ module Savon
|
|
83
90
|
@locals = locals
|
84
91
|
end
|
85
92
|
|
86
|
-
def call_with_logging(
|
87
|
-
|
93
|
+
def call_with_logging(connection)
|
94
|
+
ntlm_auth = handle_ntlm(connection) if @globals.include?(:ntlm)
|
95
|
+
@logger.log_response(connection.post(@globals[:endpoint]) { |request|
|
96
|
+
request.body = @locals[:body]
|
97
|
+
request.headers['Authorization'] = "NTLM #{auth.encode64}" if ntlm_auth
|
98
|
+
@logger.log_request(request)
|
99
|
+
})
|
88
100
|
end
|
89
101
|
|
90
|
-
def
|
91
|
-
|
92
|
-
@globals[:endpoint]
|
102
|
+
def handle_ntlm(connection)
|
103
|
+
ntlm_message = Net::NTLM::Message
|
104
|
+
response = connection.get(@globals[:endpoint]) do |request|
|
105
|
+
request.headers['Authorization'] = 'NTLM ' + ntlm_message::Type1.new.encode64
|
106
|
+
end
|
107
|
+
challenge = response.headers['www-authenticate'][/(?:NTLM|Negotiate) (.*)$/, 1]
|
108
|
+
message = ntlm_message::Type2.decode64(challenge)
|
109
|
+
message.response([:user, :password, :domain].zip(@globals[:ntlm]).to_h)
|
110
|
+
end
|
93
111
|
|
94
|
-
|
112
|
+
def build_connection(builder)
|
113
|
+
@globals[:endpoint] ||= endpoint
|
114
|
+
@locals[:soap_action] ||= soap_action
|
115
|
+
@locals[:body] = builder.to_s
|
116
|
+
@connection = SOAPRequest.new(@globals).build(
|
95
117
|
:soap_action => soap_action,
|
96
118
|
:cookies => @locals[:cookies],
|
97
119
|
:headers => @locals[:headers]
|
98
|
-
)
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
# TODO: could HTTPI do this automatically in case the header
|
113
|
-
# was not specified manually? [dh, 2013-01-04]
|
114
|
-
request.headers["Content-Length"] = request.body.bytesize.to_s
|
120
|
+
) do |connection|
|
121
|
+
if builder.multipart
|
122
|
+
ctype_headers = ["multipart/related"]
|
123
|
+
if @locals[:mtom]
|
124
|
+
ctype_headers << "type=\"#{SOAP_REQUEST_TYPE_MTOM}\""
|
125
|
+
ctype_headers << "start-info=\"text/xml\""
|
126
|
+
else
|
127
|
+
ctype_headers << "type=\"#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}\""
|
128
|
+
connection.request :gzip
|
129
|
+
end
|
130
|
+
connection.headers["Content-Type"] = (ctype_headers + ["start=\"#{builder.multipart[:start]}\"",
|
131
|
+
"boundary=\"#{builder.multipart[:multipart_boundary]}\""]).join("; ")
|
132
|
+
connection.headers["MIME-Version"] = "1.0"
|
133
|
+
end
|
115
134
|
|
116
|
-
|
135
|
+
connection.headers["Content-Length"] = @locals[:body].bytesize.to_s
|
136
|
+
end
|
117
137
|
end
|
118
138
|
|
119
139
|
def soap_action
|
@@ -138,8 +158,8 @@ module Savon
|
|
138
158
|
end
|
139
159
|
end
|
140
160
|
|
141
|
-
def
|
142
|
-
raise Error, "Observers need to return
|
161
|
+
def raise_expected_faraday_response!
|
162
|
+
raise Error, "Observers need to return a Faraday::Response to mock " \
|
143
163
|
"the request or nil to execute the request."
|
144
164
|
end
|
145
165
|
|
data/lib/savon/options.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "logger"
|
3
|
-
require "httpi"
|
4
3
|
|
5
4
|
module Savon
|
6
5
|
class Options
|
@@ -10,6 +9,10 @@ module Savon
|
|
10
9
|
assign options
|
11
10
|
end
|
12
11
|
|
12
|
+
def deprecate(option)
|
13
|
+
raise DeprecatedOptionError.new(option)
|
14
|
+
end
|
15
|
+
|
13
16
|
attr_reader :option_type
|
14
17
|
|
15
18
|
def [](option)
|
@@ -89,6 +92,7 @@ module Savon
|
|
89
92
|
:convert_attributes_to => lambda { |k,v| [k,v] },
|
90
93
|
:multipart => false,
|
91
94
|
:adapter => nil,
|
95
|
+
:middlewares => [],
|
92
96
|
:use_wsa_headers => false,
|
93
97
|
:no_message_tag => false,
|
94
98
|
:follow_redirects => false,
|
@@ -127,7 +131,7 @@ module Savon
|
|
127
131
|
@options[:namespace] = namespace
|
128
132
|
end
|
129
133
|
|
130
|
-
# The namespace
|
134
|
+
# The namespace identifier.
|
131
135
|
def namespace_identifier(identifier)
|
132
136
|
@options[:namespace_identifier] = identifier
|
133
137
|
end
|
@@ -198,13 +202,11 @@ module Savon
|
|
198
202
|
|
199
203
|
# Whether or not to log.
|
200
204
|
def log(log)
|
201
|
-
HTTPI.log = log
|
202
205
|
@options[:log] = log
|
203
206
|
end
|
204
207
|
|
205
208
|
# The logger to use. Defaults to a Savon::Logger instance.
|
206
209
|
def logger(logger)
|
207
|
-
HTTPI.logger = logger
|
208
210
|
@options[:logger] = logger
|
209
211
|
end
|
210
212
|
|
@@ -257,6 +259,7 @@ module Savon
|
|
257
259
|
|
258
260
|
# Sets the cert key file to use.
|
259
261
|
def ssl_cert_key_file(file)
|
262
|
+
deprecate('ssl_cert_key_file')
|
260
263
|
@options[:ssl_cert_key_file] = file
|
261
264
|
end
|
262
265
|
|
@@ -267,11 +270,13 @@ module Savon
|
|
267
270
|
|
268
271
|
# Sets the cert key password to use.
|
269
272
|
def ssl_cert_key_password(password)
|
273
|
+
deprecate('ssl_cert_key_password')
|
270
274
|
@options[:ssl_cert_key_password] = password
|
271
275
|
end
|
272
276
|
|
273
277
|
# Sets the cert file to use.
|
274
278
|
def ssl_cert_file(file)
|
279
|
+
deprecate('ssl_cert_file')
|
275
280
|
@options[:ssl_cert_file] = file
|
276
281
|
end
|
277
282
|
|
@@ -287,6 +292,7 @@ module Savon
|
|
287
292
|
|
288
293
|
# Sets the ca cert to use.
|
289
294
|
def ssl_ca_cert(cert)
|
295
|
+
deprecate('ssl_ca_cert')
|
290
296
|
@options[:ssl_ca_cert] = cert
|
291
297
|
end
|
292
298
|
|
@@ -311,6 +317,7 @@ module Savon
|
|
311
317
|
|
312
318
|
# HTTP digest auth credentials.
|
313
319
|
def digest_auth(*credentials)
|
320
|
+
deprecate('digest_auth')
|
314
321
|
@options[:digest_auth] = credentials.flatten
|
315
322
|
end
|
316
323
|
|
@@ -360,7 +367,7 @@ module Savon
|
|
360
367
|
@options[:multipart] = multipart
|
361
368
|
end
|
362
369
|
|
363
|
-
# Instruct Savon what
|
370
|
+
# Instruct Savon what Faraday adapter it should use instead of default
|
364
371
|
def adapter(adapter)
|
365
372
|
@options[:adapter] = adapter
|
366
373
|
end
|
@@ -378,6 +385,25 @@ module Savon
|
|
378
385
|
def follow_redirects(follow_redirects)
|
379
386
|
@options[:follow_redirects] = follow_redirects
|
380
387
|
end
|
388
|
+
|
389
|
+
# Provide middlewares for Faraday connections.
|
390
|
+
# The argument is an array, with each element being another array
|
391
|
+
# that contains the middleware class and its arguments, in the same way
|
392
|
+
# as a normal call to Faraday::RackBuilder#use
|
393
|
+
#
|
394
|
+
# See https://lostisland.github.io/faraday/#/middleware/index?id=using-middleware
|
395
|
+
# For example:
|
396
|
+
#
|
397
|
+
# client = Savon.client(
|
398
|
+
# middlewares: [
|
399
|
+
# [Faraday::Request::UrlEncoded],
|
400
|
+
# [Faraday::Response::Logger, { bodies: true }],
|
401
|
+
# [Faraday::Adapter::NetHttp]
|
402
|
+
# ]
|
403
|
+
# )
|
404
|
+
def middlewares(middlewares)
|
405
|
+
@options[:middlewares] = middlewares
|
406
|
+
end
|
381
407
|
end
|
382
408
|
|
383
409
|
class LocalOptions < Options
|
@@ -389,7 +415,9 @@ module Savon
|
|
389
415
|
defaults = {
|
390
416
|
:advanced_typecasting => true,
|
391
417
|
:response_parser => :nokogiri,
|
392
|
-
:multipart => false
|
418
|
+
:multipart => false,
|
419
|
+
:body => false,
|
420
|
+
:mtom => false
|
393
421
|
}
|
394
422
|
|
395
423
|
super defaults.merge(options)
|
@@ -397,7 +425,7 @@ module Savon
|
|
397
425
|
|
398
426
|
# The local SOAP header. Expected to be a Hash or respond to #to_s.
|
399
427
|
# Will be merged with the global SOAP header if both are Hashes.
|
400
|
-
# Otherwise the local option will be
|
428
|
+
# Otherwise the local option will be preferred.
|
401
429
|
def soap_header(header)
|
402
430
|
@options[:soap_header] = header
|
403
431
|
end
|
@@ -452,12 +480,21 @@ module Savon
|
|
452
480
|
@options[:attachments] = attachments
|
453
481
|
end
|
454
482
|
|
483
|
+
# Instruct Savon to send attachments using MTOM https://www.w3.org/TR/soap12-mtom/
|
484
|
+
def mtom(mtom)
|
485
|
+
@options[:mtom] = mtom
|
486
|
+
end
|
487
|
+
|
455
488
|
# Value of the SOAPAction HTTP header.
|
456
489
|
def soap_action(soap_action)
|
457
490
|
@options[:soap_action] = soap_action
|
458
491
|
end
|
459
492
|
|
460
|
-
# Cookies to be used for the next request
|
493
|
+
# Cookies to be used for the next request
|
494
|
+
# @param [Hash] cookies cookies associated to nil will be appended as array cookies, if you need a cookie equal to
|
495
|
+
# and empty string, set it to ""
|
496
|
+
# @example cookies({accept: 'application/json', some-cookie: 'foo', "empty-cookie": "", HttpOnly: nil})
|
497
|
+
# # => "accept=application/json; some-cookie=foo; empty-cookie=; HttpOnly"
|
461
498
|
def cookies(cookies)
|
462
499
|
@options[:cookies] = cookies
|
463
500
|
end
|
@@ -477,6 +514,11 @@ module Savon
|
|
477
514
|
@options[:response_parser] = parser
|
478
515
|
end
|
479
516
|
|
517
|
+
# Pass already configured Nori instance.
|
518
|
+
def nori(nori)
|
519
|
+
@options[:nori] = nori
|
520
|
+
end
|
521
|
+
|
480
522
|
# Instruct Savon to create a multipart response if available.
|
481
523
|
def multipart(multipart)
|
482
524
|
@options[:multipart] = multipart
|
@@ -485,5 +527,9 @@ module Savon
|
|
485
527
|
def headers(headers)
|
486
528
|
@options[:headers] = headers
|
487
529
|
end
|
530
|
+
|
531
|
+
def body(body)
|
532
|
+
@options[:body] = body
|
533
|
+
end
|
488
534
|
end
|
489
535
|
end
|
data/lib/savon/request.rb
CHANGED
@@ -1,61 +1,99 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require "
|
2
|
+
require "faraday"
|
3
3
|
|
4
4
|
module Savon
|
5
5
|
class HTTPRequest
|
6
6
|
|
7
|
-
def initialize(globals,
|
7
|
+
def initialize(globals, connection = nil)
|
8
8
|
@globals = globals
|
9
|
-
@
|
10
|
-
end
|
11
|
-
|
12
|
-
def build
|
13
|
-
@http_request
|
9
|
+
@connection = connection || Faraday::Connection.new
|
14
10
|
end
|
15
11
|
|
16
12
|
private
|
17
13
|
|
18
14
|
def configure_proxy
|
19
|
-
|
15
|
+
connection.proxy = @globals[:proxy] if @globals.include? :proxy
|
20
16
|
end
|
21
17
|
|
22
18
|
def configure_timeouts
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
connection.options.open_timeout = @globals[:open_timeout] if @globals.include? :open_timeout
|
20
|
+
connection.options.read_timeout = @globals[:read_timeout] if @globals.include? :read_timeout
|
21
|
+
connection.options.write_timeout = @globals[:write_timeout] if @globals.include? :write_timeout
|
26
22
|
end
|
27
23
|
|
28
24
|
def configure_ssl
|
29
|
-
|
30
|
-
|
31
|
-
|
25
|
+
connection.ssl.verify = @globals[:ssl_verify] if @globals.include? :ssl_verify
|
26
|
+
connection.ssl.ca_file = @globals[:ssl_ca_cert_file] if @globals.include? :ssl_ca_cert_file
|
27
|
+
connection.ssl.verify_hostname = @globals[:verify_hostname] if @globals.include? :verify_hostname
|
28
|
+
connection.ssl.ca_path = @globals[:ssl_ca_cert_path] if @globals.include? :ssl_ca_cert_path
|
29
|
+
connection.ssl.verify_mode = @globals[:ssl_verify_mode] if @globals.include? :ssl_verify_mode
|
30
|
+
connection.ssl.cert_store = @globals[:ssl_cert_store] if @globals.include? :ssl_cert_store
|
31
|
+
connection.ssl.client_cert = @globals[:ssl_cert] if @globals.include? :ssl_cert
|
32
|
+
connection.ssl.client_key = @globals[:ssl_cert_key] if @globals.include? :ssl_cert_key
|
33
|
+
connection.ssl.certificate = @globals[:ssl_certificate] if @globals.include? :ssl_certificate
|
34
|
+
connection.ssl.private_key = @globals[:ssl_private_key] if @globals.include? :ssl_private_key
|
35
|
+
connection.ssl.verify_depth = @globals[:verify_depth] if @globals.include? :verify_depth
|
36
|
+
connection.ssl.version = @globals[:ssl_version] if @globals.include? :ssl_version
|
37
|
+
connection.ssl.min_version = @globals[:ssl_min_version] if @globals.include? :ssl_min_version
|
38
|
+
connection.ssl.max_version = @globals[:ssl_max_version] if @globals.include? :ssl_max_version
|
39
|
+
connection.ssl.ciphers = @globals[:ssl_ciphers] if @globals.include? :ssl_ciphers
|
40
|
+
|
41
|
+
# No Faraday Equivalent out of box, see: https://lostisland.github.io/faraday/#/customization/ssl-options
|
42
|
+
# connection.ssl.cert_file = @globals[:ssl_cert_file] if @globals.include? :ssl_cert_file
|
43
|
+
# connection.ssl.cert_key_file = @globals[:ssl_cert_key_file] if @globals.include? :ssl_cert_key_file
|
44
|
+
# connection.ssl.ca_cert = @globals[:ssl_ca_cert] if @globals.include? :ssl_ca_cert
|
45
|
+
# connection.ssl.cert_key_password = @globals[:ssl_cert_key_password] if @globals.include? :ssl_cert_key_password
|
32
46
|
|
33
|
-
|
34
|
-
@http_request.auth.ssl.ciphers = @globals[:ssl_ciphers] if @globals.include? :ssl_ciphers
|
47
|
+
end
|
35
48
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@http_request.auth.ssl.ca_cert_file = @globals[:ssl_ca_cert_file] if @globals.include? :ssl_ca_cert_file
|
41
|
-
@http_request.auth.ssl.ca_cert_path = @globals[:ssl_ca_cert_path] if @globals.include? :ssl_ca_cert_path
|
42
|
-
@http_request.auth.ssl.ca_cert = @globals[:ssl_ca_cert] if @globals.include? :ssl_ca_cert
|
43
|
-
@http_request.auth.ssl.cert_store = @globals[:ssl_cert_store] if @globals.include? :ssl_cert_store
|
49
|
+
def configure_auth
|
50
|
+
basic_auth if @globals.include?(:basic_auth)
|
51
|
+
ntlm_auth if @globals.include?(:ntlm)
|
52
|
+
end
|
44
53
|
|
45
|
-
|
54
|
+
def basic_auth
|
55
|
+
connection.request(:authorization, :basic, *@globals[:basic_auth])
|
46
56
|
end
|
47
57
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
58
|
+
def ntlm_auth
|
59
|
+
begin
|
60
|
+
require 'rubyntlm'
|
61
|
+
require 'faraday/net_http_persistent'
|
62
|
+
connection.adapter :net_http_persistent, pool_size: 5
|
63
|
+
rescue LoadError
|
64
|
+
raise LoadError, 'Using NTLM Auth requires both `rubyntlm` and `faraday-net_http_persistent` to be installed.'
|
65
|
+
end
|
52
66
|
end
|
53
67
|
|
54
68
|
def configure_redirect_handling
|
55
|
-
if @globals
|
56
|
-
|
69
|
+
if @globals[:follow_redirects]
|
70
|
+
require 'faraday/follow_redirects'
|
71
|
+
connection.response :follow_redirects
|
57
72
|
end
|
58
73
|
end
|
74
|
+
|
75
|
+
def configure_adapter
|
76
|
+
connection.adapter(*@globals[:adapter]) unless @globals[:adapter].nil?
|
77
|
+
end
|
78
|
+
|
79
|
+
def configure_logging
|
80
|
+
connection.response(:logger, @globals[:logger], headers: @globals[:log_headers]) if @globals[:log]
|
81
|
+
end
|
82
|
+
|
83
|
+
def configure_middlewares
|
84
|
+
@globals[:middlewares].each do |middleware_args|
|
85
|
+
connection.use(*middleware_args)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def configure_gzip
|
90
|
+
if connection.headers['Accept-Encoding'] && connection.headers['Accept-Encoding'].include?('gzip')
|
91
|
+
connection.request :gzip
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
attr_reader :connection
|
59
97
|
end
|
60
98
|
|
61
99
|
class WSDLRequest < HTTPRequest
|
@@ -63,18 +101,20 @@ module Savon
|
|
63
101
|
def build
|
64
102
|
configure_proxy
|
65
103
|
configure_timeouts
|
66
|
-
configure_headers
|
67
104
|
configure_ssl
|
68
105
|
configure_auth
|
69
|
-
|
70
|
-
|
71
|
-
|
106
|
+
configure_adapter
|
107
|
+
configure_middlewares
|
108
|
+
configure_logging
|
109
|
+
configure_headers
|
110
|
+
configure_gzip
|
111
|
+
connection
|
72
112
|
end
|
73
113
|
|
74
114
|
private
|
75
115
|
|
76
116
|
def configure_headers
|
77
|
-
|
117
|
+
connection.headers = @globals[:headers] if @globals.include? :headers
|
78
118
|
end
|
79
119
|
end
|
80
120
|
|
@@ -88,26 +128,36 @@ module Savon
|
|
88
128
|
def build(options = {})
|
89
129
|
configure_proxy
|
90
130
|
configure_timeouts
|
91
|
-
configure_headers options[:soap_action], options[:headers]
|
92
|
-
configure_cookies options[:cookies]
|
93
131
|
configure_ssl
|
94
132
|
configure_auth
|
133
|
+
configure_headers(options[:soap_action], options[:headers])
|
134
|
+
configure_cookies(options[:cookies])
|
135
|
+
configure_adapter
|
136
|
+
configure_middlewares
|
137
|
+
configure_logging
|
95
138
|
configure_redirect_handling
|
96
|
-
|
97
|
-
|
139
|
+
configure_gzip
|
140
|
+
yield(connection) if block_given?
|
141
|
+
connection
|
98
142
|
end
|
99
143
|
|
100
144
|
private
|
101
145
|
|
102
146
|
def configure_cookies(cookies)
|
103
|
-
|
147
|
+
connection.headers['Cookie'] = cookies.map do |key, value|
|
148
|
+
if value.nil?
|
149
|
+
key
|
150
|
+
else
|
151
|
+
"#{key}=#{value}"
|
152
|
+
end
|
153
|
+
end.join('; ') if cookies
|
104
154
|
end
|
105
155
|
|
106
156
|
def configure_headers(soap_action, headers)
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
157
|
+
connection.headers = @globals[:headers] if @globals.include? :headers
|
158
|
+
connection.headers.merge!(headers) if headers
|
159
|
+
connection.headers["SOAPAction"] ||= %{"#{soap_action}"} if soap_action
|
160
|
+
connection.headers["Content-Type"] ||= CONTENT_TYPE[@globals[:soap_version]] % @globals[:encoding]
|
111
161
|
end
|
112
162
|
end
|
113
163
|
end
|
data/lib/savon/request_logger.rb
CHANGED
@@ -27,27 +27,30 @@ module Savon
|
|
27
27
|
def log_headers?
|
28
28
|
@globals[:log_headers]
|
29
29
|
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
30
|
def log_request(request)
|
34
|
-
|
31
|
+
return unless log?
|
32
|
+
logger.info { "SOAP request: #{request.path}" }
|
35
33
|
logger.info { headers_to_log(request.headers) } if log_headers?
|
36
34
|
logger.debug { body_to_log(request.body) }
|
37
35
|
end
|
38
36
|
|
39
37
|
def log_response(response)
|
40
|
-
|
38
|
+
return response unless log?
|
39
|
+
logger.info { "SOAP response (status #{response.status})" }
|
41
40
|
logger.debug { headers_to_log(response.headers) } if log_headers?
|
42
41
|
logger.debug { body_to_log(response.body) }
|
42
|
+
response
|
43
43
|
end
|
44
44
|
|
45
|
+
private
|
46
|
+
|
47
|
+
|
45
48
|
def headers_to_log(headers)
|
46
49
|
headers.map { |key, value| "#{key}: #{value}" }.join("\n")
|
47
50
|
end
|
48
51
|
|
49
52
|
def body_to_log(body)
|
50
|
-
LogMessage.new(body, @globals[:filters], @globals[:pretty_print_xml]).to_s
|
53
|
+
LogMessage.new(body, @globals[:filters], @globals[:pretty_print_xml]).to_s.force_encoding(@globals[:encoding])
|
51
54
|
end
|
52
55
|
|
53
56
|
end
|
data/lib/savon/response.rb
CHANGED
@@ -142,20 +142,16 @@ module Savon
|
|
142
142
|
end
|
143
143
|
|
144
144
|
def nori
|
145
|
-
return @nori if @nori
|
145
|
+
return @locals[:nori] if @locals[:nori]
|
146
146
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
non_nil_nori_options = nori_options.reject { |_, value| value.nil? }
|
157
|
-
@nori = Nori.new(non_nil_nori_options)
|
147
|
+
@nori ||= Nori.new({
|
148
|
+
:delete_namespace_attributes => @globals[:delete_namespace_attributes],
|
149
|
+
:strip_namespaces => @globals[:strip_namespaces],
|
150
|
+
:convert_tags_to => @globals[:convert_response_tags_to],
|
151
|
+
:convert_attributes_to => @globals[:convert_attributes_to],
|
152
|
+
:advanced_typecasting => @locals[:advanced_typecasting],
|
153
|
+
:parser => @locals[:response_parser]
|
154
|
+
}.reject { |_, value| value.nil? })
|
158
155
|
end
|
159
|
-
|
160
156
|
end
|
161
157
|
end
|
data/lib/savon/soap_fault.rb
CHANGED
@@ -3,9 +3,16 @@ module Savon
|
|
3
3
|
class SOAPFault < Error
|
4
4
|
|
5
5
|
def self.present?(http, xml = nil)
|
6
|
-
|
6
|
+
xml_orig ||= http.body
|
7
|
+
if xml_orig.valid_encoding?
|
8
|
+
xml = xml_orig
|
9
|
+
else
|
10
|
+
xml = xml_orig.encode(
|
11
|
+
'UTF-8', "ISO-8859-1", invalid: :replace, undef: :replace, replace: ''
|
12
|
+
)
|
13
|
+
end
|
7
14
|
fault_node = xml.include?("Fault>")
|
8
|
-
soap1_fault = xml.match(/faultcode
|
15
|
+
soap1_fault = xml.match(/faultcode\/?>/) && xml.match(/faultstring\/?>/)
|
9
16
|
soap2_fault = xml.include?("Code>") && xml.include?("Reason>")
|
10
17
|
|
11
18
|
fault_node && (soap1_fault || soap2_fault)
|
data/lib/savon/version.rb
CHANGED
data/lib/savon.rb
CHANGED
@@ -7,6 +7,14 @@ module Savon
|
|
7
7
|
UnknownOperationError = Class.new(Error)
|
8
8
|
InvalidResponseError = Class.new(Error)
|
9
9
|
|
10
|
+
class DeprecatedOptionError < Error
|
11
|
+
attr_accessor :option
|
12
|
+
def initialize(option)
|
13
|
+
@option = option
|
14
|
+
super("#{option} is deprecated as it is not supported in Faraday. See https://github.com/savonrb/savon/blob/main/UPGRADING.md for more information.")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
10
18
|
def self.client(globals = {}, &block)
|
11
19
|
Client.new(globals, &block)
|
12
20
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: savon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0.rc2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Harrington
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: nori
|
@@ -25,45 +24,61 @@ dependencies:
|
|
25
24
|
- !ruby/object:Gem::Version
|
26
25
|
version: '2.4'
|
27
26
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
27
|
+
name: faraday
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
30
29
|
requirements:
|
31
|
-
- - "
|
30
|
+
- - "~>"
|
32
31
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
34
|
-
|
32
|
+
version: '2.11'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
35
38
|
- !ruby/object:Gem::Version
|
36
|
-
version: '
|
39
|
+
version: '2.11'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: faraday-gzip
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.0'
|
37
47
|
type: :runtime
|
38
48
|
prerelease: false
|
39
49
|
version_requirements: !ruby/object:Gem::Requirement
|
40
50
|
requirements:
|
41
|
-
- - "
|
51
|
+
- - "~>"
|
42
52
|
- !ruby/object:Gem::Version
|
43
|
-
version: '
|
44
|
-
|
53
|
+
version: '2.0'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: faraday-follow_redirects
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
45
59
|
- !ruby/object:Gem::Version
|
46
|
-
version: '
|
60
|
+
version: '0.3'
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0.3'
|
47
68
|
- !ruby/object:Gem::Dependency
|
48
69
|
name: wasabi
|
49
70
|
requirement: !ruby/object:Gem::Requirement
|
50
71
|
requirements:
|
51
|
-
- - "
|
72
|
+
- - ">"
|
52
73
|
- !ruby/object:Gem::Version
|
53
|
-
version: '
|
54
|
-
- - "<"
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
version: '6'
|
74
|
+
version: '5'
|
57
75
|
type: :runtime
|
58
76
|
prerelease: false
|
59
77
|
version_requirements: !ruby/object:Gem::Requirement
|
60
78
|
requirements:
|
61
|
-
- - "
|
62
|
-
- !ruby/object:Gem::Version
|
63
|
-
version: '3.7'
|
64
|
-
- - "<"
|
79
|
+
- - ">"
|
65
80
|
- !ruby/object:Gem::Version
|
66
|
-
version: '
|
81
|
+
version: '5'
|
67
82
|
- !ruby/object:Gem::Dependency
|
68
83
|
name: akami
|
69
84
|
requirement: !ruby/object:Gem::Requirement
|
@@ -134,6 +149,34 @@ dependencies:
|
|
134
149
|
- - "~>"
|
135
150
|
- !ruby/object:Gem::Version
|
136
151
|
version: '2.5'
|
152
|
+
- !ruby/object:Gem::Dependency
|
153
|
+
name: faraday-net_http_persistent
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - "~>"
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '2.1'
|
159
|
+
type: :development
|
160
|
+
prerelease: false
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - "~>"
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '2.1'
|
166
|
+
- !ruby/object:Gem::Dependency
|
167
|
+
name: rubyntlm
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0.6'
|
173
|
+
type: :development
|
174
|
+
prerelease: false
|
175
|
+
version_requirements: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0.6'
|
137
180
|
- !ruby/object:Gem::Dependency
|
138
181
|
name: rack
|
139
182
|
requirement: !ruby/object:Gem::Requirement
|
@@ -168,6 +211,48 @@ dependencies:
|
|
168
211
|
- - "<"
|
169
212
|
- !ruby/object:Gem::Version
|
170
213
|
version: '7'
|
214
|
+
- !ruby/object:Gem::Dependency
|
215
|
+
name: httpclient
|
216
|
+
requirement: !ruby/object:Gem::Requirement
|
217
|
+
requirements:
|
218
|
+
- - ">="
|
219
|
+
- !ruby/object:Gem::Version
|
220
|
+
version: '0'
|
221
|
+
type: :development
|
222
|
+
prerelease: false
|
223
|
+
version_requirements: !ruby/object:Gem::Requirement
|
224
|
+
requirements:
|
225
|
+
- - ">="
|
226
|
+
- !ruby/object:Gem::Version
|
227
|
+
version: '0'
|
228
|
+
- !ruby/object:Gem::Dependency
|
229
|
+
name: mutex_m
|
230
|
+
requirement: !ruby/object:Gem::Requirement
|
231
|
+
requirements:
|
232
|
+
- - ">="
|
233
|
+
- !ruby/object:Gem::Version
|
234
|
+
version: '0'
|
235
|
+
type: :development
|
236
|
+
prerelease: false
|
237
|
+
version_requirements: !ruby/object:Gem::Requirement
|
238
|
+
requirements:
|
239
|
+
- - ">="
|
240
|
+
- !ruby/object:Gem::Version
|
241
|
+
version: '0'
|
242
|
+
- !ruby/object:Gem::Dependency
|
243
|
+
name: ostruct
|
244
|
+
requirement: !ruby/object:Gem::Requirement
|
245
|
+
requirements:
|
246
|
+
- - "~>"
|
247
|
+
- !ruby/object:Gem::Version
|
248
|
+
version: '0.6'
|
249
|
+
type: :development
|
250
|
+
prerelease: false
|
251
|
+
version_requirements: !ruby/object:Gem::Requirement
|
252
|
+
requirements:
|
253
|
+
- - "~>"
|
254
|
+
- !ruby/object:Gem::Version
|
255
|
+
version: '0.6'
|
171
256
|
- !ruby/object:Gem::Dependency
|
172
257
|
name: byebug
|
173
258
|
requirement: !ruby/object:Gem::Requirement
|
@@ -274,7 +359,6 @@ licenses:
|
|
274
359
|
- MIT
|
275
360
|
metadata:
|
276
361
|
rubygems_mfa_required: 'true'
|
277
|
-
post_install_message:
|
278
362
|
rdoc_options: []
|
279
363
|
require_paths:
|
280
364
|
- lib
|
@@ -289,8 +373,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
289
373
|
- !ruby/object:Gem::Version
|
290
374
|
version: '0'
|
291
375
|
requirements: []
|
292
|
-
rubygems_version: 3.
|
293
|
-
signing_key:
|
376
|
+
rubygems_version: 3.6.7
|
294
377
|
specification_version: 4
|
295
378
|
summary: Heavy metal SOAP client
|
296
379
|
test_files: []
|