$:.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