I'm not sure if this is a Mac-only problem or what, but we were getting incompatible-function-pointer errors when trying to build the RocketAMF C extensions. This fixes that! (Maybe it's like, Mac-only but as of Ruby 3.4 in specific? We're running RocketAMF in production on Ruby 3.4 right now without this. Shrug.)
196 lines
No EOL
7.1 KiB
Ruby
196 lines
No EOL
7.1 KiB
Ruby
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 <tt>messages</tt> 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 <tt>messages</tt> 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 |