Skip to content

Commit

Permalink
added support for soap headers
Browse files Browse the repository at this point in the history
this supports multiple explicit and implicit header parts
and it works the same as the request body.

renamed the accessor for the http headers from #headers to
#http_headers to separate it from the new Operation#header
method for specifying the soap header to send.

/cc savonrb/wasabi#27
  • Loading branch information
rubiii committed Jun 3, 2013
1 parent 58c5de5 commit b62b7bf
Show file tree
Hide file tree
Showing 30 changed files with 6,178 additions and 85 deletions.
39 changes: 24 additions & 15 deletions lib/savon/envelope.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
require 'builder'
require 'savon/body'
require 'savon/message'

class Savon
class Envelope

NSID = 'lol'

def initialize(operation, body)
def initialize(operation, header, body)
@logger = Logging.logger[self]

@operation = operation
@body = body

unless @body
@logger.warn("No request body Hash given for the #{operation.name.inspect} operation.")
@body = {}
end
@header = header || {}
@body = body || {}

@nsid_counter = -1
@namespaces = {}
Expand All @@ -26,10 +22,7 @@ def register_namespace(namespace)
end

def to_s
body = Body.new(self, @operation.input).build(@body)
body = build_rpc_wrapper(body) if rpc_call?

build_envelope(body)
build_envelope(build_header, build_body)
end

private
Expand All @@ -39,11 +32,27 @@ def create_nsid
"#{NSID}#{@nsid_counter}"
end

def build_envelope(body)
def build_header
return "" if @header.empty?
Message.new(self, @operation.header_parts).build(@header)
end

def build_body
return "" if @body.empty?
body = Message.new(self, @operation.body_parts).build(@body)

if rpc_call?
build_rpc_wrapper(body)
else
body
end
end

def build_envelope(header, body)
builder = Builder::XmlMarkup.new(indent: 2)

builder.tag! :env, :Envelope, collect_namespaces do |xml|
xml.tag!(:env, :Header)
xml.tag!(:env, :Header) { |xml| xml << header }
xml.tag!(:env, :Body) { |xml| xml << body }
end

Expand All @@ -52,7 +61,7 @@ def build_envelope(body)

def build_rpc_wrapper(body)
name = @operation.name
namespace = @operation.binding_operation.input[:body][:namespace]
namespace = @operation.binding_operation.input_body[:namespace]
nsid = register_namespace(namespace) if namespace

tag = [nsid, name].compact.join(':')
Expand Down
2 changes: 1 addition & 1 deletion lib/savon/body.rb → lib/savon/message.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require 'builder'

class Savon
class Body
class Message

def initialize(envelope, parts)
@logger = Logging.logger[self]
Expand Down
32 changes: 20 additions & 12 deletions lib/savon/operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,43 +36,51 @@ def initialize(operation, wsdl, http)
attr_accessor :encoding

# Public: Returns a Hash of HTTP headers to send.
def headers
return @headers if @headers
def http_headers
return @http_headers if @http_headers
headers = {}

headers['SOAPAction'] = %{"#{soap_action}"} if soap_action
headers['Content-Type'] = CONTENT_TYPE[soap_version] % encoding

@headers = headers
@http_headers = headers
end

# Public: Sets the Hash of HTTP headers.
attr_writer :headers
attr_writer :http_headers

# Public: Sets the request header Hash.
attr_accessor :header

# Public: Create an example request header Hash.
def example_header
ExampleMessage.new(@operation.header_parts).to_hash
end

# Public: Sets the request body Hash.
attr_accessor :body

# Public: Create an example request body Hash.
def example_body
ExampleMessage.new(@operation.input).to_hash
ExampleMessage.new(@operation.body_parts).to_hash
end

# Public: Returns the input body parts used to build the request body.
def body_parts
@operation.body_parts.inject([]) { |memo, part| memo + part.to_a }
end

# Public: Build the request XML for this operation.
def build
Envelope.new(@operation, body).to_s
Envelope.new(@operation, header, body).to_s
end

# Public: Call the operation.
def call
raw_response = @http.post(endpoint, headers, build)
raw_response = @http.post(endpoint, http_headers, build)
Response.new(raw_response)
end

# Public: Returns the input parts.
def input_parts
@operation.input.inject([]) { |memo, part| memo + part.to_a }
end

# Public: Returns the input style for this operation.
def input_style
@input_style ||= @operation.input_style
Expand Down
48 changes: 31 additions & 17 deletions lib/savon/wsdl/binding_operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,50 @@ def name
end

# TODO: maybe use proper classes to clean this up.
def input
return @input if @input
input = { header: {}, body: {} }
def input_headers
return @input_headers if @input_headers
input_headers = []

if header_nodes = find_input_child_nodes('header')
header_nodes.each do |header_node|
input_headers << {
encoding_style: header_node['encodingStyle'],
namespace: header_node['namespace'],
use: header_node['use'],
message: header_node['message'],
part: header_node['part']
}
end
end

input_node = @operation_node.element_children.find { |node| node.name == 'input' }
return unless input_node
@input_headers = input_headers
end

if header_node = input_node.element_children.find { |node| node.name == 'header' }
input[:header] = {
encoding_style: header_node['encodingStyle'],
namespace: header_node['namespace'],
use: header_node['use'],
message: header_node['message'],
part: header_node['part']
}
end
# TODO: maybe use proper classes to clean this up.
def input_body
return @input_body if @input_body
input_body = {}

if body_node = input_node.element_children.find { |node| node.name == 'body' }
input[:body] = {
if body_node = find_input_child_nodes('body').first
input_body = {
encoding_style: body_node['encodingStyle'],
namespace: body_node['namespace'],
use: body_node['use']
}
end

input
@input_body = input_body
end

private

def find_input_child_nodes(child_name)
input_node = @operation_node.element_children.find { |node| node.name == 'input' }
return unless input_node

input_node.element_children.select { |node| node.name == child_name }
end

def find_soap_operation_node
@operation_node.element_children.each do |node|
namespace = node.namespace.href
Expand Down
47 changes: 42 additions & 5 deletions lib/savon/wsdl/operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ def initialize(name, endpoint, binding_operation, port_type_operation, wsdl)
@endpoint = endpoint
@binding_operation = binding_operation
@port_type_operation = port_type_operation

@wsdl = wsdl
end

Expand All @@ -26,24 +25,62 @@ def soap_version
end
end

def input_style
"#{@binding_operation.style}/#{@binding_operation.input[:body][:use]}"
def header_parts
build_parts unless @header_parts
@header_parts
end

def input
@input ||= build_message(@port_type_operation.input)
def body_parts
build_parts unless @body_parts
@body_parts
end

def input_style
"#{@binding_operation.style}/#{@binding_operation.input_body[:use]}"
end

def output_style
"#{@binding_operation.style}/#{@binding_operation.output[:body][:use]}"
end

# TODO: do something useful with this!
def output
@output ||= build_message(@port_type_operation.output)
end

private

def build_parts
body_parts = collect_body_parts
header_parts = collect_header_parts

# remove explicit header parts from the body parts
header_part_names = header_parts.map { |part| part[:name] }
body_parts.reject! { |part| header_part_names.include? part[:name] }

@header_parts = MessageBuilder.new(@wsdl).build(header_parts)
@body_parts = MessageBuilder.new(@wsdl).build(body_parts)
end

def collect_body_parts
message_name = @port_type_operation.input[:message]
find_message(message_name).parts
end

def collect_header_parts
parts = []

@binding_operation.input_headers.each do |header|
next unless header[:message] && header[:part]
message_parts = find_message(header[:message]).parts

# only add the single header part from the message
parts << message_parts.find { |part| part[:name] == header[:part] }
end

parts
end

def build_message(input_output)
message_name = input_output[:message]
parts = find_message(message_name).parts
Expand Down
Loading

0 comments on commit b62b7bf

Please sign in to comment.