$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__))) $:.unshift "#{File.expand_path(File.dirname(__FILE__))}/rocketamf/" require "date" require "stringio" require 'rocketamf/extensions' require 'rocketamf/class_mapping' require 'rocketamf/constants' require 'rocketamf/remoting' # RocketAMF is a full featured AMF0/3 serializer and deserializer with support for # bi-directional Flash to Ruby class mapping, custom serialization and mapping, # remoting gateway helpers that follow AMF0/3 messaging specs, and a suite of specs # to ensure adherence to the specification documents put out by Adobe. If the C # components compile, then RocketAMF automatically takes advantage of them to # provide a substantial performance benefit. In addition, RocketAMF is fully # compatible with Ruby 1.9. # # == Performance # # RocketAMF provides native C extensions for serialization, deserialization, # remoting, and class mapping. If your environment supports them, RocketAMF will # automatically take advantage of the C serializer, deserializer, and remoting # support. The C class mapper has some substantial performance optimizations that # make it incompatible with the pure Ruby class mapper, and so it must be manually # enabled. For more information see RocketAMF::ClassMapping. Below are # some benchmarks I took using using a simple little benchmarking utility I whipped # up, which can be found in the root of the repository. # # # 100000 objects # # Ruby 1.8 # Testing native AMF0: # minimum serialize time: 1.229868s # minimum deserialize time: 0.86465s # Testing native AMF3: # minimum serialize time: 1.444652s # minimum deserialize time: 0.879407s # Testing pure AMF0: # minimum serialize time: 25.427931s # minimum deserialize time: 11.706084s # Testing pure AMF3: # minimum serialize time: 31.637864s # minimum deserialize time: 14.773969s # # == Serialization & Deserialization # # RocketAMF provides two main methods - serialize and deserialize. # Deserialization takes a String or StringIO object and the AMF version if different # from the default. Serialization takes any Ruby object and the version if different # from the default. Both default to AMF0, as it's more widely supported and slightly # faster, but AMF3 does a better job of not sending duplicate data. Which you choose # depends on what you need to communicate with and how much serialized size matters. # # == Mapping Classes Between Flash and Ruby # # RocketAMF provides a simple class mapping tool to facilitate serialization and # deserialization of typed objects. Refer to the documentation of # RocketAMF::ClassMapping for more details. If the provided class # mapping tool is not sufficient for your needs, you also have the option to # replace it with a class mapper of your own devising that matches the documented # API. # # == Remoting # # You can use RocketAMF bare to write an AMF gateway using the following code. # In addition, you can use rack-amf (http://github.com/rubyamf/rack-amf) or # RubyAMF (http://github.com/rubyamf/rubyamf), both of which provide rack-compliant # AMF gateways. # # # helloworld.ru # require 'rocketamf' # # class HelloWorldApp # APPLICATION_AMF = 'application/x-amf'.freeze # # def call env # if is_amf?(env) # # Wrap request and response # env['rack.input'].rewind # request = RocketAMF::Envelope.new.populate_from_stream(env['rack.input'].read) # response = RocketAMF::Envelope.new # # # Handle request # response.each_method_call request do |method, args| # raise "Service #{method} does not exists" unless method == 'App.helloWorld' # 'Hello world' # end # # # Pass back response # response_str = response.serialize # return [200, {'Content-Type' => APPLICATION_AMF, 'Content-Length' => response_str.length.to_s}, [response_str]] # else # return [200, {'Content-Type' => 'text/plain', 'Content-Length' => '16' }, ["Rack AMF gateway"]] # end # end # # private # def is_amf? env # return false unless env['CONTENT_TYPE'] == APPLICATION_AMF # return false unless env['PATH_INFO'] == '/amf' # return true # end # end # # run HelloWorldApp.new # # == Advanced Serialization (encode_amf and IExternalizable) # # RocketAMF provides some additional functionality to support advanced # serialization techniques. If you define an encode_amf method on your # object, it will get called during serialization. It is passed a single argument, # the serializer, and it can use the serializer stream, the serialize # method, the write_array method, the write_object method, and # the serializer version. Below is a simple example that uses write_object # to customize the property hash that is used for serialization. # # Example: # # class TestObject # def encode_amf ser # ser.write_object self, @attributes # end # end # # If you plan on using the serialize method, make sure to pass in the # current serializer version, or you could create a message that cannot be deserialized. # # Example: # # class VariableObject # def encode_amf ser # if ser.version == 0 # ser.serialize 0, true # else # ser.serialize 3, false # end # end # end # # If you wish to send and receive IExternalizable objects, you will need to # implement encode_amf, read_external, and write_external. # Below is an example of a ResultSet class that extends Array and serializes as # an array collection. RocketAMF can automatically serialize arrays as # ArrayCollection objects, so this is just an example of how you might implement # an object that conforms to IExternalizable. # # Example: # # class ResultSet < Array # def encode_amf ser # if ser.version == 0 # # Serialize as simple array in AMF0 # ser.write_array self # else # # Serialize as an ArrayCollection object # # It conforms to IExternalizable, does not have any dynamic properties, # # and has no "sealed" members. See the AMF3 specs for more details about # # object traits. # ser.write_object self, nil, { # :class_name => "flex.messaging.io.ArrayCollection", # :externalizable => true, # :dynamic => false, # :members => [] # } # end # end # # # Write self as array to stream # def write_external ser # ser.write_array(self) # end # # # Read array out and replace data with deserialized array. # def read_external des # replace(des.read_object) # end # end module RocketAMF begin require 'rocketamf/ext' rescue LoadError require 'rocketamf/pure' end # Deserialize the AMF string _source_ of the given AMF version into a Ruby # data structure and return it. Creates an instance of RocketAMF::Deserializer # with a new instance of RocketAMF::ClassMapper and calls deserialize # on it with the given source and amf version, returning the result. def self.deserialize source, amf_version = 0 des = RocketAMF::Deserializer.new(RocketAMF::ClassMapper.new) des.deserialize(amf_version, source) end # Serialize the given Ruby data structure _obj_ into an AMF stream using the # given AMF version. Creates an instance of RocketAMF::Serializer # with a new instance of RocketAMF::ClassMapper and calls serialize # on it with the given object and amf version, returning the result. def self.serialize obj, amf_version = 0 ser = RocketAMF::Serializer.new(RocketAMF::ClassMapper.new) ser.serialize(amf_version, obj) end # We use const_missing to define the active ClassMapper at runtime. This way, # heavy modification of class mapping functionality is still possible without # forcing extenders to redefine the constant. def self.const_missing const #:nodoc: if const == :ClassMapper RocketAMF.const_set(:ClassMapper, RocketAMF::ClassMapping) else super(const) end end # The base exception for AMF errors. class AMFError < StandardError; end end