Refactor AMF respone streaming
authorTom Hughes <tom@compton.nu>
Wed, 28 Sep 2011 20:35:58 +0000 (21:35 +0100)
committerTom Hughes <tom@compton.nu>
Mon, 14 Nov 2011 09:42:53 +0000 (09:42 +0000)
Using a Proc object for the response body is deprecated, so switch
to using an object with an each method that yields each response.

app/controllers/amf_controller.rb
lib/potlatch.rb

index 9c9228a7a24dbb34e8045fcb0ea8625e2545ec7e..329fab5734f1f58e2c98bb01a69a9b5f52dbf637 100644 (file)
@@ -36,8 +36,6 @@
 # * version conflict when POIs and ways are reverted
 
 class AmfController < ApplicationController
-  require 'stringio'
-
   include Potlatch
 
   # Help methods for checking boundary sanity and area size
@@ -47,55 +45,30 @@ class AmfController < ApplicationController
 
   # Main AMF handlers: process the raw AMF string (using AMF library) and
   # calls each action (private method) accordingly.
-  # ** FIXME: refactor to reduce duplication of code across read/write
   
   def amf_read
     if request.post?
-      req=StringIO.new(request.raw_post+0.chr)# Get POST data as request
-                                              # (cf http://www.ruby-forum.com/topic/122163)
-      req.read(2)                             # Skip version indicator and client ID
-
-      # Parse request
-
-      headers=AMF.getint(req)           # Read number of headers
-      headers.times do                  # Read each header
-        name=AMF.getstring(req)         #  |
-        req.getc                        #  | skip boolean
-        value=AMF.getvalue(req)         #  |
-        header["name"]=value            #  |
-      end
-
-      bodies=AMF.getint(req)            # Read number of bodies
-
       self.status = :ok
       self.content_type = Mime::AMF
-      self.response_body = proc { |response, output| 
-        a,b=bodies.divmod(256)
-        output.write 0.chr+0.chr+0.chr+0.chr+a.chr+b.chr
-        bodies.times do                 # Read each body
-          message=AMF.getstring(req)    #  | get message name
-          index=AMF.getstring(req)      #  | get index in response sequence
-          bytes=AMF.getlong(req)        #  | get total size in bytes
-          args=AMF.getvalue(req)        #  | get response (probably an array)
-          result=''
-          logger.info("Executing AMF #{message}(#{args.join(',')}):#{index}")
-
-          case message
-            when 'getpresets';        result=AMF.putdata(index,getpresets(*args))
-            when 'whichways';         result=AMF.putdata(index,whichways(*args))
-            when 'whichways_deleted'; result=AMF.putdata(index,whichways_deleted(*args))
-            when 'getway';            result=AMF.putdata(index,getway(args[0].to_i))
-            when 'getrelation';       result=AMF.putdata(index,getrelation(args[0].to_i))
-            when 'getway_old';        result=AMF.putdata(index,getway_old(args[0].to_i,args[1]))
-            when 'getway_history';    result=AMF.putdata(index,getway_history(args[0].to_i))
-            when 'getnode_history';   result=AMF.putdata(index,getnode_history(args[0].to_i))
-            when 'findgpx';           result=AMF.putdata(index,findgpx(*args))
-            when 'findrelations';     result=AMF.putdata(index,findrelations(*args))
-            when 'getpoi';            result=AMF.putdata(index,getpoi(*args))
-          end
-          output.write(result)
+      self.response_body = Dispatcher.new(request.raw_post) do |message,*args|
+        logger.info("Executing AMF #{message}(#{args.join(',')})")
+
+        case message
+          when 'getpresets';        result = getpresets(*args)
+          when 'whichways';         result = whichways(*args)
+          when 'whichways_deleted'; result = whichways_deleted(*args)
+          when 'getway';            result = getway(args[0].to_i)
+          when 'getrelation';       result = getrelation(args[0].to_i)
+          when 'getway_old';        result = getway_old(args[0].to_i,args[1])
+          when 'getway_history';    result = getway_history(args[0].to_i)
+          when 'getnode_history';   result = getnode_history(args[0].to_i)
+          when 'findgpx';           result = findgpx(*args)
+          when 'findrelations';     result = findrelations(*args)
+          when 'getpoi';            result = getpoi(*args)
         end
-      }
+        
+        result
+      end
     else
       render :nothing => true, :status => :method_not_allowed
     end
@@ -103,56 +76,35 @@ class AmfController < ApplicationController
 
   def amf_write
     if request.post?
-      req=StringIO.new(request.raw_post+0.chr)
-      req.read(2)
-      renumberednodes={}              # Shared across repeated putways
-      renumberedways={}               # Shared across repeated putways
-
-      headers=AMF.getint(req)         # Read number of headers
-      headers.times do                # Read each header
-        name=AMF.getstring(req)       #  |
-        req.getc                      #  | skip boolean
-        value=AMF.getvalue(req)       #  |
-        header["name"]=value          #  |
-      end
-
-      bodies=AMF.getint(req)          # Read number of bodies
+      renumberednodes = {}              # Shared across repeated putways
+      renumberedways = {}               # Shared across repeated putways
+      err = false                       # Abort batch on error
 
       self.status = :ok
       self.content_type = Mime::AMF
-      self.response_body = proc { |response, output| 
-        a,b=bodies.divmod(256)
-        output.write 0.chr+0.chr+0.chr+0.chr+a.chr+b.chr
-        bodies.times do               # Read each body
-          message=AMF.getstring(req)  #  | get message name
-          index=AMF.getstring(req)    #  | get index in response sequence
-          bytes=AMF.getlong(req)      #  | get total size in bytes
-          args=AMF.getvalue(req)      #  | get response (probably an array)
-          err=false                   # Abort batch on error
-
-          logger.info("Executing AMF #{message}:#{index}")
-          result=''
-          if err
-            result=[-5,nil]
-          else
-            case message
-              when 'putway';         orn=renumberednodes.dup
-                                     r=putway(renumberednodes,*args)
-                                     r[4]=renumberednodes.reject { |k,v| orn.has_key?(k) }
-                                     if r[0]==0 and r[2] != r[3] then renumberedways[r[2]] = r[3] end
-                                     result=AMF.putdata(index,r)
-              when 'putrelation';    result=AMF.putdata(index,putrelation(renumberednodes, renumberedways, *args))
-              when 'deleteway';      result=AMF.putdata(index,deleteway(*args))
-              when 'putpoi';         r=putpoi(*args)
-                                     if r[0]==0 and r[2] != r[3] then renumberednodes[r[2]] = r[3] end
-                                     result=AMF.putdata(index,r)
-              when 'startchangeset'; result=AMF.putdata(index,startchangeset(*args))
-            end
-            if result[0]==-3 then err=true end    # If a conflict is detected, don't execute any more writes
+      self.response_body = Dispatcher.new(request.raw_post) do |message,*args|
+        logger.info("Executing AMF #{message}")
+
+        if err
+          result = [-5, nil]
+        else
+          case message
+            when 'putway';         orn = renumberednodes.dup
+                                   result = putway(renumberednodes, *args)
+                                   result[4] = renumberednodes.reject { |k,v| orn.has_key?(k) }
+                                   if result[0] == 0 and result[2] != result[3] then renumberedways[result[2]] = result[3] end
+            when 'putrelation';    result = putrelation(renumberednodes, renumberedways, *args)
+            when 'deleteway';      result = deleteway(*args)
+            when 'putpoi';         result = putpoi(*args)
+                                   if result[0] == 0 and result[2] != result[3] then renumberednodes[result[2]] = result[3] end
+            when 'startchangeset'; result = startchangeset(*args)
           end
-          output.write(result)
+
+          err = true if result[0] == -3  # If a conflict is detected, don't execute any more writes
         end
-      }
+
+        result
+      end
     else
       render :nothing => true, :status => :method_not_allowed
     end
index ef3be79a80f748ebff407c6aeaf29f1e6d5ab41a..43be2f8b47e730e1e4a9dc07ba39d5e1b083d3c7 100644 (file)
@@ -1,3 +1,5 @@
+require 'stringio'
+
 # The Potlatch module provides helper functions for potlatch and its communication with the server
 module Potlatch
 
@@ -119,6 +121,48 @@ module Potlatch
 
   end
 
+  # The Dispatcher class handles decoding a series of RPC calls
+  # from the request, dispatching them, and encoding the response
+  class Dispatcher
+    def initialize(request, &block)
+      # Get stream for request data
+      @request = StringIO.new(request + 0.chr)
+
+      # Skip version indicator and client ID
+      @request.read(2)
+
+      # Skip headers
+      AMF.getint(@request).times do     # Read number of headers and loop
+        AMF.getstring(@request)         #  | skip name
+        req.getc                        #  | skip boolean
+        AMF.getvalue(@request)          #  | skip value
+      end
+
+      # Capture the dispatch routine
+      @dispatch = Proc.new
+    end
+
+    def each(&block)
+      # Read number of message bodies
+      bodies = AMF.getint(@request)
+
+      # Output response header
+      a,b = bodies.divmod(256)
+      yield 0.chr + 0.chr + 0.chr + 0.chr + a.chr + b.chr
+
+      # Process the bodies
+      bodies.times do                     # Read each body
+        name = AMF.getstring(@request)    #  | get message name
+        index = AMF.getstring(@request)   #  | get index in response sequence
+        bytes = AMF.getlong(@request)     #  | get total size in bytes
+        args = AMF.getvalue(@request)     #  | get response (probably an array)
+
+        result = @dispatch.call(name, *args)
+
+        yield AMF.putdata(index, result)
+      end
+    end
+  end
 
   # The Potlatch class is a helper for Potlatch
   class Potlatch