module RocketAMF # Container for the AMF request/response. class Envelope attr_reader :amf_version, :headers, :messages def initialize props={} @amf_version = props[:amf_version] || 0 @headers = props[:headers] || {} @messages = props[:messages] || [] end # Populates the envelope from the given stream or string using the given # class mapper, or creates a new one. Returns self for easy chaining. # # Example: # # req = RocketAMF::Envelope.new.populate_from_stream(env['rack.input'].read) #-- # Implemented in pure/remoting.rb RocketAMF::Pure::Envelope def populate_from_stream stream, class_mapper=nil raise AMFError, 'Must load "rocketamf/pure"' end # Creates the appropriate message and adds it to messages to call # the given target using the standard (old) remoting APIs. You can call multiple # targets in the same request, unlike with the flex remotings APIs. # # Example: # # req = RocketAMF::Envelope.new # req.call 'test', "arg_1", ["args", "args"] # req.call 'Controller.action' def call target, *args raise "Cannot use different call types" unless @call_type.nil? || @call_type == :simple @call_type = :simple msg_num = messages.length+1 @messages << RocketAMF::Message.new(target, "/#{msg_num}", args) end # Creates the appropriate message and adds it to messages using the # new flex (RemoteObject) remoting APIs. You can only make one flex remoting # call per envelope, and the AMF version must be set to 3. # # Example: # # req = RocketAMF::Envelope.new :amf_version => 3 # req.call_flex 'Controller.action', "arg_1", ["args", "args"] def call_flex target, *args raise "Can only call one flex target per request" if @call_type == :flex raise "Cannot use different call types" if @call_type == :simple raise "Cannot use flex remoting calls with AMF0" if @amf_version != 3 @call_type = :flex flex_msg = RocketAMF::Values::RemotingMessage.new target_parts = target.split(".") flex_msg.operation = target_parts.pop # Use pop so that a missing source is possible without issues flex_msg.source = target_parts.pop flex_msg.body = args @messages << RocketAMF::Message.new('null', '/2', flex_msg) # /2 because it always sends a command message before end # Serializes the envelope to a string using the given class mapper, or creates # a new one, and returns the result #-- # Implemented in pure/remoting.rb RocketAMF::Pure::Envelope def serialize class_mapper=nil raise AMFError, 'Must load "rocketamf/pure"' end # Builds response from the request, iterating over each method call and using # the return value as the method call's return value. Marks as envelope as # constructed after running. #-- # Iterate over all the sent messages. If they're somthing we can handle, like # a command message, then simply add the response message ourselves. If it's # a method call, then call the block with the method and args, catching errors # for handling. Then create the appropriate response message using the return # value of the block as the return value for the method call. def each_method_call request, &block raise 'Response already constructed' if @constructed # Set version from response # Can't just copy version because FMS sends version as 1 @amf_version = request.amf_version == 3 ? 3 : 0 request.messages.each do |m| # What's the request body? case m.data when Values::CommandMessage # Pings should be responded to with an AcknowledgeMessage built using the ping # Everything else is unsupported command_msg = m.data if command_msg.operation == Values::CommandMessage::CLIENT_PING_OPERATION response_value = Values::AcknowledgeMessage.new(command_msg) else e = Exception.new("CommandMessage #{command_msg.operation} not implemented") e.set_backtrace ["RocketAMF::Envelope each_method_call"] response_value = Values::ErrorMessage.new(command_msg, e) end when Values::RemotingMessage # Using RemoteObject style message calls remoting_msg = m.data acknowledge_msg = Values::AcknowledgeMessage.new(remoting_msg) method_base = remoting_msg.source.to_s.empty? ? '' : remoting_msg.source+'.' body = dispatch_call :method => method_base+remoting_msg.operation, :args => remoting_msg.body, :source => remoting_msg, :block => block # Response should be the bare ErrorMessage if there was an error if body.is_a?(Values::ErrorMessage) response_value = body else acknowledge_msg.body = body response_value = acknowledge_msg end else # Standard response message response_value = dispatch_call :method => m.target_uri, :args => m.data, :source => m, :block => block end target_uri = m.response_uri target_uri += response_value.is_a?(Values::ErrorMessage) ? '/onStatus' : '/onResult' @messages << ::RocketAMF::Message.new(target_uri, '', response_value) end @constructed = true end # Returns the result of a response envelope, or an array of results if there # are multiple action call messages. It automatically unwraps flex-style # RemoteObject response messages, where the response result is inside a # RocketAMF::Values::AcknowledgeMessage. # # Example: # # req = RocketAMF::Envelope.new # req.call('TestController.test', 'first_arg', 'second_arg') # res = RocketAMF::Envelope.new # res.each_method_call req do |method, args| # ['a', 'b'] # end # res.result #=> ['a', 'b'] def result results = [] messages.each do |msg| if msg.data.is_a?(Values::AcknowledgeMessage) results << msg.data.body else results << msg.data end end results.length > 1 ? results : results[0] end # Whether or not the response has been constructed. Can be used to prevent # serialization when no processing has taken place. def constructed? @constructed end # Return the serialized envelope as a string def to_s serialize end def dispatch_call p #:nodoc: begin p[:block].call(p[:method], p[:args]) rescue Exception => e # Create ErrorMessage object using the source message as the base Values::ErrorMessage.new(p[:source], e) end end end # RocketAMF::Envelope header class Header attr_accessor :name, :must_understand, :data def initialize name, must_understand, data @name = name @must_understand = must_understand @data = data end end # RocketAMF::Envelope message class Message attr_accessor :target_uri, :response_uri, :data def initialize target_uri, response_uri, data @target_uri = target_uri @response_uri = response_uri @data = data end end end