ap4r 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,132 @@
1
+ # Author:: Kiwamu Kato
2
+ # Copyright:: Copyright (c) 2007 Future Architect Inc.
3
+ # Licence:: MIT Licence
4
+
5
+ require 'forwardable'
6
+ require 'async_helper'
7
+
8
+ module Ap4r #:nodoc:
9
+
10
+ # This +Client+ is the Rails plugin for asynchronous processing.
11
+ # Asynchronous logics are called via various protocols, such as XML-RPC,
12
+ # SOAP, HTTP POST, and more. Now default protocol is HTTP POST.
13
+ #
14
+ # Examples: The part of calling next asynchronous logics in a controller in the HelloWorld Sample.
15
+ #
16
+ # req = WorldRequest.new([:world_id => 1, :message => "World"})
17
+ # ap4r.async_to({:controller => 'async_world', :action => 'execute'},
18
+ # {:world_id => 1, :message => "World"},
19
+ # {:dispatch_mode => :HTTP}) # skippable
20
+ #
21
+ # render :action => 'response'
22
+ #
23
+ # Complement: Above +ap4r+ method is defiend init.rb in +%RAILS_ROOT%/vendor/plugin/ap4r/+.
24
+ #
25
+ class Client
26
+ extend Forwardable
27
+ include ::Ap4r::AsyncHelper::Base
28
+
29
+ def initialize controller
30
+ @controller = controller
31
+ end
32
+
33
+ def_delegators :@controller, :logger, :url_for
34
+
35
+ # Queue a message for next asynchronous logic. Some options are supported.
36
+ #
37
+ # Use options to specify target url, etc.
38
+ # Accurate meanings are defined by a individual converter class.
39
+ # * :controller (name of next logic)
40
+ # * :action (name of next logic)
41
+ #
42
+ # Use rm_options to pass parameter in queue-put.
43
+ # Listings below are AP4R extended options.
44
+ # See the reliable-msg docuememt for more details.
45
+ # * :target_url (URL of target, prevail over :controller)
46
+ # * :target_action (action of target, prevail over :action)
47
+ # * :target_method (HTTP method, e.g. "GET", "POST", etc.)
48
+ # * :dispatch_mode (protocol in dispatching)
49
+ # * :queue_name (prevail over :controller and :action)
50
+ #
51
+ # Object of argumemts (async_params, options and rm_options) will not be modified.
52
+ # Implementors (of this class and converters) should not modify them.
53
+ #
54
+ # Examples: the most simple
55
+ #
56
+ # ap4r.async_to({:controller => 'next_controller', :action => 'next_action'},
57
+ # {:world_id => 1, :message => "World"})
58
+ #
59
+ #
60
+ # Examples: taking block
61
+ #
62
+ # ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
63
+ # body :world_id, 1
64
+ # body :message, "World"
65
+ # end
66
+ #
67
+ #
68
+ # Examples: transmitting ActiveRecord object
69
+ #
70
+ # ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
71
+ # body :world, World.find(1)
72
+ # body :message, "World"
73
+ # end
74
+ #
75
+ #
76
+ # Examples: transmitting with xml format over http (now support text, json and yaml format).
77
+ #
78
+ # ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
79
+ # body :world, World.find(1)
80
+ # body :message, "World"
81
+ # format :xml
82
+ # end
83
+ #
84
+ #
85
+ # Examples: direct assignment for formatted message body
86
+ #
87
+ # ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
88
+ # world = World.find(1).to_xml :except => ...
89
+ # body_as_xml world
90
+ # end
91
+ #
92
+ #
93
+ # Examples: setting message header
94
+ #
95
+ # ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
96
+ # body :world_id, 1
97
+ # body :message, "World"
98
+ #
99
+ # header :priority, 1
100
+ # http_header "Content-type", ...
101
+ # end
102
+ #
103
+ alias :async_to :async_dispatch
104
+
105
+ # Provides at-least-once QoS level.
106
+ # +block+ are tipically composed of database accesses and +async_to+ calls.
107
+ # Database accesses are executed transactionallly by +active_record_class+'s transaction method.
108
+ # In the +block+, +async_to+ calls invoke NOT immediate queueing but just storing messages
109
+ # to the database (assumed to be the same one as application uses).
110
+ #
111
+ # If the execution of +block+ finishes successfully, database transaction is committed and
112
+ # forward process of each stored message begins.
113
+ # Forward process composed in two parts. First puts the message into a queue, secondary update
114
+ # or delete the entry from a management table.
115
+ #
116
+ # SAF (store and forward) processing like this guarantees that any message
117
+ # is never lost and keeps reasonable performance (without two phase commit).
118
+ #
119
+ # Examples: Just call async_to method in this block.
120
+ #
121
+ # ap4r.transaction do
122
+ # req = WorldRequest.new([:world_id => 1, :message => "World"})
123
+ # ap4r.async_to({:controller => 'async_world', :action => 'execute'},
124
+ # {:world_id => 1, :message => "World"})
125
+ #
126
+ # render :action => 'response'
127
+ # end
128
+ #
129
+ alias :transaction :transaction_with_saf
130
+
131
+ end
132
+ end
@@ -3,32 +3,22 @@
3
3
  # Licence:: MIT Licence
4
4
 
5
5
  require 'reliable-msg'
6
- #require 'ap4r/stored_message'
6
+ require 'ap4r/stored_message'
7
+ require 'message_builder'
7
8
 
8
9
  module Ap4r
9
10
 
10
- # This +AsyncController+ is the Rails plugin for asynchronous processing.
11
- # Asynchronous logics are called via various protocols, such as XML-RPC,
12
- # SOAP, HTTP POST, and more. Now default protocol is HTTP POST.
11
+ # This +AsyncHelper+ is included to +Ap4rClient+ and works the Rails plugin
12
+ # for asynchronous processing.
13
13
  #
14
- # Examples: The part of calling next asynchronous logics in a controller in the HelloWorld Sample.
15
- #
16
- # req = WorldRequest.new([:world_id => 1, :message => "World"})
17
- # async_dispatch(req,
18
- # {:controller => 'async_world', :action => 'execute'},
19
- # :dispatch_mode => :XMLRPC }) # skippable
20
- #
21
- # render :action => 'response'
22
- #
23
- module AsyncController
14
+ module AsyncHelper
24
15
 
25
16
  module Base
26
17
  Converters = {}
27
18
 
28
- # TODO: constant or class variable, whick is better? 2007/05/02 by shino
29
19
  DRUBY_HOST = ENV['AP4R_DRUBY_HOST'] || 'localhost'
30
20
  DRUBY_PORT = ENV['AP4R_DRUBY_PORT'] || '6438'
31
- DRUBY_URI = "druby://#{DRUBY_HOST}:#{DRUBY_PORT}"
21
+ DRUBY_URI = "druby://#{DRUBY_HOST}:#{DRUBY_PORT}"
32
22
 
33
23
  @@default_dispatch_mode = :HTTP
34
24
  @@default_rm_options = { :delivery => :once, :dispatch_mode => @@default_dispatch_mode }
@@ -36,30 +26,7 @@ module Ap4r
36
26
 
37
27
  mattr_accessor :default_dispatch_mode, :default_rm_options, :default_queue_prefix, :saf_delete_mode
38
28
 
39
- # Provides at-least-once QoS level.
40
- # +block+ are tipically composed of database accesses and +async_dispatch+ calls.
41
- # Database accesses are executed transactionallly by +active_record_class+'s transaction method.
42
- # In the +block+, +async_dispatch+ calls invoke NOT immediate queueing but just storing messages
43
- # to the database (assumed to be the same one as application uses).
44
- #
45
- # If the execution of +block+ finishes successfully, database transaction is committed and
46
- # forward process of each stored message begins.
47
- # Forward process composed in two parts. First puts the message into a queue, secondary update
48
- # or delete the entry from a management table.
49
- #
50
- # SAF (store and forward) processing like this guarantees that any message
51
- # is never lost and keeps reasonable performance (without two phase commit).
52
- #
53
- # Examples: Just call async_dispath method in this block.
54
- #
55
- # transaction_with saf do
56
- # req = WorldRequest.new([:world_id => 1, :message => "World"})
57
- # async_dispatch(req,
58
- # {:controller => 'async_world', :action => 'execute'},
59
- # :dispatch_mode => :XMLRPC }) # skippable
60
- #
61
- # render :action => 'response'
62
- # end
29
+ # This method is aliased as ::Ap4r::Client#transaction
63
30
  #
64
31
  def transaction_with_saf(active_record_class = ::Ap4r::StoredMessage, *objects, &block)
65
32
 
@@ -78,7 +45,7 @@ module Ap4r
78
45
  # Once some error occured, such as disconnect reliable-msg or server crush,
79
46
  # which is smart to keep to put a message or stop to do it?
80
47
  # In the case of being many async messages, the former strategy is not so good.
81
- #
48
+ #
82
49
  # TODO: add delayed forward mode 2007/05/02 by shino
83
50
  Thread.current[:stored_messages].each {|k,v|
84
51
  __queue_put(v[:queue_name], v[:queue_message], v[:queue_headers])
@@ -106,27 +73,10 @@ module Ap4r
106
73
  Thread.current[:stored_messages] = nil
107
74
  end
108
75
 
109
- # Queue a message for next asynchronous logic. Some options are supported.
110
- #
111
- # Use options to specify target url, etc.
112
- # Accurate meanings are defined by a individual converter class.
113
- # * :controller (name of next logic)
114
- # * :action (name of next logic)
76
+ # This method is aliased as ::Ap4r::Client#async_to
115
77
  #
116
- # Use rm_options to pass parameter in queue-put.
117
- # Listings below are AP4R extended options.
118
- # See the reliable-msg docuememt for more details.
119
- # * :target_url (URL of target, prevail over :controller)
120
- # * :target_action (action of target, prevail over :action)
121
- # * :target_method (HTTP method, e.g. "GET", "POST", etc.)
122
- # * :dispatch_mode (protocol in dispatching)
123
- # * :queue_name (prevail over :controller and :action)
124
- #
125
- # Object of argumemts (async_params, options and rm_options) will not be modified.
126
- # Implementors (of this class and converters) should not modify them.
127
- #
128
- def async_dispatch(url_options = {}, async_params = nil, rm_options = nil)
129
- # TODO: add :url to url_options to specify :target_url shortly 2007/05/02 by shino
78
+ def async_dispatch(url_options = {}, async_params = {}, rm_options = {}, &block)
79
+
130
80
  if logger.debug?
131
81
  logger.debug("url_options: ")
132
82
  logger.debug(url_options.inspect)
@@ -138,7 +88,10 @@ module Ap4r
138
88
 
139
89
  # TODO: clone it, 2006/10/16 shino
140
90
  url_options ||= {}
141
- url_options[:controller] ||= self.controller_path.gsub("/", ".")
91
+ url_options[:controller] ||= @controller.controller_path.gsub("/", ".")
92
+ url_options[:url] ||= {:controller => url_options[:controller], :action => url_options[:action]}
93
+ url_options[:url][:controller] ||= url_options[:controller] if url_options[:url].kind_of?(Hash)
94
+
142
95
  rm_options = @@default_rm_options.merge(rm_options || {})
143
96
 
144
97
  # Only async_params is not cloned. options and rm_options are cloned before now.
@@ -150,6 +103,21 @@ module Ap4r
150
103
  queue_message = converter.make_params
151
104
  queue_headers = converter.make_rm_options
152
105
 
106
+ message_builder = ::Ap4r::MessageBuilder.new(queue_name, queue_message, queue_headers)
107
+ if block_given?
108
+ message_builder.instance_eval(&block)
109
+ end
110
+ queue_name = message_builder.queue_name
111
+ queue_headers = message_builder.message_headers
112
+ # TODO: proces flow of Converter and MessageBuilder should (probably) be reversed 2007/09/19 by shino
113
+ # This branching is ad-hoc fix
114
+ if queue_headers[:dispatch_mode] == :HTTP
115
+ queue_message = message_builder.format_message_body
116
+ else
117
+ queue_message = message_builder.message_body
118
+ end
119
+
120
+
153
121
  if Thread.current[:use_saf]
154
122
  stored_message = ::Ap4r::StoredMessage.store(queue_name, queue_message, queue_headers)
155
123
 
@@ -174,9 +142,14 @@ module Ap4r
174
142
  end
175
143
 
176
144
  def __get_queue_name(options, rm_options)
177
- rm_options[:queue_name] ||=
178
- @@default_queue_prefix.clone.concat(options[:controller].to_s).concat('.').concat(options[:action].to_s)
179
- rm_options[:queue_name]
145
+ if options[:url].kind_of?(Hash)
146
+ rm_options[:queue] ||=
147
+ @@default_queue_prefix.clone.concat(options[:url][:controller].to_s).concat('.').concat(options[:url][:action].to_s)
148
+ else
149
+ rm_options[:queue] ||=
150
+ @@default_queue_prefix.clone.chomp(".").concat(URI::parse(options[:url]).path.gsub("/", "."))
151
+ end
152
+ rm_options[:queue]
180
153
  end
181
154
 
182
155
  end
@@ -193,7 +166,7 @@ module Ap4r
193
166
  # add self to a Converters list.
194
167
  def self.dispatch_mode(mode_symbol)
195
168
  self.const_set(:DISPATCH_MODE, mode_symbol)
196
- ::Ap4r::AsyncController::Base::Converters[mode_symbol] = self
169
+ ::Ap4r::AsyncHelper::Base::Converters[mode_symbol] = self
197
170
  end
198
171
 
199
172
  def initialize(url_options, async_params, rm_options, url_for_handler)
@@ -220,6 +193,7 @@ module Ap4r
220
193
  private
221
194
  # helper method for <tt>ActionController#url_for</tt>
222
195
  def url_for(url_for_options, *parameter_for_method_reference)
196
+ return url_for_options if url_for_options.kind_of?(String)
223
197
  @url_for_handler.url_for(url_for_options, *parameter_for_method_reference)
224
198
  end
225
199
 
@@ -233,7 +207,7 @@ module Ap4r
233
207
  end
234
208
 
235
209
  def make_rm_options
236
- @rm_options[:target_url] ||= url_for(@url_options)
210
+ @rm_options[:target_url] ||= url_for(@url_options[:url])
237
211
  @rm_options[:target_method] ||= 'POST'
238
212
  #TODO: make option key to specify HTTP headers, 2006/10/16 shino
239
213
  @rm_options
@@ -254,14 +228,12 @@ module Ap4r
254
228
  end
255
229
 
256
230
  def action_api_name
257
- action_method_name = @url_options[:action]
231
+ action_method_name = @url_options[:url][:action]
258
232
  action_method_name.camelcase
259
233
  end
260
234
 
261
235
  def options_without_action
262
- new_opts = @url_options.dup
263
- new_opts[:action] = nil
264
- new_opts
236
+ @url_options[:url].reject{ |k,v| k == :action }
265
237
  end
266
238
 
267
239
  end
@@ -0,0 +1,181 @@
1
+ # Author:: Kiwamu Kato
2
+ # Copyright:: Copyright (c) 2007 Future Architect Inc.
3
+ # Licence:: MIT Licence
4
+
5
+ require 'active_record'
6
+
7
+ module Ap4r #:nodoc:
8
+
9
+ # This +MessageBuilder+ is the class for formatting message body.
10
+ # Current support formats are text, xml, json and yaml,
11
+ # and the formatted messages are sent over HTTP.
12
+ #
13
+ # Using +format+ method, this class automatically changes the format of
14
+ # the given message body and adds appropriate +Content-type+ to http header.
15
+ # Or using +body_as_*+ methods, you can directly assign formatted message body.
16
+ class MessageBuilder
17
+
18
+ def initialize(queue_name, queue_message, queue_headers)
19
+ @queue_name = queue_name
20
+ @message_body = queue_message
21
+ @message_headers = queue_headers
22
+ @format = nil
23
+ @message_body_with_format = nil
24
+ @to_xml_options = {:root => "root"}
25
+ end
26
+
27
+ attr_accessor :queue_name, :message_body, :message_headers
28
+ attr_reader :format, :to_xml_options
29
+
30
+ # Sets message body in async_to block.
31
+ # The first argument is key and the second one is value.
32
+ #
33
+ # options are for to_xml conversion on Array and Hash, ActiveRecord objects.
34
+ #
35
+ def body(k, v, options = { })
36
+ k ||= v.class
37
+ if v.kind_of? ActiveRecord::Base
38
+ @message_body[k.to_sym] = v
39
+ @to_xml_options = @to_xml_options.merge(options)
40
+ else
41
+ @message_body[k.to_sym] = v
42
+ end
43
+ end
44
+
45
+ # Sets message header in async_to block.
46
+ # The first argument is key and the second one is value.
47
+ #
48
+ # Now supports following keys:
49
+ # :expire
50
+ # :priority
51
+ # :delivery
52
+ # :max_deliveries
53
+ # :dispatch_mode
54
+ # :target_method
55
+ # :target_url
56
+ # :id
57
+ #
58
+ # For details, please refer the reliable-msg.
59
+ #
60
+ def header(k, v)
61
+ @message_headers[k.to_sym] = v
62
+ end
63
+
64
+ # Sets http header in async_to block such as 'Content_type'.
65
+ # The first argument is key and the second one is value.
66
+ #
67
+ def http_header(k, v)
68
+ @message_headers["http_header_#{k}".to_sym] = v
69
+ end
70
+
71
+ # Sets format message serialization.
72
+ # As to the format, automatically sets content-type.
73
+ # Unless any format, content-type is defined as "application/x-www-form-urlencoded".
74
+ #
75
+ def format(v)
76
+ case @format = v
77
+ when :text
78
+ set_content_type("text/plain")
79
+ when :xml
80
+ set_content_type("text/xml application/x-xml")
81
+ when :json
82
+ set_content_type("application/json")
83
+ when :yaml
84
+ set_content_type("text/plain text/yaml")
85
+ else
86
+ set_content_type("application/x-www-form-urlencoded")
87
+ end
88
+ end
89
+
90
+ # Sets text format message. No need to use +format+.
91
+ #
92
+ def body_as_text(text)
93
+ @message_body_with_format = text
94
+ format :text
95
+ end
96
+
97
+ # Sets xml format message. No need to use +format+.
98
+ #
99
+ def body_as_xml(xml)
100
+ @message_body_with_format = xml
101
+ format :xml
102
+ end
103
+
104
+ # Sets json format message. No need to use +format+.
105
+ #
106
+ def body_as_json(json)
107
+ @message_body_with_format = json
108
+ format :json
109
+ end
110
+
111
+ # Sets yaml format message. No need to use +format+.
112
+ #
113
+ def body_as_yaml(yaml)
114
+ @message_body_with_format = yaml
115
+ format :yaml
116
+ end
117
+
118
+ # Return converted message body, as to assigned format.
119
+ #
120
+ def format_message_body
121
+ return @message_body_with_format if @message_body_with_format
122
+
123
+ case @format
124
+ when :text
125
+ return @message_body.to_s
126
+ when :xml
127
+ return @message_body.to_xml @to_xml_options
128
+ when :json
129
+ return @message_body.to_json
130
+ when :yaml
131
+ return @message_body.to_yaml
132
+ else
133
+ @message_body.each do |k,v|
134
+ if v.kind_of? ActiveRecord::Base
135
+ @message_body[k] = v.attributes
136
+ end
137
+ end
138
+ return query_string(@message_body)
139
+ end
140
+ end
141
+
142
+ private
143
+ def query_string(hash)
144
+ build_query_string(hash, nil, nil)
145
+ end
146
+
147
+ def build_query_string(hash, query = nil, top = nil)
148
+ query ||= []
149
+ top ||= ""
150
+
151
+ _top = top.dup
152
+
153
+ hash.each do |k,v|
154
+ top = _top
155
+ top += top == "" ? "#{urlencode(k.to_s)}" : "[#{urlencode(k.to_s)}]"
156
+ if v.kind_of? Hash
157
+ build_query_string(v, query, top)
158
+ elsif v.kind_of? Array
159
+ v.each do |e|
160
+ query << "#{top}[]=#{urlencode(e.to_s)}"
161
+ end
162
+ else
163
+ query << "#{top}=#{urlencode(v.to_s)}"
164
+ end
165
+ end
166
+ query.join('&')
167
+ end
168
+
169
+ def simple_url_encoded_form_data(params, sep = '&')
170
+ params.map {|k,v| "#{urlencode(k.to_s)}=#{urlencode(v.to_s)}" }.join(sep)
171
+ end
172
+
173
+ def urlencode(str)
174
+ str.gsub(/[^a-zA-Z0-9_\.\-]/n) {|s| sprintf('%%%02x', s[0]) }
175
+ end
176
+
177
+ def set_content_type(type)
178
+ http_header("Content-type", type)
179
+ end
180
+ end
181
+ end