rack-amf 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.rdoc ADDED
@@ -0,0 +1,55 @@
1
+ == DESCRIPTION:
2
+
3
+ What will eventually become a full featured AMF gateway implemented as a Rack middleware and AMF0/3 serializers and deserializers. Currently includes a fully compliant AMF0/AMF3 deserializer, AMF3 serializer, and a Rack middleware that supports both standard AMF calls and <tt>RemoteObject</tt> calls.
4
+
5
+ == INSTALL:
6
+
7
+ gem install rack-amf --source="http://gemcutter.org"
8
+
9
+ == EXAMPLE:
10
+
11
+ config.ru:
12
+
13
+ require 'rack/amf'
14
+ use Rack::AMF
15
+
16
+ class TestService
17
+ def sayHello
18
+ 'Hello'
19
+ end
20
+ end
21
+
22
+ Rack::AMF::Services.register('TestService', TestService.new)
23
+
24
+ run lambda {|env| [200, { 'Content-Type' => 'text/plain', 'Content-Length' => '16' }, ["Rack AMF gateway"] ] }
25
+
26
+ == TODO:
27
+
28
+ * Write some Rails helpers so that using Rack AMF as a Rails Metal is easy.
29
+ * Support ActiveRecord serialization/deserialization
30
+ * Auto-map to Rails controllers
31
+
32
+ == LICENSE:
33
+
34
+ (The MIT License)
35
+
36
+ Copyright (c) 2009 Tony Hillerson based on work by Aaron Smith
37
+
38
+ Permission is hereby granted, free of charge, to any person obtaining
39
+ a copy of this software and associated documentation files (the
40
+ 'Software'), to deal in the Software without restriction, including
41
+ without limitation the rights to use, copy, modify, merge, publish,
42
+ distribute, sublicense, and/or sell copies of the Software, and to
43
+ permit persons to whom the Software is furnished to do so, subject to
44
+ the following conditions:
45
+
46
+ The above copyright notice and this permission notice shall be
47
+ included in all copies or substantial portions of the Software.
48
+
49
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
50
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
51
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
52
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
53
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
54
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
55
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+
6
+ require 'spec/rake/spectask'
7
+
8
+ desc 'Default: run the specs.'
9
+ task :default => :spec
10
+
11
+ Spec::Rake::SpecTask.new do |t|
12
+ t.spec_opts = ['--options', 'spec/spec.opts']
13
+ end
14
+
15
+ desc 'Generate documentation for the rubyamf plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Rack AMF'
19
+ rdoc.options << '--line-numbers' << '--main' << 'README.rdoc'
20
+ rdoc.rdoc_files.include('README.rdoc')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
23
+
24
+ spec = Gem::Specification.new do |s|
25
+ s.name = 'rack-amf'
26
+ s.version = '0.0.1'
27
+ s.summary = 'AMF serializer/deserializer and AMF gateway packaged as a rack middleware'
28
+
29
+ s.files = FileList['README.rdoc', 'Rakefile', 'lib/**/*.rb', 'spec/**/*.rb']
30
+ s.require_path = 'lib'
31
+ s.test_files = Dir[*['spec/**/*_spec.rb']]
32
+
33
+ s.has_rdoc = true
34
+ s.extra_rdoc_files = ['README.rdoc']
35
+ s.rdoc_options = ['--line-numbers', '--main', 'README.rdoc']
36
+
37
+ s.authors = ['Tony Hillerson', 'Stephen Augenstein']
38
+ s.email = 'perl.programmer@gmail.com'
39
+ s.homepage = 'http://github.com/warhammerkid/rack-amf'
40
+
41
+ s.platform = Gem::Platform::RUBY
42
+ end
43
+
44
+ Rake::GemPackageTask.new spec do |pkg|
45
+ pkg.need_tar = true
46
+ pkg.need_zip = true
47
+ end
48
+
49
+ desc 'Generate a gemspec file'
50
+ task :gemspec do
51
+ File.open("#{spec.name}.gemspec", 'w') do |f|
52
+ f.write spec.to_ruby
53
+ end
54
+ end
@@ -0,0 +1,210 @@
1
+ require 'amf/values/typed_hash'
2
+ require 'amf/values/array_collection'
3
+ require 'amf/values/messages'
4
+
5
+ module AMF
6
+ # == Class Mapping
7
+ #
8
+ # Handles class name mapping between actionscript and ruby and assists in
9
+ # serializing and deserializing data between them. Simply map an AS class to a
10
+ # ruby class and when the object is (de)serialized it will end up as the
11
+ # appropriate class.
12
+ #
13
+ # Example:
14
+ #
15
+ # AMF::ClassMapper.define do |m|
16
+ # m.map :as => 'AsClass', :ruby => 'RubyClass'
17
+ # m.map :as => 'vo.User', :ruby => 'User'
18
+ # end
19
+ #
20
+ # == Object Population/Serialization
21
+ #
22
+ # In addition to handling class name mapping, it also provides helper methods
23
+ # for populating ruby objects from AMF and extracting properties from ruby objects
24
+ # for serialization. Support for hash-like objects and objects using
25
+ # <tt>attr_accessor</tt> for properties is currently built in, but custom classes
26
+ # may need custom support. As such, it is possible to create a custom populator
27
+ # or serializer.
28
+ #
29
+ # Populators are processed in insert order and must respond to the <tt>can_handle?</tt>
30
+ # and <tt>populate</tt> methods.
31
+ #
32
+ # Example:
33
+ #
34
+ # class CustomPopulator
35
+ # def can_handle? obj
36
+ # true
37
+ # end
38
+ #
39
+ # def populate obj, props, dynamic_props
40
+ # obj.merge! props
41
+ # obj.merge!(dynamic_props) if dynamic_props
42
+ # end
43
+ # end
44
+ # AMF::ClassMapper.object_populators << CustomPopulator.new
45
+ #
46
+ # Serializers are also processed in insert order and must respond to the
47
+ # <tt>can_handle?</tt> and <tt>serialize</tt> methods.
48
+ #
49
+ # Example:
50
+ #
51
+ # class CustomSerializer
52
+ # def can_handle? obj
53
+ # true
54
+ # end
55
+ #
56
+ # def serialize obj
57
+ # {}
58
+ # end
59
+ # end
60
+ # AMF::ClassMapper.object_serializers << CustomSerializer.new
61
+ class ClassMapping
62
+ # Container for all mapped classes
63
+ class MappingSet
64
+ def initialize #:nodoc:
65
+ @as_mappings = {}
66
+ @ruby_mappings = {}
67
+
68
+ # Map defaults
69
+ map :as => 'flex.messaging.messages.AbstractMessage', :ruby => 'AMF::Values::AbstractMessage'
70
+ map :as => 'flex.messaging.messages.RemotingMessage', :ruby => 'AMF::Values::RemotingMessage'
71
+ map :as => 'flex.messaging.messages.AsyncMessage', :ruby => 'AMF::Values::AsyncMessage'
72
+ map :as => 'flex.messaging.messages.CommandMessage', :ruby => 'AMF::Values::CommandMessage'
73
+ map :as => 'flex.messaging.messages.AcknowledgeMessage', :ruby => 'AMF::Values::AcknowledgeMessage'
74
+ map :as => 'flex.messaging.messages.ErrorMessage', :ruby => 'AMF::Values::ErrorMessage'
75
+ map :as => 'flex.messaging.io.ArrayCollection', :ruby => 'AMF::Values::ArrayCollection'
76
+ end
77
+
78
+ # Map a given AS class to a ruby class.
79
+ #
80
+ # Use fully qualified names for both.
81
+ #
82
+ # Example:
83
+ #
84
+ # m.map :as 'com.example.Date', :ruby => 'Example::Date'
85
+ def map params
86
+ [:as, :ruby].each {|k| params[k] = params[k].to_s} # Convert params to strings
87
+ @as_mappings[params[:as]] = params[:ruby]
88
+ @ruby_mappings[params[:ruby]] = params[:as]
89
+ end
90
+
91
+ # Returns the AS class name for the given ruby class name, returing nil if
92
+ # not found
93
+ def get_as_class_name class_name #:nodoc:
94
+ @ruby_mappings[class_name.to_s]
95
+ end
96
+
97
+ # Returns the ruby class name for the given AS class name, returing nil if
98
+ # not found
99
+ def get_ruby_class_name class_name #:nodoc:
100
+ @as_mappings[class_name.to_s]
101
+ end
102
+ end
103
+
104
+ # Array of custom object populators.
105
+ attr_reader :object_populators
106
+
107
+ # Array of custom object serializers.
108
+ attr_reader :object_serializers
109
+
110
+ def initialize #:nodoc:
111
+ @object_populators = []
112
+ @object_serializers = []
113
+ end
114
+
115
+ # Define class mappings in the block. Block is passed a MappingSet object as
116
+ # the first parameter.
117
+ #
118
+ # Example:
119
+ #
120
+ # AMF::ClassMapper.define do |m|
121
+ # m.map :as => 'AsClass', :ruby => 'RubyClass'
122
+ # end
123
+ def define #:yields: mapping_set
124
+ yield mappings
125
+ end
126
+
127
+ # Returns the AS class name for the given ruby object. Will also take a string
128
+ # containing the ruby class name
129
+ def get_as_class_name obj
130
+ # Get class name
131
+ if obj.is_a?(String)
132
+ ruby_class_name = obj
133
+ elsif obj.is_a?(Values::TypedHash)
134
+ ruby_class_name = obj.type
135
+ else
136
+ ruby_class_name = obj.class.name
137
+ end
138
+
139
+ # Get mapped AS class name
140
+ mappings.get_as_class_name ruby_class_name
141
+ end
142
+
143
+ # Instantiates a ruby object using the mapping configuration based on the
144
+ # source AS class name. If there is no mapping defined, it returns a hash.
145
+ def get_ruby_obj as_class_name
146
+ ruby_class_name = mappings.get_ruby_class_name as_class_name
147
+ if ruby_class_name.nil?
148
+ # Populate a simple hash, since no mapping
149
+ return Values::TypedHash.new(as_class_name)
150
+ else
151
+ ruby_class = ruby_class_name.split('::').inject(Kernel) {|scope, const_name| scope.const_get(const_name)}
152
+ return ruby_class.new
153
+ end
154
+ end
155
+
156
+ # Populates the ruby object using the given properties
157
+ def populate_ruby_obj obj, props, dynamic_props=nil
158
+ # Process custom populators
159
+ @object_populators.each do |p|
160
+ next unless p.can_handle?(obj)
161
+ p.populate obj, props, dynamic_props
162
+ return obj
163
+ end
164
+
165
+ # Fallback populator
166
+ props.merge! dynamic_props if dynamic_props
167
+ hash_like = obj.respond_to?("[]=")
168
+ props.each do |key, value|
169
+ if obj.respond_to?("#{key}=")
170
+ obj.send("#{key}=", value)
171
+ elsif hash_like
172
+ obj[key.to_sym] = value
173
+ end
174
+ end
175
+ obj
176
+ end
177
+
178
+ # Extracts all exportable properties from the given ruby object and returns
179
+ # them in a hash
180
+ def props_for_serialization ruby_obj
181
+ # Proccess custom serializers
182
+ @object_serializers.each do |s|
183
+ next unless s.can_handle?(ruby_obj)
184
+ return s.serialize(ruby_obj)
185
+ end
186
+
187
+ # Handle hashes
188
+ if ruby_obj.is_a?(Hash)
189
+ # Stringify keys to make it easier later on and allow sorting
190
+ h = {}
191
+ ruby_obj.each {|k,v| h[k.to_s] = v}
192
+ return h
193
+ end
194
+
195
+ # Fallback serializer
196
+ props = {}
197
+ ruby_obj.public_methods(false).each do |method_name|
198
+ # Add them to the prop hash if they take no arguments
199
+ method_def = ruby_obj.method(method_name)
200
+ props[method_name] = ruby_obj.send(method_name) if method_def.arity == 0
201
+ end
202
+ props
203
+ end
204
+
205
+ private
206
+ def mappings
207
+ @mappings ||= MappingSet.new
208
+ end
209
+ end
210
+ end
data/lib/amf/common.rb ADDED
@@ -0,0 +1,28 @@
1
+ module AMF
2
+ class << self
3
+ # Deserialize the AMF string _source_ into a Ruby data structure and return it.
4
+ def deserialize source, amf_version = 0
5
+ if amf_version == 0
6
+ AMF::Deserializer.new.deserialize(source)
7
+ elsif amf_version == 3
8
+ AMF::AMF3Deserializer.new.deserialize(source)
9
+ else
10
+ raise AMFError, "unsupported version #{amf_version}"
11
+ end
12
+ end
13
+
14
+ # Serialize the given Ruby data structure _obj_ into an AMF stream
15
+ def serialize obj, amf_version = 0
16
+ if amf_version == 0
17
+ AMF::Serializer.new.serialize(obj)
18
+ elsif amf_version == 3
19
+ AMF::AMF3Serializer.new.serialize(obj)
20
+ else
21
+ raise AMFError, "unsupported version #{amf_version}"
22
+ end
23
+ end
24
+ end
25
+
26
+ # The base exception for AMF errors.
27
+ class AMFError < StandardError; end
28
+ end
@@ -0,0 +1,46 @@
1
+ module AMF
2
+ # AMF0 Type Markers
3
+ AMF0_NUMBER_MARKER = 0x00 #"\000"
4
+ AMF0_BOOLEAN_MARKER = 0x01 #"\001"
5
+ AMF0_STRING_MARKER = 0x02 #"\002"
6
+ AMF0_OBJECT_MARKER = 0x03 #"\003"
7
+ AMF0_MOVIE_CLIP_MARKER = 0x04 #"\004" # Unused
8
+ AMF0_NULL_MARKER = 0x05 #"\005"
9
+ AMF0_UNDEFINED_MARKER = 0x06 #"\006"
10
+ AMF0_REFERENCE_MARKER = 0x07 #"\a"
11
+ AMF0_HASH_MARKER = 0x08 #"\b"
12
+ AMF0_OBJECT_END_MARKER = 0x09 #"\t"
13
+ AMF0_STRICT_ARRAY_MARKER = 0x0A #"\n"
14
+ AMF0_DATE_MARKER = 0x0B #"\v"
15
+ AMF0_LONG_STRING_MARKER = 0x0C #"\f"
16
+ AMF0_UNSUPPORTED_MARKER = 0x0D #"\r"
17
+ AMF0_RECORDSET_MARKER = 0x0E #"\016" # Unused
18
+ AMF0_XML_MARKER = 0x0F #"\017"
19
+ AMF0_TYPED_OBJECT_MARKER = 0x10 #"\020"
20
+ AMF0_AMF3_MARKER = 0x11 #"\021"
21
+
22
+ # AMF3 Type Markers
23
+ AMF3_UNDEFINED_MARKER = 0x00 #"\000"
24
+ AMF3_NULL_MARKER = 0x01 #"\001"
25
+ AMF3_FALSE_MARKER = 0x02 #"\002"
26
+ AMF3_TRUE_MARKER = 0x03 #"\003"
27
+ AMF3_INTEGER_MARKER = 0x04 #"\004"
28
+ AMF3_DOUBLE_MARKER = 0x05 #"\005"
29
+ AMF3_STRING_MARKER = 0x06 #"\006"
30
+ AMF3_XML_DOC_MARKER = 0x07 #"\a"
31
+ AMF3_DATE_MARKER = 0x08 #"\b"
32
+ AMF3_ARRAY_MARKER = 0x09 #"\t"
33
+ AMF3_OBJECT_MARKER = 0x0A #"\n"
34
+ AMF3_XML_MARKER = 0x0B #"\v"
35
+ AMF3_BYTE_ARRAY_MARKER = 0x0C #"\f"
36
+
37
+ # Other Markers, some reused
38
+ EMPTY_STRING = AMF3_NULL_MARKER
39
+ ANONYMOUS_OBJECT = AMF3_NULL_MARKER
40
+ DYNAMIC_OBJECT = AMF3_XML_MARKER
41
+ CLOSE_DYNAMIC_OBJECT = AMF3_NULL_MARKER
42
+ CLOSE_DYNAMIC_ARRAY = AMF3_NULL_MARKER
43
+
44
+ MAX_INTEGER = 268435455
45
+ MIN_INTEGER = -268435456
46
+ end