troelskn-handsoap 0.5.2 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
![Handsoap](http://ny-image0.etsy.com/il_430xN.68558416.jpg)
|
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
|
|