rack-amf 0.0.2 → 0.0.3

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.
@@ -1,6 +1,6 @@
1
1
  == DESCRIPTION:
2
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.
3
+ A full featured AMF gateway implemented as a Rack middleware. Includes fully compliant AMF0/AMF3 serializers and deserializers and a Rack middleware that includes a service handler and the ability to easily handle requests yourself if you don't want to use the service handler.
4
4
 
5
5
  == INSTALL:
6
6
 
@@ -19,7 +19,7 @@ config.ru:
19
19
  end
20
20
  end
21
21
 
22
- Rack::AMF::Services.register('TestService', TestService.new)
22
+ Rack::AMF::Environment.register_service 'TestService', TestService.new
23
23
 
24
24
  run lambda {|env| [200, { 'Content-Type' => 'text/plain', 'Content-Length' => '16' }, ["Rack AMF gateway"] ] }
25
25
 
data/Rakefile CHANGED
@@ -23,7 +23,7 @@ end
23
23
 
24
24
  spec = Gem::Specification.new do |s|
25
25
  s.name = 'rack-amf'
26
- s.version = '0.0.2'
26
+ s.version = '0.0.3'
27
27
  s.summary = 'AMF serializer/deserializer and AMF gateway packaged as a rack middleware'
28
28
 
29
29
  s.files = FileList['README.rdoc', 'Rakefile', 'lib/**/*.rb', 'spec/**/*.rb']
@@ -51,4 +51,4 @@ task :gemspec do
51
51
  File.open("#{spec.name}.gemspec", 'w') do |f|
52
52
  f.write spec.to_ruby
53
53
  end
54
- end
54
+ end
@@ -34,13 +34,14 @@ module AMF
34
34
  AMF3_XML_MARKER = 0x0B #"\v"
35
35
  AMF3_BYTE_ARRAY_MARKER = 0x0C #"\f"
36
36
 
37
- # Other Markers
38
- EMPTY_STRING = 0x01
39
- ANONYMOUS_OBJECT = 0x01
40
- DYNAMIC_OBJECT = 0x0B
41
- CLOSE_DYNAMIC_OBJECT = 0x01
42
- CLOSE_DYNAMIC_ARRAY = 0x01
37
+ # Other AMF3 Markers
38
+ AMF3_EMPTY_STRING = 0x01
39
+ AMF3_ANONYMOUS_OBJECT = 0x01
40
+ AMF3_DYNAMIC_OBJECT = 0x0B
41
+ AMF3_CLOSE_DYNAMIC_OBJECT = 0x01
42
+ AMF3_CLOSE_DYNAMIC_ARRAY = 0x01
43
43
 
44
- MAX_INTEGER = 268435455
45
- MIN_INTEGER = -268435456
44
+ # Other Constants
45
+ MAX_INTEGER = 268435455
46
+ MIN_INTEGER = -268435456
46
47
  end
@@ -64,15 +64,15 @@ module AMF
64
64
  source.read(len)
65
65
  end
66
66
 
67
- def read_object source
67
+ def read_object source, add_to_ref_cache=true
68
68
  obj = {}
69
+ @ref_cache << obj if add_to_ref_cache
69
70
  while true
70
71
  key = read_string source
71
72
  type = read_int8 source
72
73
  break if type == AMF0_OBJECT_END_MARKER
73
74
  obj[key.to_sym] = deserialize(source, type)
74
75
  end
75
- @ref_cache << obj
76
76
  obj
77
77
  end
78
78
 
@@ -93,6 +93,8 @@ module AMF
93
93
  if key.to_i.to_s == key
94
94
  # Array
95
95
  obj = []
96
+ @ref_cache << obj
97
+
96
98
  obj[key.to_i] = deserialize(source, type)
97
99
  while true
98
100
  key = read_string source
@@ -102,7 +104,10 @@ module AMF
102
104
  end
103
105
  else
104
106
  # Hash
105
- obj = {key.to_sym => deserialize(source, type)}
107
+ obj = {}
108
+ @ref_cache << obj
109
+
110
+ obj[key.to_sym] = deserialize(source, type)
106
111
  while true
107
112
  key = read_string source
108
113
  type = read_int8 source
@@ -110,17 +115,17 @@ module AMF
110
115
  obj[key.to_sym] = deserialize(source, type)
111
116
  end
112
117
  end
113
- @ref_cache << obj
114
118
  obj
115
119
  end
116
120
 
117
121
  def read_array source
118
122
  len = read_word32_network(source)
119
123
  array = []
124
+ @ref_cache << array
125
+
120
126
  0.upto(len - 1) do
121
127
  array << deserialize(source)
122
128
  end
123
- @ref_cache << array
124
129
  array
125
130
  end
126
131
 
@@ -132,14 +137,17 @@ module AMF
132
137
  end
133
138
 
134
139
  def read_typed_object source
140
+ # Create object to add to ref cache
135
141
  class_name = read_string source
136
- props = read_object source
137
- @ref_cache.pop
138
-
139
142
  obj = ClassMapper.get_ruby_obj class_name
140
- ClassMapper.populate_ruby_obj obj, props, {}
141
143
  @ref_cache << obj
142
- obj
144
+
145
+ # Read object props
146
+ props = read_object source, false
147
+
148
+ # Populate object
149
+ ClassMapper.populate_ruby_obj obj, props, {}
150
+ return obj
143
151
  end
144
152
  end
145
153
 
@@ -53,7 +53,7 @@ module AMF
53
53
  attr_accessor :amf_version, :headers, :messages
54
54
 
55
55
  def initialize
56
- @amf_version = 3
56
+ @amf_version = 0
57
57
  @headers = []
58
58
  @messages = []
59
59
  end
@@ -95,7 +95,7 @@ module AMF
95
95
  include AMF::Pure::WriteIOHelpers
96
96
  end
97
97
 
98
- # AMF::Request or AMF::Response header
98
+ # AMF::Pure::Request or AMF::Pure::Response header
99
99
  class Header
100
100
  attr_accessor :name, :must_understand, :data
101
101
 
@@ -106,7 +106,7 @@ module AMF
106
106
  end
107
107
  end
108
108
 
109
- # AMF::Request or AMF::Response message
109
+ # AMF::Pure::Request or AMF::Pure::Response message
110
110
  class Message
111
111
  attr_accessor :target_uri, :response_uri, :data
112
112
 
@@ -5,7 +5,7 @@ module AMF
5
5
  # AMF0 implementation of serializer
6
6
  class Serializer
7
7
  def initialize
8
- @ref_cache = SerializerCache.new
8
+ @ref_cache = SerializerCache.new :object
9
9
  end
10
10
 
11
11
  def version
@@ -13,9 +13,117 @@ module AMF
13
13
  end
14
14
 
15
15
  def serialize obj, stream = ""
16
- if @ref_cache[obj] != nil
17
- # Write reference header
16
+ if obj.respond_to?(:to_amf)
17
+ stream << obj.to_amf(self)
18
+ elsif @ref_cache[obj] != nil
19
+ write_reference @ref_cache[obj], stream
20
+ elsif obj.is_a?(NilClass)
21
+ write_null stream
22
+ elsif obj.is_a?(TrueClass) || obj.is_a?(FalseClass)
23
+ write_boolean obj, stream
24
+ elsif obj.is_a?(Float) || obj.is_a?(Integer)
25
+ write_number obj, stream
26
+ elsif obj.is_a?(Symbol) || obj.is_a?(String)
27
+ write_string obj.to_s, stream
28
+ elsif obj.is_a?(Time)
29
+ write_date obj, stream
30
+ elsif obj.is_a?(Array)
31
+ write_array obj, stream
32
+ elsif obj.is_a?(Hash)
33
+ write_hash obj, stream
34
+ elsif obj.is_a?(Object)
35
+ write_object obj, stream
36
+ end
37
+ stream
38
+ end
39
+
40
+ def write_null stream
41
+ stream << AMF0_NULL_MARKER
42
+ end
43
+
44
+ def write_boolean bool, stream
45
+ stream << AMF0_BOOLEAN_MARKER
46
+ stream << pack_int8(bool ? 1 : 0)
47
+ end
48
+
49
+ def write_number num, stream
50
+ stream << AMF0_NUMBER_MARKER
51
+ stream << pack_double(num)
52
+ end
53
+
54
+ def write_string str, stream
55
+ len = str.length
56
+ if len > 2**16-1
57
+ stream << AMF0_LONG_STRING_MARKER
58
+ stream << pack_word32_network(len)
59
+ else
60
+ stream << AMF0_STRING_MARKER
61
+ stream << pack_int16_network(len)
62
+ end
63
+ stream << str
64
+ end
65
+
66
+ def write_date date, stream
67
+ stream << AMF0_DATE_MARKER
68
+
69
+ date.utc unless date.utc?
70
+ seconds = (date.to_f * 1000).to_i
71
+ stream << pack_double(seconds)
72
+
73
+ stream << pack_int16_network(0)
74
+ end
75
+
76
+ def write_reference index, stream
77
+ stream << AMF0_REFERENCE_MARKER
78
+ stream << pack_int16_network(index)
79
+ end
80
+
81
+ def write_array array, stream
82
+ @ref_cache.add_obj array
83
+ stream << AMF0_STRICT_ARRAY_MARKER
84
+ stream << pack_word32_network(array.length)
85
+ array.each do |elem|
86
+ serialize elem, stream
87
+ end
88
+ end
89
+
90
+ def write_hash hash, stream
91
+ @ref_cache.add_obj hash
92
+ stream << AMF0_HASH_MARKER
93
+ stream << pack_word32_network(hash.length)
94
+ write_prop_list hash, stream
95
+ end
96
+
97
+ def write_object obj, stream
98
+ @ref_cache.add_obj obj
99
+
100
+ # Is it a typed object?
101
+ class_name = ClassMapper.get_as_class_name obj
102
+ if class_name
103
+ stream << AMF0_TYPED_OBJECT_MARKER
104
+ stream << pack_int16_network(class_name.length)
105
+ stream << class_name
106
+ else
107
+ stream << AMF0_OBJECT_MARKER
108
+ end
109
+
110
+ write_prop_list obj, stream
111
+ end
112
+
113
+ private
114
+ include AMF::Pure::WriteIOHelpers
115
+ def write_prop_list obj, stream
116
+ # Write prop list
117
+ props = ClassMapper.props_for_serialization obj
118
+ props.sort.each do |key, value| # Sort keys before writing
119
+ stream << pack_int16_network(key.length)
120
+ stream << key
121
+ serialize value, stream
18
122
  end
123
+
124
+ # Write end
125
+ stream << pack_int16_network(0)
126
+ stream << AMF0_OBJECT_END_MARKER
19
127
  end
20
128
  end
21
129
 
@@ -24,8 +132,8 @@ module AMF
24
132
  attr_reader :string_cache
25
133
 
26
134
  def initialize
27
- @string_cache = SerializerCache.new
28
- @object_cache = SerializerCache.new
135
+ @string_cache = SerializerCache.new :string
136
+ @object_cache = SerializerCache.new :object
29
137
  end
30
138
 
31
139
  def version
@@ -121,7 +229,7 @@ module AMF
121
229
  header = array.length << 1 # make room for a low bit of 1
122
230
  header = header | 1 # set the low bit to 1
123
231
  stream << pack_integer(header)
124
- stream << CLOSE_DYNAMIC_ARRAY
232
+ stream << AMF3_CLOSE_DYNAMIC_ARRAY
125
233
  array.each do |elem|
126
234
  serialize elem, stream
127
235
  end
@@ -137,14 +245,14 @@ module AMF
137
245
  @object_cache.add_obj obj
138
246
 
139
247
  # Always serialize things as dynamic objects
140
- stream << DYNAMIC_OBJECT
248
+ stream << AMF3_DYNAMIC_OBJECT
141
249
 
142
250
  # Write class name/anonymous
143
251
  class_name = ClassMapper.get_as_class_name obj
144
252
  if class_name
145
253
  write_utf8_vr class_name, stream
146
254
  else
147
- stream << ANONYMOUS_OBJECT
255
+ stream << AMF3_ANONYMOUS_OBJECT
148
256
  end
149
257
 
150
258
  # Write out properties
@@ -155,7 +263,7 @@ module AMF
155
263
  end
156
264
 
157
265
  # Write close
158
- stream << CLOSE_DYNAMIC_OBJECT
266
+ stream << AMF3_CLOSE_DYNAMIC_OBJECT
159
267
  end
160
268
  end
161
269
 
@@ -164,7 +272,7 @@ module AMF
164
272
 
165
273
  def write_utf8_vr str, stream
166
274
  if str == ''
167
- stream << EMPTY_STRING
275
+ stream << AMF3_EMPTY_STRING
168
276
  elsif @string_cache[str] != nil
169
277
  write_reference @string_cache[str], stream
170
278
  else
@@ -181,33 +289,37 @@ module AMF
181
289
  end
182
290
 
183
291
  class SerializerCache #:nodoc:
184
- def initialize
185
- @cache_index = 0
186
- @store = {}
187
- end
188
-
189
- def [] obj
190
- @store[object_key(obj)]
292
+ def self.new type
293
+ if type == :string
294
+ StringCache.new
295
+ elsif type == :object
296
+ ObjectCache.new
297
+ end
191
298
  end
192
299
 
193
- def []= obj, value
194
- @store[object_key(obj)] = value
195
- end
300
+ class StringCache < Hash #:nodoc:
301
+ def initialize
302
+ @cache_index = 0
303
+ end
196
304
 
197
- def add_obj obj
198
- key = object_key obj
199
- if @store[key].nil?
200
- @store[key] = @cache_index
305
+ def add_obj str
306
+ self[str] = @cache_index
201
307
  @cache_index += 1
202
308
  end
203
309
  end
204
310
 
205
- private
206
- def object_key obj
207
- if obj.is_a?(String)
208
- obj
209
- else
210
- obj.object_id
311
+ class ObjectCache < Hash #:nodoc:
312
+ def initialize
313
+ @cache_index = 0
314
+ end
315
+
316
+ def [] obj
317
+ super(obj.object_id)
318
+ end
319
+
320
+ def add_obj obj
321
+ self[obj.object_id] = @cache_index
322
+ @cache_index += 1
211
323
  end
212
324
  end
213
325
  end
@@ -1,6 +1,6 @@
1
1
  module AMF
2
2
  # AMF version
3
- VERSION = '0.0.1'
3
+ VERSION = '0.0.3'
4
4
  VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
@@ -1,17 +1,68 @@
1
1
  require 'rack'
2
2
  require 'amf'
3
-
4
- require 'rack/amf/application'
5
- require 'rack/amf/service_manager'
6
- require 'rack/amf/request'
7
- require 'rack/amf/response'
3
+ require 'rack/amf/environment'
8
4
 
9
5
  module Rack::AMF
10
- APPLICATION_AMF = 'application/x-amf'.freeze
6
+ def self.new app, options={} #:nodoc:
7
+ # Set default mode
8
+ options[:mode] = :service_manager if !options[:mode]
9
+
10
+ # Which version of the middleware?
11
+ if options[:mode] == :pass_through
12
+ require 'rack/amf/middleware/pass_through'
13
+ Middleware::PassThrough.new(app, options)
14
+ elsif options[:mode] == :service_manager
15
+ require 'rack/amf/middleware/service_manager'
16
+ Middleware::ServiceManager.new(app, options)
17
+ else
18
+ raise "Invalide mode: #{options[:mode]}"
19
+ end
20
+ end
21
+ end
22
+ =begin
23
+ require 'rack'
24
+ require 'amf'
25
+ require 'rack/amf/environment'
26
+
27
+ # Rack::AMF middleware
28
+ module Rack::AMF; end;
29
+
30
+ # Bootstrap based on environment
31
+ if defined?(Rails)
32
+ # Load in needed files
33
+ require 'rack/amf/middleware/rails'
34
+
35
+ #--
36
+ # Then we'll modify new to return the middleware to use in rails
37
+ module Rack::AMF
38
+ def self.new app, options={} #:nodoc:
39
+ Middleware::Rails.new(app, options)
40
+ end
41
+ end
42
+
43
+ # Set default configs
44
+ Rack::AMF::Environment.url = '/amf'
45
+ Rack::AMF::Environment.mode = :rails
11
46
 
12
- Services = Rack::AMF::ServiceManager.new
47
+ # Install rails mods
48
+ Rack::AMF::Middleware::Rails.install_environment
49
+ else
50
+ module Rack::AMF
51
+ def self.new app, options={} #:nodoc:
52
+ # Set default mode
53
+ options[:mode] = :service_manager if !options[:mode]
13
54
 
14
- def self.new app, mode=:internal
15
- Rack::AMF::Application.new(app, mode)
55
+ # Which version of the middleware?
56
+ if options[:mode] == :pass_through
57
+ require 'rack/amf/middleware/pass_through'
58
+ Middleware::PassThrough.new(app, options)
59
+ elsif options[:mode] == :service_manager
60
+ require 'rack/amf/middleware/service_manager'
61
+ Middleware::ServiceManager.new(app, options)
62
+ else
63
+ raise "Invalide mode: #{options[:mode]}"
64
+ end
65
+ end
16
66
  end
17
- end
67
+ end
68
+ =end
@@ -0,0 +1,34 @@
1
+ module Rack::AMF
2
+ module Environment
3
+ class << self
4
+ attr_accessor :url, :mode, :debug, :services
5
+ debug = false # Set to off by default
6
+
7
+ # Used to register a service for use with the ServiceManager middleware.
8
+ # To register a service, simply pass in the root path for the service and
9
+ # an object that can receive service calls.
10
+ #
11
+ # Example:
12
+ #
13
+ # Rack::AMF::Environment.register_service 'SpecialService', SpecialService.new
14
+ # Rack::AMF::Environment.register_service 'org.rack-amf.AMFService', AMFService.new
15
+ def register_service path, service
16
+ @services ||= {}
17
+ @services[path] = service
18
+ end
19
+
20
+ # Populates the environment from the given options hash, which was passed
21
+ # in through rack
22
+ def populate options={} #:nodoc:
23
+ url = options[:url] if options.key?(:url)
24
+ debug = options[:debug] if options.key?(:debug)
25
+ mode = options[:mode] if options.key?(:mode)
26
+ end
27
+
28
+ def log data #:nodoc:
29
+ return if !debug
30
+ puts data
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ require 'rack/amf/request'
2
+ require 'rack/amf/response'
3
+
4
+ module Rack::AMF
5
+ # Provide some helper items that can be included in the various middleware
6
+ # being offered.
7
+ module Middleware #:nodoc:
8
+ APPLICATION_AMF = 'application/x-amf'.freeze
9
+
10
+ # Standard middleware call method. Calls "handle" with the environment after
11
+ # creating the request and response objects, and handles serializing the
12
+ # response after the middleware is done.
13
+ def call env #:nodoc:
14
+ return @app.call(env) unless should_handle?(env)
15
+
16
+ # Wrap request and response
17
+ env['rack-amf.request'] = Request.new(env)
18
+ env['rack-amf.response'] = Response.new(env['rack-amf.request'])
19
+
20
+ # Call handle on "inheriting" class
21
+ handle env
22
+
23
+ # Calculate length and return response
24
+ response = env['rack-amf.response'].to_s
25
+ [200, {"Content-Type" => APPLICATION_AMF, 'Content-Length' => response.length.to_s}, [response]]
26
+ end
27
+
28
+ # Check if we should handle it based on the environment
29
+ def should_handle? env #:nodoc:
30
+ return false unless env['CONTENT_TYPE'] == APPLICATION_AMF
31
+ return false if Rack::AMF::Environment.url && env['PATH_INFO'] != Rack::AMF::Environment.url
32
+ true
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,20 @@
1
+ require 'rack/amf/middleware'
2
+
3
+ module Rack::AMF::Middleware #:nodoc:
4
+ # Middleware which simply passes AMF requests through. Sets env['rack-amf.request']
5
+ # to the Rack::AMF::Request object and env['rack-amf.response'] to the
6
+ # Rack::AMF::Response object. Simply modify the response as necessary and it
7
+ # will be automatically serialized and sent.
8
+ class PassThrough
9
+ include Rack::AMF::Middleware
10
+
11
+ def initialize app, options={}
12
+ @app = app
13
+ Rack::AMF::Environment.populate options
14
+ end
15
+
16
+ def handle
17
+ @app.call env
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,39 @@
1
+ require 'rack/amf/middleware'
2
+
3
+ module Rack::AMF::Middleware #:nodoc:
4
+ class Rails
5
+ include Rack::AMF::Middleware
6
+
7
+ def initialize app, options={}
8
+ @app = app
9
+
10
+ options.delete(:url) # Too late to modify the URL
11
+ Rack::AMF::Environment.populate options
12
+ end
13
+
14
+ def handle env
15
+ @app.call env
16
+ end
17
+
18
+ def self.install_environment
19
+ return if @installed
20
+ @installed = true
21
+
22
+ # Load in files
23
+ extras_dir = File.dirname(__FILE__)+'/../rails'
24
+ Dir["#{extras_dir}/*.rb"].each {|f| require f}
25
+
26
+ # Install route
27
+ ActionController::Routing::RouteSet.class_eval do
28
+ next if self.instance_methods.include? 'draw_with_rackamf'
29
+ def draw_with_rackamf
30
+ draw_without_rackamf do |map|
31
+ map.rack_amf Rack::AMF::Environment.url, :controller => 'rack_amf', :action => 'handle'
32
+ yield map
33
+ end
34
+ end
35
+ alias_method_chain :draw, :rackamf
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,38 @@
1
+ require 'rack/amf/middleware'
2
+
3
+ module Rack::AMF::Middleware #:nodoc:
4
+ # Internal AMF handler, it uses the ServiceManager to handle request service
5
+ # mapping.
6
+ class ServiceManager
7
+ include Rack::AMF::Middleware
8
+
9
+ def initialize app, options={}
10
+ @app = app
11
+ Rack::AMF::Environment.populate options
12
+ end
13
+
14
+ def handle env
15
+ env['rack-amf.response'].each_method_call do |method, args|
16
+ handle_method method, args
17
+ end
18
+ end
19
+
20
+ private
21
+ def handle_method method, args
22
+ path = method.split('.')
23
+ method_name = path.pop
24
+ path = path.join('.')
25
+
26
+ s = Rack::AMF::Environment.services
27
+ if s[path]
28
+ if s[path].respond_to?(method_name)
29
+ s[path].send(method_name, *args)
30
+ else
31
+ raise "Service #{path} does not respond to #{method_name}"
32
+ end
33
+ else
34
+ raise "Service #{path} does not exist"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,23 @@
1
+ class RackAmfController < ApplicationController
2
+ def handle
3
+ if request.env['rack-amf.request']
4
+ RAILS_DEFAULT_LOGGER.info "[rack-amf] Handle request"
5
+ request.env['rack-amf.response'].each_method_call do |method, args|
6
+ handle_call method, args
7
+ end
8
+ else
9
+ render :text => '<html><body>Rack::AMF Gateway</body></html>'
10
+ end
11
+ end
12
+
13
+ private
14
+ def handle_call method, args
15
+ path = method.split('.')
16
+ raise "Invalid method '#{method}': Methods must look like this 'ServiceController.method'" if path.length != 2
17
+
18
+ action = path.pop
19
+ controller = path.pop
20
+
21
+
22
+ end
23
+ end
@@ -1,4 +1,5 @@
1
1
  module Rack::AMF
2
+ # Rack specific wrapper around AMF::Request
2
3
  class Request
3
4
  attr_reader :raw_request
4
5
 
@@ -7,6 +8,11 @@ module Rack::AMF
7
8
  @raw_request = ::AMF::Request.new.populate_from_stream(env['rack.input'].read)
8
9
  end
9
10
 
11
+ # Returns the request AMF version
12
+ def version
13
+ raw_request.amf_version
14
+ end
15
+
10
16
  # Returns all messages in the request
11
17
  def messages
12
18
  @raw_request.messages
@@ -1,53 +1,77 @@
1
1
  module Rack::AMF
2
+ # Rack specific wrapper around AMF::Response
2
3
  class Response
3
4
  attr_reader :raw_response
4
5
 
6
+ V = ::AMF::Values
7
+
5
8
  def initialize request
6
9
  @request = request
7
10
  @raw_response = ::AMF::Response.new
11
+ @raw_response.amf_version = @request.version == 3 ? 3 : 0 # Can't just copy version because FMS sends version as 1
8
12
  end
9
13
 
10
14
  # Builds response, iterating over each method call and using the return value
11
15
  # as the method call's return value
16
+ #--
17
+ # Iterate over all the sent messages. If they're somthing we can handle, like
18
+ # a command message, then simply add the response message ourselves. If it's
19
+ # a method call, then call the block with the method and args, catching errors
20
+ # for handling. Then create the appropriate response message using the return
21
+ # value of the block as the return value for the method call.
12
22
  def each_method_call &block
13
- @request.messages.each do |m|
14
- target_uri = m.response_uri
23
+ raise 'Response already constructed' if @constructed
15
24
 
16
- rd = m.data
17
- if rd.is_a?(::AMF::Values::CommandMessage)
18
- if rd.operation == ::AMF::Values::CommandMessage::CLIENT_PING_OPERATION
19
- data = ::AMF::Values::AcknowledgeMessage.new(rd)
25
+ @request.messages.each do |m|
26
+ # What's the request body?
27
+ case m.data
28
+ when V::CommandMessage
29
+ # Pings should be responded to with an AcknowledgeMessage built using the ping
30
+ # Everything else is unsupported
31
+ command_msg = m.data
32
+ if command_msg.operation == V::CommandMessage::CLIENT_PING_OPERATION
33
+ response_value = V::AcknowledgeMessage.new(command_msg)
20
34
  else
21
- data == ::AMF::Values::ErrorMessage.new(Exception.new("CommandMessage #{rd.operation} not implemented"), rd)
35
+ response_value = V::ErrorMessage.new(Exception.new("CommandMessage #{command_msg.operation} not implemented"), command_msg)
22
36
  end
23
- elsif rd.is_a?(::AMF::Values::RemotingMessage)
24
- am = ::AMF::Values::AcknowledgeMessage.new(rd)
25
- body = dispatch_call(rd.source+'.'+rd.operation, rd.body, rd, block)
26
- if body.is_a?(::AMF::Values::ErrorMessage)
27
- data = body
37
+ when V::RemotingMessage
38
+ # Using RemoteObject style message calls
39
+ remoting_msg = m.data
40
+ acknowledge_msg = V::AcknowledgeMessage.new(remoting_msg)
41
+ body = dispatch_call :method => remoting_msg.source+'.'+remoting_msg.operation, :args => remoting_msg.body, :source => remoting_msg, :block => block
42
+
43
+ # Response should be the bare ErrorMessage if there was an error
44
+ if body.is_a?(V::ErrorMessage)
45
+ response_value = body
28
46
  else
29
- am.body = body
30
- data = am
47
+ acknowledge_msg.body = body
48
+ response_value = acknowledge_msg
31
49
  end
32
50
  else
33
- data = dispatch_call(m.target_uri, rd, m, block)
51
+ # Standard response message
52
+ response_value = dispatch_call :method => m.target_uri, :args => m.data, :source => m, :block => block
34
53
  end
35
54
 
36
- target_uri += data.is_a?(::AMF::Values::ErrorMessage) ? '/onStatus' : '/onResult'
37
- @raw_response.messages << ::AMF::Message.new(target_uri, '', data)
55
+ target_uri = m.response_uri
56
+ target_uri += response_value.is_a?(V::ErrorMessage) ? '/onStatus' : '/onResult'
57
+ @raw_response.messages << ::AMF::Message.new(target_uri, '', response_value)
38
58
  end
59
+
60
+ @constructed = true
39
61
  end
40
62
 
63
+ # Return the serialized response as a string
41
64
  def to_s
42
65
  raw_response.serialize
43
66
  end
44
67
 
45
68
  private
46
- def dispatch_call method, args, source_message, handler
69
+ def dispatch_call p
47
70
  begin
48
- handler.call(method, args)
71
+ p[:block].call(p[:method], p[:args])
49
72
  rescue Exception => e
50
- ::AMF::Values::ErrorMessage.new(source_message, e)
73
+ # Create ErrorMessage object using the source message as the base
74
+ V::ErrorMessage.new(p[:source], e)
51
75
  end
52
76
  end
53
77
  end
@@ -2,7 +2,7 @@ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
2
 
3
3
  describe AMF::ClassMapping do
4
4
  before(:all) do
5
- class RubyClass
5
+ class ClassMappingTest
6
6
  attr_accessor :prop_a
7
7
  attr_accessor :prop_b
8
8
  attr_accessor :prop_c
@@ -12,25 +12,25 @@ describe AMF::ClassMapping do
12
12
  before :each do
13
13
  @mapper = AMF::ClassMapping.new
14
14
  @mapper.define do |m|
15
- m.map :as => 'ASClass', :ruby => 'RubyClass'
15
+ m.map :as => 'ASClass', :ruby => 'ClassMappingTest'
16
16
  end
17
17
  end
18
18
 
19
19
  it "should return AS class name for ruby objects" do
20
- @mapper.get_as_class_name(RubyClass.new).should == 'ASClass'
21
- @mapper.get_as_class_name('RubyClass').should == 'ASClass'
20
+ @mapper.get_as_class_name(ClassMappingTest.new).should == 'ASClass'
21
+ @mapper.get_as_class_name('ClassMappingTest').should == 'ASClass'
22
22
  end
23
23
 
24
24
  it "should allow config modification" do
25
25
  @mapper.define do |m|
26
- m.map :as => 'SecondClass', :ruby => 'RubyClass'
26
+ m.map :as => 'SecondClass', :ruby => 'ClassMappingTest'
27
27
  end
28
- @mapper.get_as_class_name(RubyClass.new).should == 'SecondClass'
28
+ @mapper.get_as_class_name(ClassMappingTest.new).should == 'SecondClass'
29
29
  end
30
30
 
31
31
  describe "ruby object generator" do
32
32
  it "should instantiate a ruby class" do
33
- @mapper.get_ruby_obj('ASClass').should be_a(RubyClass)
33
+ @mapper.get_ruby_obj('ASClass').should be_a(ClassMappingTest)
34
34
  end
35
35
 
36
36
  it "should properly instantiate namespaced classes" do
@@ -48,7 +48,7 @@ describe AMF::ClassMapping do
48
48
 
49
49
  describe "ruby object populator" do
50
50
  it "should populate a ruby class" do
51
- obj = @mapper.populate_ruby_obj RubyClass.new, {:prop_a => 'Data'}
51
+ obj = @mapper.populate_ruby_obj ClassMappingTest.new, {:prop_a => 'Data'}
52
52
  obj.prop_a.should == 'Data'
53
53
  end
54
54
 
@@ -84,7 +84,7 @@ describe AMF::ClassMapping do
84
84
  end
85
85
 
86
86
  it "should extract object properties" do
87
- obj = RubyClass.new
87
+ obj = ClassMappingTest.new
88
88
  obj.prop_a = 'Test A'
89
89
  obj.prop_b = 'Test B'
90
90
 
@@ -93,9 +93,9 @@ describe AMF::ClassMapping do
93
93
  end
94
94
 
95
95
  it "should extract inherited object properties" do
96
- class RubyClass2 < RubyClass
96
+ class ClassMappingTest2 < ClassMappingTest
97
97
  end
98
- obj = RubyClass2.new
98
+ obj = ClassMappingTest2.new
99
99
  obj.prop_a = 'Test A'
100
100
  obj.prop_b = 'Test B'
101
101
 
@@ -1,6 +1,10 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper.rb'
2
2
 
3
3
  describe "when deserializing" do
4
+ before :each do
5
+ AMF::ClassMapper.reset
6
+ end
7
+
4
8
  describe "AMF0" do
5
9
  it "should deserialize numbers" do
6
10
  input = object_fixture('amf0-number.bin')
@@ -58,12 +62,20 @@ describe "when deserializing" do
58
62
  output.should == ['a', 'b', 'c', 'd']
59
63
  end
60
64
 
65
+ it "should serialize strict arrays" do
66
+ input = object_fixture('amf0-strict-array.bin')
67
+ output = AMF.deserialize(input, 0)
68
+ output.should == ['a', 'b', 'c', 'd']
69
+ end
70
+
61
71
  it "should deserialize dates" do
62
72
  input = object_fixture('amf0-date.bin')
63
73
  output = AMF.deserialize(input, 0)
64
74
  output.should == Time.utc(2003, 2, 13, 5)
65
75
  end
66
76
 
77
+ it "should deserialize XML"
78
+
67
79
  it "should deserialize an unmapped object as a dynamic anonymous object" do
68
80
  input = object_fixture("amf0-typed-object.bin")
69
81
  output = AMF.deserialize(input, 0)
@@ -29,6 +29,7 @@ end
29
29
  describe "when handling responses" do
30
30
  it "should serialize a simple call" do
31
31
  resp = AMF::Response.new
32
+ resp.amf_version = 3
32
33
  resp.messages << AMF::Message.new('/1/onResult', '', 'hello')
33
34
 
34
35
  expected = request_fixture('simple-response.bin')
@@ -41,6 +42,7 @@ describe "when handling responses" do
41
42
  ak.messageId = "7B0ACE15-8D57-6AE5-B9D4-99C2D32C8246"
42
43
  ak.timestamp = 0
43
44
  resp = AMF::Response.new
45
+ resp.amf_version = 3
44
46
  resp.messages << AMF::Message.new('/1/onResult', '', ak)
45
47
 
46
48
  expected = request_fixture('acknowledge-response.bin')
@@ -3,6 +3,82 @@ require File.dirname(__FILE__) + '/../spec_helper.rb'
3
3
  require 'rexml/document'
4
4
 
5
5
  describe "when serializing" do
6
+ before :each do
7
+ AMF::ClassMapper.reset
8
+ end
9
+
10
+ describe "AMF0" do
11
+ it "should serialize nils" do
12
+ output = AMF.serialize(nil, 0)
13
+ output.should == object_fixture('amf0-null.bin')
14
+ end
15
+
16
+ it "should serialize booleans" do
17
+ output = AMF.serialize(true, 0)
18
+ output.should === object_fixture('amf0-boolean.bin')
19
+ end
20
+
21
+ it "should serialize numbers" do
22
+ output = AMF.serialize(3.5, 0)
23
+ output.should == object_fixture('amf0-number.bin')
24
+ end
25
+
26
+ it "should serialize strings" do
27
+ output = AMF.serialize("this is a テスト", 0)
28
+ output.should == object_fixture('amf0-string.bin')
29
+ end
30
+
31
+ it "should serialize arrays" do
32
+ output = AMF.serialize(['a', 'b', 'c', 'd'], 0)
33
+ output.should == object_fixture('amf0-strict-array.bin')
34
+ end
35
+
36
+ it "should serialize references" do
37
+ class OtherClass
38
+ attr_accessor :foo, :bar
39
+ end
40
+ obj = OtherClass.new
41
+ obj.foo = "baz"
42
+ obj.bar = 3.14
43
+
44
+ output = AMF.serialize({'0' => obj, '1' => obj}, 0)
45
+ output.should == object_fixture('amf0-ref-test.bin')
46
+ end
47
+
48
+ it "should serialize dates" do
49
+ output = AMF.serialize(Time.utc(2003, 2, 13, 5), 0)
50
+ output.should == object_fixture('amf0-date.bin')
51
+ end
52
+
53
+ it "should serialize hashes" do
54
+ output = AMF.serialize({:a => 'b', :c => 'd'}, 0)
55
+ output.should == object_fixture('amf0-hash.bin')
56
+ end
57
+
58
+ it "should serialize unmapped objects" do
59
+ class RubyClass
60
+ attr_accessor :foo, :baz
61
+ end
62
+ obj = RubyClass.new
63
+ obj.foo = "bar"
64
+
65
+ output = AMF.serialize(obj, 0)
66
+ output.should == object_fixture('amf0-untyped-object.bin')
67
+ end
68
+
69
+ it "should serialize mapped objects" do
70
+ class RubyClass
71
+ attr_accessor :foo, :baz
72
+ end
73
+ obj = RubyClass.new
74
+ obj.foo = "bar"
75
+ AMF::ClassMapper.define {|m| m.map :as => 'org.rackAMF.ASClass', :ruby => 'RubyClass'}
76
+
77
+ output = AMF.serialize(obj, 0)
78
+ output.should == object_fixture('amf0-typed-object.bin')
79
+ end
80
+ end
81
+
6
82
  describe "AMF3" do
7
83
  describe "simple messages" do
8
84
  it "should serialize a null" do
@@ -17,7 +17,11 @@ describe AMF::Values::ErrorMessage do
17
17
  @message = AMF::Values::ErrorMessage.new(nil, @e)
18
18
  end
19
19
 
20
- it "should serialize as a hash in AMF0"
20
+ it "should serialize as a hash in AMF0" do
21
+ response = AMF::Response.new
22
+ response.messages << AMF::Message.new('1/onStatus', '', @message)
23
+ response.serialize.should == request_fixture('amf0-error-response.bin')
24
+ end
21
25
 
22
26
  it "should extract exception properties correctly" do
23
27
  @message.faultCode.should == 'Exception'
@@ -21,4 +21,13 @@ end
21
21
  def create_rack_request(binary_path)
22
22
  env = {'rack.input' => StringIO.new(request_fixture(binary_path))}
23
23
  Rack::AMF::Request.new(env)
24
+ end
25
+
26
+ # Add reset support to ClassMapping
27
+ module AMF
28
+ class ClassMapping
29
+ def reset
30
+ @mappings = nil
31
+ end
32
+ end
24
33
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-amf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Hillerson
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-10-04 00:00:00 -04:00
13
+ date: 2009-10-19 00:00:00 -04:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
@@ -38,10 +38,14 @@ files:
38
38
  - lib/amf/values/typed_hash.rb
39
39
  - lib/amf/version.rb
40
40
  - lib/amf.rb
41
- - lib/rack/amf/application.rb
41
+ - lib/rack/amf/environment.rb
42
+ - lib/rack/amf/middleware/pass_through.rb
43
+ - lib/rack/amf/middleware/rails.rb
44
+ - lib/rack/amf/middleware/service_manager.rb
45
+ - lib/rack/amf/middleware.rb
46
+ - lib/rack/amf/rails/rack_amf_controller.rb
42
47
  - lib/rack/amf/request.rb
43
48
  - lib/rack/amf/response.rb
44
- - lib/rack/amf/service_manager.rb
45
49
  - lib/rack/amf.rb
46
50
  - spec/amf/class_mapping_set_spec.rb
47
51
  - spec/amf/class_mapping_spec.rb
@@ -1,32 +0,0 @@
1
- require 'rack/amf/request'
2
- require 'rack/amf/response'
3
-
4
- module Rack::AMF
5
- class Application
6
- def initialize app, mode
7
- @app = app
8
- @mode = mode
9
- end
10
-
11
- def call env
12
- if env['CONTENT_TYPE'] != APPLICATION_AMF
13
- return [200, {"Content-Type" => "text/plain"}, ["Hello From Rack::AMF"]]
14
- end
15
-
16
- # Wrap request and response
17
- env['amf.request'] = Request.new(env)
18
- env['amf.response'] = Response.new(env['amf.request'])
19
-
20
- # Handle request
21
- if @mode == :pass_through
22
- @app.call env
23
- elsif @mode == :internal
24
- # Have the service manager handle it
25
- Services.handle(env)
26
- end
27
-
28
- response = env['amf.response'].to_s
29
- [200, {"Content-Type" => APPLICATION_AMF, 'Content-Length' => response.length.to_s}, [response]]
30
- end
31
- end
32
- end
@@ -1,35 +0,0 @@
1
- module Rack::AMF
2
- class ServiceManager
3
- def initialize
4
- @services = {}
5
- end
6
-
7
- def register path, service
8
- @services ||= {}
9
- @services[path] = service
10
- end
11
-
12
- def handle env
13
- env['amf.response'].each_method_call do |method, args|
14
- handle_method method, args
15
- end
16
- end
17
-
18
- private
19
- def handle_method method, args
20
- path = method.split('.')
21
- method_name = path.pop
22
- path = path.join('.')
23
-
24
- if @services[path]
25
- if @services[path].respond_to?(method_name)
26
- @services[path].send(method_name, *args)
27
- else
28
- raise "Service #{path} does not respond to #{method_name}"
29
- end
30
- else
31
- raise "Service #{path} does not exist"
32
- end
33
- end
34
- end
35
- end