rack-amf 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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