rack-amf 0.0.2 → 0.0.3

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