Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faraday #998

Merged
merged 41 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
9ba2f5f
replace httpi with faraday, pull in rubyntlm since it'll be needed fo…
LukeIGS Jan 4, 2024
1473978
remove httpi mock adapter implementations, since faraday has its own …
LukeIGS Jan 4, 2024
7fd858e
create a helper for mocking out faraday responses
LukeIGS Jan 4, 2024
e6f0823
remove hashes from in front of descriptions because it breaks rubymin…
LukeIGS Jan 4, 2024
4309008
mock expectations use faraday responses now
LukeIGS Jan 4, 2024
da1c429
use faraday approaches to determining error stats
LukeIGS Jan 4, 2024
7fc03ff
more faraday field renames
LukeIGS Jan 4, 2024
017711d
use faraday for observer specs
LukeIGS Jan 4, 2024
e024205
remove direct reliance on an http client in options; add an error for…
LukeIGS Jan 4, 2024
69e68bf
update response_spec to use faraday logic
LukeIGS Jan 4, 2024
92ee244
update the integration test to use an existent service and faraday logic
LukeIGS Jan 4, 2024
118125c
more httpi removal
LukeIGS Jan 4, 2024
6f2d6aa
rip out httpi; pass the connection around and use faraday post since …
LukeIGS Jan 4, 2024
b70b003
update dependency version constraints so CI errors out for a more cle…
LukeIGS Jan 5, 2024
e9192ef
uncomment a spec that i accidentally left commented
LukeIGS Jan 5, 2024
3b6a87f
fix ssl version spec, remove specs for functionality unsupported by f…
LukeIGS Jan 5, 2024
2799f0f
remove infinite recursion comment since it was an error in the spec
LukeIGS Jan 5, 2024
4cebdf8
use from initializer for env for 2.7 compat
LukeIGS Jan 8, 2024
c5d8720
refactor to use the Responses helper to mock faraday responses
LukeIGS Jan 9, 2024
2d57a45
extraction refactor
LukeIGS Jan 9, 2024
7776c4e
standardize parenthesis use a bit more
LukeIGS Jan 9, 2024
a10ec73
extract connection to a protected attribute
LukeIGS Jan 9, 2024
fead8e7
fix a typeo
LukeIGS Jan 9, 2024
3bc87c7
mixed janitorial work
LukeIGS Jan 9, 2024
298f163
Merge branch 'master' into faraday
pcai Feb 13, 2024
6562ecf
change cookies to make more sense, document new usage
LukeIGS Jul 8, 2024
1482387
also document empty string case
LukeIGS Jul 8, 2024
5875622
Merge branch 'faraday' of github.com:LukeIGS/savon into faraday
LukeIGS Jul 8, 2024
636e2dd
Merge branch 'main' of github.com:savonrb/savon into faraday
LukeIGS Jul 8, 2024
335adeb
use the correct wasabi version
LukeIGS Jul 8, 2024
a381d7a
follow rack and remove support for digest auth
LukeIGS Jul 8, 2024
6b336a2
more digest removals, update the changelog
LukeIGS Jul 8, 2024
3f1ecbf
forgot to deprecate ssl cert key files
LukeIGS Jul 8, 2024
a19bb89
add httpclient as a development dep so we can run our tests on 3.4
LukeIGS Jul 8, 2024
04eeea6
add mutex_m as a development dep so we can run our tests on 3.4, sinc…
LukeIGS Jul 8, 2024
d35cf7b
Merge branch 'main' into faraday
pcai Jul 8, 2024
4fb9fed
don't use reflection to find the calling method for deprecate since t…
LukeIGS Jul 9, 2024
c726d38
Merge branch 'faraday' of github.com:LukeIGS/savon into faraday
LukeIGS Jul 9, 2024
892f1fb
returns the faraday response
LukeIGS Jul 9, 2024
959b618
less magic, re-enable open timeout test
LukeIGS Jul 9, 2024
a70a1e6
less magic, re-enable open timeout test
LukeIGS Jul 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

## Unreleased
* Ruby 3.0+ is required in the gemspec.
* Changes to utilize faraday instead of http
* BC BREAKING Cookies are handled differently now
* BC BREAKING Multiple pieces of functionality will rely on faraday libraries to be provided by the consuming codebase
* BC BREAKING Adapter overrides now utilize the faraday model
* BC BREAKING Multiple hard deprecations due to a lack of feature parity between Faraday and HTTPI
* Deprecates digest auth
* Deprecates ssl_cert_key_file auth, upgrade path is to read the key
in and provide it
* Deprecates encrypted ssl keys, upgrade path is to
decrypt the key and pass it to faraday in code
* Deprecates providing a ca cert, upgrade path is to provide a ca cert file
* deprecates overriding ssl ciphers, as faraday does not support this
* Add your PR changelog line here

## 2.15.0 (2024-02-10)
Expand Down
8 changes: 8 additions & 0 deletions lib/savon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ module Savon
UnknownOperationError = Class.new(Error)
InvalidResponseError = Class.new(Error)

class DeprecatedOptionError < Error
attr_accessor :option
def initialize(option)
@option = option
super("#{option} is deprecated as it is not supported in Faraday")
end
end

def self.client(globals = {}, &block)
Client.new(globals, &block)
end
Expand Down
6 changes: 3 additions & 3 deletions lib/savon/http_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Savon
class HTTPError < Error

def self.present?(http)
http.error?
!http.success?
end

def initialize(http)
Expand All @@ -14,13 +14,13 @@ def initialize(http)
attr_reader :http

def to_s
String.new("HTTP error (#{@http.code})").tap do |str_error|
String.new("HTTP error (#{@http.status})").tap do |str_error|
str_error << ": #{@http.body}" unless @http.body.empty?
end
end

def to_hash
{ :code => @http.code, :headers => @http.headers, :body => @http.body }
{ :code => @http.status, :headers => @http.headers, :body => @http.body }
end

end
Expand Down
8 changes: 4 additions & 4 deletions lib/savon/mock/expectation.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true
require "httpi"
require "faraday"

module Savon
class MockExpectation
Expand Down Expand Up @@ -41,8 +41,8 @@ def response!
unless @response
raise ExpectationError, "This expectation was not set up with a response."
end

HTTPI::Response.new(@response[:code], @response[:headers], @response[:body])
env = Faraday::Env.from(status: @response[:code], response_headers: @response[:headers], response_body: @response[:body])
Faraday::Response.new(env)
end

private
Expand Down Expand Up @@ -75,7 +75,7 @@ def equals_except_any(msg_expected, msg_real)
next if (expected_value == :any && msg_real.include?(key))
return false if expected_value != msg_real[key]
end
return true
true
end
end
end
70 changes: 43 additions & 27 deletions lib/savon/operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
require "savon/request_logger"
require "savon/http_error"
require "mail"
require 'faraday/gzip'


module Savon
class Operation
Expand Down Expand Up @@ -58,16 +60,20 @@ def call(locals = {}, &block)
builder = build(locals, &block)

response = Savon.notify_observers(@name, builder, @globals, @locals)
response ||= call_with_logging build_request(builder)
response ||= call_with_logging build_connection(builder)

raise_expected_httpi_response! unless response.kind_of?(HTTPI::Response)
raise_expected_faraday_response! unless response.kind_of?(Faraday::Response)

create_response(response)
end

def request(locals = {}, &block)
builder = build(locals, &block)
build_request(builder)
connection = build_connection(builder)
connection.build_request(:post) do |req|
req.url(@globals[:endpoint])
req.body = @locals[:body]
end
end

private
Expand All @@ -83,37 +89,47 @@ def set_locals(locals, block)
@locals = locals
end

def call_with_logging(request)
@logger.log(request) { HTTPI.post(request, @globals[:adapter]) }
def call_with_logging(connection)
ntlm_auth = handle_ntlm(connection) if @globals.include?(:ntlm)
@logger.log_response(connection.post(@globals[:endpoint]) { |request|
request.body = @locals[:body]
request.headers['Authorization'] = "NTLM #{auth.encode64}" if ntlm_auth
@logger.log_request(request)
})
end

def build_request(builder)
@locals[:soap_action] ||= soap_action
@globals[:endpoint] ||= endpoint
def handle_ntlm(connection)
ntlm_message = Net::NTLM::Message
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do this?

response = connection.get(@globals[:endpoint]) do |request|
request.headers['Authorization'] = 'NTLM ' + ntlm_message::Type1.new.encode64
end
challenge = response.headers['www-authenticate'][/(?:NTLM|Negotiate) (.*)$/, 1]
message = ntlm_message::Type2.decode64(challenge)
message.response([:user, :password, :domain].zip(@globals[:ntlm]).to_h)
end

request = SOAPRequest.new(@globals).build(
def build_connection(builder)
@globals[:endpoint] ||= endpoint
@locals[:soap_action] ||= soap_action
@locals[:body] = builder.to_s
@connection = SOAPRequest.new(@globals).build(
:soap_action => soap_action,
:cookies => @locals[:cookies],
:headers => @locals[:headers]
)

request.url = endpoint
request.body = builder.to_s

if builder.multipart
request.gzip
request.headers["Content-Type"] = ["multipart/related",
"type=\"#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}\"",
"start=\"#{builder.multipart[:start]}\"",
"boundary=\"#{builder.multipart[:multipart_boundary]}\""].join("; ")
request.headers["MIME-Version"] = "1.0"
) do |connection|
if builder.multipart
connection.request :gzip
connection.headers["Content-Type"] = %W[multipart/related
type="#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}",
start="#{builder.multipart[:start]}",
boundary="#{builder.multipart[:multipart_boundary]}"].join("; ")
connection.headers["MIME-Version"] = "1.0"
end

connection.headers["Content-Length"] = @locals[:body].bytesize.to_s
end

# TODO: could HTTPI do this automatically in case the header
# was not specified manually? [dh, 2013-01-04]
request.headers["Content-Length"] = request.body.bytesize.to_s

request
end

def soap_action
Expand All @@ -138,8 +154,8 @@ def endpoint
end
end

def raise_expected_httpi_response!
raise Error, "Observers need to return an HTTPI::Response to mock " \
def raise_expected_faraday_response!
raise Error, "Observers need to return an Faraday::Response to mock " \
"the request or nil to execute the request."
end

Expand Down
31 changes: 24 additions & 7 deletions lib/savon/options.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# frozen_string_literal: true
require "logger"
require "httpi"

module Savon
class Options
Expand All @@ -10,6 +9,11 @@ def initialize(options = {})
assign options
end

def deprecate
option = caller_locations[0].label
pcai marked this conversation as resolved.
Show resolved Hide resolved
raise DeprecatedOptionError.new(option)
end

attr_reader :option_type

def [](option)
Expand Down Expand Up @@ -127,7 +131,7 @@ def namespace(namespace)
@options[:namespace] = namespace
end

# The namespace identifer.
# The namespace identifier.
def namespace_identifier(identifier)
@options[:namespace_identifier] = identifier
end
Expand Down Expand Up @@ -198,13 +202,11 @@ def raise_errors(raise_errors)

# Whether or not to log.
def log(log)
HTTPI.log = log
@options[:log] = log
end

# The logger to use. Defaults to a Savon::Logger instance.
def logger(logger)
HTTPI.logger = logger
@options[:logger] = logger
end

Expand Down Expand Up @@ -257,6 +259,7 @@ def ssl_verify_mode(verify_mode)

# Sets the cert key file to use.
def ssl_cert_key_file(file)
deprecate
@options[:ssl_cert_key_file] = file
end

Expand All @@ -267,11 +270,13 @@ def ssl_cert_key(key)

# Sets the cert key password to use.
def ssl_cert_key_password(password)
deprecate
@options[:ssl_cert_key_password] = password
end

# Sets the cert file to use.
def ssl_cert_file(file)
deprecate
@options[:ssl_cert_file] = file
end

Expand All @@ -287,10 +292,12 @@ def ssl_ca_cert_file(file)

# Sets the ca cert to use.
def ssl_ca_cert(cert)
deprecate
@options[:ssl_ca_cert] = cert
end

def ssl_ciphers(ciphers)
deprecate
@options[:ssl_ciphers] = ciphers
end

Expand All @@ -311,6 +318,7 @@ def basic_auth(*credentials)

# HTTP digest auth credentials.
def digest_auth(*credentials)
deprecate
@options[:digest_auth] = credentials.flatten
end

Expand Down Expand Up @@ -389,15 +397,16 @@ def initialize(options = {})
defaults = {
:advanced_typecasting => true,
:response_parser => :nokogiri,
:multipart => false
:multipart => false,
:body => false
}

super defaults.merge(options)
end

# The local SOAP header. Expected to be a Hash or respond to #to_s.
# Will be merged with the global SOAP header if both are Hashes.
# Otherwise the local option will be prefered.
# Otherwise the local option will be preferred.
def soap_header(header)
@options[:soap_header] = header
end
Expand Down Expand Up @@ -457,7 +466,11 @@ def soap_action(soap_action)
@options[:soap_action] = soap_action
end

# Cookies to be used for the next request.
# Cookies to be used for the next request
# @param [Hash] cookies cookies associated to nil will be appended as array cookies, if you need a cookie equal to
# and empty string, set it to ""
# @example cookies({accept: 'application/json', some-cookie: 'foo', "empty-cookie": "", HttpOnly: nil})
# # => "accept=application/json; some-cookie=foo; empty-cookie=; HttpOnly"
def cookies(cookies)
@options[:cookies] = cookies
end
Expand Down Expand Up @@ -485,5 +498,9 @@ def multipart(multipart)
def headers(headers)
@options[:headers] = headers
end

def body(body)
@options[:body] = body
end
end
end
Loading
Loading