├── .gitignore
├── DEPLOY.markdown
├── README.markdown
├── Rakefile
├── VERSION.yml
├── generators
└── handsoap
│ ├── USAGE
│ ├── handsoap_generator.rb
│ └── templates
│ └── DUMMY
├── handsoap.gemspec
├── lib
├── handsoap.rb
└── handsoap
│ ├── compiler.rb
│ ├── deferred.rb
│ ├── http.rb
│ ├── http
│ ├── drivers.rb
│ ├── drivers
│ │ ├── abstract_driver.rb
│ │ ├── curb_driver.rb
│ │ ├── event_machine_driver.rb
│ │ ├── http_client_driver.rb
│ │ ├── mock_driver.rb
│ │ └── net_http_driver.rb
│ ├── part.rb
│ ├── request.rb
│ └── response.rb
│ ├── parser.rb
│ ├── service.rb
│ ├── xml_mason.rb
│ └── xml_query_front.rb
└── tests
├── GoogleSearch-soapui-project.xml
├── Weather.wsdl
├── WeatherSummary.wsdl
├── account_test.rb
├── benchmark_integration_test.rb
├── dispatch_test.rb
├── event_machine_test.rb
├── fault_test.rb
├── handsoap_generator_test.rb
├── http_test.rb
├── httpauth_integration_test.rb
├── httpauth_server.rb
├── parser_test.rb
├── service_integration_test.rb
├── service_test.rb
├── socket_server.rb
├── wstf-sc002-soapui-project.xml
├── xml_mason_test.rb
└── xml_query_front_test.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.log
3 | *.pid
4 | *.sqlite3
5 | *.tmproj
6 | .DS_Store
7 | log/*
8 | pkg/*
9 | tests/rails_root/*
10 | *.swp
11 | *.orig
12 |
--------------------------------------------------------------------------------
/DEPLOY.markdown:
--------------------------------------------------------------------------------
1 | This is mostly a note to my self, so I don't forget it.
2 |
3 | To make a release, do:
4 |
5 | rake version:bump:patch
6 | rake release
7 |
8 | You need `jeweler` and `gemcutter`, as well as login credentials for gemcutter.
9 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | Handsoap
2 | ===
3 |
4 | Install
5 | ---
6 |
7 | gem sources -a http://gemcutter.org
8 | sudo gem install 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 | 
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, Handsoap 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 | Maintainers & Contributors
97 | ---
98 |
99 | Handsoap is maintained by [Unwire A/S](http://www.unwire.dk), namely [Troels Knak-Nielsen](http://github.com/troelskn) and [Jimmi Westerberg](http://github.com/jimmiw), with the help of many other contributors.
100 |
101 | Use the git command below to see a list of them all. (GIT command was found at formtastic)
102 |
103 | git shortlog -n -s --no-merges
104 |
105 | License
106 | ---
107 |
108 | Copyright: [Unwire A/S](http://www.unwire.dk), 2009
109 |
110 | License: [Creative Commons Attribution 2.5 Denmark License](http://creativecommons.org/licenses/by/2.5/dk/deed.en_GB)
111 | or: [LGPL 3](http://www.gnu.org/copyleft/lesser.html)
112 | ___
113 |
114 | troelskn@gmail.com - April, 2009
115 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | begin
3 | require 'jeweler'
4 | #require 'rubygems'
5 | #gem :jeweler
6 | Jeweler::Tasks.new do |gemspec|
7 | gemspec.name = "handsoap"
8 | gemspec.summary = "Handsoap is a library for creating SOAP clients in Ruby"
9 | gemspec.email = ["troelskn@gmail.com","frontend@unwire.dk"]
10 | gemspec.homepage = "http://github.com/unwire/handsoap"
11 | gemspec.description = gemspec.summary
12 | gemspec.authors = ["Troels Knak-Nielsen", "Jimmi Westerberg"]
13 | gemspec.requirements << "You need to install either \"curb\" or \"httpclient\", using one of:\n gem install curb\n gem install httpclient"
14 | gemspec.requirements << "It is recommended that you install either \"nokogiri\" or \"libxml-ruby\""
15 | gemspec.files = FileList['lib/**/*.rb', 'generators/handsoap/templates', 'generators/**/*', '[A-Z]*.*'].to_a
16 | end
17 | Jeweler::GemcutterTasks.new
18 | rescue LoadError => err
19 | puts "Jeweler not available. Install it with: sudo gem install jeweler"
20 | p err
21 | end
22 |
23 | desc "Generates API documentation"
24 | task :rdoc do
25 | sh "rm -rf doc && rdoc lib"
26 | end
27 |
28 | require 'rake/testtask'
29 | namespace :test do
30 | desc "Remove temporary files generated by test case"
31 | task :cleanup do
32 | puts "Clearing temporary files"
33 | sh "rm -rf tests/rails_root"
34 | end
35 | Rake::TestTask.new :test do |test|
36 | # Rake::Task['test:cleanup'].invoke
37 | test.test_files = FileList.new('tests/**/*_test.rb') do |list|
38 | list.exclude 'tests/benchmark_integration_test.rb'
39 | list.exclude 'tests/service_integration_test.rb'
40 | list.exclude 'tests/httpauth_integration_test.rb'
41 | end
42 | test.libs << 'tests'
43 | test.verbose = true
44 | end
45 | end
46 |
47 | desc "Run tests and clean up afterwards"
48 | task :test => ["test:cleanup", "test:test", "test:cleanup"]
49 |
50 | task :default => [:test]
51 |
52 |
--------------------------------------------------------------------------------
/VERSION.yml:
--------------------------------------------------------------------------------
1 | ---
2 | :major: 1
3 | :minor: 4
4 | :patch: 0
5 | :build:
6 |
--------------------------------------------------------------------------------
/generators/handsoap/USAGE:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unwire/handsoap/d5e3c7d4f1cd7559da947898e5464cba12587852/generators/handsoap/USAGE
--------------------------------------------------------------------------------
/generators/handsoap/handsoap_generator.rb:
--------------------------------------------------------------------------------
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
114 |
--------------------------------------------------------------------------------
/generators/handsoap/templates/DUMMY:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unwire/handsoap/d5e3c7d4f1cd7559da947898e5464cba12587852/generators/handsoap/templates/DUMMY
--------------------------------------------------------------------------------
/handsoap.gemspec:
--------------------------------------------------------------------------------
1 | # Generated by jeweler
2 | # DO NOT EDIT THIS FILE DIRECTLY
3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4 | # -*- encoding: utf-8 -*-
5 |
6 | Gem::Specification.new do |s|
7 | s.name = "handsoap"
8 | s.version = "1.4.0"
9 |
10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11 | s.authors = ["Troels Knak-Nielsen", "Jimmi Westerberg"]
12 | s.date = "2013-11-06"
13 | s.description = "Handsoap is a library for creating SOAP clients in Ruby"
14 | s.email = ["troelskn@gmail.com", "frontend@unwire.dk"]
15 | s.extra_rdoc_files = [
16 | "README.markdown"
17 | ]
18 | s.license = 'LGPL-3'
19 | s.files = [
20 | "DEPLOY.markdown",
21 | "README.markdown",
22 | "VERSION.yml",
23 | "generators/handsoap/USAGE",
24 | "generators/handsoap/handsoap_generator.rb",
25 | "generators/handsoap/templates/DUMMY",
26 | "lib/handsoap.rb",
27 | "lib/handsoap/compiler.rb",
28 | "lib/handsoap/deferred.rb",
29 | "lib/handsoap/http.rb",
30 | "lib/handsoap/http/drivers.rb",
31 | "lib/handsoap/http/drivers/abstract_driver.rb",
32 | "lib/handsoap/http/drivers/curb_driver.rb",
33 | "lib/handsoap/http/drivers/event_machine_driver.rb",
34 | "lib/handsoap/http/drivers/http_client_driver.rb",
35 | "lib/handsoap/http/drivers/mock_driver.rb",
36 | "lib/handsoap/http/drivers/net_http_driver.rb",
37 | "lib/handsoap/http/part.rb",
38 | "lib/handsoap/http/request.rb",
39 | "lib/handsoap/http/response.rb",
40 | "lib/handsoap/parser.rb",
41 | "lib/handsoap/service.rb",
42 | "lib/handsoap/xml_mason.rb",
43 | "lib/handsoap/xml_query_front.rb"
44 | ]
45 | s.homepage = "http://github.com/unwire/handsoap"
46 | s.require_paths = ["lib"]
47 | s.requirements = ["You need to install either \"curb\" or \"httpclient\", using one of:\n gem install curb\n gem install httpclient", "It is recommended that you install either \"nokogiri\" or \"libxml-ruby\""]
48 | s.rubygems_version = "2.0.7"
49 | s.summary = "Handsoap is a library for creating SOAP clients in Ruby"
50 | end
51 |
52 |
--------------------------------------------------------------------------------
/lib/handsoap.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'handsoap/xml_mason'
3 | require 'handsoap/xml_query_front'
4 | require 'handsoap/service'
5 | require 'bigdecimal'
6 |
--------------------------------------------------------------------------------
/lib/handsoap/compiler.rb:
--------------------------------------------------------------------------------
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
183 |
--------------------------------------------------------------------------------
/lib/handsoap/deferred.rb:
--------------------------------------------------------------------------------
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
51 |
--------------------------------------------------------------------------------
/lib/handsoap/http.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'handsoap/http/request'
3 | require 'handsoap/http/response'
4 | require 'handsoap/http/drivers'
5 |
--------------------------------------------------------------------------------
/lib/handsoap/http/drivers.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'handsoap/http/drivers/abstract_driver'
3 | require 'handsoap/http/drivers/curb_driver'
4 | require 'handsoap/http/drivers/event_machine_driver'
5 | require 'handsoap/http/drivers/http_client_driver'
6 | require 'handsoap/http/drivers/net_http_driver'
7 | require 'handsoap/http/drivers/mock_driver'
8 |
9 | module Handsoap
10 | module Http
11 | @@drivers = {
12 | :curb => Drivers::CurbDriver,
13 | :em => Drivers::EventMachineDriver,
14 | :event_machine => Drivers::EventMachineDriver,
15 | :httpclient => Drivers::HttpClientDriver,
16 | :http_client => Drivers::HttpClientDriver,
17 | :net_http => Drivers::NetHttpDriver,
18 | }
19 |
20 | def self.drivers
21 | @@drivers
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/handsoap/http/drivers/abstract_driver.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | module Handsoap
4 | module Http
5 | module Drivers
6 | class AbstractDriver
7 | def self.load!
8 | end
9 |
10 | def initialize
11 | self.class.load!
12 | end
13 |
14 | # Parses a raw http response into a +Response+ or +Part+ object.
15 | def parse_http_part(headers, body, status = nil, content_type = nil)
16 | if headers.kind_of? String
17 | headers = parse_headers(headers)
18 | end
19 | headers = headers.inject({}) {|collect,item| collect[item[0].downcase] = item[1]; collect }
20 | if content_type.nil? && headers['content-type']
21 | content_type = headers['content-type'].first
22 | end
23 | boundary = parse_multipart_boundary(content_type)
24 | parts = if boundary
25 | parse_multipart(boundary, body).map {|raw_part| parse_http_part(raw_part[:head], raw_part[:body]) }
26 | end
27 | if status.nil?
28 | Handsoap::Http::Part.new(headers, body, parts)
29 | else
30 | Handsoap::Http::Response.new(status, headers, body, parts)
31 | end
32 | end
33 |
34 | # Content-Type header string -> mime-boundary | nil
35 | def parse_multipart_boundary(content_type)
36 | if %r|\Amultipart.*boundary=\"?([^\";,]+)\"?|n.match(content_type)
37 | $1.dup
38 | end
39 | end
40 |
41 | # Parses a multipart http-response body into parts.
42 | # +boundary+ is a string of the boundary token.
43 | # +content_io+ is either a string or an IO. If it's an IO, then content_length must be specified.
44 | # +content_length+ (optional) is an integer, specifying the length of +content_io+
45 | #
46 | # This code is lifted from cgi.rb
47 | #
48 | def parse_multipart(boundary, content_io, content_length = nil)
49 | if content_io.kind_of? String
50 | content_length = content_io.length
51 | content_io = StringIO.new(content_io, 'r')
52 | elsif !(content_io.kind_of? IO) || content_length.nil?
53 | raise "Second argument must be String or IO with content_length"
54 | end
55 |
56 | boundary = "--" + boundary
57 | quoted_boundary = Regexp.quote(boundary, "n")
58 | buf = ""
59 | bufsize = 10 * 1024
60 | boundary_end = ""
61 |
62 | # start multipart/form-data
63 | content_io.binmode if defined? content_io.binmode
64 | boundary_size = boundary.size + "\r\n".size
65 | content_length -= boundary_size
66 | status = content_io.read(boundary_size)
67 |
68 | if nil == status
69 | raise EOFError, "no content body"
70 | elsif "\r\n" + boundary == status
71 | extra = content_io.read("\r\n".size)
72 | unless extra == "\r\n"
73 | raise EOFError, "parse error while reading boundary"
74 | end
75 | elsif boundary + "\r\n" != status
76 | raise EOFError, "bad content body"
77 | end
78 |
79 | parts = []
80 |
81 | loop do
82 | head = nil
83 | if 10240 < content_length
84 | require "tempfile"
85 | body = Tempfile.new("Handsoap")
86 | else
87 | begin
88 | require "stringio"
89 | body = StringIO.new
90 | rescue LoadError
91 | require "tempfile"
92 | body = Tempfile.new("Handsoap")
93 | end
94 | end
95 | body.binmode if defined? body.binmode
96 |
97 | until head and /#{quoted_boundary}(?:\r\n|--)/n.match(buf)
98 |
99 | if (not head) and /\r\n\r\n/n.match(buf)
100 | buf = buf.sub(/\A((?:.|\n)*?\r\n)\r\n/n) do
101 | head = $1.dup
102 | ""
103 | end
104 | next
105 | end
106 |
107 | if head and ( ("\r\n" + boundary + "\r\n").size < buf.size )
108 | body.print buf[0 ... (buf.size - ("\r\n" + boundary + "\r\n").size)]
109 | buf[0 ... (buf.size - ("\r\n" + boundary + "\r\n").size)] = ""
110 | end
111 |
112 | c = if bufsize < content_length
113 | content_io.read(bufsize)
114 | else
115 | content_io.read(content_length)
116 | end
117 | if c.nil? || c.empty?
118 | raise EOFError, "bad content body"
119 | end
120 | buf.concat(c)
121 | content_length -= c.size
122 | end
123 |
124 | buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
125 | body.print $1
126 | if "--" == $2
127 | content_length = -1
128 | end
129 | boundary_end = $2.dup
130 | ""
131 | end
132 |
133 | body.rewind
134 | parts << {:head => head, :body => body.read(body.size)}
135 |
136 | break if buf.size == 0
137 | break if content_length == -1
138 | end
139 | raise EOFError, "bad boundary end of body part" unless boundary_end =~ /--/
140 | parts
141 | end
142 |
143 | # lifted from webrick/httputils.rb
144 | def parse_headers(raw)
145 | header = Hash.new([].freeze)
146 | field = nil
147 | tmp = raw.gsub(/^(\r\n)+|(\r\n)+$/, '')
148 | (tmp.respond_to?(:lines) ? tmp.lines : tmp).each {|line|
149 | case line
150 | when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):\s*(.*?)\s*\z/om
151 | field, value = $1, $2
152 | field.downcase!
153 | header[field] = [] unless header.has_key?(field)
154 | header[field] << value
155 | when /^\s+(.*?)\s*\z/om
156 | value = $1
157 | unless field
158 | raise "bad header '#{line.inspect}'."
159 | end
160 | header[field][-1] << " " << value
161 | else
162 | raise "bad header '#{line.inspect}'."
163 | end
164 | }
165 | header.each {|key, values|
166 | values.each {|value|
167 | value.strip!
168 | value.gsub!(/\s+/, " ")
169 | }
170 | }
171 | header
172 | end
173 | end
174 | end
175 | end
176 | end
177 |
--------------------------------------------------------------------------------
/lib/handsoap/http/drivers/curb_driver.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'handsoap/http/drivers/abstract_driver'
3 |
4 | module Handsoap
5 | module Http
6 | module Drivers
7 | class CurbDriver < AbstractDriver
8 | attr_accessor :enable_cookies
9 |
10 | def initialize
11 | @enable_cookies = false
12 | end
13 |
14 | def self.load!
15 | require 'curb'
16 | end
17 |
18 | def get_curl(url)
19 | if @curl
20 | @curl.url = url
21 | else
22 | @curl = ::Curl::Easy.new(url)
23 | @curl.timeout = Handsoap.timeout
24 | @curl.enable_cookies = @enable_cookies
25 |
26 | # enables both deflate and gzip compression of responses
27 | @curl.encoding = ''
28 |
29 | if Handsoap.follow_redirects?
30 | @curl.follow_location = true
31 | @curl.max_redirects = Handsoap.max_redirects
32 | end
33 | end
34 | @curl
35 | end
36 |
37 | private :get_curl
38 |
39 | def send_http_request(request)
40 | http_client = get_curl(request.url)
41 | # Set credentials. The driver will negotiate the actual scheme
42 | if request.username && request.password
43 | http_client.userpwd = [request.username, ":", request.password].join
44 | end
45 | http_client.cacert = request.trust_ca_file if request.trust_ca_file
46 | http_client.cert = request.client_cert_file if request.client_cert_file
47 | # I have submitted a patch for this to curb, but it's not yet supported. If you get errors, try upgrading curb.
48 | http_client.cert_key = request.client_cert_key_file if request.client_cert_key_file
49 | # pack headers
50 | headers = request.headers.inject([]) do |arr, (k,v)|
51 | arr + v.map {|x| "#{k}: #{x}" }
52 | end
53 | http_client.headers = headers
54 | # I don't think put/delete is actually supported ..
55 | case request.http_method
56 | when :get
57 | http_client.http_get
58 | when :post
59 | http_client.http_post(request.body)
60 | when :put
61 | http_client.http_put(request.body)
62 | when :delete
63 | http_client.http_delete
64 | else
65 | raise "Unsupported request method #{request.http_method}"
66 | end
67 | parse_http_part(http_client.header_str.gsub(/^HTTP.*\r\n/, ""), http_client.body_str, http_client.response_code, http_client.content_type)
68 | end
69 | end
70 | end
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/lib/handsoap/http/drivers/event_machine_driver.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | module Handsoap
4 | module Http
5 | module Drivers
6 | class EventMachineDriver < AbstractDriver
7 | def self.load!
8 | require 'eventmachine'
9 | require 'em-http'
10 | end
11 |
12 | def send_http_request_async(request)
13 | emr = EventMachine::HttpRequest.new(request.url)
14 |
15 | if request.username && request.password
16 | # TODO: Verify that this is actually supported?
17 | request.headers['authorization'] = [request.username, request.password]
18 | end
19 |
20 | case request.http_method
21 | when :get
22 | emdef = emr.get(:head => request.headers)
23 | when :post
24 | emdef = emr.post(:head => request.headers, :body => request.body)
25 | when :put
26 | emdef = emr.put(:head => request.headers, :body => request.body)
27 | when :delete
28 | emdef = emr.delete
29 | else
30 | raise "Unsupported request method #{request.http_method}"
31 | end
32 |
33 | deferred = Handsoap::Deferred.new
34 | emdef.callback do
35 | http_response = parse_http_part(emdef.response_header, emdef.response, emdef.response_header.status)
36 | deferred.trigger_callback http_response
37 | end
38 | emdef.errback do
39 | deferred.trigger_errback emdef
40 | end
41 | deferred
42 | end
43 | end
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/handsoap/http/drivers/http_client_driver.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'handsoap/http/drivers/abstract_driver'
3 |
4 | module Handsoap
5 | module Http
6 | module Drivers
7 | class HttpClientDriver < AbstractDriver
8 | def self.load!
9 | require 'httpclient'
10 | end
11 |
12 | def send_http_request(request)
13 | http_client = HTTPClient.new
14 | # Set credentials. The driver will negotiate the actual scheme
15 | if request.username && request.password
16 | domain = request.url.match(/^(http(s?):\/\/[^\/]+\/)/)[1]
17 | http_client.set_auth(domain, request.username, request.password)
18 | end
19 | http_client.ssl_config.set_trust_ca(request.trust_ca_file) if request.trust_ca_file
20 | http_client.ssl_config.set_client_cert_file(request.client_cert_file,request.client_cert_key_file) if request.client_cert_file and request.client_cert_key_file
21 | http_client.ssl_config.verify_mode = request.ssl_verify_mode if request.ssl_verify_mode
22 | # pack headers
23 | headers = request.headers.inject([]) do |arr, (k,v)|
24 | arr + v.map {|x| [k,x] }
25 | end
26 | response = http_client.request(request.http_method, request.url, nil, request.body, headers)
27 | response_headers = response.header.all.inject({}) do |h, (k, v)|
28 | k.downcase!
29 | if h[k].nil?
30 | h[k] = [v]
31 | else
32 | h[k] << v
33 | end
34 | h
35 | end
36 | parse_http_part(response_headers, response.content, response.status, response.contenttype)
37 | end
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/handsoap/http/drivers/mock_driver.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'handsoap/http/drivers/abstract_driver'
3 |
4 | module Handsoap
5 | module Http
6 | module Drivers
7 | # A mock driver for your testing needs.
8 | #
9 | # To use it, create a new instance and assign to +Handsoap::Http.drivers+. Then configure +Handsoap::Service+ to use it:
10 | #
11 | # Handsoap::Http.drivers[:mock] = Handsoap::Http::Drivers::MockDriver.new :status => 200, :headers => headers, :content => body
12 | # Handsoap.http_driver = :mock
13 | #
14 | # Remember that headers should use \r\n, rather than \n.
15 | class MockDriver < AbstractDriver
16 | attr_accessor :mock, :last_request, :is_loaded
17 |
18 | def initialize(mock)
19 | @mock = mock
20 | @is_loaded = false
21 | end
22 |
23 | def load!
24 | is_loaded = true
25 | end
26 |
27 | def new
28 | self
29 | end
30 |
31 | def send_http_request(request)
32 | @last_request = request
33 | (mock.kind_of? Hash) ?
34 | parse_http_part(mock[:headers], mock[:content], mock[:status], mock[:content_type]) : mock
35 | end
36 | end
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/lib/handsoap/http/drivers/net_http_driver.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | module Handsoap
4 | module Http
5 | module Drivers
6 | class NetHttpDriver < AbstractDriver
7 | def self.load!
8 | require 'net/http'
9 | require 'uri'
10 | end
11 |
12 | def send_http_request(request)
13 | url = request.url
14 | unless url.kind_of? ::URI::Generic
15 | url = ::URI.parse(url)
16 | end
17 |
18 | path = url.request_uri
19 |
20 | http_request = case request.http_method
21 | when :get
22 | Net::HTTP::Get.new(path)
23 | when :post
24 | Net::HTTP::Post.new(path)
25 | when :put
26 | Net::HTTP::Put.new(path)
27 | when :delete
28 | Net::HTTP::Delete.new(path)
29 | else
30 | raise "Unsupported request method #{request.http_method}"
31 | end
32 |
33 | http_client = Net::HTTP.new(url.host, url.port)
34 |
35 | #http_client.read_timeout = 120
36 | http_client.open_timeout = Handsoap.timeout
37 | http_client.read_timeout = Handsoap.timeout
38 |
39 | http_client.use_ssl = true if url.scheme == 'https'
40 |
41 | if request.username && request.password
42 | # TODO: http://codesnippets.joyent.com/posts/show/1075
43 | http_request.basic_auth request.username, request.password
44 | end
45 | request.headers.each do |k, values|
46 | values.each do |v|
47 | http_request.add_field(k, v)
48 | end
49 | end
50 | http_request.body = request.body
51 | # require 'stringio'
52 | # debug_output = StringIO.new
53 | # http_client.set_debug_output debug_output
54 | http_response = http_client.start do |client|
55 | # requesting gzipped response if server can provide it
56 | http_request['Accept-Encoding'] = 'gzip'
57 | client.request(http_request)
58 | end
59 | # puts debug_output.string
60 | # hacky-wacky
61 | def http_response.get_headers
62 | @header.inject({}) do |h, (k, v)|
63 | h[k.downcase] = v
64 | h
65 | end
66 | end
67 | # net/http only supports basic auth. We raise a warning if the server requires something else.
68 | if http_response.code == 401 && http_response.get_headers['www-authenticate']
69 | auth_type = http_response.get_headers['www-authenticate'].chomp.match(/\w+/)[0].downcase
70 | if auth_type != "basic"
71 | raise "Authentication type #{auth_type} is unsupported by net/http"
72 | end
73 | end
74 |
75 | # http://stackoverflow.com/questions/13397119/ruby-nethttp-not-decoding-gzip
76 | body =\
77 | begin
78 | Zlib::GzipReader.new(StringIO.new(http_response.body)).read
79 | rescue Zlib::GzipFile::Error, Zlib::Error # Not gzipped
80 | http_response.body
81 | end
82 | parse_http_part(http_response.get_headers, body, http_response.code)
83 | end
84 | end
85 | end
86 | end
87 | end
88 |
--------------------------------------------------------------------------------
/lib/handsoap/http/part.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | module Handsoap
4 | module Http
5 |
6 | # Represents a HTTP Part.
7 | # For simple HTTP-requests there is only one part, which is the response.
8 | class Part
9 | attr_reader :headers, :body, :parts
10 |
11 | def initialize(headers, body, parts = nil)
12 | @headers = headers
13 | @body = body
14 | @parts = parts
15 | end
16 |
17 | # Returns a header.
18 | # Returns String | Array | nil
19 | def [](key)
20 | key.to_s.downcase!
21 | (@headers[key] && @headers[key].length == 1) ? @headers[key].first : @headers[key]
22 | end
23 |
24 | # Returns the mime-type part of the content-type header
25 | def mime_type
26 | @headers['content-type'].first.match(/^[^;]+/).to_s if @headers['content-type']
27 | end
28 |
29 | # Returns the charset part of the content-type header
30 | def charset
31 | if @headers['content-type']
32 | match_data = @headers['content-type'].first.match(/^[^;]+; charset=([^;]+)/)
33 | if match_data
34 | match_data[1].to_s
35 | end
36 | end
37 | end
38 |
39 | def multipart?
40 | !! @parts
41 | end
42 |
43 | def inspect(&block)
44 | str = inspect_head
45 | if headers.any?
46 | str << headers.map { |key,values| values.map {|value| normalize_header_key(key) + ": " + value + "\n" }.join("") }.join("")
47 | end
48 | if body
49 | if multipart?
50 | if block_given?
51 | str << parts.map{|part| part.inspect(&block) }.join("")
52 | else
53 | str << parts.map{|part| part.inspect }.join("")
54 | end
55 | elsif body
56 | str << "---\n"
57 | if block_given?
58 | str << yield(body)
59 | else
60 | str << body
61 | end
62 | str << "\n---"
63 | end
64 | end
65 | end
66 |
67 | private
68 |
69 | def inspect_head
70 | "--- Part ---\n"
71 | end
72 |
73 | def normalize_header_key(key)
74 | key.split("-").map{|s| s.downcase.capitalize }.join("-")
75 | end
76 | end
77 | end
78 | end
79 |
--------------------------------------------------------------------------------
/lib/handsoap/http/request.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | module Handsoap
4 | module Http
5 |
6 | # Represents a HTTP Request.
7 | class Request
8 | attr_reader :url, :http_method, :headers, :body, :username, :password, :trust_ca_file, :client_cert_file, :client_cert_key_file,:ssl_verify_mode
9 | attr_writer :body, :http_method
10 | def initialize(url, http_method = :get)
11 | @url = url
12 | @http_method = http_method
13 | @headers = {}
14 | @body = nil
15 | @username = nil
16 | @password = nil
17 | @trust_ca_file = nil
18 | @client_cert_file = nil
19 | @client_cert_key_file = nil
20 | @ssl_verify_mode = nil
21 | end
22 | def set_trust_ca_file(ca_file)
23 | @trust_ca_file = ca_file
24 | end
25 | def set_client_cert_files(client_cert_file,client_cert_key_file)
26 | @client_cert_file = client_cert_file
27 | @client_cert_key_file = client_cert_key_file
28 | end
29 | def set_ssl_verify_mode(mode)
30 | @ssl_verify_mode = mode
31 | end
32 | def set_auth(username, password)
33 | @username = username
34 | @password = password
35 | end
36 | def add_header(key, value)
37 | if @headers[key].nil?
38 | @headers[key] = []
39 | end
40 | @headers[key] << value
41 | end
42 | def set_header(key, value)
43 | if value.nil?
44 | @headers[key] = nil
45 | else
46 | @headers[key] = [value]
47 | end
48 | end
49 | def inspect
50 | "===============\n" +
51 | "--- Request ---\n" +
52 | "#{http_method.to_s.upcase} #{url}\n" +
53 | (
54 | if username && password
55 | "Auth credentials: #{username}:#{password}\n"
56 | else
57 | ""
58 | end
59 | ) +
60 | (
61 | if headers.any?
62 | "---\n" + headers.map { |key,values| values.map {|value| key + ": " + value + "\n" }.join("") }.join("")
63 | else
64 | ""
65 | end
66 | ) +
67 | (
68 | if body
69 | "---\n" + body
70 | else
71 | ""
72 | end
73 | )
74 | end
75 | end
76 | end
77 | end
78 |
--------------------------------------------------------------------------------
/lib/handsoap/http/response.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'handsoap/http/part'
3 |
4 | module Handsoap
5 | module Http
6 |
7 | # Represents a HTTP Response.
8 | class Response < Part
9 | attr_reader :status
10 | def initialize(status, headers, body, parts = nil)
11 | @status = status.to_i
12 | super(headers, body, parts)
13 | end
14 | def primary_part
15 | # Strictly speaking, the main part doesn't need to be first, but until proven otherwise, we'll just assume that.
16 | if multipart?
17 | parts.first
18 | else
19 | self
20 | end
21 | end
22 | private
23 | def inspect_head
24 | "--- Response ---\n" + "HTTP Status: #{status}\n"
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/handsoap/parser.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'httpclient'
3 | require 'openssl'
4 | require 'nokogiri'
5 |
6 | module Handsoap
7 | # Classes for parsing a WSDL.
8 | #
9 | # Used internally by the generator.
10 | module Parser #:nodoc: all
11 | class Interface
12 | attr_accessor :name, :operations
13 |
14 | def initialize(name, operations = [])
15 | @name = name
16 | @operations = operations || []
17 | end
18 | end
19 |
20 | class Binding
21 | attr_accessor :name, :protocol, :interface, :transport, :style, :encoding, :verb, :actions
22 |
23 | def initialize(name, optional = {})
24 | @name = name
25 | @actions = optional[:actions] || []
26 | @protocol = optional[:protocol]
27 | @interface = optional[:interface]
28 | @transport = optional[:transport]
29 | @style = optional[:style]
30 | @encoding = optional[:encoding]
31 | @verb = optional[:verb]
32 | end
33 | end
34 |
35 | class Endpoint
36 | attr_accessor :name, :protocol, :binding, :url
37 |
38 | def initialize(name, protocol, binding, url)
39 | @name = name
40 | @protocol = protocol
41 | @binding = binding
42 | @url = url
43 | end
44 | end
45 |
46 | class Operation
47 | attr_accessor :name, :input, :output
48 |
49 | def initialize(name, optional = {})
50 | @name = name
51 | @input = optional[:input]
52 | @output = optional[:output]
53 | end
54 | end
55 |
56 | class Action
57 | attr_accessor :name, :soap_action, :location
58 |
59 | def initialize(name, optional = {})
60 | @name = name
61 | @soap_action = optional[:soap_action]
62 | @location = optional[:location]
63 | end
64 | end
65 |
66 | class Wsdl
67 | attr_reader :url
68 |
69 | def initialize(doc, url = "void://")
70 | @doc = doc
71 | @url = url
72 | end
73 |
74 | def self.read(url)
75 | if url =~ /^http(s?):/
76 | request = ::HTTPClient.new
77 | request.ssl_config.verify_mode = ::OpenSSL::SSL::VERIFY_NONE
78 | response = request.get(url)
79 | xml_src = response.content
80 | else
81 | xml_src = Kernel.open(url).read
82 | end
83 | self.new(Nokogiri.XML(xml_src), url)
84 | end
85 |
86 | def ns
87 | {
88 | 'wsdl1' => "http://schemas.xmlsoap.org/wsdl/",
89 | 'wsdl2' => "http://www.w3.org/ns/wsdl/",
90 | 'soap11' => "http://schemas.xmlsoap.org/wsdl/soap/",
91 | 'soap12' => "http://schemas.xmlsoap.org/wsdl/soap12/",
92 | 'http' => "http://schemas.xmlsoap.org/wsdl/http/"
93 | }
94 | end
95 | private :ns
96 |
97 | def protocol_from_ns(node)
98 | href = node.namespace.respond_to?(:href) ? node.namespace.href : @doc.namespaces["xmlns:#{node.namespace}"]
99 | case href
100 | when "http://schemas.xmlsoap.org/wsdl/soap/"
101 | :soap11
102 | when "http://schemas.xmlsoap.org/wsdl/soap12/"
103 | :soap12
104 | when "http://schemas.xmlsoap.org/wsdl/http/"
105 | :http
106 | else
107 | raise "Unknown namespace '#{href}'"
108 | end
109 | end
110 | private :protocol_from_ns
111 |
112 | def is_wsdl2?(node)
113 | href = node.namespace.respond_to?(:href) ? node.namespace.href : @doc.namespaces["xmlns:#{node.namespace}"]
114 | case href
115 | when "http://schemas.xmlsoap.org/wsdl/"
116 | false
117 | when "http://www.w3.org/ns/wsdl/"
118 | true
119 | else
120 | raise "Unknown namespace '#{href}'"
121 | end
122 | end
123 | private :is_wsdl2?
124 |
125 | def service
126 | services = @doc.xpath("//wsdl1:service|//wsdl2:service", ns)
127 | raise "Expected exactly 1 service in WSDL" if services.length != 1
128 | services[0][:name]
129 | end
130 |
131 | def interface
132 | all_interfaces = self.interfaces
133 | if all_interfaces.length != 1
134 | # There are more than one portType, so we take a pick
135 | all_bindings = self.bindings
136 | all_interfaces.each do |interface|
137 | b = all_bindings.find {|binding| binding.name == interface.name }
138 | if [:soap11, :soap12].include? b.protocol
139 | return interface
140 | end
141 | end
142 | raise "Can't find a suitable soap 1.1 or 1.2 interface/portType in WSDL"
143 | end
144 | all_interfaces.first
145 | end
146 |
147 | def target_ns
148 | @doc.root[:targetNamespace] || raise("Attribute targetNamespace not defined")
149 | end
150 |
151 | def preferred_protocol
152 | e = endpoints
153 | if e.select { |endpoint| endpoint.protocol == :soap12 }.any?
154 | :soap12
155 | elsif e.select { |endpoint| endpoint.protocol == :soap11 }.any?
156 | :soap11
157 | else
158 | raise "Can't find any soap 1.1 or soap 1.2 endpoints"
159 | end
160 | end
161 |
162 | def interfaces
163 | @doc.xpath("//wsdl1:portType|//wsdl2:interface", ns).map do |port_type|
164 | operations = port_type.xpath("./wsdl1:operation|./wsdl2:operation", ns).map do |operation|
165 | if is_wsdl2?(operation)
166 | input_node = operation.xpath("./wsdl2:input", ns).first
167 | input = input_node ? input_node[:element] : nil
168 | output_node = operation.xpath("./wsdl2:output", ns).first
169 | output = output_node ? output_node[:element] : nil
170 | else
171 | input_node = operation.xpath("./wsdl1:input", ns).first
172 | input = input_node ? input_node[:message] : nil
173 | output_node = operation.xpath("./wsdl1:output", ns).first
174 | output = output_node ? output_node[:message] : nil
175 | end
176 | Operation.new(operation[:name], :input => input, :output => output)
177 | end
178 | Interface.new(port_type[:name], operations)
179 | end
180 | end
181 |
182 | def endpoints
183 | @doc.xpath("//wsdl1:service/wsdl1:port|//wsdl2:service/wsdl2:endpoint", ns).map do |port|
184 | binding = port[:binding]
185 | if is_wsdl2?(port)
186 | location = port[:address]
187 | protocol = :binding
188 | else
189 | address = port.xpath("./soap11:address|./soap12:address|./http:address", ns).first
190 | location = address[:location]
191 | protocol = protocol_from_ns(address)
192 | end
193 | Endpoint.new(port[:name], protocol, binding, location)
194 | end
195 | end
196 |
197 | def bindings
198 | @doc.xpath("//wsdl1:binding|//wsdl2:binding", ns).map do |binding|
199 | raise "WSDL 2.0 not supported" if is_wsdl2?(binding)
200 | soap_binding = binding.xpath("./soap11:binding|./soap12:binding|./http:binding", ns).first
201 | protocol = protocol_from_ns(soap_binding)
202 | actions = []
203 | style = nil
204 | encoding = nil
205 | actions = binding.xpath("./wsdl1:operation", ns).map do |operation|
206 | soap_operation = operation.xpath("./soap11:operation|./soap12:operation|./http:operation", ns).first
207 | if soap_operation[:style]
208 | raise "Mixed styles not supported" if style && style != soap_operation[:style]
209 | style = soap_operation[:style]
210 | end
211 | xquery = []
212 | ['soap11', 'soap12', 'http'].each do |version|
213 | ['input', 'output'].each do |message_name|
214 | ['header', 'body'].each do |part_name|
215 | xquery << "./wsdl1:#{message_name}/#{version}:#{part_name}"
216 | end
217 | end
218 | end
219 | operation.xpath(xquery.join('|'), ns).each do |thing|
220 | raise "Mixed encodings not supported" if encoding && encoding != thing[:use]
221 | encoding = thing[:use]
222 | end
223 | Action.new(
224 | operation[:name],
225 | :soap_action => soap_operation[:soapAction],
226 | :location => soap_operation[:location])
227 | end
228 | Binding.new(
229 | binding[:name],
230 | :protocol => protocol,
231 | :interface => binding[:type],
232 | :transport => soap_binding[:transport],
233 | :style => style,
234 | :encoding => encoding,
235 | :verb => soap_binding[:verb],
236 | :actions => actions)
237 | end
238 | end
239 | end
240 | end
241 | end
242 |
--------------------------------------------------------------------------------
/lib/handsoap/service.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'time'
3 | require 'handsoap/xml_mason'
4 | require 'handsoap/xml_query_front'
5 | require 'handsoap/http'
6 | require 'handsoap/deferred'
7 |
8 | module Handsoap
9 |
10 | def self.store_raw_response=(boolean)
11 | @store_raw_response = boolean
12 | end
13 |
14 | def self.store_raw_response?
15 | !!@store_raw_response
16 | end
17 |
18 | def self.http_driver
19 | @http_driver || (self.http_driver = :curb)
20 | end
21 |
22 | def self.http_driver=(driver)
23 | @http_driver = driver
24 | Handsoap::Http.drivers[driver].load!
25 | return driver
26 | end
27 |
28 | def self.xml_query_driver
29 | @xml_query_driver || (self.xml_query_driver = :nokogiri)
30 | end
31 |
32 | def self.xml_query_driver=(driver)
33 | @xml_query_driver = Handsoap::XmlQueryFront.load_driver!(driver)
34 | end
35 |
36 | # Sets the timeout
37 | def self.timeout=(timeout)
38 | @timeout = timeout
39 | end
40 |
41 | # fetches the timeout
42 | # the default timeout is set to 60seconds
43 | def self.timeout
44 | @timeout || (self.timeout = 60)
45 | end
46 |
47 | # Tell Handsoap to follow redirects
48 | def self.follow_redirects!
49 | @follow_redirects = true
50 | end
51 |
52 | # Check whether Handsoap should follow redirects
53 | def self.follow_redirects?
54 | @follow_redirects || false
55 | end
56 |
57 | # Sets the max number of redirects
58 | def self.max_redirects=(max_redirects)
59 | @max_redirects = max_redirects
60 | end
61 |
62 | # Fetches the max number of redirects
63 | # The default is 1
64 | def self.max_redirects
65 | @max_redirects || (self.max_redirects = 1)
66 | end
67 |
68 | # Wraps SOAP errors in a standard class.
69 | class Fault < StandardError
70 | attr_reader :code, :reason, :details
71 |
72 | def initialize(code, reason, details)
73 | @code = code
74 | @reason = reason
75 | @details = details
76 | end
77 |
78 | def to_s
79 | "Handsoap::Fault { :code => '#{@code}', :reason => '#{@reason}' }"
80 | end
81 |
82 | def self.from_xml(node, options = { :namespace => nil })
83 | if not options[:namespace]
84 | raise "Missing option :namespace"
85 | end
86 |
87 | ns = { 'env' => options[:namespace] }
88 |
89 | # tries to find SOAP1.2 fault code
90 | fault_code = node.xpath("./env:Code/env:Value", ns).to_s
91 |
92 | # if no SOAP1.2 fault code was found, try the SOAP1.1 way
93 | unless fault_code
94 | fault_code = node.xpath('./faultcode', ns).to_s
95 |
96 | # if fault_code is blank, add the namespace and try again
97 | unless fault_code
98 | fault_code = node.xpath("//env:faultcode", ns).to_s
99 | end
100 | end
101 |
102 | # tries to find SOAP1.2 reason
103 | reason = node.xpath("./env:Reason/env:Text[1]", ns).to_s
104 |
105 | # if no SOAP1.2 faultstring was found, try the SOAP1.1 way
106 | unless reason
107 | reason = node.xpath('./faultstring', ns).to_s
108 |
109 | # if reason is blank, add the namespace and try again
110 | unless reason
111 | reason = node.xpath("//env:faultstring", ns).to_s
112 | end
113 | end
114 |
115 | details = node.xpath('./detail/*', ns)
116 | self.new(fault_code, reason, details)
117 | end
118 | end
119 |
120 | class HttpError < StandardError
121 | attr_reader :response
122 | def initialize(response)
123 | @response = response
124 | super()
125 | end
126 | end
127 |
128 | class SoapResponse
129 |
130 | attr_reader :document, :http_response, :raw_xml
131 |
132 | def initialize(document, http_response, raw_xml=nil)
133 | @document = document
134 | @http_response = http_response
135 | @raw_xml = raw_xml
136 | end
137 |
138 | def method_missing(method, *args, &block)
139 | if @document.respond_to?(method)
140 | @document.__send__ method, *args, &block
141 | else
142 | super
143 | end
144 | end
145 |
146 | end
147 |
148 | class AsyncDispatch
149 | attr_reader :action, :options, :request_block, :response_block
150 | def request(action, options = { :soap_action => :auto }, &block)
151 | @action = action
152 | @options = options
153 | @request_block = block
154 | end
155 | def response(&block)
156 | @response_block = block
157 | end
158 | end
159 |
160 | class Service
161 | @@logger = nil
162 | def self.logger=(io)
163 | @@logger = io
164 | end
165 | # Sets the endpoint for the service.
166 | # Arguments:
167 | # :uri => endpoint uri of the service. Required.
168 | # :version => 1 | 2
169 | # :envelope_namespace => Namespace of SOAP-envelope
170 | # :request_content_type => Content-Type of HTTP request.
171 | # You must supply either :version or both :envelope_namspace and :request_content_type.
172 | # :version is simply a shortcut for default values.
173 | def self.endpoint(args = {})
174 | @uri = args[:uri] || raise("Missing option :uri")
175 | if args[:version]
176 | soap_namespace = { 1 => 'http://schemas.xmlsoap.org/soap/envelope/', 2 => 'http://www.w3.org/2003/05/soap-envelope' }
177 | raise("Unknown protocol version '#{@protocol_version.inspect}'") if soap_namespace[args[:version]].nil?
178 | @envelope_namespace = soap_namespace[args[:version]]
179 | @request_content_type = args[:version] == 1 ? "text/xml" : "application/soap+xml"
180 | end
181 | @envelope_namespace = args[:envelope_namespace] unless args[:envelope_namespace].nil?
182 | @request_content_type = args[:request_content_type] unless args[:request_content_type].nil?
183 | if @envelope_namespace.nil? || @request_content_type.nil?
184 | raise("Missing option :envelope_namespace, :request_content_type or :version")
185 | end
186 | end
187 | def self.envelope_namespace
188 | @envelope_namespace
189 | end
190 | def self.request_content_type
191 | @request_content_type
192 | end
193 | def self.uri
194 | @uri
195 | end
196 | @@instance = {}
197 | def self.instance
198 | @@instance[self.to_s] ||= self.new
199 | end
200 | def self.method_missing(method, *args, &block)
201 | if instance.respond_to?(method)
202 | instance.__send__ method, *args, &block
203 | else
204 | super
205 | end
206 | end
207 | def envelope_namespace
208 | self.class.envelope_namespace
209 | end
210 | def request_content_type
211 | self.class.request_content_type
212 | end
213 | def uri
214 | self.class.uri
215 | end
216 | def http_driver_instance
217 | Handsoap::Http.drivers[Handsoap.http_driver].new
218 | end
219 | # Creates an XML document and sends it over HTTP.
220 | #
221 | # +action+ is the QName of the rootnode of the envelope.
222 | #
223 | # +options+ currently takes one option +:soap_action+, which can be one of:
224 | #
225 | # :auto sends a SOAPAction http header, deduced from the action name. (This is the default)
226 | #
227 | # +String+ sends a SOAPAction http header.
228 | #
229 | # +nil+ sends no SOAPAction http header.
230 | def invoke(action, options = { :soap_action => :auto, :http_options => nil }, &block) # :yields: Handsoap::XmlMason::Element
231 | if action
232 | if options.kind_of? String
233 | options = { :soap_action => options }
234 | end
235 | if options[:soap_action] == :auto
236 | options[:soap_action] = action.gsub(/^.+:/, "")
237 | elsif options[:soap_action] == :none
238 | options[:soap_action] = nil
239 | end
240 | doc = make_envelope do |body,header|
241 | if options[:soap_header]
242 | iterate_hash_array(header, options[:soap_header])
243 | end
244 |
245 | if options[:soap_body]
246 | action_hash = { action => options[:soap_body] }
247 | iterate_hash_array(body, action_hash)
248 | else
249 | body.add(action)
250 | end
251 | end
252 | if block_given?
253 | yield doc.find(action)
254 | end
255 | # ready to dispatch
256 | headers = {
257 | "Content-Type" => "#{self.request_content_type}; charset=UTF-8"
258 | }
259 | headers["SOAPAction"] = options[:soap_action] unless options[:soap_action].nil?
260 | on_before_dispatch(doc)
261 | request = make_http_request(self.uri, doc.to_s, headers, options[:http_options])
262 | response = http_driver_instance.send_http_request(request)
263 | parse_http_response(response)
264 | end
265 | end
266 |
267 |
268 |
269 | # Async invocation
270 | #
271 | # Creates an XML document and sends it over HTTP.
272 | #
273 | # +user_block+ Block from userland
274 | def async(user_block, &block) # :yields: Handsoap::AsyncDispatch
275 | # Setup userland handlers
276 | userland = Handsoap::Deferred.new
277 | user_block.call(userland)
278 | raise "Missing :callback" unless userland.has_callback?
279 | raise "Missing :errback" unless userland.has_errback?
280 | # Setup service level handlers
281 | dispatcher = Handsoap::AsyncDispatch.new
282 | yield dispatcher
283 | raise "Missing :request_block" unless dispatcher.request_block
284 | raise "Missing :response_block" unless dispatcher.response_block
285 | # Done with the external configuration .. let's roll
286 | action = dispatcher.action
287 | options = dispatcher.options
288 | if action #TODO: What if no action ?!?
289 | if options.kind_of? String
290 | options = { :soap_action => options }
291 | end
292 | if options[:soap_action] == :auto
293 | options[:soap_action] = action.gsub(/^.+:/, "")
294 | elsif options[:soap_action] == :none
295 | options[:soap_action] = nil
296 | end
297 | doc = make_envelope do |body,header|
298 | if options[:soap_header]
299 | iterate_hash_array(header, options[:soap_header])
300 | end
301 |
302 | if options[:soap_body]
303 | action_hash = { action => options[:soap_body] }
304 | iterate_hash_array(body, action_hash)
305 | else
306 | body.add(action)
307 | end
308 | end
309 | dispatcher.request_block.call doc.find(action)
310 | # ready to dispatch
311 | headers = {
312 | "Content-Type" => "#{self.request_content_type}; charset=UTF-8"
313 | }
314 | headers["SOAPAction"] = options[:soap_action] unless options[:soap_action].nil?
315 | on_before_dispatch(doc)
316 | request = make_http_request(self.uri, doc.to_s, headers)
317 | driver = self.http_driver_instance
318 | if driver.respond_to? :send_http_request_async
319 | deferred = driver.send_http_request_async(request)
320 | else
321 | # Fake async for sync-only drivers
322 | deferred = Handsoap::Deferred.new
323 | begin
324 | deferred.trigger_callback driver.send_http_request(request)
325 | rescue
326 | deferred.trigger_errback $!
327 | end
328 | end
329 | deferred.callback do |http_response|
330 | begin
331 | # Parse response
332 | response_document = parse_http_response(http_response)
333 | # Transform response
334 | result = dispatcher.response_block.call(response_document)
335 | # Yield to userland code
336 | userland.trigger_callback(result)
337 | rescue
338 | userland.trigger_errback $!
339 | end
340 | end
341 | # Pass driver level errors on
342 | deferred.errback do |ex|
343 | userland.trigger_errback(ex)
344 | end
345 | end
346 | return nil
347 | end
348 |
349 |
350 |
351 | #Used to iterate over a Hash, that can include Hash, Array or String/Float/Integer etc and insert it in the correct element.
352 | def iterate_hash_array(element, hash_array)
353 | hash_array.each {|hash| iterate_hash_array(element, hash) } if hash_array.is_a?(Array)
354 | hash_array.each do |name,value|
355 | if value.is_a?(Hash)
356 | element.add(name){|subelement| iterate_hash_array(subelement, value)}
357 | elsif value.is_a?(Array)
358 | element.add(name) do |subelement|
359 | value.each do |item|
360 | iterate_hash_array(subelement, item) if item.is_a?(Hash)
361 | end
362 | end
363 | else
364 | element.add name, value.to_s
365 | end
366 | end
367 | end
368 |
369 | # Hook that is called when a new request document is created.
370 | #
371 | # You can override this to add namespaces and other elements that are common to all requests (Such as authentication).
372 | def on_create_document(doc)
373 | end
374 | # Hook that is called before the message is dispatched.
375 | #
376 | # You can override this to provide filtering and logging.
377 | def on_before_dispatch(doc)
378 | end
379 | # Hook that is called after the http_client is created.
380 | #
381 | # You can override this to customize the http_client
382 | def on_after_create_http_request(http_request)
383 | end
384 | # Hook that is called when there is a response.
385 | #
386 | # You can override this to register common namespaces, useful for parsing the document.
387 | def on_response_document(doc)
388 | end
389 | # Hook that is called if there is a HTTP level error.
390 | #
391 | # Default behaviour is to raise an error.
392 | def on_http_error(response)
393 | raise HttpError, response
394 | end
395 | # Hook that is called if the dispatch returns a +Fault+.
396 | #
397 | # Default behaviour is to raise the Fault, but you can override this to provide logging and more fine-grained handling faults.
398 | #
399 | # See also: parse_soap_fault
400 | def on_fault(fault)
401 | raise fault
402 | end
403 | # Hook that is called if the response does not contain a valid SOAP enevlope.
404 | #
405 | # Default behaviour is to raise an error
406 | #
407 | # Note that if your service has operations that are one-way, you shouldn't raise an error here.
408 | # This is however a fairly exotic case, so that is why the default behaviour is to raise an error.
409 | def on_missing_document(response)
410 | raise "The response is not a valid SOAP envelope"
411 | end
412 |
413 | def debug(message = nil) #:nodoc:
414 | if @@logger
415 | if message
416 | @@logger.puts(message)
417 | end
418 | if block_given?
419 | yield @@logger
420 | end
421 | end
422 | end
423 |
424 | def make_http_request(uri, post_body, headers, http_options=nil)
425 | request = Handsoap::Http::Request.new(uri, :post)
426 |
427 | # SSL CA AND CLIENT CERTIFICATES
428 | if http_options
429 | request.set_trust_ca_file(http_options[:trust_ca_file]) if http_options[:trust_ca_file]
430 | request.set_client_cert_files(http_options[:client_cert_file], http_options[:client_cert_key_file]) if http_options[:client_cert_file] && http_options[:client_cert_key_file]
431 | request.set_ssl_verify_mode(http_options[:ssl_verify_mode]) if http_options[:ssl_verify_mode]
432 | end
433 |
434 | headers.each do |key, value|
435 | request.add_header(key, value)
436 | end
437 | request.body = post_body
438 | debug do |logger|
439 | logger.puts request.inspect
440 | end
441 | on_after_create_http_request(request)
442 | request
443 | end
444 |
445 | # Start the parsing pipe-line.
446 | # There are various stages and hooks for each, so that you can override those in your service classes.
447 | def parse_http_response(response)
448 | debug do |logger|
449 | logger.puts(response.inspect do |body|
450 | Handsoap.pretty_format_envelope(body.force_encoding('utf-8')).chomp
451 | end)
452 | end
453 | raw_xml_document = response.primary_part.body.force_encoding('utf-8')
454 | xml_document = parse_soap_response_document(raw_xml_document)
455 | soap_fault = parse_soap_fault(xml_document)
456 | # Is the response a soap-fault?
457 | unless soap_fault.nil?
458 | return on_fault(soap_fault)
459 | end
460 | # Does the http-status indicate an error?
461 | if response.status >= 400
462 | return on_http_error(response)
463 | end
464 | # Does the response contain a valid xml-document?
465 | if xml_document.nil?
466 | return on_missing_document(response)
467 | end
468 | # Everything seems in order.
469 | on_response_document(xml_document)
470 | args = [xml_document, response]
471 | args << raw_xml_document if Handsoap.store_raw_response?
472 | return SoapResponse.new(*args)
473 | end
474 |
475 | # Creates a standard SOAP envelope and yields the +Body+ element.
476 | def make_envelope # :yields: Handsoap::XmlMason::Element
477 | doc = XmlMason::Document.new do |doc|
478 | doc.alias 'env', self.envelope_namespace
479 | doc.add "env:Envelope" do |env|
480 | env.add "*:Header"
481 | env.add "*:Body"
482 | end
483 | end
484 | self.class.fire_on_create_document doc # deprecated .. use instance method
485 | on_create_document(doc)
486 | if block_given?
487 | yield doc.find("Body"),doc.find("Header")
488 | end
489 | return doc
490 | end
491 |
492 | # String -> [XmlDocument | nil]
493 | def parse_soap_response_document(http_body)
494 | begin
495 | Handsoap::XmlQueryFront.parse_string(http_body, Handsoap.xml_query_driver)
496 | rescue Handsoap::XmlQueryFront::ParseError => ex
497 | nil
498 | end
499 | end
500 |
501 | # XmlDocument -> [Fault | nil]
502 | def parse_soap_fault(document)
503 | unless document.nil?
504 | node = document.xpath('/env:Envelope/env:Body/descendant-or-self::env:Fault', { 'env' => self.envelope_namespace }).first
505 | Fault.from_xml(node, :namespace => self.envelope_namespace) unless node.nil?
506 | end
507 | end
508 | end
509 |
510 | def self.pretty_format_envelope(xml_string)
511 | if /^<.*:Envelope/.match(xml_string)
512 | begin
513 | doc = Handsoap::XmlQueryFront.parse_string(xml_string, Handsoap.xml_query_driver)
514 | rescue
515 | return xml_string
516 | end
517 | return doc.to_xml
518 | # return "\n\e[1;33m" + doc.to_s + "\e[0m"
519 | end
520 | return xml_string
521 | end
522 | end
523 |
524 | # Legacy/BC code here. This shouldn't be used in new applications.
525 | module Handsoap
526 | class Service
527 | # Registers a simple method mapping without any arguments and no parsing of response.
528 | #
529 | # This is deprecated
530 | def self.map_method(mapping)
531 | if @mapping.nil?
532 | @mapping = {}
533 | end
534 | @mapping.merge! mapping
535 | end
536 | def self.get_mapping(name)
537 | @mapping[name] if @mapping
538 | end
539 | def method_missing(method, *args, &block)
540 | action = self.class.get_mapping(method)
541 | if action
542 | invoke(action, *args, &block)
543 | else
544 | super
545 | end
546 | end
547 | # Registers a block to call when a request document is created.
548 | #
549 | # This is deprecated, in favour of #on_create_document
550 | def self.on_create_document(&block)
551 | @create_document_callback = block
552 | end
553 | def self.fire_on_create_document(doc)
554 | if @create_document_callback
555 | @create_document_callback.call doc
556 | end
557 | end
558 | private
559 | # Helper to serialize a node into a ruby string
560 | #
561 | # *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_s
562 | def xml_to_str(node, xquery = nil)
563 | n = xquery ? node.xpath(xquery, ns).first : node
564 | return if n.nil?
565 | n.to_s
566 | end
567 | alias_method :xml_to_s, :xml_to_str
568 | # Helper to serialize a node into a ruby integer
569 | #
570 | # *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_i
571 | def xml_to_int(node, xquery = nil)
572 | n = xquery ? node.xpath(xquery, ns).first : node
573 | return if n.nil?
574 | n.to_s.to_i
575 | end
576 | alias_method :xml_to_i, :xml_to_int
577 | # Helper to serialize a node into a ruby float
578 | #
579 | # *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_f
580 | def xml_to_float(node, xquery = nil)
581 | n = xquery ? node.xpath(xquery, ns).first : node
582 | return if n.nil?
583 | n.to_s.to_f
584 | end
585 | alias_method :xml_to_f, :xml_to_float
586 | # Helper to serialize a node into a ruby boolean
587 | #
588 | # *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_boolean
589 | def xml_to_bool(node, xquery = nil)
590 | n = xquery ? node.xpath(xquery, ns).first : node
591 | return if n.nil?
592 | n.to_s == "true"
593 | end
594 | # Helper to serialize a node into a ruby Time object
595 | #
596 | # *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_date
597 | def xml_to_date(node, xquery = nil)
598 | n = xquery ? node.xpath(xquery, ns).first : node
599 | return if n.nil?
600 | Time.iso8601(n.to_s)
601 | end
602 | end
603 | end
604 |
--------------------------------------------------------------------------------
/lib/handsoap/xml_mason.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | module Handsoap
4 |
5 | # XmlMason is a simple XML builder.
6 | module XmlMason
7 |
8 | XML_ESCAPE = { '&' => '&', '"' => '"', '>' => '>', '<' => '<' }
9 |
10 | def self.xml_escape(s)
11 | s.to_s.gsub(/[&"><]/) { |special| XML_ESCAPE[special] }
12 | end
13 |
14 | class Node
15 | def initialize
16 | @namespaces = {}
17 | end
18 | def add(node_name, value = nil, options = {}) # :yields: Handsoap::XmlMason::Element
19 | prefix, name = parse_ns(node_name)
20 | node = append_child Element.new(self, prefix, name, value, options)
21 | if block_given?
22 | yield node
23 | end
24 | end
25 | # Registers a prefix for a namespace.
26 | #
27 | # You must register a namespace, before you can refer it.
28 | def alias(prefix, namespaces)
29 | @namespaces[prefix] = namespaces
30 | end
31 | # Finds the first element whos +node_name+ equals +name+
32 | #
33 | # Doesn't regard namespaces/prefixes.
34 | def find(name)
35 | raise NotImplementedError.new
36 | end
37 | # Finds all elements whos +node_name+ equals +name+
38 | #
39 | # Doesn't regard namespaces/prefixes.
40 | def find_all(name)
41 | raise NotImplementedError.new
42 | end
43 | def parse_ns(name)
44 | matches = name.match /^([^:]+):(.*)$/
45 | if matches
46 | [matches[1] == '*' ? @prefix : matches[1], matches[2]]
47 | else
48 | [nil, name]
49 | end
50 | end
51 | private :parse_ns
52 | end
53 |
54 | class Document < Node
55 | def initialize # :yields: Document
56 | super
57 | @document_element = nil
58 | @xml_header = true
59 | if block_given?
60 | yield self
61 | end
62 | end
63 | def xml_header=(xml_header)
64 | @xml_header = !! xml_header
65 | end
66 | def append_child(node)
67 | if not @document_element.nil?
68 | raise "There can only be one element at the top level."
69 | end
70 | @document_element = node
71 | end
72 | def find(name)
73 | @document_element.find(name)
74 | end
75 | def find_all(name)
76 | @document_element.find_all(name)
77 | end
78 | def get_namespace(prefix)
79 | @namespaces[prefix] || raise("No alias registered for prefix '#{prefix}'")
80 | end
81 | def defines_namespace?(prefix)
82 | false
83 | end
84 | def to_s
85 | if @document_element.nil?
86 | raise "No document element added."
87 | end
88 | (@xml_header ? "\n" : "") + @document_element.to_s
89 | end
90 | end
91 |
92 | class TextNode
93 | def initialize(text)
94 | @text = text
95 | end
96 | def to_s(indentation = '')
97 | XmlMason.xml_escape(@text)
98 | end
99 | end
100 |
101 | class RawContent < TextNode
102 | def to_s(indentation = '')
103 | @text
104 | end
105 | end
106 |
107 | class Element < Node
108 | def initialize(parent, prefix, node_name, value = nil, options = {}) # :yields: Handsoap::XmlMason::Element
109 | super()
110 | # if prefix.to_s == ""
111 | # raise "missing prefix"
112 | # end
113 | @parent = parent
114 | @prefix = prefix
115 | @node_name = node_name
116 | @children = []
117 | @attributes = {}
118 | if options[:attributes]
119 | @attributes = options[:attributes]
120 | end
121 | if not value.nil?
122 | set_value value.to_s, options
123 | end
124 | if block_given?
125 | yield self
126 | end
127 | end
128 | # Returns the document that this element belongs to, or self if this is the document.
129 | def document
130 | @parent.respond_to?(:document) ? @parent.document : @parent
131 | end
132 | # Returns the qname (prefix:nodename)
133 | def full_name
134 | @prefix.nil? ? @node_name : (@prefix + ":" + @node_name)
135 | end
136 | # Adds a child node.
137 | #
138 | # You usually won't need to call this method, but will rather use +add+
139 | def append_child(node)
140 | if value_node?
141 | raise "Element already has a text value. Can't add nodes"
142 | end
143 | @children << node
144 | return node
145 | end
146 | # Sets the inner text of this element.
147 | #
148 | # By default the string is escaped, but you can pass the option flag :raw to inject XML.
149 | #
150 | # You usually won't need to call this method, but will rather use +add+
151 | def set_value(value, options = {})
152 | if @children.length > 0
153 | raise "Element already has children. Can't set value"
154 | end
155 | if options && options.include?(:raw)
156 | @children = [RawContent.new(value)]
157 | else
158 | @children = [TextNode.new(value)]
159 | end
160 | end
161 | # Sets the value of an attribute.
162 | def set_attr(name, value)
163 | full_name = parse_ns(name).join(":")
164 | @attributes[name] = value
165 | end
166 | def find(name)
167 | name = name.to_s if name.kind_of? Symbol
168 | if @node_name == name || full_name == name
169 | return self
170 | end
171 | @children.each do |node|
172 | if node.respond_to? :find
173 | tmp = node.find(name)
174 | if tmp
175 | return tmp
176 | end
177 | end
178 | end
179 | return nil
180 | end
181 | def find_all(name)
182 | name = name.to_s if name.kind_of? Symbol
183 | result = []
184 | if @node_name == name || full_name == name
185 | result << self
186 | end
187 | @children.each do |node|
188 | if node.respond_to? :find
189 | result = result.concat(node.find_all(name))
190 | end
191 | end
192 | return result
193 | end
194 | def value_node?
195 | @children.length == 1 && @children[0].kind_of?(TextNode)
196 | end
197 | def get_namespace(prefix)
198 | @namespaces[prefix] || @parent.get_namespace(prefix)
199 | end
200 | def defines_namespace?(prefix)
201 | @attributes.keys.include?("xmlns:#{prefix}") || @parent.defines_namespace?(prefix)
202 | end
203 | def to_s(indentation = '')
204 | # todo resolve attribute prefixes aswell
205 | if @prefix && (not defines_namespace?(@prefix))
206 | set_attr "xmlns:#{@prefix}", get_namespace(@prefix)
207 | end
208 | name = XmlMason.xml_escape(full_name)
209 | attr = (@attributes.any? ? (" " + @attributes.map { |key, value| XmlMason.xml_escape(key) + '="' + XmlMason.xml_escape(value) + '"' }.join(" ")) : "")
210 | if @children.any?
211 | if value_node?
212 | children = @children[0].to_s(indentation + " ")
213 | else
214 | children = @children.map { |node| "\n" + node.to_s(indentation + " ") }.join("") + "\n" + indentation
215 | end
216 | indentation + "<" + name + attr + ">" + children + "" + name + ">"
217 | else
218 | indentation + "<" + name + attr + " />"
219 | end
220 | end
221 | end
222 | end
223 |
224 | end
225 |
--------------------------------------------------------------------------------
/lib/handsoap/xml_query_front.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | module Handsoap
3 | #
4 | # A simple frontend for parsing XML document with Xpath.
5 | #
6 | # This provides a unified interface for multiple xpath-capable dom-parsers,
7 | # allowing seamless switching between the underlying implementations.
8 | #
9 | # A document is loaded using the function Handsoap::XmlQueryFront.parse_string, passing
10 | # the xml source string and a driver, which can (currently) be one of:
11 | #
12 | # :rexml
13 | # :nokogiri
14 | # :libxml
15 | #
16 | # The resulting object is a wrapper, of the type Handsoap::XmlQueryFront::XmlElement.
17 | #
18 | module XmlQueryFront
19 |
20 | # This error is raised if the document didn't parse
21 | class ParseError < RuntimeError; end
22 |
23 | # Loads requirements for a driver.
24 | #
25 | # This function is implicitly called by +parse_string+.
26 | def self.load_driver!(driver)
27 | if driver == :rexml
28 | require 'rexml/document'
29 | elsif driver == :nokogiri
30 | require 'nokogiri'
31 | begin
32 | gem('nokogiri') # work around bug in rubygems for Ruby 1.9
33 |
34 | if Gem.loaded_specs['nokogiri'].version < Gem::Version.new('1.3.0')
35 | raise "Incompatible version of Nokogiri. Please upgrade gem."
36 | end
37 | rescue NoMethodError
38 | end
39 | elsif driver == :libxml
40 | require 'libxml'
41 | else
42 | raise "Unknown driver #{driver}"
43 | end
44 | return driver
45 | end
46 |
47 | # Returns a wrapped XML parser, using the requested driver.
48 | #
49 | # +driver+ can be one of the following:
50 | # :rexml
51 | # :nokogiri
52 | # :libxml
53 | def self.parse_string(xml_string, driver)
54 | load_driver!(driver)
55 | if driver == :rexml
56 | doc = REXML::Document.new(xml_string)
57 | raise ParseError.new if doc.root.nil?
58 | XmlQueryFront::REXMLDriver.new(doc)
59 | elsif driver == :nokogiri
60 | doc = Nokogiri::XML(xml_string)
61 | raise ParseError.new unless (doc && doc.root && doc.errors.empty?)
62 | XmlQueryFront::NokogiriDriver.new(doc)
63 | elsif driver == :libxml
64 | begin
65 | LibXML::XML::Error.set_handler &LibXML::XML::Error::QUIET_HANDLER
66 | doc = XmlQueryFront::LibXMLDriver.new(LibXML::XML::Parser.string(xml_string).parse)
67 | rescue ArgumentError, LibXML::XML::Error => ex
68 | raise ParseError.new
69 | end
70 | end
71 | end
72 |
73 | # NodeSelection is a wrapper around Array, that implicitly delegates XmlElement methods to the first element.
74 | #
75 | # It makes mapping code prettier, since you often need to access the first element of a selection.
76 | class NodeSelection < Array
77 | def to_i
78 | self.first.to_i if self.any?
79 | end
80 | def to_f
81 | self.first.to_f if self.any?
82 | end
83 | def to_boolean
84 | self.first.to_boolean if self.any?
85 | end
86 | def to_date
87 | self.first.to_date if self.any?
88 | end
89 | def to_big_decimal(decimal_places = 2)
90 | self.first.to_big_decimal(decimal_places) if self.any?
91 | end
92 | def to_s
93 | self.first.to_s if self.any?
94 | end
95 | def node_name
96 | self.first.node_name if self.any?
97 | end
98 | def node_namespace
99 | self.first.node_namespace if self.any?
100 | end
101 | def xpath(expression, ns = nil)
102 | self.first.xpath(expression, ns)
103 | end
104 | def /(expression)
105 | self.first.xpath(expression)
106 | end
107 | def to_xml
108 | self.first.to_xml if self.any?
109 | end
110 | def to_raw
111 | self.first.to_raw if self.any?
112 | end
113 | end
114 |
115 | # Wraps the underlying (native) xml driver, and provides a uniform interface.
116 | module XmlElement
117 | def initialize(element, namespaces = {})
118 | @element = element
119 | @namespaces = namespaces
120 | end
121 | # Registers a prefix to refer to a namespace.
122 | #
123 | # You can either register a nemspace with this function or pass it explicitly to the +xpath+ method.
124 | def add_namespace(prefix, uri)
125 | @namespaces[prefix] = uri
126 | end
127 | # Checks that an xpath-query doesn't refer to any undefined prefixes in +ns+
128 | def assert_prefixes!(expression, ns)
129 | expression.scan(/([a-zA-Z_][a-zA-Z0-9_.-]*):[^:]+/).map{|m| m[0] }.each do |prefix|
130 | raise "Undefined prefix '#{prefix}' in #{ns.inspect}" if ns[prefix].nil?
131 | end
132 | end
133 | # Returns the value of the element as an integer.
134 | #
135 | # See +to_s+
136 | def to_i
137 | t = self.to_s
138 | return if t.nil?
139 | t.to_i
140 | end
141 | # Returns the value of the element as a float.
142 | #
143 | # See +to_s+
144 | def to_f
145 | t = self.to_s
146 | return if t.nil?
147 | t.to_f
148 | end
149 | # Returns the value of the element as an boolean.
150 | #
151 | # See +to_s+
152 | def to_boolean
153 | t = self.to_s
154 | return if t.nil?
155 | t.downcase == 'true'
156 | end
157 | # Returns the value of the element as a ruby Time object.
158 | #
159 | # See +to_s+
160 | def to_date
161 | t = self.to_s
162 | return if t.nil?
163 | Time.iso8601(t)
164 | end
165 | # Returns the value of the element as an instance of BigDecimal
166 | #
167 | # See +to_s+
168 | def to_big_decimal(decimal_places = 2)
169 | t = self.to_s
170 | return if t.nil?
171 | BigDecimal.new t, decimal_places
172 | end
173 | # Returns the inner text content of this element, or the value (if it's an attr or textnode).
174 | #
175 | # The output is a UTF-8 encoded string, without xml-entities.
176 | def to_s
177 | raise NotImplementedError.new
178 | end
179 | # Returns the underlying native element.
180 | #
181 | # You shouldn't need to use this, since doing so would void portability.
182 | def native_element
183 | @element
184 | end
185 | # Returns the node name of the current element.
186 | def node_name
187 | raise NotImplementedError.new
188 | end
189 | # Returns the node namespace uri of the current element if any, +nil+ otherwise.
190 | # Result returned for attribute nodes varies for different drivers, currently.
191 | def node_namespace
192 | raise NotImplementedError.new
193 | end
194 | # Queries the document with XPath, relative to the current element.
195 | #
196 | # +ns+ Should be a Hash of prefix => namespace
197 | #
198 | # Returns a +NodeSelection+
199 | #
200 | # See add_namespace
201 | def xpath(expression, ns = nil)
202 | raise NotImplementedError.new
203 | end
204 | # Returns a +NodeSelection+
205 | def children
206 | raise NotImplementedError.new
207 | end
208 | # Returns the outer XML for this element.
209 | def to_xml
210 | raise NotImplementedError.new
211 | end
212 | # Returns the outer XML for this element, preserving the original formatting.
213 | def to_raw
214 | raise NotImplementedError.new
215 | end
216 | # alias of +xpath+
217 | def /(expression)
218 | self.xpath(expression)
219 | end
220 | # Returns the attribute value of the underlying element.
221 | #
222 | # Shortcut for:
223 | #
224 | # (node/"@attribute_name").to_s
225 | def [](attribute_name)
226 | raise NotImplementedError.new
227 | end
228 | end
229 |
230 | # Driver for +libxml+.
231 | #
232 | # http://libxml.rubyforge.org/
233 | class LibXMLDriver
234 | include XmlElement
235 | def node_name
236 | @element.name
237 | end
238 | def node_namespace
239 | if @element.respond_to? :namespaces
240 | if namespace = @element.namespaces.namespace
241 | namespace.href
242 | end
243 | end
244 | end
245 | def xpath(expression, ns = nil)
246 | ns = {} if ns.nil?
247 | ns = @namespaces.merge(ns)
248 | assert_prefixes!(expression, ns)
249 | NodeSelection.new(@element.find(expression, ns.map{|k,v| "#{k}:#{v}" }).to_a.map{|node| LibXMLDriver.new(node, ns) })
250 | end
251 | def children
252 | NodeSelection.new(@element.children.map{|node| LibXMLDriver.new(node) })
253 | end
254 | def [](attribute_name)
255 | raise ArgumentError.new unless attribute_name.kind_of? String
256 | @element[attribute_name]
257 | end
258 | def to_xml
259 | @element.to_s(:indent => true)
260 | end
261 | def to_raw
262 | @element.to_s(:indent => false)
263 | end
264 | def to_s
265 | if @element.kind_of? LibXML::XML::Attr
266 | @element.value
267 | else
268 | @element.content
269 | end
270 | end
271 | end
272 |
273 | # Driver for +REXML+
274 | #
275 | # http://www.germane-software.com/software/rexml/
276 | class REXMLDriver
277 | include XmlElement
278 | def node_name
279 | if @element.respond_to? :name
280 | @element.name
281 | else
282 | @element.class.name.gsub(/.*::([^:]+)$/, "\\1").downcase
283 | end
284 | end
285 | def node_namespace
286 | if @element.respond_to? :namespace
287 | namespace = @element.namespace
288 | return if namespace == ''
289 | end
290 | namespace
291 | end
292 | def xpath(expression, ns = nil)
293 | ns = {} if ns.nil?
294 | ns = @namespaces.merge(ns)
295 | assert_prefixes!(expression, ns)
296 | NodeSelection.new(REXML::XPath.match(@element, expression, ns).map{|node| REXMLDriver.new(node, ns) })
297 | end
298 | def children
299 | NodeSelection.new(@element.children.map{|node| REXMLDriver.new(node) })
300 | end
301 | def [](attribute_name)
302 | raise ArgumentError.new unless attribute_name.kind_of? String
303 | @element.attributes[attribute_name]
304 | end
305 | def to_xml
306 | require 'rexml/formatters/pretty'
307 | formatter = REXML::Formatters::Pretty.new
308 | out = String.new
309 | formatter.write(@element, out)
310 | # patch for REXML's broken formatting
311 | out.gsub(/>\n\s+([^<]+)\n\s+<\//, ">\\1")
312 | end
313 | def to_raw
314 | @element.to_s
315 | end
316 | def to_s
317 | if @element.respond_to? :text
318 | @element.text
319 | else
320 | @element.value
321 | end
322 | end
323 | end
324 |
325 | # Driver for +Nokogiri+
326 | #
327 | # http://nokogiri.rubyforge.org/nokogiri/
328 | class NokogiriDriver
329 | include XmlElement
330 | def node_name
331 | @element.name
332 | end
333 | def node_namespace
334 | @element.namespace.href if @element.namespace
335 | end
336 | def xpath(expression, ns = nil)
337 | ns = {} if ns.nil?
338 | ns = @namespaces.merge(ns)
339 | assert_prefixes!(expression, ns)
340 | NodeSelection.new(@element.xpath(expression, ns).map{|node| NokogiriDriver.new(node, ns) })
341 | end
342 | def children
343 | NodeSelection.new(@element.children.map{|node| NokogiriDriver.new(node) })
344 | end
345 | def [](attribute_name)
346 | raise ArgumentError.new unless attribute_name.kind_of? String
347 | @element[attribute_name]
348 | end
349 | def to_xml
350 | @element.serialize(:encoding => 'UTF-8')
351 | end
352 | def to_raw
353 | @element.serialize(:encoding => 'UTF-8', :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
354 | end
355 | def to_s
356 | if @element.kind_of?(Nokogiri::XML::Text) || @element.kind_of?(Nokogiri::XML::CDATA)
357 | element = @element
358 | elsif @element.kind_of?(Nokogiri::XML::Attr)
359 | return @element.value
360 | else
361 | element = @element.children.first
362 | end
363 | return if element.nil?
364 | # This looks messy because it is .. Nokogiri's interface is in a flux
365 | if element.kind_of?(Nokogiri::XML::CDATA)
366 | element.serialize(:encoding => 'UTF-8').gsub(/^$/, "")
367 | else
368 | element.serialize(:encoding => 'UTF-8').gsub('<', '<').gsub('>', '>').gsub('"', '"').gsub(''', "'").gsub('&', '&')
369 | end
370 | end
371 | end
372 | end
373 | end
374 |
--------------------------------------------------------------------------------
/tests/GoogleSearch-soapui-project.xml:
--------------------------------------------------------------------------------
1 |
2 | /home/tkn/public/handsoap/testshttp://api.google.com/GoogleSearch.wsdl
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 | ]]>http://schemas.xmlsoap.org/wsdl/http://api.google.com/search/beta2http://127.0.0.1:8088/mockGoogleSearchBindingUTF-8http://api.google.com/search/beta2
145 |
146 |
147 |
148 | ?
149 | ?
150 |
151 |
152 | ]]>UTF-8http://api.google.com/search/beta2
153 |
154 |
155 |
156 | ?
157 | ?
158 | ?
159 | ?
160 | ?
161 | ?
162 | ?
163 | ?
164 | ?
165 | ?
166 |
167 |
168 | ]]>UTF-8http://api.google.com/search/beta2
169 |
170 |
171 |
172 | ?
173 | ?
174 |
175 |
176 | ]]>SEQUENCEResponse 1
177 |
178 |
179 |
180 | cid:109721102465
181 |
182 |
183 | ]]>SEQUENCEResponse 1
184 |
185 |
186 |
187 |
188 |
189 | ?
190 | ?
191 | ?
192 | ?
193 |
194 | ?
195 | ?
196 | ?
197 | ?
198 |
199 | ?
200 |
201 |
202 |
203 | ]]>SEQUENCEResponse 1
204 |
205 |
206 |
207 | ?
208 |
209 |
210 | ]]>
--------------------------------------------------------------------------------
/tests/Weather.wsdl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 | Gets Information for each WeatherID
167 |
168 |
169 |
170 |
171 | Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only
172 |
173 |
174 |
175 |
176 | Allows you to get your City's Weather, which is updated hourly. U.S. Only
177 |
178 |
179 |
180 |
181 |
182 |
183 | Gets Information for each WeatherID
184 |
185 |
186 |
187 |
188 | Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only
189 |
190 |
191 |
192 |
193 | Allows you to get your City's Weather, which is updated hourly. U.S. Only
194 |
195 |
196 |
197 |
198 |
199 |
200 | Gets Information for each WeatherID
201 |
202 |
203 |
204 |
205 | Allows you to get your City Forecast Over the Next 7 Days, which is updated hourly. U.S. Only
206 |
207 |
208 |
209 |
210 | Allows you to get your City's Weather, which is updated hourly. U.S. Only
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
--------------------------------------------------------------------------------
/tests/WeatherSummary.wsdl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
22 |
23 |
24 |
26 |
28 |
30 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
68 |
69 |
70 |
71 |
74 |
75 |
76 |
81 |
82 |
83 |
84 |
85 |
88 |
89 |
90 |
91 |
92 |
97 |
98 |
99 |
100 |
101 | WeatherSummary
102 |
103 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/tests/account_test.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'rubygems'
3 | require 'test/unit'
4 |
5 | $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
6 | require 'handsoap'
7 |
8 | ACCOUNT_SERVICE_ENDPOINT = {
9 | :uri => 'http://ws.example.org/',
10 | :version => 1
11 | }
12 |
13 | class AccountService < Handsoap::Service
14 | endpoint ACCOUNT_SERVICE_ENDPOINT
15 |
16 | def on_create_document(doc)
17 | doc.alias 'tns', 'http://schema.example.org/AccountService'
18 | doc.alias 's', "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
19 | header = doc.find("Header")
20 | header.add "s:Security" do |s|
21 | s.set_attr "env:mustUnderstand", "0"
22 | s.add "s:Username", @@username
23 | end
24 | end
25 |
26 | def on_response_document(doc)
27 | # register namespaces for the response
28 | doc.add_namespace 'ns', 'http://schema.example.org/AccountService'
29 | end
30 |
31 | @@username = ""
32 | def self.username=(username)
33 | @@username = username
34 | end
35 |
36 | # public methods
37 |
38 | def get_account_by_id(account_id)
39 | soap_action = 'http://ws.example.org/AccountService/GetAccountById'
40 | response = invoke('tns:GetAccountById', soap_action) do |message|
41 | message.add 'account-id', account_id
42 | end
43 | #
44 | #
53 | Account.new :msisdn => (node/"@msisdn").to_s,
54 | :created => (node/"@created").to_date,
55 | :buy_attempts => (node/"@buy-attempts").to_i,
56 | :blacklisted => (node/"@blacklisted").to_boolean,
57 | :application_id => (node/"@application-id").to_i,
58 | :amount_used => (node/"@amount-used").to_i,
59 | :account_id => (node/"@account-id").to_i,
60 | :credit => (node/"@credit").to_big_decimal
61 | end
62 | end
63 |
64 | class Account
65 | attr_accessor :msisdn, :application_id, :account_id, :created, :buy_attempts, :amount_used
66 | attr_writer :blacklisted
67 | def initialize(values = {})
68 | @msisdn = values[:msisdn]
69 | @application_id = values[:application_id]
70 | @account_id = values[:account_id]
71 | @created = values[:created]
72 | @buy_attempts = values[:buy_attempts]
73 | @blacklisted = values[:blacklisted] || false
74 | @amount_used = values[:amount_used]
75 | @credit = values[:credit]
76 | end
77 | def blacklisted?
78 | !! @blacklisted
79 | end
80 | end
81 |
82 |
83 | class AccountServiceTest < Test::Unit::TestCase
84 |
85 | def setup
86 | # AccountService.logger = $stdout
87 | AccountService.username = "someone"
88 | headers = 'Date: Fri, 14 Aug 2009 11:57:36 GMT
89 | Content-Type: text/xml;charset=UTF-8
90 | X-Powered-By: Servlet 2.4; JBoss-4.2.2.GA (build: SVNTag=JBoss_4_2_2_GA date=200710221139)/Tomcat-5.5
91 | Server: Apache-Coyote/1.1'.gsub(/\n/, "\r\n")
92 | body = '
93 |
94 |
95 |
96 |
97 |
98 |
99 | '
100 | Handsoap::Http.drivers[:mock] = Handsoap::Http::Drivers::MockDriver.new :headers => headers, :content => body, :status => 200
101 | Handsoap.http_driver = :mock
102 | end
103 |
104 | def test_get_account_by_id
105 | driver = Handsoap::Http.drivers[:mock].new # passthrough, doesn’t actually create a new instance
106 | result = AccountService.get_account_by_id(10)
107 | assert_equal 'http://ws.example.org/', driver.last_request.url
108 | assert_equal :post, driver.last_request.http_method
109 | assert_kind_of Account, result
110 | end
111 | end
112 |
--------------------------------------------------------------------------------
/tests/benchmark_integration_test.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
3 | require 'rubygems'
4 | require 'handsoap'
5 | require 'soap/wsdlDriver'
6 | require 'soap/header/simplehandler'
7 | require 'benchmark'
8 |
9 | #
10 | # Start the mockservice with:
11 | # sh ~/path/to/soapui-2.5.1/bin/mockservicerunner.sh -p 8088 tests/GoogleSearch-soapui-project.xml
12 | #
13 | # Run the benchmark with (100 tries):
14 | # ruby tests/benchmark_integration_test.rb 100
15 | #
16 |
17 | # handsoap mappings:
18 |
19 | class TestService < Handsoap::Service
20 | endpoint :uri => 'http://127.0.0.1:8088/mockGoogleSearchBinding', :version => 1
21 | map_method :do_spelling_suggestion => "urn:doSpellingSuggestion"
22 | on_create_document do |doc|
23 | doc.alias 'urn', "urn:GoogleSearch"
24 | doc.alias 'xsi', "http://www.w3.org/2001/XMLSchema-instance"
25 | end
26 | def do_spelling_suggestion(key, phrase)
27 | invoke("urn:doSpellingSuggestion") do |message|
28 | message.add "key" do |k|
29 | k.set_attr "xsi:type", "xsd:string"
30 | k.add key
31 | end
32 | message.add "phrase" do |k|
33 | k.set_attr "xsi:type", "xsd:string"
34 | k.add phrase
35 | end
36 | end
37 | end
38 |
39 | end
40 |
41 | # soap4r mappings:
42 |
43 | def make_soap4r
44 | SOAP::WSDLDriverFactory.new('http://127.0.0.1:8088/mockGoogleSearchBinding?WSDL').create_rpc_driver('GoogleSearchService', 'GoogleSearchPort')
45 | end
46 |
47 | def make_handsoap
48 | TestService.new
49 | end
50 |
51 | service_4 = make_soap4r
52 | service_h = make_handsoap
53 |
54 | # TestService.logger = $stdout
55 | # service_4.wiredump_dev = $stdout
56 |
57 | times = ARGV[0].to_i
58 | if times < 1
59 | times = 1
60 | end
61 | puts "Benchmarking #{times} calls ..."
62 | Benchmark.bm(32) do |x|
63 | x.report("soap4r") do
64 | (1..times).each {
65 | service_4.doSpellingSuggestion("foo", "bar")
66 | }
67 | end
68 | Handsoap.http_driver = :curb
69 | Handsoap.xml_query_driver = :nokogiri
70 | x.report("handsoap+curb+nokogiri") do
71 | (1..times).each {
72 | service_h.do_spelling_suggestion("foo", "bar")
73 | }
74 | end
75 | Handsoap.http_driver = :curb
76 | Handsoap.xml_query_driver = :libxml
77 | x.report("handsoap+curb+libxml") do
78 | (1..times).each {
79 | service_h.do_spelling_suggestion("foo", "bar")
80 | }
81 | end
82 | Handsoap.http_driver = :curb
83 | Handsoap.xml_query_driver = :rexml
84 | x.report("handsoap+curb+rexml") do
85 | (1..times).each {
86 | service_h.do_spelling_suggestion("foo", "bar")
87 | }
88 | end
89 | Handsoap.http_driver = :httpclient
90 | Handsoap.xml_query_driver = :nokogiri
91 | x.report("handsoap+httpclient+nokogiri") do
92 | (1..times).each {
93 | service_h.do_spelling_suggestion("foo", "bar")
94 | }
95 | end
96 | Handsoap.http_driver = :httpclient
97 | Handsoap.xml_query_driver = :libxml
98 | x.report("handsoap+httpclient+libxml") do
99 | (1..times).each {
100 | service_h.do_spelling_suggestion("foo", "bar")
101 | }
102 | end
103 | Handsoap.http_driver = :httpclient
104 | Handsoap.xml_query_driver = :rexml
105 | x.report("handsoap+httpclient+rexml") do
106 | (1..times).each {
107 | service_h.do_spelling_suggestion("foo", "bar")
108 | }
109 | end
110 | end
111 | puts "---------------"
112 | puts "Legend:"
113 | puts "The user CPU time, system CPU time, the sum of the user and system CPU times,"
114 | puts "and the elapsed real time. The unit of time is seconds."
115 |
--------------------------------------------------------------------------------
/tests/dispatch_test.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'rubygems'
3 | require 'test/unit'
4 | $LOAD_PATH << "#{File.dirname(__FILE__)}/../lib/"
5 | require 'handsoap.rb'
6 |
7 | def var_dump(val)
8 | puts val.to_yaml.gsub(/ !ruby\/object:.+$/, '')
9 | end
10 |
11 | class MockResponse < Handsoap::Http::Response
12 | attr_writer :status, :headers, :body, :parts
13 | end
14 |
15 | class TestService < Handsoap::Service
16 | endpoint :uri => 'http://example.com', :version => 1
17 |
18 | def on_create_document(doc)
19 | doc.alias 'sc002', "http://www.wstf.org/docs/scenarios/sc002"
20 | doc.find("Header").add "sc002:SessionData" do |s|
21 | s.add "ID", "Client-1"
22 | end
23 | end
24 |
25 | def on_response_document(doc)
26 | doc.add_namespace 'ns', 'http://www.wstf.org/docs/scenarios/sc002'
27 | end
28 |
29 | def echo(text)
30 | response = invoke('sc002:Echo') do |message|
31 | message.add "text", text
32 | end
33 | (response.document/"//ns:EchoResponse/ns:text").to_s
34 | end
35 | end
36 |
37 | class TestServiceLegacyStyle < Handsoap::Service
38 | endpoint :uri => 'http://example.com', :version => 1
39 |
40 | def on_create_document(doc)
41 | doc.alias 'sc002', "http://www.wstf.org/docs/scenarios/sc002"
42 | doc.find("Header").add "sc002:SessionData" do |s|
43 | s.add "ID", "Client-1"
44 | end
45 | end
46 |
47 | def ns
48 | { 'ns' => 'http://www.wstf.org/docs/scenarios/sc002' }
49 | end
50 |
51 | def echo(text)
52 | response = invoke('sc002:Echo') do |message|
53 | message.add "text", text
54 | end
55 | xml_to_str(response.document, "//ns:EchoResponse/ns:text/text()")
56 | end
57 | end
58 |
59 | class TestOfDispatch < Test::Unit::TestCase
60 | def setup
61 | body = '
62 |
63 |
64 |
65 | Lirum Opossum
66 |
67 |
68 | '
69 | @mock_http_response = MockResponse.new(200, {"content-type" => ["text/xml;charset=utf-8"]}, body)
70 | Handsoap::Http.drivers[:mock] = Handsoap::Http::Drivers::MockDriver.new(@mock_http_response)
71 | Handsoap.http_driver = :mock
72 | end
73 |
74 | def test_normal_usecase
75 | assert_equal "Lirum Opossum", TestService.echo("Lirum Opossum")
76 | end
77 |
78 | def test_raises_on_http_error
79 | @mock_http_response.status = 404
80 | assert_raise ::Handsoap::HttpError do
81 | TestService.echo("Lirum Opossum")
82 | end
83 | end
84 |
85 | def test_raises_on_invalid_document
86 | @mock_http_response.body = "not xml!"
87 | assert_raise RuntimeError do
88 | TestService.echo("Lirum Opossum")
89 | end
90 | end
91 |
92 | def test_raises_on_fault
93 | @mock_http_response.body = '
94 |
95 |
96 |
97 | soap:Server
98 | Not a ninja
99 |
100 |
101 |
102 | '
103 | assert_raise Handsoap::Fault do
104 | TestService.echo("Lirum Opossum")
105 | end
106 | end
107 |
108 | def test_legacy_parser_helpers
109 | assert_equal "Lirum Opossum", TestServiceLegacyStyle.echo("Lirum Opossum")
110 | end
111 |
112 | def test_multipart_response
113 | body = '
114 |
115 |
116 |
117 | Lirum Opossum
118 |
119 |
120 | '
121 | @mock_http_response.parts = [Handsoap::Http::Part.new({}, body, nil)]
122 | assert_equal "Lirum Opossum", TestService.echo("Lirum Opossum")
123 | end
124 |
125 | def test_raises_on_no_document
126 | @mock_http_response.status = 202
127 | @mock_http_response.body = ''
128 | assert_raise RuntimeError do
129 | TestService.echo("Lirum Opossum")
130 | end
131 | end
132 |
133 | end
134 |
--------------------------------------------------------------------------------
/tests/event_machine_test.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'test/unit'
3 |
4 | require "#{File.dirname(__FILE__)}/socket_server.rb"
5 |
6 | require 'eventmachine'
7 |
8 | $LOAD_PATH << "#{File.dirname(__FILE__)}/../lib/"
9 | require 'handsoap'
10 | require 'handsoap/http'
11 |
12 | class TestDeferredService < Handsoap::Service
13 | endpoint :uri => "http://127.0.0.1:#{TestSocketServer.port}/", :version => 1
14 |
15 | def on_create_document(doc)
16 | doc.alias 'sc002', "http://www.wstf.org/docs/scenarios/sc002"
17 | doc.find("Header").add "sc002:SessionData" do |s|
18 | s.add "ID", "Client-1"
19 | end
20 | end
21 |
22 | def on_response_document(doc)
23 | doc.add_namespace 'ns', 'http://www.wstf.org/docs/scenarios/sc002'
24 | end
25 |
26 | def echo(text, &block)
27 | async(block) do |dispatcher|
28 | dispatcher.request("sc002:Echo") do |m|
29 | m.add "text", text
30 | end
31 | dispatcher.response do |response|
32 | (response/"//ns:EchoResponse/ns:text").to_s
33 | end
34 | end
35 |
36 | end
37 | end
38 |
39 |
40 | SOAP_RESPONSE = "
41 |
42 |
43 |
44 | I am living in the future.
45 |
46 |
47 | ".gsub(
48 | /\n/ , "\r\n")
49 |
50 |
51 | class TestOfEventMachineDriver < Test::Unit::TestCase
52 | def driver
53 | :event_machine
54 | end
55 |
56 | def test_connect_to_example_com
57 | TestSocketServer.reset!
58 | TestSocketServer.responses << "HTTP/1.1 200 OK
59 | Server: Ruby
60 | Connection: close
61 | Content-Type: text/plain
62 | Content-Length: 2
63 | Date: Wed, 19 Aug 2009 12:13:45 GMT
64 |
65 | OK".gsub(/\n/, "\r\n")
66 |
67 | EventMachine.run do
68 | driver = Handsoap::Http.drivers[self.driver].new
69 | request = Handsoap::Http::Request.new("http://127.0.0.1:#{TestSocketServer.port}/")
70 | deferred = driver.send_http_request_async(request)
71 |
72 | deferred.callback do |response|
73 | assert_equal "Ruby", response.headers['server']
74 | assert_equal "OK", response.body
75 | assert_equal 200, response.status
76 | EventMachine.stop
77 | end
78 | end
79 | end
80 |
81 | def test_service
82 | TestSocketServer.reset!
83 | TestSocketServer.responses << "HTTP/1.1 200 OK
84 | Server: Ruby
85 | Connection: close
86 | Content-Type: application/xml
87 | Content-Length: #{SOAP_RESPONSE.size}
88 | Date: Wed, 19 Aug 2009 12:13:45 GMT
89 |
90 | ".gsub(/\n/, "\r\n") + SOAP_RESPONSE
91 |
92 | Handsoap.http_driver = :event_machine
93 |
94 | EventMachine.run do
95 | TestDeferredService.echo("I am living in the future.") do |d|
96 | d.callback do |text|
97 | assert_equal "I am living in the future.", text
98 | EventMachine.stop
99 | end
100 | d.errback do |mixed|
101 | flunk "Flunked![#{mixed}]"
102 | EventMachine.stop
103 | end
104 | end
105 | end
106 | end
107 | end
108 |
--------------------------------------------------------------------------------
/tests/fault_test.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'rubygems'
3 | require 'test/unit'
4 |
5 | $LOAD_PATH << "#{File.dirname(__FILE__)}/../lib/"
6 | require "handsoap"
7 | require 'handsoap/xml_query_front'
8 | require 'handsoap/service'
9 |
10 | class ParseFaultTestCase < Test::Unit::TestCase
11 | def get_fault_11
12 | xml_doc = '
13 |
14 |
15 |
16 | soap:Server
17 | Error while blackList account: the application does not exist
18 |
19 |
20 |
21 | '
22 | Handsoap::XmlQueryFront.parse_string(xml_doc, Handsoap.xml_query_driver)
23 | end
24 |
25 | def get_fault_12
26 | xml_doc = '
27 |
29 |
30 |
31 |
32 | env:Sender
33 |
34 | rpc:BadArguments
35 |
36 |
37 |
38 | Processing error
39 | Chyba zpracování
40 |
41 |
42 |
44 | Name does not match card number
45 | 999
46 |
47 |
48 |
49 |
50 | '
51 | Handsoap::XmlQueryFront.parse_string(xml_doc, Handsoap.xml_query_driver)
52 | end
53 |
54 | # Added to test issue 6, found here: http://github.com/unwire/handsoap/issues#issue/6
55 | # this is for SOAP 1.1 btw
56 | def get_fault_with_namespace
57 | xml_doc = '
58 |
59 |
60 |
61 | a:Client
62 | Failed to parse the request
63 | Invalid parameter
64 |
65 |
66 | '
67 |
68 | Handsoap::XmlQueryFront.parse_string(xml_doc, Handsoap.xml_query_driver)
69 | end
70 |
71 | # Tests that SOAP1.1 faults can be parsed
72 | def test_can_parse_soap_fault_11
73 | envelope_namespace = "http://schemas.xmlsoap.org/soap/envelope/"
74 |
75 | node = get_fault_11.xpath('/env:Envelope/env:Body/descendant-or-self::env:Fault', { 'env' => envelope_namespace })
76 | fault = Handsoap::Fault.from_xml(node, :namespace => envelope_namespace)
77 |
78 | assert_kind_of Handsoap::Fault, fault
79 | assert_equal 'soap:Server', fault.code
80 | assert_equal 'Error while blackList account: the application does not exist', fault.reason
81 | end
82 |
83 | # Tests that SOAP1.2 faults can be parsed
84 | def test_can_parse_soap_fault_12
85 | envelope_namespace = "http://www.w3.org/2003/05/soap-envelope"
86 |
87 | node = get_fault_12.xpath('/env:Envelope/env:Body/descendant-or-self::env:Fault', { 'env' => envelope_namespace })
88 | fault = Handsoap::Fault.from_xml(node, :namespace => envelope_namespace)
89 |
90 | assert_kind_of Handsoap::Fault, fault
91 | assert_equal 'env:Sender', fault.code
92 | assert_equal 'Processing error', fault.reason
93 | end
94 |
95 | # Added to test issue 6, found here: http://github.com/unwire/handsoap/issues#issue/6
96 | # This is for SOAP 1.1 btw
97 | def test_can_parse_soap_fault_with_namespace
98 | envelope_namespace = "http://schemas.xmlsoap.org/soap/envelope/"
99 |
100 | node = get_fault_with_namespace.xpath('/env:Envelope/env:Body/descendant-or-self::env:Fault', { 'env' => envelope_namespace })
101 | fault = Handsoap::Fault.from_xml(node, :namespace => envelope_namespace)
102 |
103 | assert_kind_of Handsoap::Fault, fault
104 | assert_equal 'a:Client', fault.code
105 | assert_equal 'Failed to parse the request', fault.reason
106 | end
107 | end
108 |
--------------------------------------------------------------------------------
/tests/handsoap_generator_test.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'rubygems'
3 | require 'test/unit'
4 | require 'rails_generator'
5 | require 'rails_generator/scripts/generate'
6 | require "#{File.dirname(__FILE__)}/../generators/handsoap/handsoap_generator.rb"
7 |
8 | module Rails
9 | module Generator
10 | module Lookup
11 | module ClassMethods
12 | def sources
13 | [PathSource.new(:user, "#{File.dirname(__FILE__)}/../generators")]
14 | end
15 | end
16 | end
17 | end
18 | end
19 |
20 | class HandsoapGeneratorTest < Test::Unit::TestCase
21 |
22 | def setup
23 | FileUtils.mkdir_p(fake_rails_root) if File.directory?(fake_rails_root)
24 | @original_files = file_list
25 | end
26 |
27 | def invoke_generator!
28 | Rails::Generator::Scripts::Generate.new.run(["handsoap", "https://mooshup.com/services/system/version?wsdl", "--backtrace", "--quiet"], :destination => fake_rails_root)
29 | end
30 |
31 | def test_can_invoke_generator
32 | invoke_generator!
33 | end
34 |
35 | def test_generator_creates_files
36 | invoke_generator!
37 | assert file_list.find {|name| name.match("app/models/version_service.rb") }
38 | assert file_list.find {|name| name.match("test/integration/version_service_test.rb") }
39 | assert File.read(fake_rails_root + "/app/models/version_service.rb").any?
40 | assert File.read(fake_rails_root + "/test/integration/version_service_test.rb").any?
41 | end
42 |
43 | def test_running_generator_twice_silently_skips_files
44 | invoke_generator!
45 | invoke_generator!
46 | end
47 |
48 | def test_can_parse_multiple_interfaces
49 | wsdl_file = File.join(File.dirname(__FILE__), 'Weather.wsdl')
50 | Rails::Generator::Scripts::Generate.new.run(["handsoap", wsdl_file, "--backtrace", "--quiet"], :destination => fake_rails_root)
51 | end
52 |
53 | private
54 |
55 | def fake_rails_root
56 | File.join(File.dirname(__FILE__), 'rails_root')
57 | end
58 |
59 | def file_list
60 | Dir.glob(File.join(fake_rails_root, "**/*"))
61 | end
62 | end
63 |
64 |
65 |
--------------------------------------------------------------------------------
/tests/http_test.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'test/unit'
3 |
4 | require "#{File.dirname(__FILE__)}/socket_server.rb"
5 |
6 | $LOAD_PATH << "#{File.dirname(__FILE__)}/../lib/"
7 | require "handsoap"
8 | require 'handsoap/http'
9 |
10 | module AbstractHttpDriverTestCase
11 |
12 | def setup
13 | Handsoap::Http.drivers[self.driver].load!
14 | end
15 |
16 | def test_connect_to_example_com
17 | TestSocketServer.reset!
18 | TestSocketServer.responses << "HTTP/1.1 200 OK
19 | Server: Ruby
20 | Connection: close
21 | Content-Type: text/plain
22 | Content-Length: 2
23 | Date: Wed, 19 Aug 2009 12:13:45 GMT
24 |
25 | OK".gsub(/\n/, "\r\n")
26 |
27 | driver = Handsoap::Http.drivers[self.driver].new
28 | request = Handsoap::Http::Request.new("http://127.0.0.1:#{TestSocketServer.port}/")
29 | response = driver.send_http_request(request)
30 | assert_equal 200, response.status
31 | assert_equal ["Ruby"], response.headers['server']
32 | assert_equal "OK", response.body
33 | end
34 |
35 | def test_chunked
36 | TestSocketServer.reset!
37 | TestSocketServer.responses << "HTTP/1.1 200 OK
38 | Server: Ruby
39 | Connection: Keep-Alive
40 | Content-Type: text/plain
41 | Transfer-Encoding: chunked
42 | Date: Wed, 19 Aug 2009 12:13:45 GMT
43 |
44 | b
45 | Hello World
46 | 0
47 |
48 | ".gsub(/\n/, "\r\n")
49 |
50 | driver = Handsoap::Http.drivers[self.driver].new
51 | request = Handsoap::Http::Request.new("http://127.0.0.1:#{TestSocketServer.port}/")
52 | response = driver.send_http_request(request)
53 | assert_equal "Hello World", response.body
54 | end
55 |
56 | end
57 |
58 | class TestOfNetHttpDriver < Test::Unit::TestCase
59 | include AbstractHttpDriverTestCase
60 | def driver
61 | :net_http
62 | end
63 | end
64 |
65 | class TestOfCurbDriver < Test::Unit::TestCase
66 | include AbstractHttpDriverTestCase
67 | def driver
68 | :curb
69 | end
70 |
71 | # Curl will use 100-Continue if Content-Length > 1024
72 | def test_continue
73 | TestSocketServer.reset!
74 | # TestSocketServer.debug = true
75 | TestSocketServer.responses << "HTTP/1.1 100 Continue
76 |
77 | ".gsub(/\n/, "\r\n")
78 | TestSocketServer.responses << "HTTP/1.1 200 OK
79 | Server: Ruby
80 | Connection: close
81 | Content-Type: text/plain
82 | Content-Length: 9
83 | Date: Wed, 19 Aug 2009 12:13:45 GMT
84 |
85 | okeydokey".gsub(/\n/, "\r\n")
86 |
87 | driver = Handsoap::Http.drivers[self.driver].new
88 | request = Handsoap::Http::Request.new("http://127.0.0.1:#{TestSocketServer.port}/", :post)
89 | request.body = (0...1099).map{ ('a'..'z').to_a[rand(26)] }.join
90 | response = driver.send_http_request(request)
91 | assert_equal "okeydokey", response.body
92 | end
93 |
94 | def test_no_retain_cookie_between_requests_by_default
95 | driver = Handsoap::Http.drivers[self.driver].new
96 |
97 | TestSocketServer.reset!
98 | TestSocketServer.responses << "HTTP/1.1 200 OK
99 | Server: Ruby
100 | Connection: close
101 | Content-Type: text/plain
102 | Date: Wed, 19 Aug 2009 12:13:45 GMT
103 | Set-Cookie: SessionId=s5x1rcvuktc3c455hgu23bxx; path=/; HttpOnly
104 |
105 | okeydokey".gsub(/\n/, "\r\n")
106 |
107 | request = Handsoap::Http::Request.new("http://localhost:#{TestSocketServer.port}/", :post)
108 | response = driver.send_http_request(request)
109 | assert_equal "okeydokey", response.body
110 |
111 | TestSocketServer.responses << "HTTP/1.1 200 OK
112 | Server: Ruby
113 | Connection: close
114 |
115 | The second body".gsub(/\n/, "\r\n")
116 |
117 | driver.send_http_request(request)
118 | # second request must NOT include the Cookie returned in Set-Cookie on the first request
119 | assert ! TestSocketServer.requests.last.include?("Cookie: SessionId=s5x1rcvuktc3c455hgu23bxx")
120 | end
121 |
122 | def test_retain_cookie_between_requests_when_cookies_enabled
123 | driver = Handsoap::Http.drivers[self.driver].new
124 | driver.enable_cookies = true # enable in-built Cookie support in Curb
125 |
126 | TestSocketServer.reset!
127 | TestSocketServer.responses << "HTTP/1.1 200 OK
128 | Server: Ruby
129 | Connection: close
130 | Content-Type: text/plain
131 | Date: Wed, 19 Aug 2009 12:13:45 GMT
132 | Set-Cookie: SessionId=s5x1rcvuktc3c455hgu23bxx; path=/; HttpOnly
133 |
134 | okeydokey".gsub(/\n/, "\r\n")
135 |
136 | request = Handsoap::Http::Request.new("http://localhost:#{TestSocketServer.port}/", :post)
137 | response = driver.send_http_request(request)
138 | assert_equal "okeydokey", response.body
139 |
140 | TestSocketServer.responses << "HTTP/1.1 200 OK
141 | Server: Ruby
142 | Connection: close
143 |
144 | The second body".gsub(/\n/, "\r\n")
145 |
146 | driver.send_http_request(request)
147 | # second request must include the Cookie returned in Set-Cookie on the first request
148 | assert TestSocketServer.requests.last.include?("Cookie: SessionId=s5x1rcvuktc3c455hgu23bxx")
149 | end
150 |
151 | end
152 |
153 | class TestOfHttpclientDriver < Test::Unit::TestCase
154 | include AbstractHttpDriverTestCase
155 | def driver
156 | :httpclient
157 | end
158 | end
159 |
160 | class TestOfHttp < Test::Unit::TestCase
161 | def test_parse_multipart_small
162 | boundary = 'MIMEBoundaryurn_uuid_FF5B45112F1A1EA3831249088019646'
163 | content_io = '--MIMEBoundaryurn_uuid_FF5B45112F1A1EA3831249088019646
164 | Content-Type: application/xop+xml; charset=UTF-8; type="text/xml"
165 | Content-Transfer-Encoding: binary
166 | Content-ID: <0.urn:uuid:FF5B45112F1A1EA3831249088019647@apache.org>
167 |
168 |
169 |
170 |
171 |
172 | Lirum Opossum
173 |
174 |
175 |
176 | --MIMEBoundaryurn_uuid_FF5B45112F1A1EA3831249088019646--
177 | '
178 | content_io.gsub!(/\n/, "\r\n")
179 | parts = Handsoap::Http::Drivers::AbstractDriver.new.parse_multipart(boundary, content_io)
180 | assert_equal 1, parts.size
181 | assert parts.first[:body] =~ /^ 8080)
10 | server = HTTPServer.new(config)
11 | yield server if block_given?
12 | ['INT', 'TERM'].each {|signal|
13 | trap(signal) {server.shutdown}
14 | }
15 | server.start
16 | end
17 |
18 | start_webrick { |server|
19 | htdigest = HTTPAuth::Htdigest.new('/tmp/webrick-htdigest')
20 | htdigest.set_passwd "Restricted", "user", "password"
21 | authenticator = HTTPAuth::DigestAuth.new(
22 | :UserDB => htdigest,
23 | :Realm => "Restricted"
24 | )
25 |
26 | server.mount_proc('/') {|request, response|
27 | response.body = "basic
\ndigest\n"
28 | }
29 |
30 | server.mount_proc('/basic') {|request, response|
31 | HTTPAuth.basic_auth(request, response, "Restricted") {|user, pass|
32 | # this block returns true if
33 | # authentication token is valid
34 | user == 'user' && pass == 'password'
35 | }
36 | response.body = "You are authenticated to see the super secret data\n"
37 | }
38 |
39 | server.mount_proc('/digest') {|request, response|
40 | authenticator.authenticate(request, response)
41 | response.body = "You are authenticated to see the super secret data\n"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/parser_test.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'rubygems'
3 | require 'test/unit'
4 | $LOAD_PATH << "#{File.dirname(__FILE__)}/../lib/"
5 | require 'handsoap/parser.rb'
6 |
7 | def var_dump(val)
8 | puts val.to_yaml.gsub(/ !ruby\/object:.+$/, '')
9 | end
10 |
11 | # Amazon is rpc + literal and is self-contained
12 | # http://soap.amazon.com/schemas2/AmazonWebServices.wsdl
13 | class TestOfAmazonWebServicesConnection < Test::Unit::TestCase
14 | def test_can_connect_and_read_wsdl
15 | wsdl = Handsoap::Parser::Wsdl.read('http://soap.amazon.com/schemas2/AmazonWebServices.wsdl')
16 | assert_kind_of Handsoap::Parser::Interface, wsdl.interfaces.first
17 | end
18 | end
19 |
20 | class TestOfAmazonWebServices < Test::Unit::TestCase
21 | def get_wsdl
22 | unless @wsdl
23 | @wsdl = Handsoap::Parser::Wsdl.read('http://soap.amazon.com/schemas2/AmazonWebServices.wsdl')
24 | end
25 | return @wsdl
26 | end
27 | def test_can_parse_services
28 | wsdl = get_wsdl
29 | services = wsdl.endpoints
30 | assert_kind_of Array, services
31 | assert_kind_of Handsoap::Parser::Endpoint, services.first
32 | assert_equal 'AmazonSearchPort', services.first.name
33 | assert_equal 'typens:AmazonSearchBinding', services.first.binding
34 | end
35 | def test_can_parse_port_types
36 | wsdl = get_wsdl
37 | port_types = wsdl.interfaces
38 | assert_kind_of Array, port_types
39 | assert_kind_of Handsoap::Parser::Interface, port_types.first
40 | assert_equal 'AmazonSearchPort', port_types.first.name
41 | assert_kind_of Array, port_types.first.operations
42 | assert_equal 'KeywordSearchRequest', port_types.first.operations.first.name
43 | end
44 | def test_can_parse_bindings
45 | wsdl = get_wsdl
46 | bindings = wsdl.bindings
47 | assert_kind_of Array, bindings
48 | assert_equal 'AmazonSearchBinding', bindings.first.name
49 | assert_kind_of Array, bindings.first.actions
50 | assert_equal 'KeywordSearchRequest', bindings.first.actions.first.name
51 | end
52 | end
53 |
54 | # Thomas-Bayer is rpc + document and has external type definitions
55 | # http://www.thomas-bayer.com/names-service/soap?wsdl
56 | class TestOfThomasBayerNameServiceConnection < Test::Unit::TestCase
57 | def test_can_connect_and_read_wsdl
58 | wsdl = Handsoap::Parser::Wsdl.read('http://www.thomas-bayer.com/names-service/soap?wsdl')
59 | assert_kind_of Handsoap::Parser::Interface, wsdl.interfaces.first
60 | end
61 | end
62 |
63 | class TestOfThomasBayerNameService < Test::Unit::TestCase
64 | def get_wsdl
65 | unless @wsdl
66 | @wsdl = Handsoap::Parser::Wsdl.read('http://www.thomas-bayer.com/names-service/soap?wsdl')
67 | end
68 | return @wsdl
69 | end
70 | def test_can_parse_services
71 | wsdl = get_wsdl
72 | services = wsdl.endpoints
73 | assert_kind_of Array, services
74 | assert_kind_of Handsoap::Parser::Endpoint, services.first
75 | assert_equal 'NamesServicePort', services.first.name
76 | assert_equal 'tns:NamesServicePortBinding', services.first.binding
77 | end
78 | def test_can_parse_port_types
79 | wsdl = get_wsdl
80 | port_types = wsdl.interfaces
81 | assert_kind_of Array, port_types
82 | assert_kind_of Handsoap::Parser::Interface, port_types.first
83 | assert_equal 'NamesService', port_types.first.name
84 | assert_kind_of Array, port_types.first.operations
85 | assert_equal 'getCountries', port_types.first.operations.first.name
86 | end
87 | def test_can_parse_bindings
88 | wsdl = get_wsdl
89 | bindings = wsdl.bindings
90 | assert_kind_of Array, bindings
91 | assert_equal 'NamesServicePortBinding', bindings.first.name
92 | assert_kind_of Array, bindings.first.actions
93 | assert_equal 'getCountries', bindings.first.actions.first.name
94 | end
95 | end
96 |
--------------------------------------------------------------------------------
/tests/service_integration_test.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'rubygems'
3 | $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
4 | require 'handsoap'
5 |
6 | class TestService < Handsoap::Service
7 | endpoint :uri => 'http://127.0.0.1:8088/mocksc002SOAP11Binding', :version => 1
8 | map_method :begin => "sc002:Begin"
9 | map_method :notify => "sc002:Notify"
10 | map_method :echo => "sc002:Echo"
11 | def on_create_document(doc)
12 | doc.alias 'sc002', "http://www.wstf.org/docs/scenarios/sc002"
13 | doc.find("Header").add "sc002:SessionData" do |session_data|
14 | session_data.add "ID", "Client-1"
15 | end
16 | end
17 | def on_missing_document(http_response_body)
18 | # pass
19 | end
20 | end
21 |
22 | Handsoap::Service.logger = $stdout
23 |
24 | s = TestService.new
25 |
26 | s.begin
27 |
28 | s.notify do |x|
29 | x.add "text", "Hello"
30 | end
31 |
32 | s.echo do |x|
33 | x.add "text", "Hello"
34 | end
35 |
36 |
--------------------------------------------------------------------------------
/tests/service_test.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'test/unit'
3 |
4 | $LOAD_PATH << "#{File.dirname(__FILE__)}/../lib/"
5 | require "handsoap"
6 |
7 | class TestFollowRedirects < Test::Unit::TestCase
8 | def test_follow_redirects
9 | assert !Handsoap.follow_redirects?
10 | Handsoap.follow_redirects!
11 | assert Handsoap.follow_redirects?
12 | end
13 |
14 | def test_max_redirects
15 | assert_equal Handsoap.max_redirects, 1
16 | Handsoap.max_redirects = 10
17 | assert_equal Handsoap.max_redirects, 10
18 | end
19 | end
--------------------------------------------------------------------------------
/tests/socket_server.rb:
--------------------------------------------------------------------------------
1 | require 'socket'
2 | include Socket::Constants
3 |
4 | class TestSocketServer
5 |
6 | class << self
7 | attr_accessor :requests, :responses, :debug
8 | attr_reader :port
9 | end
10 |
11 | def self.reset!
12 | @debug = false
13 | @requests = []
14 | @responses = []
15 | end
16 |
17 | def self.start
18 | @socket = Socket.new AF_INET, SOCK_STREAM, 0
19 | @socket.bind Socket.pack_sockaddr_in(0, "127.0.0.1")
20 | @port = @socket.getsockname.unpack("snA*")[1]
21 | self.reset!
22 | @socket_thread = Thread.new do
23 | while true
24 | @socket.listen 1
25 | client_fd, client_sockaddr = @socket.sysaccept
26 | client_socket = Socket.for_fd client_fd
27 | while @responses.any?
28 | @requests << client_socket.recvfrom(8192)[0]
29 | response = @responses.shift
30 | if @debug
31 | puts "---"
32 | puts @requests
33 | puts "---"
34 | puts response
35 | end
36 | client_socket.print response
37 | end
38 | client_socket.close
39 | end
40 | end
41 | end
42 |
43 | self.start
44 | end
45 |
--------------------------------------------------------------------------------
/tests/xml_mason_test.rb:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | require 'rubygems'
3 | $LOAD_PATH << "#{File.dirname(__FILE__)}/../lib/"
4 | require "handsoap"
5 | require 'handsoap/xml_mason'
6 |
7 | require 'test/unit'
8 |
9 | class TestOfXmlMason < Test::Unit::TestCase
10 | def test_namespaces_are_automagically_assigned_upon_usage
11 | doc = Handsoap::XmlMason::Document.new do |doc|
12 | doc.alias 'x', 'http://example.com/x'
13 | doc.alias 'y', 'http://example.com/y'
14 | doc.add 'x:body' do |b|
15 | b.add 'y:yonks'
16 | end
17 | end
18 | xml = "\n" +
19 | "\n" +
20 | " \n" +
21 | ""
22 | assert_equal xml, doc.to_s
23 | end
24 | def test_namespaces_are_only_declared_on_the_topmost_level
25 | doc = Handsoap::XmlMason::Document.new do |doc|
26 | doc.alias 'x', 'http://example.com/x'
27 | doc.add 'x:body' do |b|
28 | b.add 'x:yonks'
29 | end
30 | end
31 | xml = "\n" +
32 | "\n" +
33 | " \n" +
34 | ""
35 | assert_equal xml, doc.to_s
36 | end
37 | def test_unused_namespaces_arent_included
38 | doc = Handsoap::XmlMason::Document.new do |doc|
39 | doc.alias 'x', 'http://example.com/x'
40 | doc.alias 'y', 'http://example.com/y'
41 | doc.add 'x:body' do |b|
42 | b.add 'x:yonks'
43 | end
44 | end
45 | xml = "\n" +
46 | "\n" +
47 | " \n" +
48 | ""
49 | assert_equal xml, doc.to_s
50 | end
51 | def test_textnodes_arent_indented
52 | doc = Handsoap::XmlMason::Document.new do |doc|
53 | doc.add 'body' do |b|
54 | b.add 'yonks', "lorem\nipsum\ndolor\nsit amet"
55 | end
56 | end
57 | contents = doc.to_s.match(/([\w\W]*)<\/yonks>/)[1]
58 | assert_equal "lorem\nipsum\ndolor\nsit amet", contents
59 | end
60 | def test_node_contents_is_escaped
61 | doc = Handsoap::XmlMason::Document.new do |doc|
62 | doc.add 'body' do |b|
63 | b.add 'yonks' do |y|
64 | y.set_value 'bold'
65 | end
66 | end
67 | end
68 | contents = doc.to_s.match(/([\w\W]*)<\/yonks>/)[1]
69 | assert_equal "<b>bold</b>", contents
70 | end
71 | def test_node_contents_is_not_escaped_if_flag_raw
72 | doc = Handsoap::XmlMason::Document.new do |doc|
73 | doc.add 'body' do |b|
74 | b.add 'yonks' do |y|
75 | y.set_value 'bold', :raw
76 | end
77 | end
78 | end
79 | contents = doc.to_s.match(/([\w\W]*)<\/yonks>/)[1]
80 | assert_equal "bold", contents
81 | end
82 | def test_finder_can_locate_node_by_nodename
83 | node = nil
84 | doc = Handsoap::XmlMason::Document.new do |doc|
85 | doc.add 'body' do |b|
86 | b.add 'yonks', "lorem\nipsum\ndolor\nsit amet"
87 | b.add 'ninja' do |n|
88 | node = n
89 | n.set_value "ninja"
90 | end
91 | b.add 'ninjitsu' do |n|
92 | n.set_value "ninjitsu"
93 | end
94 | end
95 | end
96 | assert_equal "ninjitsu", node.document.find('ninjitsu').to_s
97 | assert_equal "ninjitsu", node.document.find(:ninjitsu).to_s
98 | end
99 | def test_xml_header_is_optional
100 | doc = Handsoap::XmlMason::Document.new do |doc|
101 | doc.add "foo", "Lorem Ipsum"
102 | end
103 | doc.xml_header = false
104 | assert_equal "Lorem Ipsum", doc.to_s
105 | end
106 | end
107 |
108 | =begin
109 |
110 | # doc = Handsoap::XmlMason::Document.new do |doc|
111 | # doc.alias 'env', "http://www.w3.org/2003/05/soap-envelope"
112 | # doc.alias 'm', "http://travelcompany.example.org/reservation"
113 | # doc.alias 'n', "http://mycompany.example.com/employees"
114 | # doc.alias 'p', "http://travelcompany.example.org/reservation/travel"
115 | # doc.alias 'q', "http://travelcompany.example.org/reservation/hotels"
116 |
117 | # doc.add "env:Envelope" do |env|
118 | # env.add "Header" do |header|
119 | # header.add 'm:reservation' do |r|
120 | # r.set_attr 'env:role', "http://www.w3.org/2003/05/soap-envelope/role/next"
121 | # r.set_attr 'env:mustUnderstand', "true"
122 | # r.add 'reference', "uuid:093a2da1-q345-739r-ba5d-pqff98fe8j7d"
123 | # r.add 'dateAndTime', "2001-11-29T13:20:00.000-05:00"
124 | # end
125 | # header.add 'n:passenger' do |p|
126 | # p.set_attr 'env:role', "http://www.w3.org/2003/05/soap-envelope/role/next"
127 | # p.set_attr 'env:mustUnderstand', "true"
128 | # p.add 'name', "Åke Jógvan Øyvind"
129 | # end
130 | # end
131 | # env.add "Body" do |body|
132 | # body.add 'p:itinerary' do |i|
133 | # i.add 'departure' do |d|
134 | # d.add 'departing', "New York"
135 | # d.add 'arriving', "Los Angeles"
136 | # d.add 'departureDate', "2001-12-14"
137 | # d.add 'departureTime', "late afternoon"
138 | # d.add 'seatPreference', "aisle"
139 | # end
140 | # i.add 'return' do |r|
141 | # r.add 'departing', "Los Angeles"
142 | # r.add 'arriving', "New York"
143 | # r.add 'departureDate', "2001-12-20"
144 | # r.add 'departureTime', "mid-morning"
145 | # r.add 'seatPreference'
146 | # end
147 | # end
148 | # body.add 'q:lodging' do |l|
149 | # l.add 'preference', "none"
150 | # end
151 | # end
152 | # end
153 | # end
154 |
155 | # puts doc
156 | # puts doc.find("Body")
157 | # puts doc.find_all("departureTime")
158 |
159 | =end
160 |
--------------------------------------------------------------------------------
/tests/xml_query_front_test.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'test/unit'
3 |
4 | $LOAD_PATH << "#{File.dirname(__FILE__)}/../lib/"
5 | require "handsoap"
6 | require 'handsoap/xml_query_front'
7 |
8 | module AbstractXmlDriverTestCase
9 | def xml_source
10 | '
11 |
13 |
14 | ' + "bl\303\245b\303\246rgr\303\270d" + '
15 |
16 | blåbærgrød
17 |
19 |
21 |
22 | 3f8ceabd-2d15-47f0-b35e-d52ee868a4a6
23 |
24 |
25 | http://location_to_thumbnail_for_www.alexa.com
27 | www.alexa.com
28 |
29 |
30 | Success
31 |
32 |
33 |
35 |
36 | 3f8ceabd-2d15-47f0-b35e-d52ee868a4a6
37 |
38 |
39 | http://location_to_thumbnail_for_www.amazon.com
41 | www.amazon.com
42 |
43 |
44 | Success
45 |
46 |
47 |
49 |
50 | 3f8ceabd-2d15-47f0-b35e-d52ee868a4a6
51 |
52 |
53 | http://location_to_thumbnail_for_www.a9.com
55 | www.a9.com
56 |
57 |
58 | Success
59 |
60 |
61 |
62 |
63 | '
64 | end
65 | def create_default_document
66 | doc = Handsoap::XmlQueryFront.parse_string(xml_source, driver)
67 | doc.add_namespace("foo", "http://ast.amazonaws.com/doc/2006-05-15/")
68 | doc.add_namespace("aws", "http://ast.amazonaws.com/doc/2006-05-15/")
69 | doc
70 | end
71 | def test_query_for_undefined_prefix_raises
72 | doc = Handsoap::XmlQueryFront.parse_string(xml_source, driver)
73 | assert_raise RuntimeError do
74 | doc.xpath("//aws:OperationRequest")
75 | end
76 | end
77 | def test_axis_isnt_interpreted_as_a_namespace
78 | doc = Handsoap::XmlQueryFront.parse_string(xml_source, driver)
79 | doc.xpath('/env:Envelope/env:Body/descendant-or-self::env:Fault', { 'env' => "void://" })
80 | end
81 | def test_query_for_defined_prefix
82 | doc = Handsoap::XmlQueryFront.parse_string(xml_source, driver)
83 | doc.add_namespace("aws", "http://ast.amazonaws.com/doc/2006-05-15/")
84 | doc.xpath("//aws:OperationRequest")
85 | end
86 | def test_get_node_name
87 | doc = create_default_document
88 | assert_equal "Thumbnail", doc.xpath("//aws:Response/aws:ThumbnailResult/*").first.node_name
89 | end
90 | def test_get_node_namespace
91 | doc = create_default_document
92 | assert_equal "http://ast.amazonaws.com/doc/2006-05-15/", doc.xpath("//aws:Response/aws:ThumbnailResult/*").first.node_namespace
93 | end
94 | def test_get_nil_node_namespace
95 | doc = create_default_document
96 | assert_equal nil, doc.xpath("//entity-test").first.node_namespace
97 | end
98 | def test_get_attribute_name
99 | doc = create_default_document
100 | assert_equal "Exists", doc.xpath("//aws:Thumbnail/@Exists").first.node_name
101 | end
102 | def test_get_text_selection_as_string
103 | doc = create_default_document
104 | assert_equal "http://location_to_thumbnail_for_www.alexa.com", doc.xpath("//aws:Thumbnail[1]/text()").to_s
105 | end
106 | def test_query_with_multiple_prefixes_for_same_namespace
107 | doc = create_default_document
108 | assert_equal "3f8ceabd-2d15-47f0-b35e-d52ee868a4a6", doc.xpath("//foo:OperationRequest/aws:RequestId").first.to_s
109 | end
110 | def test_hpricot_style_searching_is_supported
111 | doc = create_default_document
112 | assert_equal "3f8ceabd-2d15-47f0-b35e-d52ee868a4a6", (doc/"//foo:OperationRequest/aws:RequestId").first.to_s
113 | end
114 | def test_query_result_is_mappable
115 | doc = create_default_document
116 | assert_equal "3f8ceabd-2d15-47f0-b35e-d52ee868a4a6\n3f8ceabd-2d15-47f0-b35e-d52ee868a4a6\n3f8ceabd-2d15-47f0-b35e-d52ee868a4a6", doc.xpath("//foo:OperationRequest/aws:RequestId").map{|e| e.to_s }.join("\n")
117 | end
118 | def test_resultset_inherits_prefixes
119 | doc = create_default_document
120 | assert_equal "3f8ceabd-2d15-47f0-b35e-d52ee868a4a6", doc.xpath("//foo:OperationRequest").first.xpath("aws:RequestId").first.to_s
121 | end
122 | def test_resultset_delegates_slash
123 | doc = create_default_document
124 | operation_request = (doc/"//foo:OperationRequest")
125 | assert_equal "3f8ceabd-2d15-47f0-b35e-d52ee868a4a6", (operation_request/"aws:RequestId").to_s
126 | end
127 | def test_attribute_can_cast_to_boolean
128 | doc = create_default_document
129 | assert_kind_of TrueClass, doc.xpath("//aws:Thumbnail/@Exists").first.to_boolean
130 | end
131 | def test_text_content_is_utf8
132 | doc = create_default_document
133 | assert_equal "bl\303\245b\303\246rgr\303\270d", doc.xpath("//encoding-test").first.to_s
134 | end
135 | def test_cdata_has_no_surrounding_markers
136 | doc = create_default_document
137 | assert_equal "character data", doc.xpath("//cdata-test").first.to_s
138 | end
139 | def test_entity_escaped_text_content_is_utf8
140 | doc = create_default_document
141 | assert_equal "bl\303\245b\303\246rgr\303\270d", doc.xpath("//entity-test").first.to_s
142 | end
143 | def test_entity_escaped_attribute_is_utf8
144 | doc = create_default_document
145 | assert_equal "bl\303\245b\303\246rgr\303\270d", doc.xpath("//aws:Thumbnail/@attr-test").first.to_s
146 | end
147 | def test_error_on_parsing_non_xml
148 | assert_raise Handsoap::XmlQueryFront::ParseError do
149 | doc = Handsoap::XmlQueryFront.parse_string("blah", driver)
150 | end
151 | end
152 | def test_error_on_parsing_empty_string
153 | assert_raise Handsoap::XmlQueryFront::ParseError do
154 | doc = Handsoap::XmlQueryFront.parse_string("", driver)
155 | end
156 | end
157 | def test_error_on_parsing_empty_document
158 | assert_raise Handsoap::XmlQueryFront::ParseError do
159 | doc = Handsoap::XmlQueryFront.parse_string("", driver)
160 | end
161 | end
162 | def test_serialize_pretty
163 | doc = Handsoap::XmlQueryFront.parse_string('blah', driver)
164 | assert_equal "\n blah\n", doc.xpath("//foo").to_xml
165 | end
166 | def test_serialize_raw
167 | str = "\n\t\tblah\n\n"
168 | doc = Handsoap::XmlQueryFront.parse_string("" + str, driver)
169 | assert_equal str, doc.xpath("//foo").to_raw
170 | end
171 | def test_an_unformatted_string_can_be_serialized_raw
172 | doc = Handsoap::XmlQueryFront.parse_string('blah', driver)
173 | assert_equal "blah", doc.xpath("//foo").to_raw
174 | end
175 | def test_query_by_syntactic_sugar
176 | doc = create_default_document
177 | assert_equal 3, (doc/"//aws:OperationRequest[1]/aws:RequestId").to_i
178 | assert_equal (doc/"//aws:OperationRequest[1]/aws:RequestId").to_i, (doc/"//aws:OperationRequest[1]/aws:RequestId").first.to_i
179 | end
180 | def test_attribute_hash_access
181 | doc = create_default_document
182 | node = doc.xpath("//aws:Thumbnail").first
183 | assert_equal "bl\303\245b\303\246rgr\303\270d", node['attr-test']
184 | end
185 | def test_attribute_hash_access_fails_with_a_symbol
186 | doc = create_default_document
187 | node = doc.xpath("//aws:Thumbnail").first
188 | assert_raise ArgumentError do
189 | assert_equal "foobar", node[:foo]
190 | end
191 | end
192 | def test_select_children
193 | doc = create_default_document
194 | node = doc.xpath("//aws:ThumbnailResponse").first
195 | result = node.children.map { |node| node.node_name }.join(",")
196 | assert_equal "text,Response,text,Response,text,Response,text", result
197 | end
198 | end
199 |
200 | class TestOfREXMLDriver < Test::Unit::TestCase
201 | include AbstractXmlDriverTestCase
202 | def driver
203 | :rexml
204 | end
205 | end
206 |
207 | class TestOfNokogiriDriver < Test::Unit::TestCase
208 | include AbstractXmlDriverTestCase
209 | def driver
210 | :nokogiri
211 | end
212 | end
213 |
214 | class TestOfLibXMLDriver < Test::Unit::TestCase
215 | include AbstractXmlDriverTestCase
216 | def driver
217 | :libxml
218 | end
219 | end
220 |
--------------------------------------------------------------------------------