savon 2.17.4 → 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 -115
- data/README.md +52 -52
- data/Rakefile +2 -6
- data/lib/savon/block_interface.rb +4 -3
- data/lib/savon/builder.rb +37 -60
- data/lib/savon/client.rb +13 -44
- data/lib/savon/header.rb +40 -87
- data/lib/savon/http_error.rb +9 -9
- 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 +92 -112
- 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 +31 -45
- 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 -71
- data/lib/savon/addressing.rb +0 -60
- data/lib/savon/effective_options.rb +0 -124
- data/lib/savon/faraday_migration_hint.rb +0 -186
- data/lib/savon/transport/faraday.rb +0 -101
- data/lib/savon/transport/httpi.rb +0 -138
- data/lib/savon/transport/logging.rb +0 -60
- data/lib/savon/transport/response.rb +0 -73
data/lib/savon/operation.rb
CHANGED
|
@@ -1,67 +1,54 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
require "savon/options"
|
|
4
|
-
require "savon/effective_options"
|
|
5
3
|
require "savon/block_interface"
|
|
4
|
+
require "savon/request"
|
|
6
5
|
require "savon/builder"
|
|
7
6
|
require "savon/response"
|
|
7
|
+
require "savon/request_logger"
|
|
8
8
|
require "savon/http_error"
|
|
9
|
-
require "savon/transport/httpi"
|
|
10
|
-
require "savon/transport/faraday"
|
|
11
9
|
require "mail"
|
|
10
|
+
require 'faraday/gzip'
|
|
11
|
+
|
|
12
12
|
|
|
13
13
|
module Savon
|
|
14
|
-
# Represents a single named SOAP operation.
|
|
15
|
-
#
|
|
16
|
-
# Bridges the SOAP layer (envelope building, action headers, multipart) and the
|
|
17
|
-
# transport layer (execution, logging). Knows nothing about transport internals
|
|
18
|
-
# such as proxy, SSL, or auth.
|
|
19
14
|
class Operation
|
|
20
|
-
|
|
21
|
-
# SOAP 1.1 §6 (HTTP binding), SOAP 1.2 Part 2 §7.1.4 (HTTP media type)
|
|
22
|
-
CONTENT_TYPE = {
|
|
23
|
-
1 => "text/xml;charset=%s",
|
|
24
|
-
2 => "application/soap+xml;charset=%s"
|
|
25
|
-
}.freeze
|
|
26
|
-
|
|
27
|
-
# Maps SOAP version to the base MIME type used in multipart requests.
|
|
28
|
-
# RFC 2387 §3.1 (multipart/related Content-Type parameter)
|
|
15
|
+
|
|
29
16
|
SOAP_REQUEST_TYPE = {
|
|
30
17
|
1 => "text/xml",
|
|
31
18
|
2 => "application/soap+xml"
|
|
32
|
-
}
|
|
19
|
+
}
|
|
33
20
|
|
|
34
|
-
def self.create(operation_name, wsdl, globals
|
|
21
|
+
def self.create(operation_name, wsdl, globals)
|
|
35
22
|
if wsdl.document?
|
|
36
23
|
ensure_name_is_symbol! operation_name
|
|
37
|
-
ensure_exists! operation_name, wsdl
|
|
24
|
+
ensure_exists! operation_name, wsdl
|
|
38
25
|
end
|
|
39
26
|
|
|
40
|
-
new(operation_name, wsdl, globals
|
|
27
|
+
new(operation_name, wsdl, globals)
|
|
41
28
|
end
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
def self.ensure_exists!(operation_name, wsdl, transport)
|
|
30
|
+
def self.ensure_exists!(operation_name, wsdl)
|
|
45
31
|
unless wsdl.soap_actions.include? operation_name
|
|
46
32
|
raise UnknownOperationError, "Unable to find SOAP operation: #{operation_name.inspect}\n" \
|
|
47
33
|
"Operations provided by your service: #{wsdl.soap_actions.inspect}"
|
|
48
34
|
end
|
|
49
35
|
rescue Wasabi::Resolver::HTTPError => e
|
|
50
|
-
raise HTTPError
|
|
36
|
+
raise HTTPError.new(e.response)
|
|
51
37
|
end
|
|
52
38
|
|
|
53
39
|
def self.ensure_name_is_symbol!(operation_name)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
58
44
|
end
|
|
59
45
|
|
|
60
|
-
def initialize(name, wsdl, globals
|
|
61
|
-
@name
|
|
62
|
-
@wsdl
|
|
63
|
-
@globals
|
|
64
|
-
|
|
46
|
+
def initialize(name, wsdl, globals)
|
|
47
|
+
@name = name
|
|
48
|
+
@wsdl = wsdl
|
|
49
|
+
@globals = globals
|
|
50
|
+
|
|
51
|
+
@logger = RequestLogger.new(globals)
|
|
65
52
|
end
|
|
66
53
|
|
|
67
54
|
def build(locals = {}, &block)
|
|
@@ -69,45 +56,32 @@ module Savon
|
|
|
69
56
|
Builder.new(@name, @wsdl, @globals, @locals)
|
|
70
57
|
end
|
|
71
58
|
|
|
72
|
-
# Executes the SOAP operation and returns a Savon::Response.
|
|
73
|
-
#
|
|
74
|
-
# Observer short-circuit: if any registered observer returns a
|
|
75
|
-
# Transport::Response (or legacy HTTPI::Response), the HTTP call
|
|
76
|
-
# is skipped and that response is used directly.
|
|
77
59
|
def call(locals = {}, &block)
|
|
78
60
|
builder = build(locals, &block)
|
|
79
|
-
observer_response = Savon.notify_observers(@name, builder, @globals, @locals)
|
|
80
|
-
|
|
81
|
-
transport_response =
|
|
82
|
-
if observer_response.nil?
|
|
83
|
-
body = builder.to_s
|
|
84
|
-
headers = soap_headers(builder)
|
|
85
|
-
@transport.post(endpoint.to_s, headers, body, @locals)
|
|
86
|
-
else
|
|
87
|
-
normalize_observer_response(observer_response)
|
|
88
|
-
end
|
|
89
61
|
|
|
90
|
-
|
|
62
|
+
response = Savon.notify_observers(@name, builder, @globals, @locals)
|
|
63
|
+
response ||= call_with_logging build_connection(builder)
|
|
64
|
+
|
|
65
|
+
raise_expected_faraday_response! unless response.kind_of?(Faraday::Response)
|
|
66
|
+
|
|
67
|
+
create_response(response)
|
|
91
68
|
end
|
|
92
69
|
|
|
93
|
-
# Builds and returns the HTTPI::Request that would be sent for this
|
|
94
|
-
# operation, without executing it. Useful for inspection and debugging.
|
|
95
|
-
# Not supported with transport: :faraday.
|
|
96
70
|
def request(locals = {}, &block)
|
|
97
|
-
if @globals[:transport] == :faraday
|
|
98
|
-
raise ArgumentError, "#request returns an HTTPI::Request and is not supported " \
|
|
99
|
-
"with transport: :faraday. Use client.faraday to configure " \
|
|
100
|
-
"the connection"
|
|
101
|
-
end
|
|
102
|
-
|
|
103
71
|
builder = build(locals, &block)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
72
|
+
connection = build_connection(builder)
|
|
73
|
+
connection.build_request(:post) do |req|
|
|
74
|
+
req.url(@globals[:endpoint])
|
|
75
|
+
req.body = @locals[:body]
|
|
76
|
+
end
|
|
107
77
|
end
|
|
108
78
|
|
|
109
79
|
private
|
|
110
80
|
|
|
81
|
+
def create_response(response)
|
|
82
|
+
Response.new(response, @globals, @locals)
|
|
83
|
+
end
|
|
84
|
+
|
|
111
85
|
def set_locals(locals, block)
|
|
112
86
|
locals = LocalOptions.new(locals)
|
|
113
87
|
BlockInterface.new(locals).evaluate(block) if block
|
|
@@ -115,69 +89,75 @@ module Savon
|
|
|
115
89
|
@locals = locals
|
|
116
90
|
end
|
|
117
91
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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)
|
|
123
109
|
end
|
|
124
110
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
"
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
headers["
|
|
144
|
-
else
|
|
145
|
-
headers["Content-Type"] = CONTENT_TYPE[@globals[:soap_version]] % @globals[:encoding]
|
|
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
|
|
146
130
|
end
|
|
147
131
|
|
|
148
|
-
action = soap_action
|
|
149
|
-
headers["SOAPAction"] = %("#{action}") if action
|
|
150
132
|
|
|
151
|
-
headers
|
|
152
133
|
end
|
|
153
134
|
|
|
154
|
-
# The resolved SOAPAction of the request, used for the HTTP header.
|
|
155
|
-
# {Savon::EffectiveOptions#soap_action} owns the precedence rules.
|
|
156
135
|
def soap_action
|
|
157
|
-
|
|
136
|
+
# soap_action explicitly set to something falsy
|
|
137
|
+
return if @locals.include?(:soap_action) && !@locals[:soap_action]
|
|
138
|
+
|
|
139
|
+
# get the soap_action from local options
|
|
140
|
+
@locals[:soap_action] ||
|
|
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])
|
|
158
145
|
end
|
|
159
146
|
|
|
160
|
-
# The resolved endpoint URL the request is sent to.
|
|
161
|
-
# {Savon::EffectiveOptions#endpoint} owns the precedence rules.
|
|
162
147
|
def endpoint
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
# HTTPI::Response with a deprecation warning (legacy observer support),
|
|
170
|
-
# and raises on anything else.
|
|
171
|
-
def normalize_observer_response(observer_response)
|
|
172
|
-
return observer_response if observer_response.is_a?(Transport::Response)
|
|
173
|
-
|
|
174
|
-
if observer_response.is_a?(HTTPI::Response)
|
|
175
|
-
warn "Observers returning HTTPI::Response is deprecated - return Savon::Transport::Response instead.", uplevel: 1
|
|
176
|
-
return Transport::Response.from_httpi(observer_response)
|
|
148
|
+
@globals[:endpoint] || @wsdl.endpoint.tap do |url|
|
|
149
|
+
if @globals[:host]
|
|
150
|
+
host_url = URI.parse(@globals[:host])
|
|
151
|
+
url.host = host_url.host
|
|
152
|
+
url.port = host_url.port
|
|
153
|
+
end
|
|
177
154
|
end
|
|
155
|
+
end
|
|
178
156
|
|
|
179
|
-
|
|
180
|
-
|
|
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."
|
|
181
160
|
end
|
|
161
|
+
|
|
182
162
|
end
|
|
183
163
|
end
|