savon 2.17.2 → 3.0.0.rc1
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 +17 -91
- data/README.md +52 -52
- data/Rakefile +2 -6
- data/lib/savon/block_interface.rb +4 -3
- data/lib/savon/builder.rb +33 -34
- data/lib/savon/client.rb +13 -44
- data/lib/savon/header.rb +12 -12
- data/lib/savon/http_error.rb +5 -3
- data/lib/savon/log_message.rb +3 -2
- data/lib/savon/message.rb +7 -6
- data/lib/savon/mock/expectation.rb +24 -38
- data/lib/savon/mock/spec_helper.rb +11 -7
- data/lib/savon/mock.rb +0 -1
- data/lib/savon/model.rb +25 -25
- data/lib/savon/operation.rb +74 -104
- data/lib/savon/options.rb +175 -276
- data/lib/savon/qualified_message.rb +1 -2
- data/lib/savon/request.rb +147 -0
- data/lib/savon/request_logger.rb +57 -0
- data/lib/savon/response.rb +23 -33
- data/lib/savon/soap_fault.rb +6 -6
- data/lib/savon/string_utils.rb +4 -3
- data/lib/savon/version.rb +1 -2
- data/lib/savon.rb +11 -2
- metadata +130 -69
- data/lib/savon/faraday_migration_hint.rb +0 -186
- data/lib/savon/transport/faraday.rb +0 -98
- data/lib/savon/transport/httpi.rb +0 -135
- data/lib/savon/transport/logging.rb +0 -60
- data/lib/savon/transport/response.rb +0 -44
data/lib/savon/header.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
require "akami"
|
|
4
3
|
require "gyoku"
|
|
5
4
|
require "securerandom"
|
|
6
5
|
|
|
7
6
|
module Savon
|
|
8
7
|
class Header
|
|
8
|
+
|
|
9
9
|
def initialize(globals, locals)
|
|
10
|
-
@gyoku_options = { key_converter
|
|
10
|
+
@gyoku_options = { :key_converter => globals[:convert_request_keys_to] }
|
|
11
11
|
|
|
12
12
|
@wsse_auth = locals[:wsse_auth].nil? ? globals[:wsse_auth] : locals[:wsse_auth]
|
|
13
13
|
@wsse_timestamp = locals[:wsse_timestamp].nil? ? globals[:wsse_timestamp] : locals[:wsse_timestamp]
|
|
@@ -41,7 +41,7 @@ module Savon
|
|
|
41
41
|
|
|
42
42
|
def build_header
|
|
43
43
|
header =
|
|
44
|
-
if global_header.
|
|
44
|
+
if global_header.kind_of?(Hash) && local_header.kind_of?(Hash)
|
|
45
45
|
global_header.merge(local_header)
|
|
46
46
|
elsif local_header
|
|
47
47
|
local_header
|
|
@@ -58,17 +58,16 @@ module Savon
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def build_wsa_header
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
})
|
|
61
|
+
return '' unless @globals[:use_wsa_headers]
|
|
62
|
+
convert_to_xml({
|
|
63
|
+
'wsa:Action' => @locals[:soap_action],
|
|
64
|
+
'wsa:To' => @globals[:endpoint],
|
|
65
|
+
'wsa:MessageID' => "urn:uuid:#{SecureRandom.uuid}"
|
|
66
|
+
})
|
|
68
67
|
end
|
|
69
68
|
|
|
70
69
|
def convert_to_xml(hash_or_string)
|
|
71
|
-
if hash_or_string.
|
|
70
|
+
if hash_or_string.kind_of? Hash
|
|
72
71
|
Gyoku.xml(hash_or_string, gyoku_options)
|
|
73
72
|
else
|
|
74
73
|
hash_or_string.to_s
|
|
@@ -79,11 +78,12 @@ module Savon
|
|
|
79
78
|
wsse = Akami.wsse
|
|
80
79
|
wsse.credentials(*wsse_auth) if wsse_auth
|
|
81
80
|
wsse.timestamp = wsse_timestamp if wsse_timestamp
|
|
82
|
-
if wsse_signature
|
|
81
|
+
if wsse_signature && wsse_signature.have_document?
|
|
83
82
|
wsse.signature = wsse_signature
|
|
84
83
|
end
|
|
85
84
|
|
|
86
85
|
wsse
|
|
87
86
|
end
|
|
87
|
+
|
|
88
88
|
end
|
|
89
89
|
end
|
data/lib/savon/http_error.rb
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Savon
|
|
4
4
|
class HTTPError < Error
|
|
5
|
+
|
|
5
6
|
def self.present?(http)
|
|
6
|
-
http.
|
|
7
|
+
!http.success?
|
|
7
8
|
end
|
|
8
9
|
|
|
9
10
|
def initialize(http)
|
|
@@ -13,13 +14,14 @@ module Savon
|
|
|
13
14
|
attr_reader :http
|
|
14
15
|
|
|
15
16
|
def to_s
|
|
16
|
-
String.new("HTTP error (#{@http.
|
|
17
|
+
String.new("HTTP error (#{@http.status})").tap do |str_error|
|
|
17
18
|
str_error << ": #{@http.body}" unless @http.body.empty?
|
|
18
19
|
end
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def to_hash
|
|
22
|
-
{ code
|
|
23
|
+
{ :code => @http.status, :headers => @http.headers, :body => @http.body }
|
|
23
24
|
end
|
|
25
|
+
|
|
24
26
|
end
|
|
25
27
|
end
|
data/lib/savon/log_message.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
require "nokogiri"
|
|
4
3
|
|
|
5
4
|
module Savon
|
|
6
5
|
class LogMessage
|
|
6
|
+
|
|
7
7
|
def initialize(message, filters = [], pretty_print = false)
|
|
8
8
|
@message = message
|
|
9
9
|
@filters = filters
|
|
@@ -46,7 +46,8 @@ module Savon
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def nokogiri_options
|
|
49
|
-
@pretty_print ? { indent
|
|
49
|
+
@pretty_print ? { :indent => 2 } : { :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML }
|
|
50
50
|
end
|
|
51
|
+
|
|
51
52
|
end
|
|
52
53
|
end
|
data/lib/savon/message.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
require "savon/qualified_message"
|
|
4
3
|
require "gyoku"
|
|
5
4
|
|
|
6
5
|
module Savon
|
|
7
6
|
class Message
|
|
7
|
+
|
|
8
8
|
def initialize(message_tag, namespace_identifier, types, used_namespaces, message, element_form_default, key_converter, unwrap)
|
|
9
9
|
@message_tag = message_tag
|
|
10
10
|
@namespace_identifier = namespace_identifier
|
|
@@ -18,20 +18,21 @@ module Savon
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def to_s
|
|
21
|
-
return @message.to_s unless @message.
|
|
21
|
+
return @message.to_s unless @message.kind_of? Hash
|
|
22
22
|
|
|
23
23
|
if @element_form_default == :qualified
|
|
24
24
|
@message = QualifiedMessage.new(@types, @used_namespaces, @key_converter).to_hash(@message, [@message_tag.to_s])
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
gyoku_options = {
|
|
28
|
-
element_form_default
|
|
29
|
-
namespace
|
|
30
|
-
key_converter
|
|
31
|
-
unwrap
|
|
28
|
+
:element_form_default => @element_form_default,
|
|
29
|
+
:namespace => @namespace_identifier,
|
|
30
|
+
:key_converter => @key_converter,
|
|
31
|
+
:unwrap => @unwrap
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
Gyoku.xml(@message, gyoku_options)
|
|
35
35
|
end
|
|
36
|
+
|
|
36
37
|
end
|
|
37
38
|
end
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "httpi"
|
|
4
|
-
require "savon/transport/response"
|
|
2
|
+
require "faraday"
|
|
5
3
|
|
|
6
4
|
module Savon
|
|
7
|
-
# A single test expectation set up by Savon's mock interface.
|
|
8
|
-
# One expectation covers one operation call in one test.
|
|
9
|
-
#
|
|
10
|
-
# Records the expected operation name and message, captures what was
|
|
11
|
-
# actually called, and either returns a synthetic response or raises
|
|
12
|
-
# an error on mismatch.
|
|
13
5
|
class MockExpectation
|
|
6
|
+
|
|
14
7
|
def initialize(operation_name)
|
|
15
|
-
@expected = { operation_name
|
|
8
|
+
@expected = { :operation_name => operation_name }
|
|
16
9
|
@actual = nil
|
|
17
10
|
end
|
|
18
11
|
|
|
@@ -22,15 +15,15 @@ module Savon
|
|
|
22
15
|
end
|
|
23
16
|
|
|
24
17
|
def returns(response)
|
|
25
|
-
response = { code
|
|
18
|
+
response = { :code => 200, :headers => {}, :body => response } if response.kind_of?(String)
|
|
26
19
|
@response = response
|
|
27
20
|
self
|
|
28
21
|
end
|
|
29
22
|
|
|
30
|
-
def actual(operation_name,
|
|
23
|
+
def actual(operation_name, builder, globals, locals)
|
|
31
24
|
@actual = {
|
|
32
|
-
operation_name
|
|
33
|
-
message
|
|
25
|
+
:operation_name => operation_name,
|
|
26
|
+
:message => locals[:message]
|
|
34
27
|
}
|
|
35
28
|
end
|
|
36
29
|
|
|
@@ -44,49 +37,42 @@ module Savon
|
|
|
44
37
|
verify_message!
|
|
45
38
|
end
|
|
46
39
|
|
|
47
|
-
# Builds and returns a Transport::Response from the configured response hash.
|
|
48
|
-
#
|
|
49
|
-
# @return [Transport::Response]
|
|
50
|
-
# @raise [ExpectationError] if no response was configured for this expectation
|
|
51
40
|
def response!
|
|
52
41
|
unless @response
|
|
53
42
|
raise ExpectationError, "This expectation was not set up with a response."
|
|
54
43
|
end
|
|
55
|
-
|
|
56
|
-
|
|
44
|
+
env = Faraday::Env.from(status: @response[:code], response_headers: @response[:headers], response_body: @response[:body])
|
|
45
|
+
Faraday::Response.new(env)
|
|
57
46
|
end
|
|
58
47
|
|
|
59
48
|
private
|
|
60
49
|
|
|
61
50
|
def verify_operation_name!
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
unless @expected[:operation_name] == @actual[:operation_name]
|
|
52
|
+
raise ExpectationError, "Expected a request to the #{@expected[:operation_name].inspect} operation.\n" \
|
|
53
|
+
"Received a request to the #{@actual[:operation_name].inspect} operation instead."
|
|
54
|
+
end
|
|
66
55
|
end
|
|
67
56
|
|
|
68
57
|
def verify_message!
|
|
69
58
|
return if @expected[:message].eql? :any
|
|
59
|
+
unless equals_except_any(@expected[:message], @actual[:message])
|
|
60
|
+
expected_message = " with this message: #{@expected[:message].inspect}" if @expected[:message]
|
|
61
|
+
expected_message ||= " with no message."
|
|
70
62
|
|
|
71
|
-
|
|
63
|
+
actual_message = " with this message: #{@actual[:message].inspect}" if @actual[:message]
|
|
64
|
+
actual_message ||= " with no message."
|
|
72
65
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
actual_message = " with this message: #{@actual[:message].inspect}" if @actual[:message]
|
|
77
|
-
actual_message ||= " with no message."
|
|
78
|
-
|
|
79
|
-
raise ExpectationError, "Expected a request to the #{@expected[:operation_name].inspect} operation\n#{expected_message}\n" \
|
|
80
|
-
"Received a request to the #{@actual[:operation_name].inspect} operation\n#{actual_message}"
|
|
66
|
+
raise ExpectationError, "Expected a request to the #{@expected[:operation_name].inspect} operation\n#{expected_message}\n" \
|
|
67
|
+
"Received a request to the #{@actual[:operation_name].inspect} operation\n#{actual_message}"
|
|
68
|
+
end
|
|
81
69
|
end
|
|
82
70
|
|
|
83
71
|
def equals_except_any(msg_expected, msg_real)
|
|
84
|
-
|
|
85
|
-
return
|
|
86
|
-
return false if msg_expected.nil? || msg_real.nil? # If both are nil has returned true
|
|
87
|
-
|
|
72
|
+
return true if msg_expected === msg_real
|
|
73
|
+
return false if (msg_expected.nil? || msg_real.nil?) # If both are nil has returned true
|
|
88
74
|
msg_expected.each do |key, expected_value|
|
|
89
|
-
next if expected_value == :any &&
|
|
75
|
+
next if (expected_value == :any && msg_real.include?(key))
|
|
90
76
|
return false if expected_value != msg_real[key]
|
|
91
77
|
end
|
|
92
78
|
true
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
require "savon/mock"
|
|
4
3
|
|
|
5
4
|
module Savon
|
|
6
5
|
module SpecHelper
|
|
6
|
+
|
|
7
7
|
class Interface
|
|
8
|
+
|
|
8
9
|
def mock!
|
|
9
10
|
Savon.observers << self
|
|
10
11
|
end
|
|
@@ -26,12 +27,14 @@ module Savon
|
|
|
26
27
|
def notify(operation_name, builder, globals, locals)
|
|
27
28
|
expectation = expectations.shift
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
expectation.actual(operation_name, builder, globals, locals)
|
|
30
|
+
if expectation
|
|
31
|
+
expectation.actual(operation_name, builder, globals, locals)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
expectation.verify!
|
|
34
|
+
expectation.response!
|
|
35
|
+
else
|
|
36
|
+
raise ExpectationError, "Unexpected request to the #{operation_name.inspect} operation."
|
|
37
|
+
end
|
|
35
38
|
rescue ExpectationError
|
|
36
39
|
@expectations.clear
|
|
37
40
|
raise
|
|
@@ -39,12 +42,12 @@ module Savon
|
|
|
39
42
|
|
|
40
43
|
def verify!
|
|
41
44
|
return if expectations.empty?
|
|
42
|
-
|
|
43
45
|
expectations.each(&:verify!)
|
|
44
46
|
rescue ExpectationError
|
|
45
47
|
@expectations.clear
|
|
46
48
|
raise
|
|
47
49
|
end
|
|
50
|
+
|
|
48
51
|
end
|
|
49
52
|
|
|
50
53
|
def savon
|
|
@@ -55,5 +58,6 @@ module Savon
|
|
|
55
58
|
super if defined? super
|
|
56
59
|
savon.verify!
|
|
57
60
|
end
|
|
61
|
+
|
|
58
62
|
end
|
|
59
63
|
end
|
data/lib/savon/mock.rb
CHANGED
data/lib/savon/model.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
module Savon
|
|
4
3
|
module Model
|
|
4
|
+
|
|
5
5
|
def self.extended(base)
|
|
6
6
|
base.setup
|
|
7
7
|
end
|
|
@@ -28,30 +28,26 @@ module Savon
|
|
|
28
28
|
|
|
29
29
|
# Defines a class-level SOAP operation.
|
|
30
30
|
def define_class_operation(operation)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
class_operation_module.module_eval %{
|
|
32
|
+
def #{StringUtils.snakecase(operation.to_s)}(locals = {})
|
|
33
|
+
client.call #{operation.inspect}, locals
|
|
34
|
+
end
|
|
35
|
+
}
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# Defines an instance-level SOAP operation.
|
|
39
39
|
def define_instance_operation(operation)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Returns the generated Ruby method name for a SOAP operation.
|
|
48
|
-
def operation_method_name(operation)
|
|
49
|
-
StringUtils.snakecase(operation.to_s).to_sym
|
|
40
|
+
instance_operation_module.module_eval %{
|
|
41
|
+
def #{StringUtils.snakecase(operation.to_s)}(locals = {})
|
|
42
|
+
self.class.#{StringUtils.snakecase(operation.to_s)} locals
|
|
43
|
+
end
|
|
44
|
+
}
|
|
50
45
|
end
|
|
51
46
|
|
|
52
47
|
# Class methods.
|
|
53
48
|
def class_operation_module
|
|
54
|
-
@class_operation_module ||= Module.new
|
|
49
|
+
@class_operation_module ||= Module.new {
|
|
50
|
+
|
|
55
51
|
def client(globals = {})
|
|
56
52
|
@client ||= Savon::Client.new(globals)
|
|
57
53
|
rescue InitializationError
|
|
@@ -64,22 +60,26 @@ module Savon
|
|
|
64
60
|
|
|
65
61
|
def raise_initialization_error!
|
|
66
62
|
raise InitializationError,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
"Expected the model to be initialized with either a WSDL document or the SOAP endpoint and target namespace options.\n" \
|
|
64
|
+
"Make sure to setup the model by calling the .client class method before calling the .global method.\n\n" \
|
|
65
|
+
"client(wsdl: '/Users/me/project/service.wsdl') # to use a local WSDL document\n" \
|
|
66
|
+
"client(wsdl: 'http://example.com?wsdl') # to use a remote WSDL document\n" \
|
|
67
|
+
"client(endpoint: 'http://example.com', namespace: 'http://v1.example.com') # if you don't have a WSDL document"
|
|
72
68
|
end
|
|
73
|
-
|
|
69
|
+
|
|
70
|
+
}.tap { |mod| extend(mod) }
|
|
74
71
|
end
|
|
75
72
|
|
|
76
73
|
# Instance methods.
|
|
77
74
|
def instance_operation_module
|
|
78
|
-
@instance_operation_module ||= Module.new
|
|
75
|
+
@instance_operation_module ||= Module.new {
|
|
76
|
+
|
|
79
77
|
def client
|
|
80
78
|
self.class.client
|
|
81
79
|
end
|
|
82
|
-
|
|
80
|
+
|
|
81
|
+
}.tap { |mod| include(mod) }
|
|
83
82
|
end
|
|
83
|
+
|
|
84
84
|
end
|
|
85
85
|
end
|
data/lib/savon/operation.rb
CHANGED
|
@@ -1,42 +1,30 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
require "savon/options"
|
|
4
3
|
require "savon/block_interface"
|
|
4
|
+
require "savon/request"
|
|
5
5
|
require "savon/builder"
|
|
6
6
|
require "savon/response"
|
|
7
|
+
require "savon/request_logger"
|
|
7
8
|
require "savon/http_error"
|
|
8
|
-
require "savon/transport/httpi"
|
|
9
|
-
require "savon/transport/faraday"
|
|
10
9
|
require "mail"
|
|
10
|
+
require 'faraday/gzip'
|
|
11
|
+
|
|
11
12
|
|
|
12
13
|
module Savon
|
|
13
|
-
# Represents a single named SOAP operation.
|
|
14
|
-
#
|
|
15
|
-
# Bridges the SOAP layer (envelope building, action headers, multipart) and the
|
|
16
|
-
# transport layer (execution, logging). Knows nothing about transport internals
|
|
17
|
-
# such as proxy, SSL, or auth.
|
|
18
14
|
class Operation
|
|
19
|
-
|
|
20
|
-
# SOAP 1.1 §6 (HTTP binding), SOAP 1.2 Part 2 §7.1.4 (HTTP media type)
|
|
21
|
-
CONTENT_TYPE = {
|
|
22
|
-
1 => "text/xml;charset=%s",
|
|
23
|
-
2 => "application/soap+xml;charset=%s"
|
|
24
|
-
}.freeze
|
|
25
|
-
|
|
26
|
-
# Maps SOAP version to the base MIME type used in multipart requests.
|
|
27
|
-
# RFC 2387 §3.1 (multipart/related Content-Type parameter)
|
|
15
|
+
|
|
28
16
|
SOAP_REQUEST_TYPE = {
|
|
29
17
|
1 => "text/xml",
|
|
30
18
|
2 => "application/soap+xml"
|
|
31
|
-
}
|
|
19
|
+
}
|
|
32
20
|
|
|
33
|
-
def self.create(operation_name, wsdl, globals
|
|
21
|
+
def self.create(operation_name, wsdl, globals)
|
|
34
22
|
if wsdl.document?
|
|
35
23
|
ensure_name_is_symbol! operation_name
|
|
36
24
|
ensure_exists! operation_name, wsdl
|
|
37
25
|
end
|
|
38
26
|
|
|
39
|
-
new(operation_name, wsdl, globals
|
|
27
|
+
new(operation_name, wsdl, globals)
|
|
40
28
|
end
|
|
41
29
|
|
|
42
30
|
def self.ensure_exists!(operation_name, wsdl)
|
|
@@ -45,21 +33,22 @@ module Savon
|
|
|
45
33
|
"Operations provided by your service: #{wsdl.soap_actions.inspect}"
|
|
46
34
|
end
|
|
47
35
|
rescue Wasabi::Resolver::HTTPError => e
|
|
48
|
-
raise HTTPError
|
|
36
|
+
raise HTTPError.new(e.response)
|
|
49
37
|
end
|
|
50
38
|
|
|
51
39
|
def self.ensure_name_is_symbol!(operation_name)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
40
|
+
unless operation_name.kind_of? Symbol
|
|
41
|
+
raise ArgumentError, "Expected the first parameter (the name of the operation to call) to be a symbol\n" \
|
|
42
|
+
"Actual: #{operation_name.inspect} (#{operation_name.class})"
|
|
43
|
+
end
|
|
56
44
|
end
|
|
57
45
|
|
|
58
|
-
def initialize(name, wsdl, globals
|
|
59
|
-
@name
|
|
60
|
-
@wsdl
|
|
61
|
-
@globals
|
|
62
|
-
|
|
46
|
+
def initialize(name, wsdl, globals)
|
|
47
|
+
@name = name
|
|
48
|
+
@wsdl = wsdl
|
|
49
|
+
@globals = globals
|
|
50
|
+
|
|
51
|
+
@logger = RequestLogger.new(globals)
|
|
63
52
|
end
|
|
64
53
|
|
|
65
54
|
def build(locals = {}, &block)
|
|
@@ -67,41 +56,24 @@ module Savon
|
|
|
67
56
|
Builder.new(@name, @wsdl, @globals, @locals)
|
|
68
57
|
end
|
|
69
58
|
|
|
70
|
-
# Executes the SOAP operation and returns a Savon::Response.
|
|
71
|
-
#
|
|
72
|
-
# Observer short-circuit: if any registered observer returns a
|
|
73
|
-
# Transport::Response (or legacy HTTPI::Response), the HTTP call
|
|
74
|
-
# is skipped and that response is used directly.
|
|
75
59
|
def call(locals = {}, &block)
|
|
76
|
-
builder
|
|
60
|
+
builder = build(locals, &block)
|
|
61
|
+
|
|
77
62
|
response = Savon.notify_observers(@name, builder, @globals, @locals)
|
|
63
|
+
response ||= call_with_logging build_connection(builder)
|
|
78
64
|
|
|
79
|
-
response
|
|
80
|
-
if response.nil?
|
|
81
|
-
body = builder.to_s
|
|
82
|
-
headers = soap_headers(builder)
|
|
83
|
-
@transport.post(endpoint.to_s, headers, body, @locals)
|
|
84
|
-
else
|
|
85
|
-
normalize_observer_response(response)
|
|
86
|
-
end
|
|
65
|
+
raise_expected_faraday_response! unless response.kind_of?(Faraday::Response)
|
|
87
66
|
|
|
88
67
|
create_response(response)
|
|
89
68
|
end
|
|
90
69
|
|
|
91
|
-
# Builds and returns the HTTPI::Request that would be sent for this
|
|
92
|
-
# operation, without executing it. Useful for inspection and debugging.
|
|
93
|
-
# Not supported with transport: :faraday.
|
|
94
70
|
def request(locals = {}, &block)
|
|
95
|
-
if @globals[:transport] == :faraday
|
|
96
|
-
raise ArgumentError, "#request returns an HTTPI::Request and is not supported " \
|
|
97
|
-
"with transport: :faraday. Use client.faraday to configure " \
|
|
98
|
-
"the connection"
|
|
99
|
-
end
|
|
100
|
-
|
|
101
71
|
builder = build(locals, &block)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
72
|
+
connection = build_connection(builder)
|
|
73
|
+
connection.build_request(:post) do |req|
|
|
74
|
+
req.url(@globals[:endpoint])
|
|
75
|
+
req.body = @locals[:body]
|
|
76
|
+
end
|
|
105
77
|
end
|
|
106
78
|
|
|
107
79
|
private
|
|
@@ -117,33 +89,47 @@ module Savon
|
|
|
117
89
|
@locals = locals
|
|
118
90
|
end
|
|
119
91
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
92
|
+
def call_with_logging(connection)
|
|
93
|
+
ntlm_auth = handle_ntlm(connection) if @globals.include?(:ntlm)
|
|
94
|
+
@logger.log_response(connection.post(@globals[:endpoint]) { |request|
|
|
95
|
+
request.body = @locals[:body]
|
|
96
|
+
request.headers['Authorization'] = "NTLM #{auth.encode64}" if ntlm_auth
|
|
97
|
+
@logger.log_request(request)
|
|
98
|
+
})
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def handle_ntlm(connection)
|
|
102
|
+
ntlm_message = Net::NTLM::Message
|
|
103
|
+
response = connection.get(@globals[:endpoint]) do |request|
|
|
104
|
+
request.headers['Authorization'] = 'NTLM ' + ntlm_message::Type1.new.encode64
|
|
105
|
+
end
|
|
106
|
+
challenge = response.headers['www-authenticate'][/(?:NTLM|Negotiate) (.*)$/, 1]
|
|
107
|
+
message = ntlm_message::Type2.decode64(challenge)
|
|
108
|
+
message.response([:user, :password, :domain].zip(@globals[:ntlm]).to_h)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def build_connection(builder)
|
|
112
|
+
@globals[:endpoint] ||= endpoint
|
|
113
|
+
@locals[:soap_action] ||= soap_action
|
|
114
|
+
@locals[:body] = builder.to_s
|
|
115
|
+
@connection = SOAPRequest.new(@globals).build(
|
|
116
|
+
:soap_action => soap_action,
|
|
117
|
+
:cookies => @locals[:cookies],
|
|
118
|
+
:headers => @locals[:headers]
|
|
119
|
+
) do |connection|
|
|
120
|
+
if builder.multipart
|
|
121
|
+
connection.request :gzip
|
|
122
|
+
connection.headers["Content-Type"] = %W[multipart/related
|
|
123
|
+
type="#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}",
|
|
124
|
+
start="#{builder.multipart[:start]}",
|
|
125
|
+
boundary="#{builder.multipart[:multipart_boundary]}"].join("; ")
|
|
126
|
+
connection.headers["MIME-Version"] = "1.0"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
connection.headers["Content-Length"] = @locals[:body].bytesize.to_s
|
|
141
130
|
end
|
|
142
131
|
|
|
143
|
-
action = soap_action
|
|
144
|
-
headers["SOAPAction"] = %("#{action}") if action
|
|
145
132
|
|
|
146
|
-
headers
|
|
147
133
|
end
|
|
148
134
|
|
|
149
135
|
def soap_action
|
|
@@ -152,10 +138,10 @@ module Savon
|
|
|
152
138
|
|
|
153
139
|
# get the soap_action from local options
|
|
154
140
|
@locals[:soap_action] ||
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
141
|
+
# with no local option, but a wsdl, ask it for the soap_action
|
|
142
|
+
@wsdl.document? && @wsdl.soap_action(@name.to_sym) ||
|
|
143
|
+
# if there is no soap_action up to this point, fallback to a simple default
|
|
144
|
+
Gyoku.xml_tag(@name, :key_converter => @globals[:convert_request_keys_to])
|
|
159
145
|
end
|
|
160
146
|
|
|
161
147
|
def endpoint
|
|
@@ -168,26 +154,10 @@ module Savon
|
|
|
168
154
|
end
|
|
169
155
|
end
|
|
170
156
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
# HTTPI::Response with a deprecation warning (legacy observer support),
|
|
175
|
-
# and raises on anything else.
|
|
176
|
-
def normalize_observer_response(response)
|
|
177
|
-
return response if response.is_a?(Transport::Response)
|
|
178
|
-
|
|
179
|
-
if response.is_a?(HTTPI::Response)
|
|
180
|
-
warn "Observers returning HTTPI::Response is deprecated - return Savon::Transport::Response instead.", uplevel: 1
|
|
181
|
-
return Transport::Response.new(
|
|
182
|
-
response.code,
|
|
183
|
-
response.headers,
|
|
184
|
-
response.body,
|
|
185
|
-
cookies: HTTPI::Cookie.list_from_headers(response.headers)
|
|
186
|
-
)
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
raise Error, "Observers need to return a Savon::Transport::Response " \
|
|
190
|
-
"to mock the request or nil to execute the request."
|
|
157
|
+
def raise_expected_faraday_response!
|
|
158
|
+
raise Error, "Observers need to return a Faraday::Response to mock " \
|
|
159
|
+
"the request or nil to execute the request."
|
|
191
160
|
end
|
|
161
|
+
|
|
192
162
|
end
|
|
193
163
|
end
|