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 +55 -0
- data/Rakefile +54 -0
- data/lib/amf/class_mapping.rb +210 -0
- data/lib/amf/common.rb +28 -0
- data/lib/amf/constants.rb +46 -0
- data/lib/amf/pure/deserializer.rb +353 -0
- data/lib/amf/pure/io_helpers.rb +94 -0
- data/lib/amf/pure/remoting.rb +120 -0
- data/lib/amf/pure/serializer.rb +218 -0
- data/lib/amf/pure.rb +14 -0
- data/lib/amf/values/array_collection.rb +9 -0
- data/lib/amf/values/messages.rb +133 -0
- data/lib/amf/values/typed_hash.rb +13 -0
- data/lib/amf/version.rb +9 -0
- data/lib/amf.rb +17 -0
- data/lib/rack/amf/application.rb +32 -0
- data/lib/rack/amf/request.rb +15 -0
- data/lib/rack/amf/response.rb +54 -0
- data/lib/rack/amf/service_manager.rb +35 -0
- data/lib/rack/amf.rb +17 -0
- data/spec/amf/class_mapping_set_spec.rb +34 -0
- data/spec/amf/class_mapping_spec.rb +109 -0
- data/spec/amf/deserializer_spec.rb +301 -0
- data/spec/amf/remoting_spec.rb +37 -0
- data/spec/amf/serializer_spec.rb +254 -0
- data/spec/amf/values/array_collection_spec.rb +6 -0
- data/spec/amf/values/messages_spec.rb +27 -0
- data/spec/rack/request_spec.rb +5 -0
- data/spec/rack/response_spec.rb +46 -0
- data/spec/rack/service_manager_spec.rb +26 -0
- data/spec/spec_helper.rb +24 -0
- metadata +97 -0
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'amf/pure/io_helpers'
|
2
|
+
|
3
|
+
module AMF
|
4
|
+
module Pure
|
5
|
+
# AMF0 implementation of serializer
|
6
|
+
class Serializer
|
7
|
+
def initialize
|
8
|
+
@ref_cache = SerializerCache.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def version
|
12
|
+
0
|
13
|
+
end
|
14
|
+
|
15
|
+
def serialize obj, stream = ""
|
16
|
+
if @ref_cache[obj] != nil
|
17
|
+
# Write reference header
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# AMF3 implementation of serializer
|
23
|
+
class AMF3Serializer
|
24
|
+
attr_reader :string_cache
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@string_cache = SerializerCache.new
|
28
|
+
@object_cache = SerializerCache.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def version
|
32
|
+
3
|
33
|
+
end
|
34
|
+
|
35
|
+
def serialize obj, stream = ""
|
36
|
+
if obj.respond_to?(:to_amf)
|
37
|
+
stream << obj.to_amf(self)
|
38
|
+
elsif obj.is_a?(NilClass)
|
39
|
+
write_null stream
|
40
|
+
elsif obj.is_a?(TrueClass)
|
41
|
+
write_true stream
|
42
|
+
elsif obj.is_a?(FalseClass)
|
43
|
+
write_false stream
|
44
|
+
elsif obj.is_a?(Float)
|
45
|
+
write_float obj, stream
|
46
|
+
elsif obj.is_a?(Integer)
|
47
|
+
write_integer obj, stream
|
48
|
+
elsif obj.is_a?(Symbol) || obj.is_a?(String)
|
49
|
+
write_string obj.to_s, stream
|
50
|
+
elsif obj.is_a?(Time)
|
51
|
+
write_date obj, stream
|
52
|
+
elsif obj.is_a?(Array)
|
53
|
+
write_array obj, stream
|
54
|
+
elsif obj.is_a?(Hash) || obj.is_a?(Object)
|
55
|
+
write_object obj, stream
|
56
|
+
end
|
57
|
+
stream
|
58
|
+
end
|
59
|
+
|
60
|
+
def write_reference index, stream
|
61
|
+
header = index << 1 # shift value left to leave a low bit of 0
|
62
|
+
stream << pack_integer(header)
|
63
|
+
end
|
64
|
+
|
65
|
+
def write_null stream
|
66
|
+
stream << AMF3_NULL_MARKER
|
67
|
+
end
|
68
|
+
|
69
|
+
def write_true stream
|
70
|
+
stream << AMF3_TRUE_MARKER
|
71
|
+
end
|
72
|
+
|
73
|
+
def write_false stream
|
74
|
+
stream << AMF3_FALSE_MARKER
|
75
|
+
end
|
76
|
+
|
77
|
+
def write_integer int, stream
|
78
|
+
if int < MIN_INTEGER || int > MAX_INTEGER # Check valid range for 29 bits
|
79
|
+
write_float int.to_f, stream
|
80
|
+
else
|
81
|
+
stream << AMF3_INTEGER_MARKER
|
82
|
+
stream << pack_integer(int)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def write_float float, stream
|
87
|
+
stream << AMF3_DOUBLE_MARKER
|
88
|
+
stream << pack_double(float)
|
89
|
+
end
|
90
|
+
|
91
|
+
def write_string str, stream
|
92
|
+
stream << AMF3_STRING_MARKER
|
93
|
+
write_utf8_vr str, stream
|
94
|
+
end
|
95
|
+
|
96
|
+
def write_date date, stream
|
97
|
+
stream << AMF3_DATE_MARKER
|
98
|
+
if @object_cache[date] != nil
|
99
|
+
write_reference @object_cache[date], stream
|
100
|
+
else
|
101
|
+
# Cache date
|
102
|
+
@object_cache.add_obj date
|
103
|
+
|
104
|
+
# Build AMF string
|
105
|
+
date.utc unless date.utc?
|
106
|
+
seconds = (date.to_f * 1000).to_i
|
107
|
+
stream << pack_integer(AMF3_NULL_MARKER)
|
108
|
+
stream << pack_double(seconds)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def write_array array, stream
|
113
|
+
stream << AMF3_ARRAY_MARKER
|
114
|
+
if @object_cache[array] != nil
|
115
|
+
write_reference @object_cache[array], stream
|
116
|
+
else
|
117
|
+
# Cache array
|
118
|
+
@object_cache.add_obj array
|
119
|
+
|
120
|
+
# Build AMF string
|
121
|
+
header = array.length << 1 # make room for a low bit of 1
|
122
|
+
header = header | 1 # set the low bit to 1
|
123
|
+
stream << pack_integer(header)
|
124
|
+
stream << CLOSE_DYNAMIC_ARRAY
|
125
|
+
array.each do |elem|
|
126
|
+
serialize elem, stream
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def write_object obj, stream
|
132
|
+
stream << AMF3_OBJECT_MARKER
|
133
|
+
if @object_cache[obj] != nil
|
134
|
+
write_reference @object_cache[obj], stream
|
135
|
+
else
|
136
|
+
# Cache object
|
137
|
+
@object_cache.add_obj obj
|
138
|
+
|
139
|
+
class_name = ClassMapper.get_as_class_name obj
|
140
|
+
|
141
|
+
# Any object that has a class name isn't dynamic
|
142
|
+
unless class_name
|
143
|
+
stream << DYNAMIC_OBJECT
|
144
|
+
end
|
145
|
+
|
146
|
+
# Write class name/anonymous
|
147
|
+
if class_name
|
148
|
+
write_utf8_vr class_name, stream
|
149
|
+
else
|
150
|
+
stream << ANONYMOUS_OBJECT
|
151
|
+
end
|
152
|
+
|
153
|
+
# Write out properties
|
154
|
+
props = ClassMapper.props_for_serialization obj
|
155
|
+
props.sort.each do |key, val| # Sort props until Ruby 1.9 becomes common
|
156
|
+
write_utf8_vr key.to_s, stream
|
157
|
+
serialize val, stream
|
158
|
+
end
|
159
|
+
|
160
|
+
# Write close
|
161
|
+
stream << CLOSE_DYNAMIC_OBJECT
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
include AMF::Pure::WriteIOHelpers
|
167
|
+
|
168
|
+
def write_utf8_vr str, stream
|
169
|
+
if str == ''
|
170
|
+
stream << EMPTY_STRING
|
171
|
+
elsif @string_cache[str] != nil
|
172
|
+
write_reference @string_cache[str], stream
|
173
|
+
else
|
174
|
+
# Cache string
|
175
|
+
@string_cache.add_obj str
|
176
|
+
|
177
|
+
# Build AMF string
|
178
|
+
header = str.length << 1 # make room for a low bit of 1
|
179
|
+
header = header | 1 # set the low bit to 1
|
180
|
+
stream << pack_integer(header)
|
181
|
+
stream << str
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
class SerializerCache #:nodoc:
|
187
|
+
def initialize
|
188
|
+
@cache_index = 0
|
189
|
+
@store = {}
|
190
|
+
end
|
191
|
+
|
192
|
+
def [] obj
|
193
|
+
@store[object_key(obj)]
|
194
|
+
end
|
195
|
+
|
196
|
+
def []= obj, value
|
197
|
+
@store[object_key(obj)] = value
|
198
|
+
end
|
199
|
+
|
200
|
+
def add_obj obj
|
201
|
+
key = object_key obj
|
202
|
+
if @store[key].nil?
|
203
|
+
@store[key] = @cache_index
|
204
|
+
@cache_index += 1
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
def object_key obj
|
210
|
+
if obj.is_a?(String)
|
211
|
+
obj
|
212
|
+
else
|
213
|
+
obj.object_id
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
data/lib/amf/pure.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'amf/constants'
|
2
|
+
require 'amf/pure/deserializer'
|
3
|
+
require 'amf/pure/serializer'
|
4
|
+
require 'amf/pure/remoting'
|
5
|
+
|
6
|
+
module AMF
|
7
|
+
# This module holds all the modules/classes that implement AMF's
|
8
|
+
# functionality in pure ruby.
|
9
|
+
module Pure
|
10
|
+
$DEBUG and warn "Using pure library for AMF."
|
11
|
+
end
|
12
|
+
|
13
|
+
include AMF::Pure
|
14
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module AMF
|
2
|
+
module Values #:nodoc:
|
3
|
+
# Base class for all special AS3 response messages. Maps to
|
4
|
+
# <tt>flex.messaging.messages.AbstractMessage</tt>
|
5
|
+
class AbstractMessage
|
6
|
+
attr_accessor :clientId
|
7
|
+
attr_accessor :destination
|
8
|
+
attr_accessor :messageId
|
9
|
+
attr_accessor :timestamp
|
10
|
+
attr_accessor :timeToLive
|
11
|
+
attr_accessor :headers
|
12
|
+
attr_accessor :body
|
13
|
+
|
14
|
+
def rand_uuid
|
15
|
+
[8,4,4,4,12].map {|n| rand_hex_3(n)}.join('-').to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def rand_hex_3(l)
|
20
|
+
"%0#{l}x" % rand(1 << l*4)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Maps to <tt>flex.messaging.messages.RemotingMessage</tt>
|
25
|
+
class RemotingMessage < AbstractMessage
|
26
|
+
# The name of the service to be called including package name
|
27
|
+
attr_accessor :source
|
28
|
+
|
29
|
+
# The name of the method to be called
|
30
|
+
attr_accessor :operation
|
31
|
+
|
32
|
+
# The arguments to call the method with
|
33
|
+
attr_accessor :parameters
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@clientId = rand_uuid
|
37
|
+
@destination = nil
|
38
|
+
@messageId = rand_uuid
|
39
|
+
@timestamp = Time.new.to_i*100
|
40
|
+
@timeToLive = 0
|
41
|
+
@headers = {}
|
42
|
+
@body = nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Maps to <tt>flex.messaging.messages.AsyncMessage</tt>
|
47
|
+
class AsyncMessage < AbstractMessage
|
48
|
+
attr_accessor :correlationId
|
49
|
+
end
|
50
|
+
|
51
|
+
# Maps to <tt>flex.messaging.messages.CommandMessage</tt>
|
52
|
+
class CommandMessage < AsyncMessage
|
53
|
+
SUBSCRIBE_OPERATION = 0
|
54
|
+
UNSUSBSCRIBE_OPERATION = 1
|
55
|
+
POLL_OPERATION = 2
|
56
|
+
CLIENT_SYNC_OPERATION = 4
|
57
|
+
CLIENT_PING_OPERATION = 5
|
58
|
+
CLUSTER_REQUEST_OPERATION = 7
|
59
|
+
LOGIN_OPERATION = 8
|
60
|
+
LOGOUT_OPERATION = 9
|
61
|
+
SESSION_INVALIDATE_OPERATION = 10
|
62
|
+
MULTI_SUBSCRIBE_OPERATION = 11
|
63
|
+
DISCONNECT_OPERATION = 12
|
64
|
+
UNKNOWN_OPERATION = 10000
|
65
|
+
|
66
|
+
attr_accessor :operation
|
67
|
+
|
68
|
+
def initialize
|
69
|
+
@operation = UNKNOWN_OPERATION
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Maps to <tt>flex.messaging.messages.AcknowledgeMessage</tt>
|
74
|
+
class AcknowledgeMessage < AsyncMessage
|
75
|
+
def initialize message=nil
|
76
|
+
@clientId = rand_uuid
|
77
|
+
@destination = nil
|
78
|
+
@messageId = rand_uuid
|
79
|
+
@timestamp = Time.new.to_i*100
|
80
|
+
@timeToLive = 0
|
81
|
+
@headers = {}
|
82
|
+
@body = nil
|
83
|
+
|
84
|
+
if message.is_a?(AbstractMessage)
|
85
|
+
@correlationId = message.messageId
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Maps to <tt>flex.messaging.messages.ErrorMessage</tt> in AMF3 mode
|
91
|
+
class ErrorMessage < AcknowledgeMessage
|
92
|
+
# Extended data that will facilitate custom error processing on the client
|
93
|
+
attr_accessor :extendedData
|
94
|
+
|
95
|
+
# The fault code for the error, which defaults to the class name of the
|
96
|
+
# causing exception
|
97
|
+
attr_accessor :faultCode
|
98
|
+
|
99
|
+
# Detailed description of what caused the error
|
100
|
+
attr_accessor :faultDetail
|
101
|
+
|
102
|
+
# A simple description of the error
|
103
|
+
attr_accessor :faultString
|
104
|
+
|
105
|
+
# Optional "root cause" of the error
|
106
|
+
attr_accessor :rootCause
|
107
|
+
|
108
|
+
def initialize message, exception
|
109
|
+
super message
|
110
|
+
|
111
|
+
@e = exception
|
112
|
+
@faultCode = @e.class.name
|
113
|
+
@faultDetail = @e.backtrace.join("\n")
|
114
|
+
@faultString = @e.message
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_amf serializer
|
118
|
+
stream = ""
|
119
|
+
if serializer.version == 0
|
120
|
+
data = {
|
121
|
+
:faultCode => @faultCode,
|
122
|
+
:faultDetail => @faultDetail,
|
123
|
+
:faultString => @faultString
|
124
|
+
}
|
125
|
+
serializer.write_hash(data, stream)
|
126
|
+
else
|
127
|
+
serializer.write_object(self, stream)
|
128
|
+
end
|
129
|
+
stream
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module AMF
|
2
|
+
module Values #:nodoc:
|
3
|
+
# Hash-like object that can store a type string. Used to preserve type information
|
4
|
+
# for unmapped objects after deserialization.
|
5
|
+
class TypedHash < Hash
|
6
|
+
attr_reader :type
|
7
|
+
|
8
|
+
def initialize type
|
9
|
+
@type = type
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/amf/version.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
module AMF
|
2
|
+
# AMF version
|
3
|
+
VERSION = '0.0.1'
|
4
|
+
VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
|
5
|
+
VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
|
6
|
+
VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
|
7
|
+
VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
|
8
|
+
VARIANT_BINARY = false
|
9
|
+
end
|
data/lib/amf.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
$:.unshift "#{File.expand_path(File.dirname(__FILE__))}/amf/"
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'amf/version'
|
6
|
+
require 'amf/common'
|
7
|
+
|
8
|
+
module AMF
|
9
|
+
begin
|
10
|
+
raise LoadError, 'C extensions not implemented'
|
11
|
+
rescue LoadError
|
12
|
+
require 'amf/pure'
|
13
|
+
end
|
14
|
+
require 'amf/class_mapping'
|
15
|
+
|
16
|
+
ClassMapper = AMF::ClassMapping.new
|
17
|
+
end
|
@@ -0,0 +1,32 @@
|
|
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
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Rack::AMF
|
2
|
+
class Request
|
3
|
+
attr_reader :raw_request
|
4
|
+
|
5
|
+
def initialize env
|
6
|
+
env['rack.input'].rewind
|
7
|
+
@raw_request = ::AMF::Request.new.populate_from_stream(env['rack.input'].read)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns all messages in the request
|
11
|
+
def messages
|
12
|
+
@raw_request.messages
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Rack::AMF
|
2
|
+
class Response
|
3
|
+
attr_reader :raw_response
|
4
|
+
|
5
|
+
def initialize request
|
6
|
+
@request = request
|
7
|
+
@raw_response = ::AMF::Response.new
|
8
|
+
end
|
9
|
+
|
10
|
+
# Builds response, iterating over each method call and using the return value
|
11
|
+
# as the method call's return value
|
12
|
+
def each_method_call &block
|
13
|
+
@request.messages.each do |m|
|
14
|
+
target_uri = m.response_uri
|
15
|
+
|
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)
|
20
|
+
else
|
21
|
+
data == ::AMF::Values::ErrorMessage.new(Exception.new("CommandMessage #{rd.operation} not implemented"), rd)
|
22
|
+
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
|
28
|
+
else
|
29
|
+
am.body = body
|
30
|
+
data = am
|
31
|
+
end
|
32
|
+
else
|
33
|
+
data = dispatch_call(m.target_uri, rd, m, block)
|
34
|
+
end
|
35
|
+
|
36
|
+
target_uri += data.is_a?(::AMF::Values::ErrorMessage) ? '/onStatus' : '/onResult'
|
37
|
+
@raw_response.messages << ::AMF::Message.new(target_uri, '', data)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
raw_response.serialize
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def dispatch_call method, args, source_message, handler
|
47
|
+
begin
|
48
|
+
handler.call(method, args)
|
49
|
+
rescue Exception => e
|
50
|
+
::AMF::Values::ErrorMessage.new(source_message, e)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,35 @@
|
|
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
|
data/lib/rack/amf.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rack'
|
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'
|
8
|
+
|
9
|
+
module Rack::AMF
|
10
|
+
APPLICATION_AMF = 'application/x-amf'.freeze
|
11
|
+
|
12
|
+
Services = Rack::AMF::ServiceManager.new
|
13
|
+
|
14
|
+
def self.new app, mode=:internal
|
15
|
+
Rack::AMF::Application.new(app, mode)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe AMF::ClassMapping::MappingSet do
|
4
|
+
before :each do
|
5
|
+
@config = AMF::ClassMapping::MappingSet.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should retrieve AS mapping for ruby class" do
|
9
|
+
@config.map :as => 'ASTest', :ruby => 'RubyTest'
|
10
|
+
@config.get_as_class_name('RubyTest').should == 'ASTest'
|
11
|
+
@config.get_as_class_name('BadClass').should be_nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should retrive ruby class name mapping for AS class" do
|
15
|
+
@config.map :as => 'ASTest', :ruby => 'RubyTest'
|
16
|
+
@config.get_ruby_class_name('ASTest').should == 'RubyTest'
|
17
|
+
@config.get_ruby_class_name('BadClass').should be_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should map special classes by default" do
|
21
|
+
SPECIAL_CLASSES = [
|
22
|
+
'flex.messaging.messages.AcknowledgeMessage',
|
23
|
+
'flex.messaging.messages.ErrorMessage',
|
24
|
+
'flex.messaging.messages.CommandMessage',
|
25
|
+
'flex.messaging.messages.ErrorMessage',
|
26
|
+
'flex.messaging.messages.RemotingMessage',
|
27
|
+
'flex.messaging.io.ArrayCollection'
|
28
|
+
]
|
29
|
+
|
30
|
+
SPECIAL_CLASSES.each do |as_class|
|
31
|
+
@config.get_ruby_class_name(as_class).should_not be_nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|