troelskn-handsoap 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +10 -162
- data/VERSION.yml +1 -1
- data/lib/handsoap/http.rb +24 -1
- data/lib/handsoap/service.rb +0 -106
- metadata +2 -2
data/README.markdown
CHANGED
@@ -15,6 +15,8 @@ Handsoap is a library for creating SOAP clients in Ruby.
|
|
15
15
|
|
16
16
|
API docs are at [http://rdoc.info/projects/troelskn/handsoap](http://rdoc.info/projects/troelskn/handsoap)
|
17
17
|
|
18
|
+
Some usage information is to be found in [the wiki](http://wiki.github.com/troelskn/handsoap).
|
19
|
+
|
18
20
|

|
19
21
|
|
20
22
|
Why
|
@@ -70,180 +72,26 @@ The protocol also contains a large and unwieldy specification of how to do the (
|
|
70
72
|
|
71
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.
|
72
74
|
|
73
|
-
API
|
75
|
+
API documentation
|
74
76
|
---
|
75
77
|
|
76
78
|
In addition to this guide, there's autogenerated API documentation available at [http://rdoc.info/projects/troelskn/handsoap](http://rdoc.info/projects/troelskn/handsoap)
|
77
79
|
|
78
|
-
|
80
|
+
Getting started
|
79
81
|
---
|
80
82
|
|
81
|
-
|
82
|
-
|
83
|
-
Handsoap can use either [curb](http://curb.rubyforge.org/) or [httpclient](http://dev.ctor.org/http-access2) for HTTP-connectivity. The former is recommended, and default, but for portability you might choose 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.
|
83
|
+
For getting started with Handsoap, you should read [the guide in the wiki](http://wiki.github.com/troelskn/handsoap/recommendations).
|
84
84
|
|
85
|
-
|
86
|
-
|
87
|
-
There is also 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.
|
88
|
-
|
89
|
-
Recommendations
|
85
|
+
The toolbox
|
90
86
|
---
|
91
87
|
|
92
|
-
|
93
|
-
|
94
|
-
1. You need a WSDL for the service you want to consume.
|
95
|
-
|
96
|
-
2. Figure out the url for the endpoint, as well as the protocol version. Put this in a config file.
|
97
|
-
* To find the endpoint, look inside the wsdl, for `<soap:address location="..">`
|
98
|
-
|
99
|
-
3. Create a service class. Add endpoints and protocol. Alias needed namespace(s).
|
100
|
-
* To find the namespace(s), look in the samples from soapUI. It will be imported as `v1`
|
101
|
-
* Note that you can now use the provided generator to skip this step.
|
102
|
-
|
103
|
-
4. Open the wsdl in [soapUI](http://www.soapui.org/).
|
104
|
-
|
105
|
-
5. In soapUI, find a sample request for the method you want to use. Copy+paste the body-part.
|
106
|
-
|
107
|
-
6. Create a method in your service class (Use ruby naming convention)
|
108
|
-
|
109
|
-
7. Write Ruby-code (using XmlMason) to generate a request that is similar to the example from soapUI. (In your copy+paste buffer)
|
110
|
-
|
111
|
-
8. Write Ruby-code to parse the response (an XML-document) into Ruby data types.
|
112
|
-
|
113
|
-
9. Write an integration test to verify that your method works as expected. You can use soapUI to [generate a mock-service](http://www.soapui.org/userguide/mock/getting_started.html).
|
114
|
-
|
115
|
-
Repeat point 5..9 for each method that you need to use.
|
116
|
-
Between each iteration, you should refactor shared code into helper functions.
|
117
|
-
|
118
|
-
###Configuration
|
119
|
-
|
120
|
-
If you use Rails, you should put the endpoint in a constant in the environment file. That way, you can have different endpoints for test/development/production/etc.
|
121
|
-
|
122
|
-
If you don't use Rails, it's still a good idea to move this information to a config file.
|
123
|
-
|
124
|
-
The configuration could look like this:
|
125
|
-
|
126
|
-
# wsdl: http://example.org/ws/service?WSDL
|
127
|
-
EXAMPLE_SERVICE_ENDPOINT = {
|
128
|
-
:uri => 'http://example.org/ws/service',
|
129
|
-
:version => 2
|
130
|
-
}
|
131
|
-
|
132
|
-
If you use Rails, you will need to load the gem from the `config/environment.rb` file, using:
|
133
|
-
|
134
|
-
config.gem 'troelskn-handsoap', :lib => 'handsoap', :source => "http://gems.github.com"
|
135
|
-
|
136
|
-
If you use the standard development environment of Rails, you may run into troubles with cached classes. Add the following line to the initializer:
|
137
|
-
|
138
|
-
ActiveSupport::Dependencies.explicitly_unloadable_constants << 'Handsoap::Service'
|
139
|
-
|
140
|
-
###Generator
|
141
|
-
|
142
|
-
From version 0.2.0 Handsoap sports a generator, that creates the service class + an integration test case. This is just a rough starting point for your service - You still have to fill out the actual mappings to/from xml, but at least it saves your some copy-pasting from this guide.
|
143
|
-
|
144
|
-
To use the generator, create a Rails project and run the script `script/generate handsoap`, giving the url of the WSDL.
|
145
|
-
|
146
|
-
###Service class
|
147
|
-
|
148
|
-
Put your service in a file under `app/models`. You should extend `Handsoap::Service`.
|
149
|
-
|
150
|
-
You need to provide the endpoint and the SOAP version (1.1 or 1.2). If in doubt, use version 2.
|
151
|
-
|
152
|
-
A service usually has a namespace for describing the message-body ([RPC/Litteral style](http://www.ibm.com/developerworks/webservices/library/ws-whichwsdl/#N1011F)). You should set this in the `on_create_document` handler. Likewise, the response returned *from* the server will contain elements that typically are defined in a single namespace relevant to the service. You can register this in the handler `on_response_document`.
|
153
|
-
|
154
|
-
A typical service looks like the following:
|
155
|
-
|
156
|
-
# -*- coding: utf-8 -*-
|
157
|
-
require 'handsoap'
|
158
|
-
|
159
|
-
class Example::FooService < Handsoap::Service
|
160
|
-
endpoint EXAMPLE_SERVICE_ENDPOINT
|
161
|
-
def on_create_document(doc)
|
162
|
-
# register namespaces for the request
|
163
|
-
doc.alias 'tns', "http://example.org/ws/spec"
|
164
|
-
end
|
165
|
-
def on_response_document(doc)
|
166
|
-
# register namespaces for the response
|
167
|
-
doc.add_namespace 'ns', 'http://example.org/ws/spec'
|
168
|
-
end
|
169
|
-
|
170
|
-
# public methods
|
171
|
-
# todo
|
172
|
-
|
173
|
-
private
|
174
|
-
# helpers
|
175
|
-
# todo
|
176
|
-
end
|
177
|
-
|
178
|
-
The above would go in the file `app/models/example/foo_service.rb`
|
179
|
-
|
180
|
-
###Integration tests
|
181
|
-
|
182
|
-
Since you're writing mappings manually, it's a good idea to write tests that verify that the service works. If you use standard Rails with `Test::Unit`, you should put these in an integration-test.
|
183
|
-
|
184
|
-
For the sample service above, you would create a file in `test/integration/example/foo_service.rb`, with the following content:
|
185
|
-
|
186
|
-
# -*- coding: utf-8 -*-
|
187
|
-
require 'test_helper'
|
188
|
-
|
189
|
-
# Example::FooService.logger = $stdout
|
190
|
-
|
191
|
-
class Example::FooServiceTest < Test::Unit::TestCase
|
192
|
-
def test_update_icon
|
193
|
-
icon = { :href => 'http://www.example.com/icon.jpg', :type => 'image/jpeg' }
|
194
|
-
result = Example::FooService.update_icon!(icon)
|
195
|
-
assert_equal icon.type, result.type
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
Note the commented-out line. If you set a logger on the service-class, you can see exactly which XML goes forth and back, which is very useful for debugging.
|
200
|
-
|
201
|
-
###Methods
|
202
|
-
|
203
|
-
You should use Ruby naming-conventions for methods names. If the method has side-effects, you should postfix it with an exclamation.
|
204
|
-
Repeat code inside the invoke-block, should be refactored out to *builders*, and the response should be parsed with a *parser*.
|
205
|
-
|
206
|
-
def update_icon!(icon)
|
207
|
-
response = invoke("tns:UpdateIcon") do |message|
|
208
|
-
build_icon!(message, icon)
|
209
|
-
end
|
210
|
-
parse_icon(response/"//icon").first)
|
211
|
-
end
|
212
|
-
|
213
|
-
|
214
|
-
###Helpers
|
215
|
-
|
216
|
-
You'll end up with two kinds of helpers; Ruby->XML transformers (aka. *builders*) and XML->Ruby transformers (aka. *parsers*).
|
217
|
-
It's recommended that you stick to the following style/naming scheme:
|
218
|
-
|
219
|
-
# icon -> xml
|
220
|
-
def build_icon!(message, icon)
|
221
|
-
message.add "icon" do |i|
|
222
|
-
i.set_attr "href", icon[:href]
|
223
|
-
i.set_attr "type", icon[:type]
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
# xml -> icon
|
228
|
-
def parse_icon(node)
|
229
|
-
{ :href => (node/"@href").to_s, :type => (node/"@type").to_s }
|
230
|
-
end
|
88
|
+
The Handsoap toolbox consists of the following components.
|
231
89
|
|
232
|
-
or, if you
|
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.
|
233
91
|
|
234
|
-
|
235
|
-
def build_icon!(message, icon)
|
236
|
-
message.add "icon" do |i|
|
237
|
-
i.set_attr "href", icon.href
|
238
|
-
i.set_attr "type", icon.type
|
239
|
-
end
|
240
|
-
end
|
92
|
+
For parsing XML, Handsoap defaults to use [Nokogiri](http://github.com/tenderlove/nokogiri/tree/master). Handsoap has an abstraction layer, so that you can switch between REXML, Nokogiri and ruby-libxml. Besides providing portability between these parsers, Handsop also gives some helper functions that are meaningful when parsing SOAP envelopes.
|
241
93
|
|
242
|
-
|
243
|
-
def parse_icon(node)
|
244
|
-
Icon.new :href => (node/"@href").to_s,
|
245
|
-
:type => (node/"@type").to_s
|
246
|
-
end
|
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.
|
247
95
|
|
248
96
|
License
|
249
97
|
---
|
data/VERSION.yml
CHANGED
data/lib/handsoap/http.rb
CHANGED
@@ -237,6 +237,29 @@ module Handsoap
|
|
237
237
|
end
|
238
238
|
end
|
239
239
|
|
240
|
+
# A mock driver for your testing needs.
|
241
|
+
#
|
242
|
+
# To use it, create a new instance and assign to +Handsoap::Http.drivers+. Then configure +Handsoap::Service+ to use it:
|
243
|
+
#
|
244
|
+
# Handsoap::Http.drivers[:mock] = Handsoap::Http::HttpMock.new :status => 200, :headers => headers, :content => body
|
245
|
+
# Handsoap.http_driver = :mock
|
246
|
+
#
|
247
|
+
# Remember that headers should use \r\n, rather than \n.
|
248
|
+
class HttpMock
|
249
|
+
attr_accessor :mock, :last_request, :is_loaded
|
250
|
+
def initialize(mock)
|
251
|
+
@mock = mock
|
252
|
+
@is_loaded = false
|
253
|
+
end
|
254
|
+
def load!
|
255
|
+
is_loaded = true
|
256
|
+
end
|
257
|
+
def send_http_request(request)
|
258
|
+
@last_request = request
|
259
|
+
Handsoap::Http.parse_http_part(mock[:headers], mock[:content], mock[:status], mock[:content_type])
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
240
263
|
# Parses a raw http response into a +Response+ or +Part+ object.
|
241
264
|
def self.parse_http_part(headers, body, status = nil, content_type = nil)
|
242
265
|
if headers.kind_of? String
|
@@ -363,7 +386,7 @@ module Handsoap
|
|
363
386
|
def self.parse_headers(raw)
|
364
387
|
header = Hash.new([].freeze)
|
365
388
|
field = nil
|
366
|
-
raw.each {|line|
|
389
|
+
raw.gsub(/^(\r\n)+|(\r\n)+$/, '').each {|line|
|
367
390
|
case line
|
368
391
|
when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):\s*(.*?)\s*\z/om
|
369
392
|
field, value = $1, $2
|
data/lib/handsoap/service.rb
CHANGED
@@ -290,112 +290,6 @@ module Handsoap
|
|
290
290
|
end
|
291
291
|
return xml_string
|
292
292
|
end
|
293
|
-
|
294
|
-
# Parses a multipart http-response body into parts.
|
295
|
-
# +boundary+ is a string of the boundary token.
|
296
|
-
# +content_io+ is either a string or an IO. If it's an IO, then content_length must be specified.
|
297
|
-
# +content_length+ (optional) is an integer, specifying the length of +content_io+
|
298
|
-
#
|
299
|
-
# This code is lifted from cgi.rb
|
300
|
-
#
|
301
|
-
def self.parse_multipart(boundary, content_io, content_length = nil)
|
302
|
-
if content_io.kind_of? String
|
303
|
-
content_length = content_io.length
|
304
|
-
content_io = StringIO.new(content_io, 'r')
|
305
|
-
elsif !(content_io.kind_of? IO) || content_length.nil?
|
306
|
-
raise "Second argument must be String or IO with content_length"
|
307
|
-
end
|
308
|
-
|
309
|
-
boundary = "--" + boundary
|
310
|
-
quoted_boundary = Regexp.quote(boundary, "n")
|
311
|
-
buf = ""
|
312
|
-
bufsize = 10 * 1024
|
313
|
-
boundary_end = ""
|
314
|
-
|
315
|
-
# start multipart/form-data
|
316
|
-
content_io.binmode if defined? content_io.binmode
|
317
|
-
boundary_size = boundary.size + "\r\n".size
|
318
|
-
content_length -= boundary_size
|
319
|
-
status = content_io.read(boundary_size)
|
320
|
-
if nil == status
|
321
|
-
raise EOFError, "no content body"
|
322
|
-
elsif boundary + "\r\n" != status
|
323
|
-
raise EOFError, "bad content body"
|
324
|
-
end
|
325
|
-
|
326
|
-
parts = []
|
327
|
-
|
328
|
-
loop do
|
329
|
-
head = nil
|
330
|
-
if 10240 < content_length
|
331
|
-
require "tempfile"
|
332
|
-
body = Tempfile.new("CGI")
|
333
|
-
else
|
334
|
-
begin
|
335
|
-
require "stringio"
|
336
|
-
body = StringIO.new
|
337
|
-
rescue LoadError
|
338
|
-
require "tempfile"
|
339
|
-
body = Tempfile.new("CGI")
|
340
|
-
end
|
341
|
-
end
|
342
|
-
body.binmode if defined? body.binmode
|
343
|
-
|
344
|
-
until head and /#{quoted_boundary}(?:\r\n|--)/n.match(buf)
|
345
|
-
|
346
|
-
if (not head) and /\r\n\r\n/n.match(buf)
|
347
|
-
buf = buf.sub(/\A((?:.|\n)*?\r\n)\r\n/n) do
|
348
|
-
head = $1.dup
|
349
|
-
""
|
350
|
-
end
|
351
|
-
next
|
352
|
-
end
|
353
|
-
|
354
|
-
if head and ( ("\r\n" + boundary + "\r\n").size < buf.size )
|
355
|
-
body.print buf[0 ... (buf.size - ("\r\n" + boundary + "\r\n").size)]
|
356
|
-
buf[0 ... (buf.size - ("\r\n" + boundary + "\r\n").size)] = ""
|
357
|
-
end
|
358
|
-
|
359
|
-
c = if bufsize < content_length
|
360
|
-
content_io.read(bufsize)
|
361
|
-
else
|
362
|
-
content_io.read(content_length)
|
363
|
-
end
|
364
|
-
if c.nil? || c.empty?
|
365
|
-
raise EOFError, "bad content body"
|
366
|
-
end
|
367
|
-
buf.concat(c)
|
368
|
-
content_length -= c.size
|
369
|
-
end
|
370
|
-
|
371
|
-
buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
|
372
|
-
body.print $1
|
373
|
-
if "--" == $2
|
374
|
-
content_length = -1
|
375
|
-
end
|
376
|
-
boundary_end = $2.dup
|
377
|
-
""
|
378
|
-
end
|
379
|
-
|
380
|
-
body.rewind
|
381
|
-
parts << {:head => head, :body => body.read(body.size)}
|
382
|
-
|
383
|
-
# if body.kind_of? ::StringIO
|
384
|
-
# parts << {:head => head, :body => body.string}
|
385
|
-
# elsif body.kind_of? ::Tempfile
|
386
|
-
# body.rewind
|
387
|
-
# parts << {:head => head, :body => body.read(body.size)}
|
388
|
-
# else
|
389
|
-
# raise "body must be StringIO or Tempfile"
|
390
|
-
# end
|
391
|
-
|
392
|
-
break if buf.size == 0
|
393
|
-
break if content_length == -1
|
394
|
-
end
|
395
|
-
raise EOFError, "bad boundary end of body part" unless boundary_end =~ /--/
|
396
|
-
parts
|
397
|
-
end
|
398
|
-
|
399
293
|
end
|
400
294
|
|
401
295
|
# Legacy/BC code here. This shouldn't be used in new applications.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: troelskn-handsoap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Troels Knak-Nielsen
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-08-
|
12
|
+
date: 2009-08-20 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|