handsoap 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,105 @@
1
+ Handsoap
2
+ ===
3
+
4
+ Install
5
+ ---
6
+
7
+ gem sources -a http://gems.github.com
8
+ sudo gem install unwire-handsoap curb nokogiri
9
+
10
+ What
11
+ ---
12
+ Handsoap is a library for creating SOAP clients in Ruby.
13
+
14
+ [Watch a tutorial](http://www.vimeo.com/4813848), showing how to use Handsoap. The final application can be found at: [http://github.com/troelskn/handsoap-example/tree/master](http://github.com/troelskn/handsoap-example/tree/master)
15
+
16
+ API docs are at [http://rdoc.info/projects/unwire/handsoap/](http://rdoc.info/projects/unwire/handsoap/)
17
+
18
+ Some usage information is to be found in [the wiki](http://wiki.github.com/unwire/handsoap).
19
+
20
+ ![Handsoap](http://ny-image0.etsy.com/il_430xN.68558416.jpg)
21
+
22
+ Why
23
+ ---
24
+
25
+ Ruby already has a SOAP-client library, [soap4r](http://dev.ctor.org/soap4r), so why create another one?
26
+
27
+ > Let me summarize SOAP4R: it smells like Java code built on a Monday morning by an EJB coder.
28
+ >
29
+ > -- [Ruby In Practice: REST, SOAP, WebSphere MQ and SalesForce](http://blog.labnotes.org/2008/01/28/ruby-in-practice-rest-soap-websphere-mq-and-salesforce/)
30
+
31
+ OK, not entirely fair, but soap4r has problems. It's incomplete and buggy. If you try to use it for any real-world services, you quickly run into compatibility issues. You can get around some of them, if you have control over the service, but you may not always be that lucky. In the end, even if you get it working, it has a bulky un-Rubyish feel to it.
32
+
33
+ Handsoap tries to do better by taking a minimalistic approach. Instead of a full abstraction layer, it is more like a toolbox with which you can write SOAP bindings. You could think of it as a [ffi](http://c2.com/cgi/wiki?ForeignFunctionInterface) targeting SOAP.
34
+
35
+ This means that you generally need to do more manual labor in the cases where soap4r would have automated the mapping. It also means that you need to get your hands dirty with wsdl, xsd and other heavyweight specifications. However, it does give you some tools to help you stay sane.
36
+
37
+ There are several benefits of using Handsoap:
38
+
39
+ * It supports the entire SOAP specification, all versions (because you have to implement it your self).
40
+ * You actually get a sporting chance to debug and fix protocol level bugs.
41
+ * It's much faster than soap4r, because it uses fast low-level libraries for xml-parsing and http-communication.
42
+
43
+ To summarise, soap4r takes an optimistic approach, where Handsoap expects things to fail. If soap4r works for you today, it's probably the better choice. If you find your self strugling with it, Handsoap will offer a more smooth ride. It won't magically fix things for you though.
44
+
45
+ Handsoap vs. soap4r benchmark
46
+ ---
47
+
48
+ Benchmarks are always unfair, but my experiments has placed Handsoap at being approximately double as fast as soap4r. I'd love any suggestions for a more precise measure.
49
+
50
+ $ ruby tests/benchmark_test.rb 1000
51
+ Benchmarking 1000 calls ...
52
+ user system total real
53
+ handsoap 0.750000 0.090000 0.840000 ( 1.992437)
54
+ soap4r 2.240000 0.140000 2.380000 ( 3.605836)
55
+ ---------------
56
+ Legend:
57
+ The user CPU time, system CPU time, the sum of the user and system CPU times,
58
+ and the elapsed real time. The unit of time is seconds.
59
+
60
+ SOAP basics
61
+ ---
62
+
63
+ SOAP is a protocol that is tunneled through XML over HTTP. Apart from using the technology for transportation, it doesn't have much to do with HTTP. Some times, it hasn't even got much to do with XML either.
64
+
65
+ A SOAP client basically consists of three parts:
66
+
67
+ * A http-connectivity layer,
68
+ * a mechanism for marshalling native data types to XML,
69
+ * and a mechanism for unmarshalling XML to native data types.
70
+
71
+ The protocol also contains a large and unwieldy specification of how to do the (un)marshalling, which can be used as the basis for automatically mapping to a rich type model. This makes the protocol fitting for .net/Java, but is a huge overhead for a very dynamically typed language such as Ruby. Much of the complexity of clients such as soap4r, is in the parts that tries to use this specification. Handsoap expects you to manually write the code that marshals/unmarshals, thereby bypassing this complexity (or rather - pass it to the programmer)
72
+
73
+ Handsoap only supports RPC-style SOAP. This seems to be the most common style. It's probably possible to add support for Document-style with little effort, but until I see the need I'm not going there.
74
+
75
+ API documentation
76
+ ---
77
+
78
+ In addition to this guide, there's autogenerated API documentation available at [http://rdoc.info/projects/unwire/handsoap/](http://rdoc.info/projects/unwire/handsoap/)
79
+
80
+ Getting started
81
+ ---
82
+
83
+ For getting started with Handsoap, you should read [the guide in the wiki](http://wiki.github.com/unwire/handsoap/recommendations).
84
+
85
+ The toolbox
86
+ ---
87
+
88
+ The Handsoap toolbox consists of the following components.
89
+
90
+ Handsoap can use either [curb](http://curb.rubyforge.org/), [Net::HTTP](http://www.ruby-doc.org/stdlib/libdoc/net/http/rdoc/index.html) or [httpclient](http://dev.ctor.org/http-access2) for HTTP-connectivity. The former is recommended, and default, but for portability you might choose one of the latter. You usually don't need to interact at the HTTP-level, but if you do (for example, if you have to use SSL), you can do so through a thin abstraction layer.
91
+
92
+ For parsing XML, Handsoap defaults to use [Nokogiri](http://github.com/tenderlove/nokogiri/tree/master). Handsoap has an abstraction layer, so that you can switch between REXML, Nokogiri and ruby-libxml. Besides providing portability between these parsers, Handsop also gives some helper functions that are meaningful when parsing SOAP envelopes.
93
+
94
+ Finally, there is a library for generating XML, which you'll use when mapping from Ruby to SOAP. It's quite similar to [Builder](http://builder.rubyforge.org/), but is tailored towards being used for writing SOAP-messages. The name of this library is `XmlMason` and it is included/part of Handsoap.
95
+
96
+ License
97
+ ---
98
+
99
+ Copyright: [Unwire A/S](http://www.unwire.dk), 2009
100
+
101
+ License: [Creative Commons Attribution 2.5 Denmark License](http://creativecommons.org/licenses/by/2.5/dk/deed.en_GB)
102
+ or: [LGPL 3](http://www.gnu.org/copyleft/lesser.html)
103
+ ___
104
+
105
+ troelskn@gmail.com - April, 2009
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 0
3
+ :major: 1
4
+ :minor: 1
File without changes
@@ -0,0 +1,113 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "#{File.dirname(__FILE__)}/../../lib/handsoap/parser.rb"
3
+ require "#{File.dirname(__FILE__)}/../../lib/handsoap/compiler.rb"
4
+ require 'pathname'
5
+
6
+ # TODO
7
+ # options:
8
+ # soap_actions (true/false)
9
+ # soap_version (1/2/auto)
10
+ # basename
11
+ class HandsoapGenerator < Rails::Generator::Base
12
+ def initialize(runtime_args, runtime_options = {})
13
+ super
14
+ # Wsdl argument is required.
15
+ usage if @args.empty?
16
+ @wsdl_uri = @args.shift
17
+ @basename = @args.shift
18
+ end
19
+
20
+ # def add_options!(opt)
21
+ # opt.on('--soap-actions') { |value| options[:soap_actions] = true }
22
+ # opt.on('--no-soap-actions') { |value| options[:soap_actions] = false }
23
+ # end
24
+
25
+ def banner
26
+ "Generates the scaffold for a Handsoap binding." +
27
+ "\n" + "You still have to fill in most of the meat, but this gives you a head start." +
28
+ "\n" + "Usage: #{$0} #{spec.name} URI [BASENAME] [OPTIONS]" +
29
+ "\n" + " URI URI of the WSDL to generate from" +
30
+ "\n" + " BASENAME The basename to use for the service. If omitted, the name will be deducted from the URL." +
31
+ # "\n" +
32
+ # "\n" + "The following options are available:" +
33
+ # "\n" + " --soap-actions If set, stubs will be generated with soap-action parameters. (Default)" +
34
+ # "\n" + " --no-soap-actions If set, stubs will be generated without soap-action parameters." +
35
+ # "\n" + " --soap-version-1 If set, the generator will look for SOAP v 1.1 endpoints." +
36
+ # "\n" + " --soap-version-2 If set, the generator will look for SOAP v 1.2 endpoints." +
37
+ ""
38
+ end
39
+
40
+ def manifest
41
+ wsdl = Handsoap::Parser::Wsdl.read(@wsdl_uri)
42
+ compiler = Handsoap::Compiler.new(wsdl, @basename)
43
+ protocol = wsdl.preferred_protocol
44
+ file_name = compiler.service_basename
45
+ record do |m|
46
+ m.directory "app"
47
+ m.directory "app/models"
48
+ m.file_contents "app/models/#{file_name}_service.rb" do |file|
49
+ file.write compiler.compile_service(protocol, :soap_actions)
50
+ end
51
+ m.directory "test"
52
+ m.directory "test/integration"
53
+ m.file_contents "test/integration/#{file_name}_service_test.rb" do |file|
54
+ file.write compiler.compile_test(protocol)
55
+ end
56
+ # TODO
57
+ # Ask user about which endpoints to use ?
58
+ m.message do |out|
59
+ out.puts "----"
60
+ out.puts "Endpoints in WSDL"
61
+ out.puts " You should copy these to the appropriate environment files."
62
+ out.puts " (Eg. `config/environments/*.rb`)"
63
+ out.puts "----"
64
+ out.puts compiler.compile_endpoints(protocol)
65
+ out.puts "----"
66
+ end
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ module Handsoap #:nodoc:
73
+ module Generator #:nodoc:
74
+ module Commands #:nodoc:
75
+ module Create
76
+ def file_contents(relative_destination, &block)
77
+ destination = destination_path(relative_destination)
78
+ temp_file = Tempfile.new("handsoap_generator")
79
+ canonical_path = Pathname.new(source_path("/.")).realpath.to_s
80
+ temp_file_relative_path = relative_path(temp_file.path, canonical_path)
81
+ begin
82
+ yield temp_file
83
+ temp_file.close
84
+ return self.file(temp_file_relative_path, relative_destination)
85
+ ensure
86
+ temp_file.unlink
87
+ end
88
+ end
89
+
90
+ def message(&block)
91
+ yield $stdout unless logger.quiet
92
+ end
93
+
94
+ private
95
+
96
+ # Convert the given absolute path into a path
97
+ # relative to the second given absolute path.
98
+ # http://www.justskins.com/forums/file-relative-path-handling-97116.html
99
+ def relative_path(abspath, relative_to)
100
+ path = abspath.split(File::SEPARATOR)
101
+ rel = relative_to.split(File::SEPARATOR)
102
+ while (path.length > 0) && (path.first == rel.first)
103
+ path.shift
104
+ rel.shift
105
+ end
106
+ ('..' + File::SEPARATOR) * rel.length + path.join(File::SEPARATOR)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ Rails::Generator::Commands::Create.send :include, Handsoap::Generator::Commands::Create
File without changes
@@ -0,0 +1,4 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'handsoap/xml_mason'
3
+ require 'handsoap/xml_query_front'
4
+ require 'handsoap/service'
@@ -0,0 +1,182 @@
1
+ # -*- coding: utf-8 -*-
2
+ module Handsoap
3
+ # Used internally to generate Ruby source code
4
+ class CodeWriter #:nodoc: all
5
+
6
+ def initialize
7
+ @buffer = ""
8
+ @indentation = 0
9
+ end
10
+
11
+ def begin(text)
12
+ puts(text)
13
+ indent
14
+ end
15
+
16
+ def end(str = "end")
17
+ unindent
18
+ puts(str)
19
+ end
20
+
21
+ def puts(text = "")
22
+ @buffer << text.gsub(/^(.*)$/, (" " * @indentation) + "\\1")
23
+ @buffer << "\n" # unless @buffer.match(/\n$/)
24
+ end
25
+
26
+ def indent
27
+ @indentation = @indentation + 1
28
+ end
29
+
30
+ def unindent
31
+ @indentation = @indentation - 1
32
+ end
33
+
34
+ def to_s
35
+ @buffer
36
+ end
37
+ end
38
+
39
+ # Used internally by the generator to generate a Service stub.
40
+ class Compiler #:nodoc: all
41
+
42
+ def initialize(wsdl, basename = nil)
43
+ @wsdl = wsdl
44
+ if basename
45
+ @basename = basename.gsub(/[^a-zA-Z0-9]/, "_").gsub(/_+/, "_").gsub(/(^_+|_+$)/, '')
46
+ else
47
+ @basename = @wsdl.service
48
+ end
49
+ @basename = underscore(@basename).gsub(/_service$/, "")
50
+ end
51
+
52
+ def write
53
+ writer = CodeWriter.new
54
+ yield writer
55
+ writer.to_s
56
+ end
57
+
58
+ def underscore(camel_cased_word)
59
+ camel_cased_word.to_s.gsub(/::/, '/').
60
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
61
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
62
+ tr("-", "_").
63
+ downcase
64
+ end
65
+
66
+ def camelize(lower_case_and_underscored_word)
67
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) {
68
+ "::" + $1.upcase
69
+ }.gsub(/(^|_)(.)/) {
70
+ $2.upcase
71
+ }
72
+ end
73
+
74
+ def method_name(operation)
75
+ if operation.name.match /^(get|find|select|fetch)/i
76
+ "#{underscore(operation.name)}"
77
+ else
78
+ "#{underscore(operation.name)}!"
79
+ end
80
+ end
81
+
82
+ def service_basename
83
+ @basename
84
+ end
85
+
86
+ def service_name
87
+ camelize(service_basename) + "Service"
88
+ end
89
+
90
+ def endpoint_name
91
+ "#{service_basename.upcase}_SERVICE_ENDPOINT"
92
+ end
93
+
94
+ def detect_protocol
95
+ if endpoints.select { |endpoint| endpoint.protocol == :soap12 }.any?
96
+ :soap12
97
+ elsif endpoints.select { |endpoint| endpoint.protocol == :soap11 }.any?
98
+ :soap11
99
+ else
100
+ raise "Can't find any soap 1.1 or soap 1.2 endpoints"
101
+ end
102
+ end
103
+
104
+ def compile_endpoints(protocol)
105
+ version = protocol == :soap12 ? 2 : 1
106
+ @wsdl.endpoints.select { |endpoint| endpoint.protocol == protocol }.map do |endpoint|
107
+ write do |w|
108
+ w.puts "# wsdl: #{@wsdl.url}"
109
+ w.begin "#{endpoint_name} = {"
110
+ w.puts ":uri => '#{endpoint.url}',"
111
+ w.puts ":version => #{version}"
112
+ w.end "}"
113
+ end
114
+ end
115
+ end
116
+
117
+ def compile_service(protocol, *options)
118
+ binding = @wsdl.bindings.find { |b| b.protocol == protocol }
119
+ raise "Can't find binding for requested protocol (#{protocol})" unless binding
120
+ write do |w|
121
+ w.puts "# -*- coding: utf-8 -*-"
122
+ w.puts "require 'handsoap'"
123
+ w.puts
124
+ w.begin "class #{service_name} < Handsoap::Service"
125
+ w.puts "endpoint #{endpoint_name}"
126
+ w.begin "def on_create_document(doc)"
127
+ w.puts "# register namespaces for the request"
128
+ w.puts "doc.alias 'tns', '#{@wsdl.target_ns}'"
129
+ w.end
130
+ w.puts
131
+ w.begin "def on_response_document(doc)"
132
+ w.puts "# register namespaces for the response"
133
+ w.puts "doc.add_namespace 'ns', '#{@wsdl.target_ns}'"
134
+ w.end
135
+ w.puts
136
+ w.puts "# public methods"
137
+ @wsdl.interface.operations.each do |operation|
138
+ action = binding.actions.find { |a| a.name == operation.name }
139
+ raise "Can't find action for operation #{operation.name}" unless action
140
+ w.puts
141
+ w.begin "def #{method_name(operation)}"
142
+ # TODO allow :soap_action => :none
143
+ if operation.name != action.soap_action && options.include?(:soap_actions)
144
+ w.puts "soap_action = '#{action.soap_action}'"
145
+ maybe_soap_action = ", soap_action"
146
+ else
147
+ maybe_soap_action = ""
148
+ end
149
+ w.begin((operation.output ? 'response = ' : '') + "invoke('tns:#{operation.name}'#{maybe_soap_action}) do |message|")
150
+ w.puts 'raise "TODO"'
151
+ w.end
152
+ w.end
153
+ end
154
+ w.puts
155
+ w.puts "private"
156
+ w.puts "# helpers"
157
+ w.puts "# TODO"
158
+ w.end
159
+ end
160
+ end
161
+
162
+ def compile_test(protocol)
163
+ binding = @wsdl.bindings.find { |b| b.protocol == protocol }
164
+ raise "Can't find binding for requested protocol (#{protocol})" unless binding
165
+ write do |w|
166
+ w.puts "# -*- coding: utf-8 -*-"
167
+ w.puts "require 'test_helper'"
168
+ w.puts
169
+ w.puts "# #{service_name}.logger = $stdout"
170
+ w.puts
171
+ w.begin "class #{service_name}Test < Test::Unit::TestCase"
172
+ @wsdl.interface.operations.each do |operation|
173
+ w.puts
174
+ w.begin "def test_#{underscore(operation.name)}"
175
+ w.puts "result = #{service_name}.#{method_name(operation)}"
176
+ w.end
177
+ end
178
+ w.end
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,50 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Handsoap
4
+ class Deferred
5
+ def initialize
6
+ @callback = nil
7
+ @callback_cache = nil
8
+ @errback = nil
9
+ @errback_cache = nil
10
+ end
11
+ def has_callback?
12
+ !! @callback
13
+ end
14
+ def has_errback?
15
+ !! @errback
16
+ end
17
+ def callback(&block)
18
+ raise "Already assigned a block for callback" if @callback
19
+ @callback = block
20
+ if @callback_cache
21
+ payload = @callback_cache
22
+ trigger_callback(*payload)
23
+ end
24
+ self
25
+ end
26
+ def errback(&block)
27
+ raise "Already assigned a block for errback" if @errback
28
+ @errback = block
29
+ if @errback_cache
30
+ payload = @errback_cache
31
+ trigger_errback(*payload)
32
+ end
33
+ self
34
+ end
35
+ def trigger_callback(*args)
36
+ if @callback
37
+ @callback.call(*args)
38
+ else
39
+ @callback_cache = args
40
+ end
41
+ end
42
+ def trigger_errback(*args)
43
+ if @errback
44
+ @errback.call(*args)
45
+ else
46
+ @errback_cache = args
47
+ end
48
+ end
49
+ end
50
+ end