ap4r 0.1.1 → 0.2.0
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/CHANGELOG +7 -0
- data/README +5 -5
- data/Rakefile +19 -3
- data/lib/ap4r/queue_manager_ext.rb +137 -65
- data/lib/ap4r/store_and_forward.rb +126 -0
- data/lib/ap4r/stored_message.rb +95 -0
- data/lib/ap4r/version.rb +2 -2
- data/rails_plugin/ap4r/init.rb +2 -2
- data/rails_plugin/ap4r/lib/async_controller.rb +260 -41
- metadata +23 -21
data/CHANGELOG
CHANGED
data/README
CHANGED
@@ -11,7 +11,8 @@ http://rubyforge.org/projects/ap4r/
|
|
11
11
|
1. Business logics can be implemented as simple Web applications, or ruby code, whether it's called asynchronously or synchronously.
|
12
12
|
1. Asynchronous messaging is reliable by RDBMS persistence (now MySQL only) or file persistence, under the favor of reliable-msg.
|
13
13
|
1. Load balancing over multiple AP4R processes on single/multiple servers is supported.
|
14
|
-
1. Asynchronous logics are called via various protocols, such as XML-RPC, SOAP, HTTP PUT, and more.
|
14
|
+
1. Asynchronous logics are called via various protocols, such as XML-RPC, SOAP, HTTP PUT, and more.
|
15
|
+
1. Using store and forward function, at-least-omce QoS level is provided.
|
15
16
|
|
16
17
|
== Typical process flow
|
17
18
|
|
@@ -124,12 +125,12 @@ Since the asynchronous logic sleeps ten seconds and appends to the file, wait br
|
|
124
125
|
* Make sample application available
|
125
126
|
* If you have an SVN client,
|
126
127
|
|
127
|
-
$ svn co svn://rubyforge.org/var/ap4r/tags/ap4r-0.
|
128
|
+
$ svn co svn://rubyforge.org/var/ap4r/tags/ap4r-0.2.0/sample [some directory]
|
128
129
|
|
129
130
|
* Unless
|
130
131
|
1. download the sample from RubyForge
|
131
132
|
http://rubyforge.org/projects/ap4r/
|
132
|
-
filename: HelloWorld.
|
133
|
+
filename: HelloWorld.tar.gz
|
133
134
|
|
134
135
|
1. and extract to an appropricate directory(ex. HelloWorld ).
|
135
136
|
There are app/, components/,... directories under HelloWorld.
|
@@ -152,6 +153,7 @@ Since the asynchronous logic sleeps ten seconds and appends to the file, wait br
|
|
152
153
|
* Execute a synchronous logic.
|
153
154
|
* http://localhost:3000/sync_hello/execute
|
154
155
|
* A file HelloWorld.txt is creted under HelloWorld/ which contains a word "Hello".
|
156
|
+
* execute_via_soap, execute_via_http and execute_with_saf actions are also available.
|
155
157
|
|
156
158
|
* Confirm the execution of an asynchronous logic.
|
157
159
|
* Wait ten seconds,
|
@@ -164,9 +166,7 @@ Since the asynchronous logic sleeps ten seconds and appends to the file, wait br
|
|
164
166
|
|
165
167
|
== Future Plan
|
166
168
|
|
167
|
-
* add protocols to invoke asynchronous logics
|
168
169
|
* only-once QoS
|
169
|
-
* at-lease-once QoS
|
170
170
|
* DLQ recovery
|
171
171
|
* flow volume control, load balancing
|
172
172
|
* 7x24 support (e.g. rolling database tables)
|
data/Rakefile
CHANGED
@@ -96,10 +96,20 @@ Rake::RDocTask.new { |rdoc|
|
|
96
96
|
|
97
97
|
# AP4R release ----------------------------------------------------------------
|
98
98
|
|
99
|
-
desc "Make gem
|
100
|
-
task :
|
99
|
+
desc "Make gem"
|
100
|
+
task :gem_release => [ :make_release_dir, :copy_to_ap4r_from_sample, :gem]
|
101
101
|
|
102
|
-
task :
|
102
|
+
task :copy_to_ap4r_from_sample do
|
103
|
+
FileUtils.cp(HELLO_WORLD_DIR + '/db/migrate/001_create_table_for_saf.rb', './lib/ap4r/xxx_create_table_for_saf.rb')
|
104
|
+
|
105
|
+
FileUtils.cp(HELLO_WORLD_DIR + '/vendor/plugins/ap4r/init.rb', './rails_plugin/ap4r/init.rb')
|
106
|
+
FileUtils.cp(HELLO_WORLD_DIR + '/vendor/plugins/ap4r/lib/async_controller.rb', './rails_plugin/ap4r/lib/async_controller.rb')
|
107
|
+
end
|
108
|
+
|
109
|
+
desc "Make sample tgz"
|
110
|
+
task :sample_release => [ :make_release_dir, :make_sample_tgz, :copy_to_release_dir ]
|
111
|
+
|
112
|
+
task :make_sample_tgz => [ :make_temp_dir, :copy_sample, :execute_migration, :make_tgz ]
|
103
113
|
|
104
114
|
task :make_release_dir do
|
105
115
|
make_dir RELEASE_DIR
|
@@ -126,6 +136,12 @@ task :copy_sample do
|
|
126
136
|
}
|
127
137
|
end
|
128
138
|
|
139
|
+
task :execute_migration do
|
140
|
+
Dir.chdir('temp/')
|
141
|
+
`rake db:migrate`
|
142
|
+
Dir.chdir('../')
|
143
|
+
end
|
144
|
+
|
129
145
|
task :make_tgz do
|
130
146
|
Dir.chdir('temp/')
|
131
147
|
`tar czvf HelloWorld.tar.gz HelloWorld/`
|
@@ -8,12 +8,16 @@ require 'ap4r/retention_history'
|
|
8
8
|
|
9
9
|
require 'soap/wsdlDriver'
|
10
10
|
require 'xmlrpc/client'
|
11
|
-
require '
|
11
|
+
require 'uri'
|
12
|
+
require 'net/http'
|
12
13
|
|
14
|
+
require 'active_support'
|
13
15
|
require 'yaml'
|
14
16
|
require 'thread'
|
15
17
|
require 'pp'
|
16
18
|
|
19
|
+
require 'uuid'
|
20
|
+
|
17
21
|
module ReliableMsg
|
18
22
|
module LifecycleListener #:nodoc:
|
19
23
|
|
@@ -32,8 +36,8 @@ module ReliableMsg
|
|
32
36
|
|
33
37
|
# Hooks original initialize method to add lifecyle listeners.
|
34
38
|
#--
|
35
|
-
# TODO: Make dispatchers and carriers lifecyle listeners
|
36
|
-
# and separate them from QueueManager
|
39
|
+
# TODO: Make dispatchers and carriers lifecyle listeners, 2006/09/01 shino
|
40
|
+
# TODO: and separate them from QueueManager, 2006/09/01 shino
|
37
41
|
def initialize options = nil #:notnew:
|
38
42
|
initialize_original options
|
39
43
|
@global_lock ||= Mutex.new
|
@@ -48,13 +52,18 @@ module ReliableMsg
|
|
48
52
|
end
|
49
53
|
|
50
54
|
def start
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
begin
|
56
|
+
@global_lock.synchronize do
|
57
|
+
return if @@active == self
|
58
|
+
@dispatch_targets = ''
|
59
|
+
start_original
|
60
|
+
start_dispatchers
|
61
|
+
start_carriers
|
62
|
+
@lifecycle_listeners.each {|l| l.start }
|
63
|
+
end
|
64
|
+
rescue Exception => err
|
65
|
+
@logger.warn{"Error in starting queue-manager #{err}"}
|
66
|
+
@logger.warn{err.backtrace.join("\n")}
|
58
67
|
end
|
59
68
|
end
|
60
69
|
|
@@ -69,49 +78,110 @@ module ReliableMsg
|
|
69
78
|
end
|
70
79
|
|
71
80
|
def start_dispatchers
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
until Thread.current[:dying]
|
82
|
-
sleep 0.1
|
83
|
-
# logger.debug{ "try dispatch #{mq} #{mq.name}" }
|
84
|
-
begin
|
85
|
-
mq.get{|m|
|
86
|
-
logger.debug{"dispatcher get message\n#{m.to_yaml}"} if m
|
87
|
-
# TODO: divede into comcrete classes by protocols.
|
88
|
-
|
89
|
-
# version 1 SOAP
|
90
|
-
# driver = SOAP::WSDLDriverFactory.new(m[:target_url]).create_rpc_driver
|
91
|
-
# logger.debug(driver)
|
92
|
-
# response = driver.send(m[:target_action], m.object)
|
93
|
-
# version 2 XML-RPC
|
94
|
-
endpoint = m[:target_url]
|
95
|
-
client = XMLRPC::Client.new2(endpoint)
|
96
|
-
success, response = client.call2(m[:target_action], m.object)
|
97
|
-
logger.debug{"dispatcher get response\n#{response.to_yaml}"}
|
98
|
-
raise response unless success
|
99
|
-
}
|
100
|
-
rescue Exception => err
|
101
|
-
logger.warn("dispatch err #{err.inspect}")
|
102
|
-
logger.warn(err.backtrace.join("\n"))
|
103
|
-
end
|
104
|
-
end
|
105
|
-
logger.info "end dispatcher #{mq} (index #{index})"
|
81
|
+
begin
|
82
|
+
return unless @config.dispatchers
|
83
|
+
@logger.info{ "about to start dispatchers with config #{@config.dispatchers.to_yaml}" }
|
84
|
+
@disps = ThreadGroup.new
|
85
|
+
@config.dispatchers.each{ |conf|
|
86
|
+
conf["threads"].to_i.times { |index|
|
87
|
+
Thread.fork(@disps, conf["targets"], index){|group, targets, index|
|
88
|
+
dispatch_loop(group, targets, index)
|
89
|
+
}
|
106
90
|
}
|
91
|
+
@dispatch_targets.concat(conf["targets"]).concat(';')
|
92
|
+
@logger.info{ "dispatch targets are : #{@dispatch_targets}" }
|
107
93
|
}
|
108
|
-
@
|
109
|
-
|
110
|
-
|
111
|
-
|
94
|
+
@logger.info "queue manager has forked dispatchers"
|
95
|
+
rescue Exception => err
|
96
|
+
@logger.warn{"Error in starting dipatchers #{err}"}
|
97
|
+
@logger.warn{err.backtrace.join("\n")}
|
98
|
+
raise err
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def dispatch_loop(group, targets, index)
|
103
|
+
group.add Thread.current
|
104
|
+
mq = MultiQueue.new targets
|
105
|
+
@logger.info{ "start dispatcher: targets= #{mq} (index #{index})" }
|
106
|
+
until Thread.current[:dying]
|
107
|
+
sleep 0.1
|
108
|
+
# @logger.debug{ "try dispatch #{mq} #{mq.name}" }
|
109
|
+
# TODO: needs timeout?, 2006/10/16 shino
|
110
|
+
begin
|
111
|
+
mq.get{|m|
|
112
|
+
@logger.debug{"dispatcher get message\n#{m.to_yaml}"} if m
|
113
|
+
response =
|
114
|
+
case m.headers[:dispatch_mode]
|
115
|
+
when :HTTP
|
116
|
+
dispatch_via_http(m)
|
117
|
+
when :XMLRPC
|
118
|
+
dispatch_via_xmlrpc(m)
|
119
|
+
when :SOAP
|
120
|
+
dispatch_via_soap(m)
|
121
|
+
else
|
122
|
+
raise "undefined dispatch mode #{m.headers[:mode]}"
|
123
|
+
end
|
124
|
+
@logger.debug{"dispatcher get response\n#{response.to_yaml}"}
|
125
|
+
}
|
126
|
+
rescue Exception => err
|
127
|
+
@logger.warn("dispatch err #{err.inspect}")
|
128
|
+
@logger.warn(err.backtrace.join("\n"))
|
129
|
+
end
|
130
|
+
end
|
131
|
+
@logger.info{"end dispatcher #{mq} (index #{index})"}
|
132
|
+
end
|
133
|
+
|
134
|
+
# Dispatch via a HTTP protocol
|
135
|
+
# Current implementation uses POST method, irrespectively options[:target_method]
|
136
|
+
#
|
137
|
+
# Determination of "success" is two fold
|
138
|
+
# * status code should be 200, other codes (including 201-2xx) are treated as error
|
139
|
+
# * when status code is 200, body should include a string "true"
|
140
|
+
def dispatch_via_http(message)
|
141
|
+
#TODO: Add some request headers e.g. User-Agent and Accept, 2006/10/12 shino
|
142
|
+
#TODO: Now support POST only, 2006/10/12 shino
|
143
|
+
response = Net::HTTP.post_form(URI.parse(message[:target_url]),
|
144
|
+
message.object)
|
145
|
+
@logger.debug{"response status [#{response.code} #{response.message}]"}
|
146
|
+
|
147
|
+
#TODO: make the difinition of success variable, 2006/10/13 shino
|
148
|
+
unless response.kind_of?(Net::HTTPOK)
|
149
|
+
error_message = "HTTP Response FAILURE, status [#{response.code} #{response.message}]"
|
150
|
+
@logger.error(error_message)
|
151
|
+
@logger.info{response.to_yaml}
|
152
|
+
#TODO: must create AP4R specific Exception class, 2006/10/12 shino
|
153
|
+
raise StandardError.new(error_message)
|
154
|
+
end
|
155
|
+
|
156
|
+
unless response.body =~ /true/
|
157
|
+
error_message = "HTTP Response FAILURE, status [#{response.code} #{response.message}], body [#{response.body}]"
|
158
|
+
#TODO: Refactor error logging, 2006/10/13 shino
|
159
|
+
@logger.error(error_message)
|
160
|
+
@logger.info{response.to_yaml}
|
161
|
+
#TODO: must create AP4R specific Exception class, 2006/10/12 shino
|
162
|
+
raise StandardError.new(error_message)
|
163
|
+
end
|
164
|
+
|
165
|
+
response
|
166
|
+
end
|
167
|
+
|
168
|
+
def dispatch_via_xmlrpc(message)
|
169
|
+
endpoint = message[:target_url]
|
170
|
+
client = XMLRPC::Client.new2(endpoint)
|
171
|
+
success, response = client.call2(message[:target_action], message.object)
|
172
|
+
raise response unless success
|
173
|
+
response
|
174
|
+
end
|
175
|
+
|
176
|
+
def dispatch_via_soap(message)
|
177
|
+
@logger.debug{message[:target_url]}
|
178
|
+
driver = SOAP::WSDLDriverFactory.new(message[:target_url]).create_rpc_driver
|
179
|
+
@logger.debug{driver}
|
180
|
+
driver.send(message[:target_action], message.object)
|
112
181
|
end
|
113
182
|
|
114
183
|
def stop_dispatchers
|
184
|
+
@logger.info{"stop_dispatchers #{@disps}"}
|
115
185
|
return unless @disps
|
116
186
|
@disps.list.each {|d| d[:dying] = true}
|
117
187
|
@disps.list.each {|d| d.join }
|
@@ -125,32 +195,34 @@ module ReliableMsg
|
|
125
195
|
@carriers = ThreadGroup.new
|
126
196
|
conf.each { |remote|
|
127
197
|
remote["threads"].times { |index|
|
128
|
-
Thread.fork(@carriers, remote, index
|
198
|
+
Thread.fork(@carriers, remote, index){|group, remote, index|
|
199
|
+
# TODO: refactor structure, 2006/10/06 shino
|
129
200
|
group.add Thread.current
|
130
|
-
logger.info{ "starting a carrier (index #{index}) for the queue manager #{remote['source_uri']}" }
|
201
|
+
@logger.info{ "starting a carrier (index #{index}) for the queue manager #{remote['source_uri']}" }
|
131
202
|
uri = remote['source_uri']
|
132
203
|
until Thread.current[:dying]
|
133
204
|
begin
|
134
205
|
sleep 0.1
|
135
|
-
#TODO check :dying flag here and break
|
136
|
-
#TODO cache DRbObject if necessary
|
206
|
+
#TODO check :dying flag here and break, 2006/09/01 shino
|
207
|
+
#TODO cache DRbObject if necessary, 2006/09/01 shino
|
137
208
|
remote_qm = DRb::DRbObject.new_with_uri(uri)
|
138
209
|
queue_name = remote_qm.stale_queue @dispatch_targets
|
139
210
|
next unless queue_name
|
140
211
|
|
141
|
-
logger.debug{ "stale queue name : #{queue_name}" }
|
212
|
+
@logger.debug{ "stale queue name : #{queue_name}" }
|
142
213
|
q = ReliableMsg::Queue.new queue_name, :drb_uri => uri
|
143
214
|
q.get { |m|
|
144
215
|
unless m
|
145
|
-
logger.debug{ "carrier strikes at the air (T_T)" }
|
216
|
+
@logger.debug{ "carrier strikes at the air (T_T)" }
|
146
217
|
next
|
147
218
|
end
|
148
|
-
#logger.debug{ "carrier gets a message\n#{m.to_yaml}" }
|
219
|
+
# @logger.debug{ "carrier gets a message\n#{m.to_yaml}" }
|
149
220
|
|
150
|
-
# TODO: decide the better one, and delete another
|
221
|
+
# TODO: decide the better one, and delete another, 2006/09/01 shino
|
222
|
+
# TODO: or switchable implementation in versions, 2006/10/16 shino
|
151
223
|
|
152
224
|
#version 1: use thread fork so queue manager use a different tx
|
153
|
-
# TODO probably should have a thread as an instance variable or in a thread local
|
225
|
+
# TODO probably should have a thread as an instance variable or in a thread local, 2006/09/01 shino
|
154
226
|
#Thread.fork(m) {|m|
|
155
227
|
# local_queue = ReliableMsg::Queue.new queue_name
|
156
228
|
# local_queue.put m.object
|
@@ -160,29 +232,29 @@ module ReliableMsg
|
|
160
232
|
begin
|
161
233
|
tx = Thread.current[ReliableMsg::Client::THREAD_CURRENT_TX]
|
162
234
|
Thread.current[ReliableMsg::Client::THREAD_CURRENT_TX] = nil
|
163
|
-
#logger.debug{ "before tx: #{tx}" }
|
235
|
+
# @logger.debug{ "before tx: #{tx}" }
|
164
236
|
ReliableMsg::Queue.new(queue_name).put(m.object)
|
165
237
|
ensure
|
166
238
|
Thread.current[ReliableMsg::Client::THREAD_CURRENT_TX] = tx
|
167
|
-
#logger.debug{ "after tx: #{Thread.current[ReliableMsg::Client::THREAD_CURRENT_TX]}" }
|
239
|
+
# @logger.debug{ "after tx: #{Thread.current[ReliableMsg::Client::THREAD_CURRENT_TX]}" }
|
168
240
|
end
|
169
241
|
}
|
170
242
|
rescue Exception => ex
|
171
|
-
logger.warn "error in remote-get/local-put #{ex}\n#{ex.backtrace.join("\n\t")}\n"
|
243
|
+
@logger.warn "error in remote-get/local-put #{ex}\n#{ex.backtrace.join("\n\t")}\n"
|
172
244
|
end
|
173
245
|
end
|
174
|
-
logger.info
|
246
|
+
@logger.info{"ends a carrier (index #{index}) for the queue manager #{remote['uri']}"}
|
175
247
|
}
|
176
248
|
}
|
177
249
|
}
|
178
|
-
@logger.info
|
250
|
+
@logger.info{"queue manager has forked all carriers"}
|
179
251
|
end
|
180
252
|
|
181
253
|
def stop_carriers
|
182
|
-
@logger.info
|
254
|
+
@logger.info{"stop_carriers #{@carriers}"}
|
183
255
|
return unless @carriers
|
184
|
-
@carriers.list.each
|
185
|
-
@carriers.list.each
|
256
|
+
@carriers.list.each{|d| d[:dying] = true}
|
257
|
+
@carriers.list.each{|d| d.join }
|
186
258
|
end
|
187
259
|
|
188
260
|
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# Author:: Kiwamu Kato
|
2
|
+
# Copyright:: Copyright (c) 2006 Future System Consulting Corp.
|
3
|
+
# Licence:: MIT Licence
|
4
|
+
|
5
|
+
begin require 'rubygems'; rescue LoadError; end
|
6
|
+
|
7
|
+
require 'reliable-msg'
|
8
|
+
require 'ap4r/stored_message'
|
9
|
+
|
10
|
+
module AP4R
|
11
|
+
|
12
|
+
# This +StoreAndForward+ provides at-least-once QoS level which
|
13
|
+
# guarantees not to lose any message.
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
#
|
17
|
+
# include StoreAndFoward
|
18
|
+
#
|
19
|
+
# name = "queue.test.sample"
|
20
|
+
# message = "hogehoge"
|
21
|
+
# options = {}
|
22
|
+
#
|
23
|
+
# sm = StoreMessages.store(name, message, options)
|
24
|
+
# forward_by_queue_info(sm.id, {:queue_name => name,
|
25
|
+
# :queue_message => message, :queue_headers => options})
|
26
|
+
#
|
27
|
+
module StoreAndForward
|
28
|
+
|
29
|
+
DRUBY_URI = "druby://#{ENV['AP4R_DRUBY_HOST']||'localhost'}:#{ENV['AP4R_DRUBY_PORT'] || \
|
30
|
+
'6438'}"
|
31
|
+
|
32
|
+
# This method needs information about stored message, such as
|
33
|
+
# putting queue's name, message, options, as aruments.
|
34
|
+
# And those values stucked queue_info hash and following keys are necessary.
|
35
|
+
# * :queue_name
|
36
|
+
# * :queue_message
|
37
|
+
# * :queue_headers
|
38
|
+
#
|
39
|
+
# As :queue_headers, some options are supported.
|
40
|
+
# See the reliable-msg docuememt for more details.
|
41
|
+
#
|
42
|
+
# And this method's options is now :delete_mode only.
|
43
|
+
# See the StoreMessage rdoc for more details.
|
44
|
+
#
|
45
|
+
def __ap4r_forward_by_queue_info(stored_message_id, queue_info, options)
|
46
|
+
__ap4r_queue_put(queue_info[:queue_name], queue_info[:queue_message], queue_info[:queue_headers])
|
47
|
+
StoredMessage.destroy_if_exists(stored_message_id, options)
|
48
|
+
end
|
49
|
+
alias :forward_by_queue_info :__ap4r_forward_by_queue_info
|
50
|
+
|
51
|
+
|
52
|
+
# This method does't need information about stored message.
|
53
|
+
# All that is required is stored_message_id.
|
54
|
+
# Find target record by stored_message_id, make queue information, such as
|
55
|
+
# queue name, message, options, for putting into queue.
|
56
|
+
# Now under implementation.
|
57
|
+
#
|
58
|
+
# And this method's options is now :delete_mode only.
|
59
|
+
# See the StoreMessage rdoc for more details.
|
60
|
+
#
|
61
|
+
def __ap4r_forward_by_stored_message_id(stored_message_id, options)
|
62
|
+
# TODO: Find record and make queue info , 2006/10/13 kato-k
|
63
|
+
queue_name = nil
|
64
|
+
queue_message = nil
|
65
|
+
queue_headers = nil
|
66
|
+
__ap4r_forward_by_queue_info(queue_name, queue_message, queue_headers)
|
67
|
+
end
|
68
|
+
alias :forward_by_stored_message_id :__ap4r_forward_by_stored_message_id
|
69
|
+
|
70
|
+
|
71
|
+
# Put a message into queue.
|
72
|
+
# As queue_headers, some options are supported.
|
73
|
+
# See the reliable-msg docuememt for more details.
|
74
|
+
def __ap4r_queue_put(queue_name, queue_message, queue_headers)
|
75
|
+
q = ReliableMsg::Queue.new(queue_name, :drb_uri => @@drb_uri || DRUBY_URI)
|
76
|
+
q.put(queue_message, queue_headers)
|
77
|
+
end
|
78
|
+
alias :queue_put :__ap4r_queue_put
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
#--
|
84
|
+
# For test
|
85
|
+
if __FILE__ == $0
|
86
|
+
|
87
|
+
class TestSaf
|
88
|
+
|
89
|
+
include ::AP4R::StoreAndForward
|
90
|
+
|
91
|
+
def connect
|
92
|
+
unless ActiveRecord::Base.connected?
|
93
|
+
#TODO: Get parameters from config, 2006/10/12 kato-k
|
94
|
+
ActiveRecord::Base.establish_connection(
|
95
|
+
:adapter => 'sqlite3',
|
96
|
+
:database => '../../samples/HelloWorld/db/hello_world_development.db'
|
97
|
+
)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def async_dispatch_with_saf(queue_name, queue_message, rm_options = {})
|
102
|
+
|
103
|
+
connect()
|
104
|
+
stored_message_id = ::AP4R::StoredMessage.store(queue_name, queue_message, rm_options)
|
105
|
+
forward_by_queue_info(
|
106
|
+
stored_message_id,
|
107
|
+
{
|
108
|
+
:queue_name => queue_name,
|
109
|
+
:queue_message => queue_message,
|
110
|
+
:queue_headers => rm_options
|
111
|
+
},
|
112
|
+
options = {} )
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
queue_message = "Hello World !"
|
117
|
+
queue_name = "queue.test.sample"
|
118
|
+
rm_options = {
|
119
|
+
:drb_uri=>"druby://localhost:6437",
|
120
|
+
:priority => 0,
|
121
|
+
:delivery => :repeated
|
122
|
+
}
|
123
|
+
|
124
|
+
TestSaf.new.async_dispatch_with_saf(queue_name, queue_message, rm_options)
|
125
|
+
|
126
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# Author:: Kiwamu Kato
|
2
|
+
# Copyright:: Copyright (c) 2006 Future System Consulting Corp.
|
3
|
+
# Licence:: MIT Licence
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'active_record'
|
7
|
+
require 'uuid'
|
8
|
+
rescue LoadError
|
9
|
+
require 'rubygems'
|
10
|
+
require_gem 'activerecord'
|
11
|
+
require_gem 'uuid'
|
12
|
+
end
|
13
|
+
|
14
|
+
module AP4R
|
15
|
+
|
16
|
+
# This class is the model class for SAF(store and foward).
|
17
|
+
# The migration file is located at following path,
|
18
|
+
# ap4r/lib/ap4r/xxx_create_table_for_saf.rb
|
19
|
+
# Don't forget to create table, before use SAF.
|
20
|
+
class StoredMessage < ActiveRecord::Base
|
21
|
+
|
22
|
+
STATUS_STORED = 0
|
23
|
+
STATUS_FORWARDED = 1
|
24
|
+
STATUS_FAILED = -1
|
25
|
+
|
26
|
+
PHYSICAL = :physical
|
27
|
+
LOGICAL = :logical
|
28
|
+
|
29
|
+
#--
|
30
|
+
# TODO: modify directory structure and be reloadable, 2006/10/17 kato-k
|
31
|
+
#++
|
32
|
+
# Temporarily, exclude this class from auto-reload targets of rails.
|
33
|
+
def self.reloadable?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
# Insert queue information, such as queue name and message, for next logic.
|
38
|
+
#
|
39
|
+
# duplication_check_id is generated from UUID and should be unique
|
40
|
+
# in all records of StoreMessages.
|
41
|
+
# So, using this id, it's possible to protect to execute same asynchronous
|
42
|
+
# processing by same message.
|
43
|
+
# But by default, record of StoreMessages is removed after putting a message
|
44
|
+
# into queue completed.
|
45
|
+
#
|
46
|
+
def self.store(queue_name, queue_message, rm_options = {})
|
47
|
+
sm = StoredMessage.new do |s|
|
48
|
+
s.duplication_check_id = UUID.new
|
49
|
+
s.queue = queue_name
|
50
|
+
s.status = STATUS_STORED
|
51
|
+
s.object = Marshal::dump(queue_message)
|
52
|
+
s.headers = Marshal::dump(rm_options)
|
53
|
+
end
|
54
|
+
|
55
|
+
begin
|
56
|
+
sm.save!
|
57
|
+
rescue Exception => error
|
58
|
+
raise error
|
59
|
+
end
|
60
|
+
sm
|
61
|
+
end
|
62
|
+
|
63
|
+
# Destroy a record by id.
|
64
|
+
# Some options are supported.
|
65
|
+
# * :delete_mode (:physical or :logical)
|
66
|
+
# Default delete mmode is physical.
|
67
|
+
# If you need logical delete, for example you neeed checking message
|
68
|
+
# duplication etc, Set the instance variable appropriately.
|
69
|
+
def self.destroy_if_exists(id, options)
|
70
|
+
result = nil
|
71
|
+
begin
|
72
|
+
result = StoredMessage.find(id)
|
73
|
+
rescue ActiveRecord::RecordNotFound
|
74
|
+
# There are chances that othere thread or process already forwarded.
|
75
|
+
return nil
|
76
|
+
end
|
77
|
+
result.destroy_or_update(options)
|
78
|
+
end
|
79
|
+
|
80
|
+
def destroy_or_update(options = {:delete_mode => PHYSICAL})
|
81
|
+
case options[:delete_mode]
|
82
|
+
when PHYSICAL
|
83
|
+
# TODO: Confirm to raise error, 2006/10/17 kato-k
|
84
|
+
self.destroy
|
85
|
+
when LOGICAL
|
86
|
+
self.status = STATUS_FORWARDED
|
87
|
+
self.save!
|
88
|
+
else
|
89
|
+
raise "unknown delete mode: #{options[:delete_mode]}"
|
90
|
+
end
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
data/lib/ap4r/version.rb
CHANGED
data/rails_plugin/ap4r/init.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
# Licence:: MIT Licence
|
4
4
|
|
5
5
|
require 'reliable-msg'
|
6
|
+
#require 'ap4r/stored_message'
|
6
7
|
|
7
8
|
module AP4R
|
8
9
|
# This +asyncController+ is the Rails plugin for asynchronous processing.
|
@@ -11,54 +12,272 @@ module AP4R
|
|
11
12
|
#
|
12
13
|
# Examples: The part of calling next asynchronous logics in a controller in the HelloWorld Sample.
|
13
14
|
#
|
14
|
-
# req = WorldRequest.new
|
15
|
-
# req
|
16
|
-
#
|
17
|
-
#
|
18
|
-
# req_hash = {}
|
19
|
-
# req.each_pair{|k,v| req_hash[k.to_sym]=v}
|
15
|
+
# req = WorldRequest.new([:world_id => 1, :message => "World"})
|
16
|
+
# async_dispatch(req,
|
17
|
+
# {:controller => 'async_world', :action => 'execute',
|
18
|
+
# :mode => :XMLRPC })
|
20
19
|
#
|
21
20
|
# render :action => 'response'
|
22
21
|
#
|
23
|
-
# async_dispatch(req_hash,
|
24
|
-
# :controller => 'async_world', :action => 'execute',
|
25
|
-
# :mode => :XMLRPC)
|
26
|
-
#
|
27
22
|
module AsyncController
|
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
|
-
|
23
|
+
|
24
|
+
module Base
|
25
|
+
Converters = {}
|
26
|
+
DRUBY_URI = "druby://#{ENV['AP4R_DRUBY_HOST']||'localhost'}:#{ENV['AP4R_DRUBY_PORT'] || '6438'}"
|
27
|
+
|
28
|
+
@@default_dispatch_mode = :XMLRPC
|
29
|
+
@@default_rm_options = { :delivery => :once }
|
30
|
+
@@default_queue_prefix = "queue."
|
31
|
+
|
32
|
+
mattr_accessor :default_dispatch_mode, :default_rm_options, :default_queue_prefix
|
33
|
+
|
34
|
+
# Provide at-least-once QoS level.
|
35
|
+
# Execute application logic and store the message for next logic to the application
|
36
|
+
# database. This is a store processing. After commit to the database,
|
37
|
+
# put the message into queue and update or delte a management table.
|
38
|
+
# This is a forward processing.
|
39
|
+
# SAF(store and forward) processing like this guarantees that any message
|
40
|
+
# is never lost and keeps reasonable performance.
|
41
|
+
#
|
42
|
+
# Examples: Just call async_dispath method in this block.
|
43
|
+
#
|
44
|
+
# transaction_with saf do
|
45
|
+
# req = WorldRequest.new([:world_id => 1, :message => "World"})
|
46
|
+
# async_dispatch(req,
|
47
|
+
# {:controller => 'async_world', :action => 'execute',
|
48
|
+
# :mode => :XMLRPC })
|
49
|
+
#
|
50
|
+
# render :action => 'response'
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
def transaction_with_saf(active_record_class = StoredMessage, *objects, &block)
|
54
|
+
|
55
|
+
Thread.current[:use_saf] = true
|
56
|
+
Thread.current[:stored_messages] = {}
|
57
|
+
|
58
|
+
# store
|
59
|
+
active_record_class ||= StoredMessage
|
60
|
+
active_record_class.transaction(*objects, &block)
|
61
|
+
|
62
|
+
# forward
|
63
|
+
forwarded_messages = {}
|
64
|
+
begin
|
65
|
+
|
66
|
+
# TODO: reconsider forwarding strategy, 2006/10/13 kato-k
|
67
|
+
# Once some error occured, such as disconnect reliable-msg or server crush,
|
68
|
+
# which is smart to keep to put a message or stop to do it?
|
69
|
+
# In the case of being many async messages, the former strategy is not so good.
|
70
|
+
Thread.current[:stored_messages].each {|k,v|
|
71
|
+
__queue_put(v[:queue_name], v[:queue_message], v[:queue_headers])
|
72
|
+
forwarded_messages[k] = v
|
73
|
+
}
|
74
|
+
rescue Exception => err
|
75
|
+
# Don't raise any Exception. Response to user already completed.
|
76
|
+
# nop
|
77
|
+
logger.warn("Failed to put a message into queue: #{err}")
|
78
|
+
end
|
79
|
+
|
80
|
+
begin
|
81
|
+
StoredMessage.transaction do
|
82
|
+
options = {:delete_mode => @delete_mode || :physical}
|
83
|
+
forwarded_messages.keys.each {|id|
|
84
|
+
::AP4R::StoredMessage.destroy_if_exists(id, options)
|
85
|
+
}
|
86
|
+
end
|
87
|
+
rescue Exception => err
|
88
|
+
# Don't raise any Exception. Response to user already completed.
|
89
|
+
# nop
|
90
|
+
logger.warn("Failed to put a message into queue: #{err}")
|
91
|
+
end
|
92
|
+
|
93
|
+
ensure
|
94
|
+
Thread.current[:use_saf] = false
|
95
|
+
Thread.current[:stored_messages] = nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# Queue a message for next asynchronous logic. Some options are supported.
|
99
|
+
#
|
100
|
+
# Use options to specify target url, etc.
|
101
|
+
# Accurate meanings are defined by a individual converter class.
|
102
|
+
# * :controller (name of next logic)
|
103
|
+
# * :action (name of next logic)
|
104
|
+
#
|
105
|
+
# Use rm_options to pass parameter in queue-put.
|
106
|
+
# Listings below are AP4R extended options.
|
107
|
+
# See the reliable-msg docuememt for more details.
|
108
|
+
# * :target_url (URL of target, prevail over :controller)
|
109
|
+
# * :target_action (action of target, prevail over :action)
|
110
|
+
# * :target_method (HTTP method, e.g. "GET", "POST", etc.)
|
111
|
+
# * :dispatch_mode (protocol in dispatching)
|
112
|
+
# * :queue_name (prevail over :controller and :action)
|
113
|
+
#
|
114
|
+
# Object of argumemts (async_params, options and rm_options) will not be modified.
|
115
|
+
# Implementors (of this class and converters) should not modify them.
|
116
|
+
#
|
117
|
+
def async_dispatch(async_params = nil, options = {}, rm_options = nil)
|
118
|
+
if logger.debug?
|
119
|
+
logger.debug("async_params: ")
|
120
|
+
logger.debug(async_params.inspect)
|
121
|
+
logger.debug("options: ")
|
122
|
+
logger.debug(options.inspect)
|
123
|
+
logger.debug("rm_options")
|
124
|
+
logger.debug(rm_options.inspect)
|
125
|
+
end
|
126
|
+
|
127
|
+
# TODO: clone it, 2006/10/16 shino
|
128
|
+
options ||= {}
|
129
|
+
rm_options = @@default_rm_options.merge(rm_options || {})
|
130
|
+
# TODO: put into :dispatch_mode to @@default_rm_options
|
131
|
+
rm_options[:dispatch_mode] ||= @@default_dispatch_mode
|
132
|
+
|
133
|
+
# Only async_params is not cloned. options and rm_options are cloned before now.
|
134
|
+
# This is a current contract between this class and converter classes.
|
135
|
+
converter = Converters[rm_options[:dispatch_mode]].new(self, async_params, options, rm_options)
|
136
|
+
logger.debug{"druby uri for queue-manager : #{DRUBY_URI}"}
|
137
|
+
|
138
|
+
queue_name = __get_queue_name(options, rm_options)
|
139
|
+
queue_message = converter.make_params
|
140
|
+
queue_headers = converter.make_rm_options
|
141
|
+
|
142
|
+
if Thread.current[:use_saf]
|
143
|
+
stored_message = ::AP4R::StoredMessage.store(queue_name, queue_message, queue_headers)
|
144
|
+
|
145
|
+
Thread.current[:stored_messages].store(
|
146
|
+
stored_message.id,
|
147
|
+
{
|
148
|
+
:queue_message => queue_message,
|
149
|
+
:queue_name => queue_name,
|
150
|
+
:queue_headers => queue_headers
|
151
|
+
} )
|
152
|
+
return stored_message.id
|
153
|
+
end
|
154
|
+
|
155
|
+
__queue_put(queue_name, queue_message, queue_headers)
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
def __queue_put(queue_name, queue_message, queue_headers)
|
160
|
+
q = ReliableMsg::Queue.new(queue_name, :drb_uri => DRUBY_URI)
|
161
|
+
q.put(queue_message, queue_headers)
|
162
|
+
end
|
163
|
+
|
164
|
+
def __get_queue_name(options, rm_options)
|
165
|
+
rm_options[:queue_name] ||
|
166
|
+
@@default_queue_prefix.concat(options[:controller].to_s).concat('.').concat(options[:action].to_s)
|
167
|
+
end
|
55
168
|
|
56
|
-
def target_url_name(target_url, mode)
|
57
|
-
target_url + "/api"
|
58
169
|
end
|
59
170
|
|
60
|
-
|
61
|
-
|
171
|
+
#--
|
172
|
+
# TODO: pluralize it, 2006/10/16 shino
|
173
|
+
module Converter #:nodoc:
|
174
|
+
|
175
|
+
# A base class for converter classes.
|
176
|
+
# Responsibilities of subclasses are as folows
|
177
|
+
# * by +make_params+, convert async_params to appropriate object
|
178
|
+
# * by +make_rm_options+, make appropriate +Hash+ passed by <tt>ReliableMsg::Queue#put</tt>
|
179
|
+
class Base
|
180
|
+
# Difine a constant +DISPATCH_MODE+ to value 'mode_symbol' and
|
181
|
+
# add self to a Converters list.
|
182
|
+
def self.dispatch_mode(mode_symbol)
|
183
|
+
self.const_set(:DISPATCH_MODE, mode_symbol)
|
184
|
+
::AP4R::AsyncController::Base::Converters[mode_symbol] = self
|
185
|
+
end
|
186
|
+
|
187
|
+
def initialize(controller, async_params, options, rm_options)
|
188
|
+
@controller = controller
|
189
|
+
@async_params = async_params
|
190
|
+
@options = options
|
191
|
+
@rm_options = rm_options
|
192
|
+
end
|
193
|
+
|
194
|
+
# helper method for <tt>ActionController#url_for</tt>
|
195
|
+
def url_for(url_for_options, *parameter_for_method_reference)
|
196
|
+
@controller.url_for(url_for_options, *parameter_for_method_reference)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Returns a object which passed to <tt>ReliableMsg::Queue.put(message, headers)</tt>'s
|
200
|
+
# first argument +message+.
|
201
|
+
# Should be implemented by subclasses.
|
202
|
+
def make_params
|
203
|
+
raise 'must be implemented'
|
204
|
+
end
|
205
|
+
|
206
|
+
# Returns a object which passed to <tt>ReliableMsg::Queue.put(message, headers)</tt>'s
|
207
|
+
# second argument +headers+.
|
208
|
+
# Should be implemented by subclasses.
|
209
|
+
def make_rm_options
|
210
|
+
raise 'must be implemented'
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
class Http < Base
|
215
|
+
dispatch_mode :HTTP
|
216
|
+
|
217
|
+
def make_params
|
218
|
+
@async_params
|
219
|
+
end
|
220
|
+
|
221
|
+
def make_rm_options
|
222
|
+
@rm_options[:target_url] ||= url_for(@options)
|
223
|
+
@rm_options[:target_method] ||= 'POST'
|
224
|
+
#TODO: make option key to specify HTTP headers, 2006/10/16 shino
|
225
|
+
@rm_options
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
class WebService < Base
|
230
|
+
def make_params
|
231
|
+
message_obj = {}
|
232
|
+
@async_params.each_pair{|k,v| message_obj[k.to_sym]=v}
|
233
|
+
message_obj
|
234
|
+
end
|
235
|
+
|
236
|
+
def make_rm_options
|
237
|
+
@rm_options[:target_url] ||= target_url_name
|
238
|
+
@rm_options[:target_action] ||= action_api_name
|
239
|
+
@rm_options
|
240
|
+
end
|
241
|
+
|
242
|
+
def action_api_name
|
243
|
+
action_method_name = @options[:action]
|
244
|
+
action_method_name.camelcase
|
245
|
+
end
|
246
|
+
|
247
|
+
def options_without_action
|
248
|
+
new_opts = @options.dup
|
249
|
+
new_opts[:action] = nil
|
250
|
+
new_opts
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
254
|
+
|
255
|
+
class XmlRpc < WebService
|
256
|
+
dispatch_mode :XMLRPC
|
257
|
+
|
258
|
+
def target_url_name
|
259
|
+
url_for(options_without_action) + rails_api_url_suffix
|
260
|
+
end
|
261
|
+
|
262
|
+
private
|
263
|
+
def rails_api_url_suffix
|
264
|
+
'/api'
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
class SOAP < WebService
|
269
|
+
dispatch_mode :SOAP
|
270
|
+
|
271
|
+
def target_url_name
|
272
|
+
url_for(options_without_action) + rails_wsdl_url_suffix
|
273
|
+
end
|
274
|
+
|
275
|
+
private
|
276
|
+
def rails_wsdl_url_suffix
|
277
|
+
'/service.wsdl'
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
62
281
|
end
|
63
282
|
end
|
64
283
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: ap4r
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2006-10-
|
6
|
+
version: 0.2.0
|
7
|
+
date: 2006-10-17 00:00:00 +09:00
|
8
8
|
summary: Asynchronous Processing for Ruby.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -29,48 +29,50 @@ authors:
|
|
29
29
|
- Shunichi Shinohara
|
30
30
|
- Kiwamu Kato
|
31
31
|
files:
|
32
|
-
- bin
|
33
32
|
- CHANGELOG
|
34
|
-
- config
|
35
|
-
- lib
|
36
|
-
- MIT-LICENSE
|
37
|
-
- rails_plugin
|
38
33
|
- Rakefile
|
34
|
+
- bin
|
39
35
|
- README
|
36
|
+
- lib
|
37
|
+
- MIT-LICENSE
|
38
|
+
- config
|
40
39
|
- script
|
40
|
+
- rails_plugin
|
41
41
|
- bin/ap4r_setup
|
42
|
-
- config/ap4r_settings.rb
|
43
|
-
- config/log4r.yaml
|
44
|
-
- config/queues.cfg
|
45
42
|
- config/queues_disk.cfg
|
46
43
|
- config/queues_mysql.cfg
|
44
|
+
- config/log4r.yaml
|
45
|
+
- config/ap4r_settings.rb
|
46
|
+
- config/queues.cfg
|
47
47
|
- rails_plugin/ap4r
|
48
48
|
- rails_plugin/ap4r/init.rb
|
49
49
|
- rails_plugin/ap4r/lib
|
50
50
|
- rails_plugin/ap4r/lib/async_controller.rb
|
51
51
|
- script/irm
|
52
|
-
- script/
|
52
|
+
- script/stop
|
53
53
|
- script/loop.rb
|
54
54
|
- script/start
|
55
|
-
- script/
|
56
|
-
- lib/ap4r
|
55
|
+
- script/loop.cmd
|
57
56
|
- lib/ap4r.rb
|
58
|
-
- lib/ap4r
|
59
|
-
- lib/ap4r/multi_queue.rb
|
57
|
+
- lib/ap4r
|
60
58
|
- lib/ap4r/queue_manager_ext.rb
|
61
59
|
- lib/ap4r/queue_manager_ext_debug.rb
|
60
|
+
- lib/ap4r/stored_message.rb
|
61
|
+
- lib/ap4r/store_and_forward.rb
|
62
62
|
- lib/ap4r/retention_history.rb
|
63
|
-
- lib/ap4r/script
|
64
|
-
- lib/ap4r/start_with_log4r.rb
|
65
63
|
- lib/ap4r/util
|
64
|
+
- lib/ap4r/start_with_log4r.rb
|
65
|
+
- lib/ap4r/multi_queue.rb
|
66
|
+
- lib/ap4r/message_store_ext.rb
|
67
|
+
- lib/ap4r/script
|
66
68
|
- lib/ap4r/version.rb
|
69
|
+
- lib/ap4r/util/loc.rb
|
70
|
+
- lib/ap4r/util/queue_client.rb
|
71
|
+
- lib/ap4r/util/irm.rb
|
67
72
|
- lib/ap4r/script/base.rb
|
73
|
+
- lib/ap4r/script/workspace_generator.rb
|
68
74
|
- lib/ap4r/script/queue_manager_control.rb
|
69
75
|
- lib/ap4r/script/setup.rb
|
70
|
-
- lib/ap4r/script/workspace_generator.rb
|
71
|
-
- lib/ap4r/util/irm.rb
|
72
|
-
- lib/ap4r/util/loc.rb
|
73
|
-
- lib/ap4r/util/queue_client.rb
|
74
76
|
test_files: []
|
75
77
|
|
76
78
|
rdoc_options:
|