Module: RocketAMF

Defined in:
lib/rocketamf.rb,
lib/rocketamf/ext.rb,
lib/rocketamf/pure.rb,
lib/rocketamf/remoting.rb,
lib/rocketamf/constants.rb,
lib/rocketamf/class_mapping.rb,
lib/rocketamf/pure/remoting.rb,
lib/rocketamf/pure/io_helpers.rb,
lib/rocketamf/pure/serializer.rb,
lib/rocketamf/values/messages.rb,
lib/rocketamf/pure/deserializer.rb,
lib/rocketamf/values/typed_hash.rb,
ext/rocketamf_ext/rocketamf_ext.c

Overview

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 (github.com/rubyamf/rack-amf) or RubyAMF (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

Defined Under Namespace

Modules: Ext, Pure, Values Classes: AMFError, ClassMapping, Envelope, Header, MappingSet, Message

Constant Summary collapse

Deserializer =

:stopdoc: Import serializer/deserializer

RocketAMF::Pure::Deserializer
Serializer =
RocketAMF::Pure::Serializer
AMF0_NUMBER_MARKER =

AMF0 Type Markers

0x00
AMF0_BOOLEAN_MARKER =

“000”

0x01
AMF0_STRING_MARKER =

“001”

0x02
AMF0_OBJECT_MARKER =

“002”

0x03
AMF0_MOVIE_CLIP_MARKER =

“003”

0x04
AMF0_NULL_MARKER =

“004” # Unused

0x05
AMF0_UNDEFINED_MARKER =

“005”

0x06
AMF0_REFERENCE_MARKER =

“006”

0x07
AMF0_HASH_MARKER =

“a”

0x08
AMF0_OBJECT_END_MARKER =

“b”

0x09
AMF0_STRICT_ARRAY_MARKER =

“t”

0x0A
AMF0_DATE_MARKER =

“n”

0x0B
AMF0_LONG_STRING_MARKER =

“v”

0x0C
AMF0_UNSUPPORTED_MARKER =

“f”

0x0D
AMF0_RECORDSET_MARKER =

“r”

0x0E
AMF0_XML_MARKER =

“016” # Unused

0x0F
AMF0_TYPED_OBJECT_MARKER =

“017”

0x10
AMF0_AMF3_MARKER =

“020”

0x11
AMF3_UNDEFINED_MARKER =

AMF3 Type Markers

0x00
AMF3_NULL_MARKER =

“000”

0x01
AMF3_FALSE_MARKER =

“001”

0x02
AMF3_TRUE_MARKER =

“002”

0x03
AMF3_INTEGER_MARKER =

“003”

0x04
AMF3_DOUBLE_MARKER =

“004”

0x05
AMF3_STRING_MARKER =

“005”

0x06
AMF3_XML_DOC_MARKER =

“006”

0x07
AMF3_DATE_MARKER =

“a”

0x08
AMF3_ARRAY_MARKER =

“b”

0x09
AMF3_OBJECT_MARKER =

“t”

0x0A
AMF3_XML_MARKER =

“n”

0x0B
AMF3_BYTE_ARRAY_MARKER =

“v”

0x0C
AMF3_VECTOR_INT_MARKER =

“f”

0x0D
AMF3_VECTOR_UINT_MARKER =

“r”

0x0E
AMF3_VECTOR_DOUBLE_MARKER =

“016”

0x0F
AMF3_VECTOR_OBJECT_MARKER =

“017”

0x10
AMF3_DICT_MARKER =

“020”

0x11
AMF3_EMPTY_STRING =

Other AMF3 Markers

0x01
AMF3_CLOSE_DYNAMIC_OBJECT =
0x01
AMF3_CLOSE_DYNAMIC_ARRAY =
0x01
MAX_INTEGER =

Other Constants

268435455
MIN_INTEGER =
-268435456

Class Method Summary collapse

Class Method Details

.const_missing(const) ⇒ Object

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.



206
207
208
209
210
211
212
# File 'lib/rocketamf.rb', line 206

def self.const_missing const #:nodoc:
  if const == :ClassMapper
    RocketAMF.const_set(:ClassMapper, RocketAMF::ClassMapping)
  else
    super(const)
  end
end

.deserialize(source, amf_version = 0) ⇒ Object

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.



189
190
191
192
# File 'lib/rocketamf.rb', line 189

def self.deserialize source, amf_version = 0
  des = RocketAMF::Deserializer.new(RocketAMF::ClassMapper.new)
  des.deserialize(amf_version, source)
end

.serialize(obj, amf_version = 0) ⇒ Object

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.



198
199
200
201
# File 'lib/rocketamf.rb', line 198

def self.serialize obj, amf_version = 0
  ser = RocketAMF::Serializer.new(RocketAMF::ClassMapper.new)
  ser.serialize(amf_version, obj)
end