savon 2.17.3 → 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 -99
- 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 +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 +82 -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 +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 -69
- 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,66 +1,54 @@
|
|
|
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
|
-
ensure_exists! operation_name, wsdl
|
|
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
|
-
|
|
43
|
-
def self.ensure_exists!(operation_name, wsdl, transport)
|
|
30
|
+
def self.ensure_exists!(operation_name, wsdl)
|
|
44
31
|
unless wsdl.soap_actions.include? operation_name
|
|
45
32
|
raise UnknownOperationError, "Unable to find SOAP operation: #{operation_name.inspect}\n" \
|
|
46
33
|
"Operations provided by your service: #{wsdl.soap_actions.inspect}"
|
|
47
34
|
end
|
|
48
35
|
rescue Wasabi::Resolver::HTTPError => e
|
|
49
|
-
raise HTTPError
|
|
36
|
+
raise HTTPError.new(e.response)
|
|
50
37
|
end
|
|
51
38
|
|
|
52
39
|
def self.ensure_name_is_symbol!(operation_name)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
57
44
|
end
|
|
58
45
|
|
|
59
|
-
def initialize(name, wsdl, globals
|
|
60
|
-
@name
|
|
61
|
-
@wsdl
|
|
62
|
-
@globals
|
|
63
|
-
|
|
46
|
+
def initialize(name, wsdl, globals)
|
|
47
|
+
@name = name
|
|
48
|
+
@wsdl = wsdl
|
|
49
|
+
@globals = globals
|
|
50
|
+
|
|
51
|
+
@logger = RequestLogger.new(globals)
|
|
64
52
|
end
|
|
65
53
|
|
|
66
54
|
def build(locals = {}, &block)
|
|
@@ -68,45 +56,32 @@ module Savon
|
|
|
68
56
|
Builder.new(@name, @wsdl, @globals, @locals)
|
|
69
57
|
end
|
|
70
58
|
|
|
71
|
-
# Executes the SOAP operation and returns a Savon::Response.
|
|
72
|
-
#
|
|
73
|
-
# Observer short-circuit: if any registered observer returns a
|
|
74
|
-
# Transport::Response (or legacy HTTPI::Response), the HTTP call
|
|
75
|
-
# is skipped and that response is used directly.
|
|
76
59
|
def call(locals = {}, &block)
|
|
77
60
|
builder = build(locals, &block)
|
|
78
|
-
observer_response = Savon.notify_observers(@name, builder, @globals, @locals)
|
|
79
|
-
|
|
80
|
-
transport_response =
|
|
81
|
-
if observer_response.nil?
|
|
82
|
-
body = builder.to_s
|
|
83
|
-
headers = soap_headers(builder)
|
|
84
|
-
@transport.post(endpoint.to_s, headers, body, @locals)
|
|
85
|
-
else
|
|
86
|
-
normalize_observer_response(observer_response)
|
|
87
|
-
end
|
|
88
61
|
|
|
89
|
-
|
|
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)
|
|
90
68
|
end
|
|
91
69
|
|
|
92
|
-
# Builds and returns the HTTPI::Request that would be sent for this
|
|
93
|
-
# operation, without executing it. Useful for inspection and debugging.
|
|
94
|
-
# Not supported with transport: :faraday.
|
|
95
70
|
def request(locals = {}, &block)
|
|
96
|
-
if @globals[:transport] == :faraday
|
|
97
|
-
raise ArgumentError, "#request returns an HTTPI::Request and is not supported " \
|
|
98
|
-
"with transport: :faraday. Use client.faraday to configure " \
|
|
99
|
-
"the connection"
|
|
100
|
-
end
|
|
101
|
-
|
|
102
71
|
builder = build(locals, &block)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
72
|
+
connection = build_connection(builder)
|
|
73
|
+
connection.build_request(:post) do |req|
|
|
74
|
+
req.url(@globals[:endpoint])
|
|
75
|
+
req.body = @locals[:body]
|
|
76
|
+
end
|
|
106
77
|
end
|
|
107
78
|
|
|
108
79
|
private
|
|
109
80
|
|
|
81
|
+
def create_response(response)
|
|
82
|
+
Response.new(response, @globals, @locals)
|
|
83
|
+
end
|
|
84
|
+
|
|
110
85
|
def set_locals(locals, block)
|
|
111
86
|
locals = LocalOptions.new(locals)
|
|
112
87
|
BlockInterface.new(locals).evaluate(block) if block
|
|
@@ -114,33 +89,47 @@ module Savon
|
|
|
114
89
|
@locals = locals
|
|
115
90
|
end
|
|
116
91
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
138
130
|
end
|
|
139
131
|
|
|
140
|
-
action = soap_action
|
|
141
|
-
headers["SOAPAction"] = %("#{action}") if action
|
|
142
132
|
|
|
143
|
-
headers
|
|
144
133
|
end
|
|
145
134
|
|
|
146
135
|
def soap_action
|
|
@@ -149,10 +138,10 @@ module Savon
|
|
|
149
138
|
|
|
150
139
|
# get the soap_action from local options
|
|
151
140
|
@locals[:soap_action] ||
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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])
|
|
156
145
|
end
|
|
157
146
|
|
|
158
147
|
def endpoint
|
|
@@ -165,21 +154,10 @@ module Savon
|
|
|
165
154
|
end
|
|
166
155
|
end
|
|
167
156
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
# HTTPI::Response with a deprecation warning (legacy observer support),
|
|
172
|
-
# and raises on anything else.
|
|
173
|
-
def normalize_observer_response(observer_response)
|
|
174
|
-
return observer_response if observer_response.is_a?(Transport::Response)
|
|
175
|
-
|
|
176
|
-
if observer_response.is_a?(HTTPI::Response)
|
|
177
|
-
warn "Observers returning HTTPI::Response is deprecated - return Savon::Transport::Response instead.", uplevel: 1
|
|
178
|
-
return Transport::Response.from_httpi(observer_response)
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
raise Error, "Observers need to return a Savon::Transport::Response " \
|
|
182
|
-
"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."
|
|
183
160
|
end
|
|
161
|
+
|
|
184
162
|
end
|
|
185
163
|
end
|