rack-amf 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +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
|