lib/rforce/binding.rb in rforce-0.11 vs lib/rforce/binding.rb in rforce-0.12
- old
+ new
@@ -42,103 +42,91 @@
AssignmentRuleHeaderUsingRuleId = '<partner:AssignmentRuleHeader soap:mustUnderstand="1"><partner:assignmentRuleId>%s</partner:assignmentRuleId></partner:AssignmentRuleHeader>'
AssignmentRuleHeaderUsingDefaultRule = '<partner:AssignmentRuleHeader soap:mustUnderstand="1"><partner:useDefaultRule>true</partner:useDefaultRule></partner:AssignmentRuleHeader>'
MruHeader = '<partner:MruHeader soap:mustUnderstand="1"><partner:updateMru>true</partner:updateMru></partner:MruHeader>'
ClientIdHeader = '<partner:CallOptions soap:mustUnderstand="1"><partner:client>%s</partner:client></partner:CallOptions>'
- # Connect to the server securely. If you pass an oauth hash, it
- # must contain the keys :consumer_key, :consumer_secret,
+ # Create a binding to the server (after which you can call login
+ # or login_with_oauth to connect to it). If you pass an oauth
+ # hash, it must contain the keys :consumer_key, :consumer_secret,
# :access_token, :access_secret, and :login_url.
#
# proxy may be a URL of the form http://user:pass@example.com:port
#
- def initialize(url, sid = nil, oauth = nil, proxy = nil)
+ # if a logger is specified, it will be used for very verbose SOAP logging
+ #
+ def initialize(url, sid = nil, oauth = nil, proxy = nil, logger = nil)
@session_id = sid
@oauth = oauth
@proxy = proxy
@batch_size = DEFAULT_BATCH_SIZE
-
- init_server(url)
+ @logger = logger
+ @url = URI.parse(url)
end
-
def show_debug
ENV['SHOWSOAP'] == 'true'
end
+ def create_server(url)
+ server = Net::HTTP.Proxy(@proxy).new(url.host, url.port)
+ server.use_ssl = (url.scheme == 'https')
+ server.verify_mode = OpenSSL::SSL::VERIFY_NONE
- def init_server(url)
- @url = URI.parse(url)
+ # run ruby with -d or env variable SHOWSOAP=true to see SOAP wiredumps.
+ server.set_debug_output $stderr if show_debug
- if (@oauth)
- consumer = OAuth::Consumer.new \
- @oauth[:consumer_key],
- @oauth[:consumer_secret],
- {
- :site => url,
- :proxy => @proxy
- }
-
- consumer.http.set_debug_output $stderr if show_debug
-
- @server = OAuth::AccessToken.new \
- consumer,
- @oauth[:access_token],
- @oauth[:access_secret]
-
- class << @server
- alias_method :post2, :post
- end
- else
- @server = Net::HTTP.Proxy(@proxy).new(@url.host, @url.port)
- @server.use_ssl = @url.scheme == 'https'
- @server.verify_mode = OpenSSL::SSL::VERIFY_NONE
-
- # run ruby with -d or env variable SHOWSOAP=true to see SOAP wiredumps.
- @server.set_debug_output $stderr if show_debug
- end
+ return server
end
- # Connect to remote server
- #
- def connect(user, password)
- @user = user
- @password = password
-
- call_remote(:login, [:username, user, :password, password])
- end
-
# Log in to the server with a user name and password, remembering
# the session ID returned to us by Salesforce.
def login(user, password)
- response = connect(user, password)
+ @user = user
+ @password = password
+ @server = create_server(@url)
+ response = call_remote(:login, [:username, user, :password, password])
raise "Incorrect user name / password [#{response.Fault}]" unless response.loginResponse
- result = response[:loginResponse][:result]
+ result = response[:loginResponse][:result]
@session_id = result[:sessionId]
+ @url = URI.parse(result[:serverUrl])
+ @server = create_server(@url)
- init_server(result[:serverUrl])
-
- response
+ return response
end
# Log in to the server with OAuth, remembering
# the session ID returned to us by Salesforce.
def login_with_oauth
- result = @server.post \
- @oauth[:login_url],
+ consumer = OAuth::Consumer.new \
+ @oauth[:consumer_key],
+ @oauth[:consumer_secret]
+
+ access = OAuth::AccessToken.new \
+ consumer, @oauth[:access_token],
+ @oauth[:access_secret]
+
+ login_url = @oauth[:login_url]
+
+ result = access.post \
+ login_url,
'',
{'content-type' => 'application/x-www-form-urlencoded'}
case result
when Net::HTTPSuccess
- doc = REXML::Document.new result.body
+ doc = REXML::Document.new result.body
@session_id = doc.elements['*/sessionId'].text
- server_url = doc.elements['*/serverUrl'].text
- init_server server_url
+ @url = URI.parse(doc.elements['*/serverUrl'].text)
+ @server = access
- return {:sessionId => @sessionId, :serverUrl => server_url}
+ class << @server
+ alias_method :post2, :post
+ end
+
+ return {:sessionId => @session_id, :serverUrl => @url.to_s}
when Net::HTTPUnauthorized
raise 'Invalid OAuth tokens'
else
raise "Unexpected error: #{response.inspect}"
end
@@ -146,12 +134,15 @@
# Call a method on the remote server. Arguments can be
# a hash or (if order is important) an array of alternating
# keys and values.
def call_remote(method, args)
+ # Different URI requirements for regular vs. OAuth. This is
+ # *screaming* for a refactor.
+ fallback_soap_url = @oauth ? @url.to_s : @url.path
- urn, soap_url = block_given? ? yield : ["urn:partner.soap.sforce.com", @url.path]
+ urn, soap_url = block_given? ? yield : ["urn:partner.soap.sforce.com", fallback_soap_url]
# Create XML text from the arguments.
expanded = ''
@builder = Builder::XmlMarkup.new(:target => expanded)
expand(@builder, {method => args}, urn)
@@ -179,10 +170,11 @@
end
# Fill in the blanks of the SOAP envelope with our
# session ID and the expanded XML of our request.
request = (Envelope % [@session_id, extra_headers, expanded])
+ @logger && @logger.info("RForce request: #{request}")
# reset the batch size for the next request
@batch_size = DEFAULT_BATCH_SIZE
# gzip request
@@ -199,33 +191,46 @@
headers['Accept-Encoding'] = 'gzip'
headers['Content-Encoding'] = 'gzip'
end
# Send the request to the server and read the response.
+ @logger && @logger.info("RForce request to host #{@server} url #{soap_url} headers: #{headers}")
response = @server.post2(soap_url, request.lstrip, headers)
# decode if we have encoding
content = decode(response)
+ # Fix charset encoding. Needed because the "content" variable may contain a UTF-8
+ # or ISO-8859-1 string, but is carrying the US-ASCII encoding.
+ content = fix_encoding(content)
+
# Check to see if INVALID_SESSION_ID was raised and try to relogin in
if method != :login and @session_id and content =~ /sf:INVALID_SESSION_ID/
- login(@user, @password)
+ if @user
+ login(@user, @password)
+ else
+ raise "INVALID_SESSION_ID"
+ end
# repackage and rencode request with the new session id
request = (Envelope % [@session_id, extra_headers, expanded])
request = encode(request)
# Send the request to the server and read the response.
response = @server.post2(soap_url, request.lstrip, headers)
content = decode(response)
+
+ # Fix charset encoding. Needed because the "content" variable may contain a UTF-8
+ # or ISO-8859-1 string, but is carrying the US-ASCII encoding.
+ content = fix_encoding(content)
end
+ @logger && @logger.info("RForce response: #{content}")
SoapResponse.new(content).parse
end
-
# decode gzip
def decode(response)
encoding = response['Content-Encoding']
# return body if no encoding
@@ -244,11 +249,10 @@
else
response.body
end
end
-
# encode gzip
def encode(request)
return request if show_debug
begin
@@ -259,9 +263,31 @@
ensure
gzw.close
end
end
+ # fix invalid US-ASCII strings by applying the correct encoding on ruby 1.9+
+ def fix_encoding(string)
+ if [:valid_encoding?, :force_encoding].all? { |m| string.respond_to?(m) }
+ if !string.valid_encoding?
+ # The 2 possible encodings in responses are UTF-8 and ISO-8859-1
+ # http://www.salesforce.com/us/developer/docs/api/Content/implementation_considerations.htm#topic-title_international
+ #
+ ["UTF-8", "ISO-8859-1"].each do |encoding_name|
+
+ s = string.dup.force_encoding(encoding_name)
+
+ if s.valid_encoding?
+ return s
+ end
+ end
+
+ raise "Invalid encoding in SOAP response: not in [US-ASCII, UTF-8, ISO-8859-1]"
+ end
+ end
+
+ return string
+ end
# Turns method calls on this object into remote SOAP calls.
def method_missing(method, *args)
unless args.empty? || (args.size == 1 && [Hash, Array].include?(args[0].class))
raise 'Expected at most 1 Hash or Array argument'