-
Notifications
You must be signed in to change notification settings - Fork 58
Recommendations
This guide gives recommendations for how to work with Handsoap. You don’t need to follow these conventions at all – This is just the workflow that I have found useful.
- You need a WSDL for the service you want to consume.
- Figure out the url for the endpoint, as well as the protocol version. Put this in a config file.
- To find the endpoint, look inside the wsdl, for
<soap:address location="..">
- Create a service class. Add endpoints and protocol. Alias needed namespace(s).
- To find the namespace(s), look in the samples from soapUI. It will be imported as
v1
- Note that you can now use the provided generator to skip this step.
- Open the wsdl in soapUI
- In soapUI, find a sample request for the method you want to use. Copy+paste the body-part.
- Create a method in your service class (Use ruby naming convention)
- Write Ruby-code (using XmlMason) to generate a request that is similar to the example from soapUI. (In your copy+paste buffer)
- Write Ruby-code to parse the response (an XML-document) into Ruby data types.
- Write an integration test to verify that your method works as expected. You can use soapUI to generate a mock-service
Repeat point 5..9 for each method that you need to use.
Between each iteration, you should refactor shared code into helper functions.
If you use Rails, you should put the endpoint in a constant in the environment file. That way, you can have different endpoints for test/development/production/etc.
If you don’t use Rails, it’s still a good idea to move this information to a config file.
The configuration could look like this:
# wsdl: http://example.org/ws/service?WSDL
EXAMPLE_SERVICE_ENDPOINT = {
:uri => 'http://example.org/ws/service',
:version => 2
}
If you use Rails, you will need to load the gem from the config/environment.rb
file, using:
config.gem 'troelskn-handsoap', :lib => 'handsoap', :source => "http://gems.github.com"
If you use the standard development environment of Rails, you may run into troubles with cached classes. Add the following line to the initializer:
ActiveSupport::Dependencies.explicitly_unloadable_constants << 'Handsoap::Service'
From version 0.2.0 Handsoap sports a generator, that creates the service class + an integration test case. This is just a rough starting point for your service – You still have to fill out the actual mappings to/from xml, but at least it saves your some copy-pasting from this guide.
To use the generator, create a Rails project and run the script script/generate handsoap
, giving the url of the WSDL.
Put your service in a file under app/models
. You should extend Handsoap::Service
.
You need to provide the endpoint and the SOAP version (1.1 or 1.2). If in doubt, use version 2.
A service usually has a namespace for describing the message-body (RPC/Literal style). You should set this in the on_create_document
handler. Likewise, the response returned from the server will contain elements that typically are defined in a single namespace relevant to the service. You can register this in the handler on_response_document
.
A typical service looks like the following:
# -*- coding: utf-8 -*-
require 'handsoap'
class Example::FooService < Handsoap::Service
endpoint EXAMPLE_SERVICE_ENDPOINT
def on_create_document(doc)
# register namespaces for the request
doc.alias 'tns', "http://example.org/ws/spec"
end
def on_response_document(doc)
# register namespaces for the response
doc.add_namespace 'ns', 'http://example.org/ws/spec'
end
# public methods
# todo
private
# helpers
# todo
end
The above would go in the file app/models/example/foo_service.rb
Since you’re writing mappings manually, it’s a good idea to write tests that verify that the service works. If you use standard Rails with Test::Unit
, you should put these in an integration-test.
For the sample service above, you would create a file in test/integration/example/foo_service.rb
, with the following content:
# -*- coding: utf-8 -*-
require 'test_helper'
# Example::FooService.logger = $stdout
class Example::FooServiceTest < Test::Unit::TestCase
def test_update_icon
icon = { :href => 'http://www.example.com/icon.jpg', :type => 'image/jpeg' }
result = Example::FooService.update_icon!(icon)
assert_equal icon.type, result.type
end
end
Note the commented-out line. If you set a logger on the service-class, you can see exactly which XML goes forth and back, which is very useful for debugging.
You should use Ruby naming-conventions for methods names. If the method has side-effects, you should postfix it with an exclamation.
Repeat code inside the invoke-block, should be refactored out to builders, and the response should be parsed with a parser.
def update_icon!(icon)
response = invoke("tns:UpdateIcon") do |message|
build_icon!(message, icon)
end
parse_icon(response/"//icon").first)
end
You’ll end up with two kinds of helpers; Ruby→XML transformers (aka. builders) and XML→Ruby transformers (aka. parsers).
It’s recommended that you stick to the following style/naming scheme:
# icon -> xml
def build_icon!(message, icon)
message.add "icon" do |i|
i.set_attr "href", icon[:href]
i.set_attr "type", icon[:type]
end
end
# xml -> icon
def parse_icon(node)
{ :href => (node/"@href").to_s, :type => (node/"@type").to_s }
end
or, if you prefer, you can use a class to represent entities:
# icon -> xml
def build_icon!(message, icon)
message.add "icon" do |i|
i.set_attr "href", icon.href
i.set_attr "type", icon.type
end
end
# xml -> icon
def parse_icon(node)
Icon.new :href => (node/"@href").to_s,
:type => (node/"@type").to_s
end