simplews 1.1.5 → 1.1.6
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|