simplews 1.1.5 → 1.1.6
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/History.txt +6 -0
- data/lib/simplews/asynchronous.rb +207 -13
- data/lib/simplews/version.rb +1 -1
- data/lib/simplews.rb +77 -7
- data/test/simplews/test_asynchronous.rb +3 -4
- metadata +1 -1
data/History.txt
CHANGED
@@ -1,13 +1,98 @@
|
|
1
1
|
require File.join(File.dirname(File.dirname(__FILE__)) + '/simplews')
|
2
2
|
|
3
3
|
require 'yaml'
|
4
|
+
|
5
|
+
# This class extends SimpleWS and allows provides with a framework to
|
6
|
+
# run asynchronous jobs. Job are initiated and can be latter be accessed
|
7
|
+
# using a unique job id. The status can be queried, and when finished,
|
8
|
+
# the results can be gathered. Jobs are also able to save their states
|
9
|
+
# between executions of the server.
|
10
|
+
#
|
11
|
+
# The class provides a instance function start_job, that receives a
|
12
|
+
# block of code and creates a new thread to run this block of code. It
|
13
|
+
# also provides some helper functions to set the state of the job, as
|
14
|
+
# well as messages or to abort of report errors.
|
15
|
+
#
|
16
|
+
# Each job can produce a number of results saved into files that can
|
17
|
+
# latter be accessed by the client. At any time the code inside the
|
18
|
+
# block can access any of the following helper functions: step, read,
|
19
|
+
# write, abort_process, job_name and save. As in the following example.
|
20
|
+
# (+process+ will be a method served by the server):
|
21
|
+
#
|
22
|
+
# def process(name)
|
23
|
+
# start_job(name, %w(test.txt)) do
|
24
|
+
# begin
|
25
|
+
# write('test.txt', job_name)
|
26
|
+
# step(:init)
|
27
|
+
# sleep 2
|
28
|
+
# step(:step1, "Step1")
|
29
|
+
# sleep 2
|
30
|
+
# step(:step2, "Step2")
|
31
|
+
# sleep 2
|
32
|
+
# step(:step2, "Step3")
|
33
|
+
# rescue
|
34
|
+
# error($!.message)
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# Note that any file specified in the write or read method is relative
|
40
|
+
# to the workdir directory. The optional array passed as second argument
|
41
|
+
# to start_job lists the results the job will be generating and that
|
42
|
+
# will be accessible latter to the clients, one could have also done
|
43
|
+
# this:
|
44
|
+
#
|
45
|
+
# def process(name)
|
46
|
+
# actual_name = start_job(name) do
|
47
|
+
# begin
|
48
|
+
# write(job_name + ".txt", "Hi")
|
49
|
+
# step(:init)
|
50
|
+
# sleep 2
|
51
|
+
# step(:step1, "Step1")
|
52
|
+
# sleep 2
|
53
|
+
# step(:step2, "Step2")
|
54
|
+
# sleep 2
|
55
|
+
# step(:step2, "Step3")
|
56
|
+
# rescue
|
57
|
+
# error($!.message)
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# set_results(actual_name, [ actual_name + ".txt" ])
|
62
|
+
#
|
63
|
+
# actual_name
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# Note in this example that the name provided to start_job is only a
|
67
|
+
# suggestion, it a job is already available with that name a number will
|
68
|
+
# be prepended to it. The actual name used is return by the start_job
|
69
|
+
# method and is the one that must be used in the set_results method.
|
70
|
+
# Also note that the +process+ method in the example has to return the
|
71
|
+
# actual name of the job so it will be accessible to the client.
|
72
|
+
#
|
73
|
+
#
|
74
|
+
# The clients have a number of methods available to query and modify the
|
75
|
+
# state of the job, as well as to gather results: status, messages,
|
76
|
+
# done, error, aborted, abort, results and result.
|
77
|
+
#
|
78
|
+
#
|
79
|
+
# While reading the documentation please note that there are three types
|
80
|
+
# of methods here, methods used inside the web server methods, methods
|
81
|
+
# used inside code blocks executed by Job Threads, and methods that are
|
82
|
+
# exported by the web server. Its important to call each method in the
|
83
|
+
# right context.
|
84
|
+
#
|
4
85
|
class SimpleWS::Asynchronous < SimpleWS
|
5
86
|
|
87
|
+
|
6
88
|
class JobNotFound < Exception; end
|
7
89
|
class JobAborted < Exception; end
|
8
90
|
|
9
91
|
|
10
|
-
class Job < Thread
|
92
|
+
class Job < Thread # :nodoc:
|
93
|
+
|
94
|
+
private
|
95
|
+
|
11
96
|
def self.random_name(s="", num=20)
|
12
97
|
num.times{
|
13
98
|
r = rand
|
@@ -40,6 +125,10 @@ class SimpleWS::Asynchronous < SimpleWS
|
|
40
125
|
end
|
41
126
|
end
|
42
127
|
|
128
|
+
public
|
129
|
+
|
130
|
+
# Returns the job specified in the +name+. If it is not currently
|
131
|
+
# found but it has a saved state, then it is loaded.
|
43
132
|
def self.job(name)
|
44
133
|
if @@jobs[name].nil?
|
45
134
|
if File.exists?(File.join(@@workdir, ".save/#{name}.yaml"))
|
@@ -47,7 +136,7 @@ class SimpleWS::Asynchronous < SimpleWS
|
|
47
136
|
job.load
|
48
137
|
@@jobs[name] = job
|
49
138
|
else
|
50
|
-
raise JobNotFound, "Job with name '#{ name }' was not found"
|
139
|
+
raise SimpleWS::Asynchronous::JobNotFound, "Job with name '#{ name }' was not found"
|
51
140
|
end
|
52
141
|
end
|
53
142
|
@@jobs[name]
|
@@ -74,7 +163,7 @@ class SimpleWS::Asynchronous < SimpleWS
|
|
74
163
|
def self.workdir=(workdir)
|
75
164
|
@@workdir = workdir
|
76
165
|
begin
|
77
|
-
FileUtils.
|
166
|
+
FileUtils.mkdir_p(@@workdir) unless File.exist?( @@workdir)
|
78
167
|
FileUtils.mkdir(File.join(@@workdir,'.save')) unless File.exist?( File.join(@@workdir,'.save'))
|
79
168
|
rescue
|
80
169
|
puts "Error creating work directory:"
|
@@ -139,9 +228,9 @@ class SimpleWS::Asynchronous < SimpleWS
|
|
139
228
|
@name
|
140
229
|
end
|
141
230
|
|
142
|
-
def step(status, message=
|
231
|
+
def step(status, message= nil)
|
143
232
|
@status = status
|
144
|
-
@messages << message if message != ""
|
233
|
+
@messages << message if message && message != ""
|
145
234
|
end
|
146
235
|
|
147
236
|
def read(path)
|
@@ -204,18 +293,37 @@ class SimpleWS::Asynchronous < SimpleWS
|
|
204
293
|
############################################################################################3
|
205
294
|
#{{{ Server
|
206
295
|
|
296
|
+
# SERVER METHOD:
|
297
|
+
#
|
298
|
+
# Sets the workdir for the jobs
|
207
299
|
def set_workdir(workdir)
|
208
300
|
Job::workdir = workdir
|
209
301
|
end
|
210
302
|
|
303
|
+
# SERVER METHOD:
|
304
|
+
#
|
305
|
+
# Returns the workdir that any jobs created will be using
|
211
306
|
def workdir
|
212
307
|
Job::workdir
|
213
308
|
end
|
214
309
|
|
310
|
+
# SERVER METHOD:
|
311
|
+
#
|
312
|
+
# Starts a new job. The parameter +name+ is just a suggestion, if it
|
313
|
+
# is not available a number will be prepended to it. Results are a
|
314
|
+
# list of relative paths to files where results are expected to be
|
315
|
+
# stored. All paths are relative to the jobs workdir. Finally, block
|
316
|
+
# is the block of code to be executed by the Job Thread.
|
317
|
+
#
|
318
|
+
# Returns the actual name used as unique identifier for the job
|
215
319
|
def start_job(name, results = [], &block)
|
216
320
|
Job.start(name, results, &block)
|
217
321
|
end
|
218
322
|
|
323
|
+
# SERVER METHOD:
|
324
|
+
#
|
325
|
+
# Sets the list of paths to results for a particular job. This
|
326
|
+
# overwrites the ones specified by start_job.
|
219
327
|
def set_results(name, results)
|
220
328
|
Job.job(name).results = results
|
221
329
|
end
|
@@ -223,68 +331,154 @@ class SimpleWS::Asynchronous < SimpleWS
|
|
223
331
|
|
224
332
|
|
225
333
|
#{{{ Thread helper methods
|
334
|
+
|
335
|
+
|
336
|
+
# CODE HELPER FUNCTION:
|
337
|
+
#
|
338
|
+
# Saves the job to it can survive server restarts. This is
|
339
|
+
# automatically called when the code is finished.
|
226
340
|
def save
|
227
341
|
Thread.current.save
|
228
342
|
end
|
229
343
|
|
230
|
-
|
231
|
-
|
232
|
-
|
344
|
+
# CODE HELPER FUNCTION:
|
345
|
+
#
|
346
|
+
# Writes +content+ into the file specified by +path+. The +path+`is
|
347
|
+
# taken relative to the job's workdir.
|
348
|
+
def write(path, content)
|
349
|
+
Thread.current.write(path, content)
|
233
350
|
end
|
234
351
|
|
235
|
-
|
236
|
-
|
352
|
+
# CODE HELPER FUNCTION:
|
353
|
+
#
|
354
|
+
# Reads the content of the file specified by +path+ relative to the
|
355
|
+
# job's workdir.
|
356
|
+
def read(path)
|
357
|
+
Thread.current.read(path)
|
237
358
|
end
|
238
359
|
|
239
|
-
|
240
|
-
|
360
|
+
|
361
|
+
# CODE HELPER FUNCTION:
|
362
|
+
#
|
363
|
+
# Sets the status of the job and adds a message to the message queue
|
364
|
+
# if the message is specified, and is different from an empty string.
|
365
|
+
# This method is called automatically in the following situations.
|
366
|
+
#
|
367
|
+
# * The block has finished executing:
|
368
|
+
# step(:done)
|
369
|
+
# * The job recieves an untreated SimpleWS::Asynchronous::JobAborted exception
|
370
|
+
# step(:aborted, $!.message)
|
371
|
+
# * The job recieves any other exception
|
372
|
+
# step(:error, $!.message)
|
373
|
+
#
|
374
|
+
def step(status, message=nil)
|
375
|
+
Thread.current.step(status, message)
|
241
376
|
end
|
242
377
|
|
378
|
+
|
379
|
+
# CODE HELPER FUNCTION:
|
380
|
+
#
|
381
|
+
# Returns the actual name of the job running the code.
|
243
382
|
def job_name
|
244
383
|
Thread.current.name
|
245
384
|
end
|
246
385
|
|
386
|
+
# CODE HELPER FUNCTION:
|
387
|
+
#
|
388
|
+
# Aborts the job raising a SimpleWS::Asynchronous::JobAborted
|
389
|
+
# exception.
|
247
390
|
def abort_process
|
248
391
|
Thread.current.abort
|
249
392
|
end
|
250
393
|
|
251
394
|
|
252
|
-
|
253
395
|
#{{{ WS Methods
|
254
396
|
|
397
|
+
# CLIENT METHOD SERVED BY DEFAULT:
|
398
|
+
#
|
399
|
+
# +name+ is the unique job identifier.
|
400
|
+
#
|
401
|
+
# Returns job status of the job.
|
255
402
|
def status(name)
|
256
403
|
Job.job(name).status
|
257
404
|
end
|
258
405
|
|
406
|
+
# CLIENT METHOD SERVED BY DEFAULT:
|
407
|
+
#
|
408
|
+
# +name+ is the unique job identifier.
|
409
|
+
#
|
410
|
+
# Raises a SimpleWS::Asynchronous::JobAborted exception in the job
|
259
411
|
def abort(name)
|
260
412
|
Job.job(name).abort
|
261
413
|
end
|
262
414
|
|
415
|
+
# CLIENT METHOD SERVED BY DEFAULT:
|
416
|
+
#
|
417
|
+
# +name+ is the unique job identifier.
|
418
|
+
#
|
419
|
+
# Checks if the job is still running or has finished, either normally
|
420
|
+
# or after been aborted or suffering an error.
|
263
421
|
def done(name)
|
264
422
|
Job.job(name).done?
|
265
423
|
end
|
266
424
|
|
425
|
+
# CLIENT METHOD SERVED BY DEFAULT:
|
426
|
+
#
|
427
|
+
# +name+ is the unique job identifier.
|
428
|
+
#
|
429
|
+
# Checks if the job has been aborted.
|
267
430
|
def aborted(name)
|
268
431
|
Job.job(name).aborted?
|
269
432
|
end
|
270
433
|
|
434
|
+
# CLIENT METHOD SERVED BY DEFAULT:
|
435
|
+
#
|
436
|
+
# +name+ is the unique job identifier.
|
437
|
+
#
|
438
|
+
# Checks if the job has suffered an error
|
271
439
|
def error(name)
|
272
440
|
Job.job(name).error?
|
273
441
|
end
|
274
442
|
|
443
|
+
# CLIENT METHOD SERVED BY DEFAULT:
|
444
|
+
#
|
445
|
+
# +name+ is the unique job identifier.
|
446
|
+
#
|
447
|
+
# Returns the list of messages of the job.
|
275
448
|
def messages(name)
|
276
449
|
Job.job(name).messages
|
277
450
|
end
|
278
451
|
|
452
|
+
# CLIENT METHOD SERVED BY DEFAULT:
|
453
|
+
#
|
454
|
+
# +name+ is the unique job identifier.
|
455
|
+
#
|
456
|
+
# Returns a list of unique results identifiers, one for each of the
|
457
|
+
# results of the job.
|
279
458
|
def results(name)
|
280
459
|
Job.job(name).results
|
281
460
|
end
|
282
461
|
|
462
|
+
# CLIENT METHOD SERVED BY DEFAULT:
|
463
|
+
#
|
464
|
+
# +name+ is the unique job identifier.
|
465
|
+
# +result+ is the unique result identifier as returned by the results
|
466
|
+
# method.
|
467
|
+
#
|
468
|
+
# Retrieves a result of a given job.
|
283
469
|
def result(name, result)
|
284
470
|
Job.job(name).result(result)
|
285
471
|
end
|
286
472
|
|
287
473
|
|
474
|
+
# Creates an instance. All the parameters are as in SimpleWS, except
|
475
|
+
# for the additional workdir parameter. It the workdir is not provided
|
476
|
+
# a directory in the /tmp directory with the name of the server will
|
477
|
+
# be used.
|
478
|
+
#
|
479
|
+
# It serves by default the methods: abort, messages, status, done,
|
480
|
+
# error, abort, aborted, results and result, as well as the +wsdl+
|
481
|
+
# method from SimpleWS.
|
288
482
|
def initialize(name="WS", description="", host="localhost", port="1984", workdir=nil, *args)
|
289
483
|
super(name, description, host, port, *args)
|
290
484
|
@results = {}
|
data/lib/simplews/version.rb
CHANGED
data/lib/simplews.rb
CHANGED
@@ -2,8 +2,55 @@ require 'soap/rpc/standaloneServer'
|
|
2
2
|
require 'builder'
|
3
3
|
|
4
4
|
|
5
|
+
# SimpleWS is a class that wraps SOAP::RPC::StandaloneServer to ease the
|
6
|
+
# implementation of Web Services, specifically the generation of the
|
7
|
+
# +WSDL+ file. It provides a particular syntax that allows to specify
|
8
|
+
# the methods that are going to be served along with the types of the
|
9
|
+
# parameters and of the response so that the +WSDL+ can be generated
|
10
|
+
# accordingly. Actual Servers can be instances of this class where
|
11
|
+
# methods are assigned dynamically or of classes that extend SimpleWS.
|
12
|
+
# class TestWS < SimpleWS
|
13
|
+
# def hi(name)
|
14
|
+
# "Hi #{name}, how are you?"
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# def initialize(*args)
|
18
|
+
# super(*args)
|
19
|
+
# serve :hi, %w(name), :name => :string, :return => :string
|
20
|
+
#
|
21
|
+
# serve :bye, %w(name), :name => :string, :return => :string do |name|
|
22
|
+
# "Bye bye #{name}. See you soon."
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# # Creating a server and starting it
|
29
|
+
#
|
30
|
+
# server = TestWS.new("TestWS", "Greeting Services", 'localhost', '1984')
|
31
|
+
# server.wsdl("TestWS.wsdl")
|
32
|
+
#
|
33
|
+
# Thread.new do
|
34
|
+
# server.start
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# # Client code. This could be run in another process.
|
38
|
+
#
|
39
|
+
# driver = SimpleWS::get_driver('http://localhost:1984', "TestWS")
|
40
|
+
# puts driver.hi('Gladis') #=> "Hi Gladis, how are you?"
|
41
|
+
# puts driver.bye('Gladis') #=> "Bye bye Gladis. See you soon."
|
42
|
+
#
|
43
|
+
|
44
|
+
|
45
|
+
|
5
46
|
class SimpleWS < SOAP::RPC::StandaloneServer
|
6
47
|
|
48
|
+
# This is a helper function for clients. Given the +url+ where the
|
49
|
+
# server is listening, as well as the name of the server, it can
|
50
|
+
# manually access the +wsdl+ function and retrieve the complete +WSDL+
|
51
|
+
# file. This works *only* in web servers of class SimpleWS, not on
|
52
|
+
# the general SOAP::RPC::StandaloneServer or any other type of web
|
53
|
+
# server.
|
7
54
|
def self.get_wsdl(url, name)
|
8
55
|
require 'soap/rpc/driver'
|
9
56
|
driver = SOAP::RPC::Driver.new(url, "urn:#{ name }")
|
@@ -11,6 +58,8 @@ class SimpleWS < SOAP::RPC::StandaloneServer
|
|
11
58
|
driver.wsdl
|
12
59
|
end
|
13
60
|
|
61
|
+
# Builds on the get_wsdl function to provide the client with a
|
62
|
+
# reference to the driver. Again, only works with SimpleWS servers.
|
14
63
|
def self.get_driver(url, name)
|
15
64
|
require 'soap/wsdlDriver'
|
16
65
|
require 'fileutils'
|
@@ -24,6 +73,12 @@ class SimpleWS < SOAP::RPC::StandaloneServer
|
|
24
73
|
driver
|
25
74
|
end
|
26
75
|
|
76
|
+
# Creates an instance of a Server. The parameter +name+ specifies the
|
77
|
+
# +namespace+ used in the +WSDL+, +description+ is the description
|
78
|
+
# also included in the +WSDL+. The parameters +host+ and +port+,
|
79
|
+
# specify where the server will be listening, they are parameters of
|
80
|
+
# the +super+ class SOAP::RPC::StandaloneServer, they are made
|
81
|
+
# explicit here because they are included in the +WSDL+ as well.
|
27
82
|
def initialize(name="WS", description="", host="localhost", port="1984", *args)
|
28
83
|
super(description, "urn:#{ name }", host, port, *args)
|
29
84
|
@host = host
|
@@ -37,11 +92,20 @@ class SimpleWS < SOAP::RPC::StandaloneServer
|
|
37
92
|
serve :wsdl, %w(), :return => :string
|
38
93
|
end
|
39
94
|
|
40
|
-
def name
|
41
|
-
@name
|
42
|
-
end
|
43
|
-
|
44
95
|
|
96
|
+
# This method tells the server to provide a method named by the +name+
|
97
|
+
# parameter, with arguments listed in the +args+ parameter. The
|
98
|
+
# +types+ parameter specifies the types of the arguments as will be
|
99
|
+
# described in the +WSDL+ file (see the TYPES2WSDL constant). The
|
100
|
+
# actual method called will be the one with the same name. Optionally
|
101
|
+
# a block can be provided, this block will be used to define a
|
102
|
+
# function named as in name.
|
103
|
+
#
|
104
|
+
# If the method returns something, then the type of the return value
|
105
|
+
# must be specified in the +types+ parameter as :return. If not value
|
106
|
+
# for :return parameter is specified in the +types+ parameter the
|
107
|
+
# method is taken to return no value. Other than that, if a parameter
|
108
|
+
# type is omitted it is taken to be :string.
|
45
109
|
def serve(name, args=[], types={}, &block)
|
46
110
|
|
47
111
|
if block
|
@@ -54,6 +118,11 @@ class SimpleWS < SOAP::RPC::StandaloneServer
|
|
54
118
|
nil
|
55
119
|
end
|
56
120
|
|
121
|
+
# If +filename+ is specified it saves the +WSDL+ file in that file. If
|
122
|
+
# no +filename+ is specified it returns a string containing the
|
123
|
+
# +WSDL+. The no parameter version is served by default, so that
|
124
|
+
# clients can use this method to access the complete +WSDL+ file, as
|
125
|
+
# used in get_wsdl and get_driver.
|
57
126
|
def wsdl(filename = nil)
|
58
127
|
wsdl = WSDL_STUB.dup
|
59
128
|
wsdl.gsub!(/\$\{MESSAGES\}/m,@messages.join("\n"))
|
@@ -83,7 +152,7 @@ class SimpleWS < SOAP::RPC::StandaloneServer
|
|
83
152
|
args.each{|param|
|
84
153
|
type = types[param.to_s] || types[param.to_sym] || :string
|
85
154
|
type = type.to_sym
|
86
|
-
xml.part :name => param, :type =>
|
155
|
+
xml.part :name => param, :type => TYPES2WSDL[type]
|
87
156
|
}
|
88
157
|
end
|
89
158
|
@messages << message
|
@@ -91,7 +160,7 @@ class SimpleWS < SOAP::RPC::StandaloneServer
|
|
91
160
|
type = types[:return] || types["return"]
|
92
161
|
if type
|
93
162
|
type = type.to_sym
|
94
|
-
xml.part :name => 'return', :type =>
|
163
|
+
xml.part :name => 'return', :type => TYPES2WSDL[type]
|
95
164
|
end
|
96
165
|
end
|
97
166
|
@messages << message
|
@@ -117,6 +186,7 @@ class SimpleWS < SOAP::RPC::StandaloneServer
|
|
117
186
|
|
118
187
|
end
|
119
188
|
|
189
|
+
|
120
190
|
WSDL_STUB =<<EOT
|
121
191
|
<?xml version="1.0"?>
|
122
192
|
<definitions xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
|
@@ -167,7 +237,7 @@ ${BINDINGS}
|
|
167
237
|
</definitions>
|
168
238
|
EOT
|
169
239
|
|
170
|
-
|
240
|
+
TYPES2WSDL = {
|
171
241
|
:boolean => 'xsd:boolean',
|
172
242
|
:string => 'xsd:string',
|
173
243
|
:integer => 'xsd:integer',
|
@@ -9,7 +9,7 @@ class TestSimpleWS < Test::Unit::TestCase
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def process(name)
|
12
|
-
|
12
|
+
actual_name = start_job(name, %w(test.txt)) do
|
13
13
|
begin
|
14
14
|
write('test.txt', job_name)
|
15
15
|
step(:init)
|
@@ -20,12 +20,11 @@ class TestSimpleWS < Test::Unit::TestCase
|
|
20
20
|
sleep 2
|
21
21
|
step(:step2, "Step3")
|
22
22
|
rescue
|
23
|
-
|
24
|
-
raise $!
|
23
|
+
error($!.message)
|
25
24
|
end
|
26
25
|
end
|
27
26
|
|
28
|
-
|
27
|
+
actual_name
|
29
28
|
end
|
30
29
|
|
31
30
|
def initialize(*args)
|