[ap4r-devel] [260] branches/200709_gihyo/async_shop: Modified: asynchronization.

kato-k at rubyforge.org kato-k at rubyforge.org
Fri Aug 24 05:31:35 EDT 2007


Revision: 260
Author:   kato-k
Date:     2007-08-24 05:31:34 -0400 (Fri, 24 Aug 2007)

Log Message:
-----------
Modified: asynchronization.

Modified Paths:
--------------
    branches/200709_gihyo/async_shop/as_rails/app/controllers/async_shop_controller.rb

Added Paths:
-----------
    branches/200709_gihyo/async_shop/as_ap4r/
    branches/200709_gihyo/async_shop/as_ap4r/config/
    branches/200709_gihyo/async_shop/as_ap4r/config/ap4r_settings.rb
    branches/200709_gihyo/async_shop/as_ap4r/config/log4r.yaml
    branches/200709_gihyo/async_shop/as_ap4r/config/queues.cfg
    branches/200709_gihyo/async_shop/as_ap4r/config/queues_disk.cfg
    branches/200709_gihyo/async_shop/as_ap4r/config/queues_mysql.cfg
    branches/200709_gihyo/async_shop/as_ap4r/log/
    branches/200709_gihyo/async_shop/as_ap4r/public/
    branches/200709_gihyo/async_shop/as_ap4r/script/
    branches/200709_gihyo/async_shop/as_ap4r/script/irm
    branches/200709_gihyo/async_shop/as_ap4r/script/loop.cmd
    branches/200709_gihyo/async_shop/as_ap4r/script/loop.rb
    branches/200709_gihyo/async_shop/as_ap4r/script/mongrel_ap4r
    branches/200709_gihyo/async_shop/as_ap4r/script/start
    branches/200709_gihyo/async_shop/as_ap4r/script/stop
    branches/200709_gihyo/async_shop/as_ap4r/tmp/
    branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/
    branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/init.rb
    branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/
    branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/ap4r_client.rb
    branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/async_helper.rb
    branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/message_builder.rb

Added: branches/200709_gihyo/async_shop/as_ap4r/config/ap4r_settings.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_ap4r/config/ap4r_settings.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_ap4r/config/ap4r_settings.rb	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1,5 @@
+Ap4r::Configuration.setup {|services|
+  services.add 'queues_disk.cfg', :host => 'localhost', :name => :rm1
+#  services.add 'queues.cfg', :name => :rm2
+}
+

Added: branches/200709_gihyo/async_shop/as_ap4r/config/log4r.yaml
===================================================================
--- branches/200709_gihyo/async_shop/as_ap4r/config/log4r.yaml	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_ap4r/config/log4r.yaml	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1,61 @@
+purpose    : Test
+description: This is the YAML doc
+  this yaml has many many unnecessary setting
+  for testing/explanation only.
+  edit properly for normal use.
+
+---
+
+log4r_config:
+  # define all pre config ...
+  pre_config:
+    global:
+      level: DEBUG
+    root  :
+      level: DEBUG
+    parameters:
+      - name   : x
+        value  : aaa
+      - name   : y
+        value  : bbb
+
+  # define all loggers ...
+  loggers:
+    - name      : qm_logger
+      level     : DEBUG
+      additive  : 'false'
+      trace     : 'false'      
+      outputters:
+        - stderr
+        - logfile 
+
+    - name      :  yourlogger  #not used yet...
+      level     : INFO
+      outputters: 
+        - stderr
+        - logfile 
+
+  # define all outputters (incl. formatters)      
+  outputters:
+    - type     : StderrOutputter
+      name     : stderr 
+      level    : DEBUG
+      only_at  :
+        - INFO
+        - WARN
+        - FATAL
+      formatter:
+        date_pattern: '%y%m%d %H:%M:%S'
+        pattern     : '%d %l: %m '
+        type        : PatternFormatter
+
+    - type        : DateFileOutputter
+      name        : logfile
+      level       : DEBUG
+      date_pattern: '%Y%m%d'
+      trunc       : 'false'
+      dirname     : "#{HOME}/logs"
+      formatter   :
+        date_pattern: '%y%m%d %H:%M:%S'
+        pattern     : '%d %l: %m'
+        type        : PatternFormatter

Added: branches/200709_gihyo/async_shop/as_ap4r/config/queues.cfg
===================================================================
--- branches/200709_gihyo/async_shop/as_ap4r/config/queues.cfg	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_ap4r/config/queues.cfg	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1,20 @@
+--- 
+store: 
+  type: mysql
+  host: localhost
+  database: test
+  username: test
+  password: 
+drb: 
+  host: 
+  port: 6438
+  acl: allow 127.0.0.1
+dispatchers:
+  -
+    targets: queue.*
+    threads: 1
+#carriers:
+#  - 
+#    source_uri: druby://another.host.local:6438
+#    threads: 1
+

Added: branches/200709_gihyo/async_shop/as_ap4r/config/queues_disk.cfg
===================================================================
--- branches/200709_gihyo/async_shop/as_ap4r/config/queues_disk.cfg	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_ap4r/config/queues_disk.cfg	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1,15 @@
+--- 
+store: 
+  type: disk
+drb: 
+  host: 
+  port: 6438
+  acl: allow 127.0.0.1 allow ::1 allow 10.0.0.0/8
+dispatchers:
+  -
+    targets: queue.*
+    threads: 1
+#carriers:
+#  - 
+#    source_uri: druby://another.host.local:6438
+#    threads: 1

Added: branches/200709_gihyo/async_shop/as_ap4r/config/queues_mysql.cfg
===================================================================
--- branches/200709_gihyo/async_shop/as_ap4r/config/queues_mysql.cfg	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_ap4r/config/queues_mysql.cfg	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1,19 @@
+--- 
+store: 
+  type: mysql
+  host: localhost
+  database: ap4r
+  username: ap4r
+  password: ap4r
+drb: 
+  host: 
+  port: 6438
+  acl: allow 127.0.0.1 allow 10.0.0.0/8 allow ::1
+dispatchers:
+  -
+    targets: queue.*
+    threads: 1
+#carriers:
+#  - 
+#    source_uri: druby://another.host.local:6438
+#    threads: 1

Added: branches/200709_gihyo/async_shop/as_ap4r/script/irm
===================================================================
--- branches/200709_gihyo/async_shop/as_ap4r/script/irm	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_ap4r/script/irm	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1,4 @@
+require 'rubygems'
+require 'ap4r'
+
+require 'ap4r/util/irm'

Added: branches/200709_gihyo/async_shop/as_ap4r/script/loop.cmd
===================================================================
--- branches/200709_gihyo/async_shop/as_ap4r/script/loop.cmd	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_ap4r/script/loop.cmd	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1,3 @@
+ at ECHO OFF
+
+ruby %~dp0loop.rb manager start -c queues%1.cfg

Added: branches/200709_gihyo/async_shop/as_ap4r/script/loop.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_ap4r/script/loop.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_ap4r/script/loop.rb	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1,8 @@
+load_path = "-Ilib/"
+
+loop{
+  puts; puts '=== queue manager starting ==='
+  system "ruby #{load_path} script/queues.rb #{ARGV.join(' ')}"
+  puts; puts '=== queue manager stopped ==='
+}
+

Added: branches/200709_gihyo/async_shop/as_ap4r/script/mongrel_ap4r
===================================================================
--- branches/200709_gihyo/async_shop/as_ap4r/script/mongrel_ap4r	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_ap4r/script/mongrel_ap4r	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1,4 @@
+require 'rubygems'
+require 'ap4r'
+
+load 'ap4r/mongrel_ap4r.rb'

Added: branches/200709_gihyo/async_shop/as_ap4r/script/start
===================================================================
--- branches/200709_gihyo/async_shop/as_ap4r/script/start	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_ap4r/script/start	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1,5 @@
+require 'rubygems'
+require 'ap4r/script/setup'
+
+require 'ap4r/script/queue_manager_control'
+Ap4r::Script::QueueManagerControl.new.start(ARGV)

Added: branches/200709_gihyo/async_shop/as_ap4r/script/stop
===================================================================
--- branches/200709_gihyo/async_shop/as_ap4r/script/stop	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_ap4r/script/stop	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1 @@
+

Modified: branches/200709_gihyo/async_shop/as_rails/app/controllers/async_shop_controller.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/app/controllers/async_shop_controller.rb	2007-08-24 07:58:12 UTC (rev 259)
+++ branches/200709_gihyo/async_shop/as_rails/app/controllers/async_shop_controller.rb	2007-08-24 09:31:34 UTC (rev 260)
@@ -18,8 +18,10 @@
       ActiveRecord::Base.transaction do
         @order = Order.new(params[:order])
         @order.save
-        payment(@order[:id])
 
+        ap4r.async_to({:action => 'payment'},
+                      {:order_id => @order.id})
+
         flash[:notice] = 'Order was successfully created.'
         redirect_to :action => 'list'
       end
@@ -29,12 +31,13 @@
     end
   end
 
-  def payment(order_id)
+  def payment
     sleep 5
 
     payment = Payment.new
-    payment.order_id = order_id
+    payment.order_id = params[:order_id]
     payment.save
+    render :text => "true"
   end
 
   def destroy

Added: branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/init.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/init.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/init.rb	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1,11 @@
+# Author:: Shunichi Shinohara
+# Copyright:: Copyright (c) 2007 Future Architect Inc.
+# Licence:: MIT Licence
+
+require 'ap4r_client'
+
+class ActionController::Base
+  def ap4r
+    @ap4r_client ||= ::Ap4r::Client.new(self)
+  end
+end

Added: branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/ap4r_client.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/ap4r_client.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/ap4r_client.rb	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1,132 @@
+# Author:: Kiwamu Kato
+# Copyright:: Copyright (c) 2007 Future Architect Inc.
+# Licence:: MIT Licence
+
+require 'forwardable'
+require 'async_helper'
+
+module Ap4r #:nodoc:
+
+  # This +Client+ is the Rails plugin for asynchronous processing.
+  # Asynchronous logics are called via various protocols, such as XML-RPC,
+  # SOAP, HTTP POST, and more. Now default protocol is HTTP POST.
+  #
+  # Examples: The part of calling next asynchronous logics in a controller in the HelloWorld Sample.
+  #
+  #     req = WorldRequest.new([:world_id => 1, :message => "World"})
+  #     ap4r.async_to({:controller => 'async_world', :action => 'execute'},
+  #                   {:world_id => 1, :message => "World"},
+  #                   {:dispatch_mode => :HTTP}) # skippable
+  #
+  #     render :action => 'response'
+  #
+  # Complement: Above +ap4r+ method is defiend init.rb in +%RAILS_ROOT%/vendor/plugin/ap4r/+.
+  #
+  class Client
+    extend Forwardable
+    include ::Ap4r::AsyncHelper::Base
+
+    def initialize controller
+      @controller = controller
+    end
+
+    def_delegators :@controller, :logger, :url_for
+
+    # Queue a message for next asynchronous logic. Some options are supported.
+    #
+    # Use options to specify target url, etc.
+    # Accurate meanings are defined by a individual converter class.
+    # * :controller (name of next logic)
+    # * :action (name of next logic)
+    #
+    # Use rm_options to pass parameter in queue-put.
+    # Listings below are AP4R extended options.
+    # See the reliable-msg docuememt for more details.
+    # * :target_url (URL of target, prevail over :controller)
+    # * :target_action (action of target, prevail over :action)
+    # * :target_method (HTTP method, e.g. "GET", "POST", etc.)
+    # * :dispatch_mode (protocol in dispatching)
+    # * :queue_name (prevail over :controller and :action)
+    #
+    # Object of argumemts (async_params, options and rm_options) will not be modified.
+    # Implementors (of this class and converters) should not modify them.
+    #
+    # Examples: the most simple
+    #
+    #     ap4r.async_to({:controller => 'next_controller', :action => 'next_action'},
+    #                   {:world_id => 1, :message => "World"})
+    #
+    #
+    # Examples: taking block
+    #
+    #     ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
+    #       body :world_id, 1
+    #       body :message, "World"
+    #     end
+    #
+    #
+    # Examples: transmitting ActiveRecord object
+    #
+    #     ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
+    #       body :world, World.find(1)
+    #       body :message, "World"
+    #     end
+    #
+    #
+    # Examples: transmitting with xml format over http (now support text, json and yaml format).
+    #
+    #     ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
+    #       body :world, World.find(1)
+    #       body :message, "World"
+    #       format :xml
+    #     end
+    #
+    #
+    # Examples: direct assignment for formatted message body
+    #
+    #     ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
+    #       world = World.find(1).to_xml :except => ...
+    #       body_as_xml world
+    #     end
+    #
+    #
+    # Examples: setting message header
+    #
+    #     ap4r.async_to({:controller => 'next_controller', :action => 'next_action'}) do
+    #       body :world_id, 1
+    #       body :message, "World"
+    #
+    #       header :priority, 1
+    #       http_header "Content-type", ...
+    #     end
+    #
+    alias :async_to :async_dispatch
+
+    # Provides at-least-once QoS level.
+    # +block+ are tipically composed of database accesses and +async_to+ calls.
+    # Database accesses are executed transactionallly by +active_record_class+'s transaction method.
+    # In the +block+, +async_to+ calls invoke NOT immediate queueing but just storing messages
+    # to the database (assumed to be the same one as application uses).
+    #
+    # If the execution of +block+ finishes successfully, database transaction is committed and
+    # forward process of each stored message begins.
+    # Forward process composed in two parts. First puts the message into a queue, secondary update
+    # or delete the entry from a management table.
+    #
+    # SAF (store and forward) processing like this guarantees that any message
+    # is never lost and keeps reasonable performance (without two phase commit).
+    #
+    # Examples: Just call async_to method in this block.
+    #
+    #     ap4r.transaction do
+    #       req = WorldRequest.new([:world_id => 1, :message => "World"})
+    #       ap4r.async_to({:controller => 'async_world', :action => 'execute'},
+    #                     {:world_id => 1, :message => "World"})
+    #
+    #       render :action => 'response'
+    #     end
+    #
+    alias :transaction :transaction_with_saf
+
+  end
+end

Added: branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/async_helper.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/async_helper.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/async_helper.rb	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1,262 @@
+# Author:: Shunichi Shinohara
+# Copyright:: Copyright (c) 2007 Future Architect Inc.
+# Licence:: MIT Licence
+
+require 'reliable-msg'
+require 'ap4r/stored_message'
+require 'message_builder'
+
+module Ap4r
+
+  # This +AsyncHelper+ is included to +Ap4rClient+ and works the Rails plugin
+  # for asynchronous processing.
+  #
+  module AsyncHelper
+
+    module Base
+      Converters = {}
+
+      DRUBY_HOST = ENV['AP4R_DRUBY_HOST'] || 'localhost'
+      DRUBY_PORT = ENV['AP4R_DRUBY_PORT'] || '6438'
+      DRUBY_URI = "druby://#{DRUBY_HOST}:#{DRUBY_PORT}"
+
+      @@default_dispatch_mode = :HTTP
+      @@default_rm_options = { :delivery => :once, :dispatch_mode => @@default_dispatch_mode }
+      @@default_queue_prefix = "queue."
+
+      mattr_accessor :default_dispatch_mode, :default_rm_options, :default_queue_prefix, :saf_delete_mode
+
+      # This method is aliased as ::Ap4r::Client#transaction
+      #
+      def transaction_with_saf(active_record_class = ::Ap4r::StoredMessage, *objects, &block)
+
+        Thread.current[:use_saf] = true
+        Thread.current[:stored_messages] = {}
+
+        # store
+        active_record_class ||= ::Ap4r::StoredMessage
+        active_record_class.transaction(*objects, &block)
+
+        # forward
+        forwarded_messages = {}
+        begin
+
+          # TODO: reconsider forwarding strategy, 2006/10/13 kato-k
+          # Once some error occured, such as disconnect reliable-msg or server crush,
+          # which is smart to keep to put a message or stop to do it?
+          # In the case of being many async messages, the former strategy is not so good.
+          #
+          # TODO: add delayed forward mode 2007/05/02 by shino
+          Thread.current[:stored_messages].each {|k,v|
+            __queue_put(v[:queue_name], v[:queue_message], v[:queue_headers])
+            forwarded_messages[k] = v
+          }
+        rescue Exception => err
+          # Don't raise any Exception. Application logic has already completed and messages are saved.
+          logger.warn("Failed to put a message into queue: #{err}")
+        end
+
+        begin
+          StoredMessage.transaction do
+            options = {:delete_mode => @@saf_delete_mode || :physical}
+            forwarded_messages.keys.each {|id|
+              ::Ap4r::StoredMessage.destroy_if_exists(id, options)
+            }
+          end
+        rescue Exception => err
+          # Don't raise any Exception. Application logic has already completed and messages are saved.
+          logger.warn("Failed to put a message into queue: #{err}")
+        end
+
+      ensure
+        Thread.current[:use_saf] = false
+        Thread.current[:stored_messages] = nil
+      end
+
+      # This method is aliased as ::Ap4r::Client#async_to
+      #
+      def async_dispatch(url_options = {}, async_params = {}, rm_options = {}, &block)
+
+        if logger.debug?
+          logger.debug("url_options: ")
+          logger.debug(url_options.inspect)
+          logger.debug("async_params: ")
+          logger.debug(async_params.inspect)
+          logger.debug("rm_options: ")
+          logger.debug(rm_options.inspect)
+        end
+
+        # TODO: clone it, 2006/10/16 shino
+        url_options ||= {}
+        url_options[:controller] ||= @controller.controller_path.gsub("/", ".")
+        url_options[:url] ||= {:controller => url_options[:controller], :action => url_options[:action]}
+        url_options[:url][:controller] ||= url_options[:controller] if url_options[:url].kind_of?(Hash)
+
+        rm_options = @@default_rm_options.merge(rm_options || {})
+
+        # Only async_params is not cloned. options and rm_options are cloned before now.
+        # This is a current contract between this class and converter classes.
+        converter = Converters[rm_options[:dispatch_mode]].new(url_options, async_params, rm_options, self)
+        logger.debug{"druby uri for queue-manager : #{DRUBY_URI}"}
+
+        queue_name = __get_queue_name(url_options, rm_options)
+        queue_message = converter.make_params
+        queue_headers = converter.make_rm_options
+
+        message_builder = ::Ap4r::MessageBuilder.new(queue_name, queue_message, queue_headers)
+        if block_given?
+          message_builder.instance_eval(&block)
+        end
+        queue_name = message_builder.queue_name
+        queue_message = message_builder.format_message_body
+        queue_headers = message_builder.message_headers
+
+        if Thread.current[:use_saf]
+          stored_message = ::Ap4r::StoredMessage.store(queue_name, queue_message, queue_headers)
+
+          Thread.current[:stored_messages].store(
+                                                 stored_message.id,
+                                                 {
+                                                   :queue_message => queue_message,
+                                                   :queue_name => queue_name,
+                                                   :queue_headers => queue_headers
+                                                 } )
+          return stored_message.id
+        end
+
+        __queue_put(queue_name, queue_message, queue_headers)
+      end
+
+      private
+      def __queue_put(queue_name, queue_message, queue_headers)
+        # TODO: can use a Queue instance repeatedly? 2007/05/02 by shino
+        q = ReliableMsg::Queue.new(queue_name, :drb_uri => DRUBY_URI)
+        q.put(queue_message, queue_headers)
+      end
+
+      def __get_queue_name(options, rm_options)
+        if options[:url].kind_of?(Hash)
+          rm_options[:queue_name] ||=
+            @@default_queue_prefix.clone.concat(options[:url][:controller].to_s).concat('.').concat(options[:url][:action].to_s)
+        else
+          rm_options[:queue_name] ||=
+            @@default_queue_prefix.clone.chomp(".").concat(URI::parse(options[:url]).path.gsub("/", "."))
+        end
+        rm_options[:queue_name]
+      end
+
+    end
+
+    module Converters #:nodoc:
+
+      # A base class for converter classes.
+      # Responsibilities of subclasses are as folows
+      # * by +make_params+, convert async_params to appropriate object
+      # * by +make_rm_options+, make appropriate +Hash+ passed by <tt>ReliableMsg::Queue#put</tt>
+      class Base
+
+        # Difine a constant +DISPATCH_MODE+ to value 'mode_symbol' and
+        # add self to a Converters list.
+        def self.dispatch_mode(mode_symbol)
+          self.const_set(:DISPATCH_MODE, mode_symbol)
+          ::Ap4r::AsyncHelper::Base::Converters[mode_symbol] = self
+        end
+
+        def initialize(url_options, async_params, rm_options, url_for_handler)
+          @url_options = url_options
+          @async_params = async_params
+          @rm_options = rm_options
+          @url_for_handler = url_for_handler
+        end
+
+        # Returns a object which passed to <tt>ReliableMsg::Queue.put(message, headers)</tt>'s
+        # first argument +message+.
+        # Should be implemented by subclasses.
+        def make_params
+          raise 'must be implemented in subclasses'
+        end
+
+        # Returns a object which passed to <tt>ReliableMsg::Queue.put(message, headers)</tt>'s
+        # second argument +headers+.
+        # Should be implemented by subclasses.
+        def make_rm_options
+          raise 'must be implemented in subclasses'
+        end
+
+        private
+        # helper method for <tt>ActionController#url_for</tt>
+        def url_for(url_for_options, *parameter_for_method_reference)
+          return url_for_options if url_for_options.kind_of?(String)
+          @url_for_handler.url_for(url_for_options, *parameter_for_method_reference)
+        end
+
+      end
+
+      class Http < Base
+        dispatch_mode :HTTP
+
+        def make_params
+          @async_params
+        end
+
+        def make_rm_options
+          @rm_options[:target_url] ||= url_for(@url_options[:url])
+          @rm_options[:target_method] ||= 'POST'
+          #TODO: make option key to specify HTTP headers, 2006/10/16 shino
+          @rm_options
+        end
+      end
+
+      class WebService < Base
+        def make_params
+          message_obj = {}
+          @async_params.each_pair{|k,v| message_obj[k.to_sym]=v}
+          message_obj
+        end
+
+        def make_rm_options
+          @rm_options[:target_url] ||= target_url_name
+          @rm_options[:target_action] ||= action_api_name
+          @rm_options
+        end
+
+        def action_api_name
+          action_method_name = @url_options[:url][:action]
+          action_method_name.camelcase
+        end
+
+        def options_without_action
+          @url_options[:url].reject{ |k,v| k == :action }
+        end
+
+      end
+
+      class XmlRpc < WebService
+        dispatch_mode :XMLRPC
+
+        def target_url_name
+          url_for(options_without_action) + rails_api_url_suffix
+        end
+
+        private
+        def rails_api_url_suffix
+          '/api'
+        end
+      end
+
+      class SOAP < WebService
+        dispatch_mode :SOAP
+
+        def target_url_name
+          url_for(options_without_action) + rails_wsdl_url_suffix
+        end
+
+        private
+        def rails_wsdl_url_suffix
+          '/service.wsdl'
+        end
+      end
+
+    end
+  end
+end

Added: branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/message_builder.rb
===================================================================
--- branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/message_builder.rb	                        (rev 0)
+++ branches/200709_gihyo/async_shop/as_rails/vendor/plugins/ap4r/lib/message_builder.rb	2007-08-24 09:31:34 UTC (rev 260)
@@ -0,0 +1,181 @@
+# Author:: Kiwamu Kato
+# Copyright:: Copyright (c) 2007 Future Architect Inc.
+# Licence:: MIT Licence
+
+require 'active_record'
+
+module Ap4r #:nodoc:
+
+  # This +MessageBuilder+ is the class for formatting message body.
+  # Current support formats are text, xml, json and yaml,
+  # and the formatted messages are sent over HTTP.
+  #
+  # Using +format+ method, this class automatically changes the format of
+  # the given message body and adds appropriate +Content-type+ to http header.
+  # Or using +body_as_*+ methods, you can directly assign formatted message body.
+  class MessageBuilder
+
+    def initialize(queue_name, queue_message, queue_headers)
+      @queue_name = queue_name
+      @message_body = queue_message
+      @message_headers = queue_headers
+      @format = nil
+      @message_body_with_format = nil
+      @to_xml_options = {:root => "root"}
+    end
+
+    attr_accessor :queue_name, :message_body, :message_headers
+    attr_reader :format, :to_xml_options
+
+    # Sets message body in async_to block.
+    # The first argument is key and the second one is value.
+    #
+    # options are for to_xml conversion on Array and Hash, ActiveRecord objects.
+    #
+    def body(k, v, options = { })
+      k ||= v.class
+      if v.kind_of? ActiveRecord::Base
+        @message_body[k.to_sym] = v
+        @to_xml_options = @to_xml_options.merge(options)
+      else
+        @message_body[k.to_sym] = v
+      end
+    end
+
+    # Sets message header in async_to block.
+    # The first argument is key and the second one is value.
+    #
+    # Now supports following keys:
+    #   :expire
+    #   :priority
+    #   :delivery
+    #   :max_deliveries
+    #   :dispatch_mode
+    #   :target_method
+    #   :target_url
+    #   :id
+    #
+    # For details, please refer the reliable-msg.
+    #
+    def header(k, v)
+      @message_headers[k.to_sym] = v
+    end
+
+    # Sets http header in async_to block such as 'Content_type'.
+    # The first argument is key and the second one is value.
+    #
+    def http_header(k, v)
+      @message_headers["http_header_#{k}".to_sym] = v
+    end
+
+    # Sets format message serialization.
+    # As to the format, automatically sets content-type.
+    # Unless any format, content-type is defined as "application/x-www-form-urlencoded".
+    #
+    def format(v)
+      case @format = v
+      when :text
+        set_content_type("text/plain")
+      when :xml
+        set_content_type("text/xml application/x-xml")
+      when :json
+        set_content_type("application/json")
+      when :yaml
+        set_content_type("text/plain text/yaml")
+      else
+        set_content_type("application/x-www-form-urlencoded")
+      end
+    end
+
+    # Sets text format message. No need to use +format+.
+    #
+    def body_as_text(text)
+      @message_body_with_format = text
+      format :text
+    end
+
+    # Sets xml format message. No need to use +format+.
+    #
+    def body_as_xml(xml)
+      @message_body_with_format = xml
+      format :xml
+    end
+
+    # Sets json format message. No need to use +format+.
+    #
+    def body_as_json(json)
+      @message_body_with_format = json
+      format :json
+    end
+
+    # Sets yaml format message. No need to use +format+.
+    #
+    def body_as_yaml(yaml)
+      @message_body_with_format = yaml
+      format :yaml
+    end
+
+    # Return converted message body, as to assigned format.
+    #
+    def format_message_body
+      return @message_body_with_format  if @message_body_with_format
+
+      case @format
+      when :text
+        return @message_budy.to_s
+      when :xml
+        return @message_body.to_xml @to_xml_options
+      when :json
+        return @message_body.to_json
+      when :yaml
+        return @message_body.to_yaml
+      else
+        @message_body.each do |k,v|
+          if v.kind_of? ActiveRecord::Base
+            @message_body[k] = v.attributes
+          end
+        end
+        return query_string(@message_body)
+      end
+    end
+
+    private
+    def query_string(hash)
+      build_query_string(hash, nil, nil)
+    end
+
+    def build_query_string(hash, query = nil, top = nil)
+      query ||= []
+      top ||= ""
+
+      _top = top.dup
+
+      hash.each do |k,v|
+        top = _top
+        top += top == "" ? "#{urlencode(k.to_s)}" : "[#{urlencode(k.to_s)}]"
+        if v.kind_of? Hash
+          build_query_string(v, query, top)
+        elsif v.kind_of? Array
+          v.each do |e|
+            query << "#{top}[]=#{urlencode(e.to_s)}"
+          end
+        else
+          query << "#{top}=#{urlencode(v.to_s)}"
+        end
+      end
+      query.join('&')
+    end
+
+    def simple_url_encoded_form_data(params, sep = '&')
+      params.map {|k,v| "#{urlencode(k.to_s)}=#{urlencode(v.to_s)}" }.join(sep)
+    end
+
+    def urlencode(str)
+      str.gsub(/[^a-zA-Z0-9_\.\-]/n) {|s| sprintf('%%%02x', s[0]) }
+    end
+
+    def set_content_type(type)
+      http_header("Content-type", type)
+    end
+  end
+end




More information about the ap4r-devel mailing list