revent 0.1 → 0.2
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/lib/revent/CallEvent.as +25 -0
- data/lib/revent/Client.as +95 -0
- data/lib/revent/amf3/LICENSE +22 -0
- data/lib/revent/amf3/README +6 -0
- data/lib/revent/amf3/amf3.rb +11 -0
- data/lib/revent/amf3/app/configuration.rb +79 -0
- data/lib/revent/amf3/app/fault_object.rb +13 -0
- data/lib/revent/amf3/exception/rubyamf_exception.rb +95 -0
- data/lib/revent/amf3/io/amf_deserializer.rb +315 -0
- data/lib/revent/amf3/io/amf_serializer.rb +184 -0
- data/lib/revent/amf3/io/read_write.rb +308 -0
- data/lib/revent/amf3/util/string.rb +33 -0
- data/lib/revent/amf3/util/vo_helper.rb +121 -0
- data/lib/revent/as_r.rb +178 -0
- data/lib/revent/r_r.rb +176 -0
- data/test/as_r/Document.as +63 -0
- data/test/as_r/client.fla +0 -0
- data/test/as_r/client.swf +0 -0
- data/test/as_r/server.rb +48 -0
- data/{sample → test/r_r}/client.rb +0 -0
- data/{sample → test/r_r}/server.rb +0 -0
- metadata +31 -6
- data/lib/revent.rb +0 -166
@@ -0,0 +1,33 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
def to_snake! # no one should change these unless they can benchmark and prove their way is faster. =)
|
4
|
+
@cached_snake_strings ||= {}
|
5
|
+
@cached_snake_strings[self] ||= (
|
6
|
+
while x = index(/([a-z\d])([A-Z])/) # unfortunately have to use regex for this one
|
7
|
+
y=x+1
|
8
|
+
self[x..y] = self[x..x]+"_"+self[y..y].downcase
|
9
|
+
end
|
10
|
+
self
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Aryk: This might be a better way of writing it. I made a lightweight version to use in my modifications. Feel free to adapt this to the rest.
|
15
|
+
def to_camel! # no one should change these unless they can benchmark and prove their way is faster. =)
|
16
|
+
@cached_camel_strings ||= {}
|
17
|
+
@cached_camel_strings[self] ||= (
|
18
|
+
# new_string = self.dup # need to do this since sometimes the string is frozen
|
19
|
+
while x = index("_")
|
20
|
+
y=x+1
|
21
|
+
self[x..y] = self[y..y].capitalize # in my tests, it was faster than upcase
|
22
|
+
end
|
23
|
+
self
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_title
|
28
|
+
title = self.dup
|
29
|
+
title[0..0] = title[0..0].upcase
|
30
|
+
title
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module RubyAMF
|
2
|
+
module VoHelper
|
3
|
+
class VoHash < Hash
|
4
|
+
attr_accessor :_explicitType
|
5
|
+
end
|
6
|
+
|
7
|
+
require "#{File.dirname(__FILE__)}/../app/configuration" # cant put this at the top because VoHash has to be instantiated for app/configuration to work
|
8
|
+
class VoUtil
|
9
|
+
|
10
|
+
include RubyAMF::Configuration
|
11
|
+
include RubyAMF::Exceptions
|
12
|
+
|
13
|
+
#moved logic here so AMF3 and AMF0 can use it
|
14
|
+
def self.get_vo_for_incoming(obj,action_class_name)
|
15
|
+
if (mapping = ClassMappings.get_vo_mapping_for_actionscript_class(action_class_name)) || ## if there is a map use, that class
|
16
|
+
(ruby_obj = ClassMappings.assume_types&&(action_class_name.constantize.new rescue false)) # if no map, and try to get the assumed type
|
17
|
+
if mapping #if there's a map, then we default to it's specification.
|
18
|
+
obj.reject!{|k,v| mapping[:ignore_fields][k]}
|
19
|
+
ruby_obj = mapping[:ruby].constantize.new
|
20
|
+
end
|
21
|
+
if ruby_obj.is_a?(ActiveRecord::Base) # put all the attributes fields into the attribute instance variable
|
22
|
+
attributes = {} # extract attributes
|
23
|
+
if mapping
|
24
|
+
obj.each_key do |field|
|
25
|
+
if ClassMappings.attribute_names[mapping[:ruby]][field]
|
26
|
+
attributes[field] = obj.delete(field)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
else # for assumed types when there is no mapping
|
30
|
+
attribs = (ruby_obj.attribute_names + ["id"]).inject({}){|hash, attr| hash[attr]=true ; hash}
|
31
|
+
obj.each_key do |field|
|
32
|
+
if attribs[field]
|
33
|
+
attributes[field] = obj.delete(field)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
attributes.delete("id") if attributes["id"]==0 # id attribute cannot be zero
|
38
|
+
ruby_obj.instance_variable_set("@attributes", attributes) # bypasses any overwriting of the attributes=(value) method (also allows 'id' to be set)
|
39
|
+
ruby_obj.instance_variable_set("@new_record", false) if attributes["id"] # the record already exists in the database
|
40
|
+
obj.each_key do |field|
|
41
|
+
if reflection = ruby_obj.class.reflections[field.to_sym] # is it an association
|
42
|
+
value = obj.delete(field) # get rid of the field so it doesnt get added in the next loop
|
43
|
+
case reflection.macro
|
44
|
+
when :has_one
|
45
|
+
ruby_obj.send("set_#{field}_target", value)
|
46
|
+
when :belongs_to
|
47
|
+
ruby_obj.send("#{field}=", value)
|
48
|
+
when :has_many, :has_many_and_belongs_to
|
49
|
+
ruby_obj.send("#{field}").target = value
|
50
|
+
when :composed_of
|
51
|
+
ruby_obj.send("#{field}=", value) # this sets the attributes to the corresponding values
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
obj.each do |field, value| # whatever is left, set them as instance variables in the object
|
57
|
+
ruby_obj.instance_variable_set("@#{field}", value)
|
58
|
+
end
|
59
|
+
ruby_obj
|
60
|
+
else # then we are still left with a normal hash, lets see if we need to change the type of the keys
|
61
|
+
case ClassMappings.hash_key_access
|
62
|
+
when :symbol : obj.symbolize_keys!
|
63
|
+
when :string : obj # by default the keys are a string type, so just return the obj
|
64
|
+
when :indifferent : HashWithIndifferentAccess.new(obj)
|
65
|
+
# else # TODO: maybe add a raise FlexError since they somehow put the wrong value for this feature
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Aryk: I tried to make this more efficent and clean.
|
71
|
+
def self.get_vo_hash_for_outgoing(obj)
|
72
|
+
new_object = VoHash.new #use VoHash because one day, we might do away with the class Object patching
|
73
|
+
instance_vars = obj.instance_variables
|
74
|
+
if map = ClassMappings.get_vo_mapping_for_ruby_class(obj.class.to_s)
|
75
|
+
if map[:type]=="active_record"
|
76
|
+
attributes_hash = obj.attributes
|
77
|
+
(map[:attributes]||attributes_hash.keys).each do |attr| # need to use dup because sometimes the attr is frozen from the AR attributes hash
|
78
|
+
attr_name = attr
|
79
|
+
attr_name = attr_name.dup.to_camel! if ClassMappings.translate_case # need to do it this way because the string might be frozen if it came from the attributes_hash.keys
|
80
|
+
new_object[attr_name] = attributes_hash[attr]
|
81
|
+
end
|
82
|
+
instance_vars = [] # reset the instance_vars for the associations, this way no unwanted instance variables (ie @new_record, @read_only) can get through
|
83
|
+
# Note: if you did not specify associations, it will not show up even if you eager loaded them.
|
84
|
+
if map[:associations] # Aryk: if they opted for assocations, make sure that they are loaded in. This is great for composed_of, since it cannot be included on a find
|
85
|
+
map[:associations].each do |assoc|
|
86
|
+
instance_vars << ("@"+assoc) if obj.send(assoc) # this will make sure they are instantiated and only load it if they have a value.
|
87
|
+
end
|
88
|
+
elsif ClassMappings.check_for_associations
|
89
|
+
instance_vars = obj.instance_variables.reject{|assoc| ["@attributes","@new_record","@read_only"].include?(assoc)}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
new_object._explicitType = map[:actionscript] # Aryk: This only works on the Hash because rubyAMF extended class Object to have this accessor, probably not the best idea, but its already there.
|
93
|
+
# Tony: There's some duplication in here. Had trouble consolidating the logic though. Ruby skills failed.
|
94
|
+
elsif ClassMappings.assume_types
|
95
|
+
new_object._explicitType = obj.class.to_s
|
96
|
+
if obj.is_a?(ActiveRecord::Base)
|
97
|
+
obj.attributes.keys.each do |key|
|
98
|
+
attr_name = key
|
99
|
+
attr_name = attr_name.dup.to_camel! if ClassMappings.translate_case # need to do it this way because the string might be frozen if it came from the attributes_hash.keys
|
100
|
+
new_object[attr_name] = obj.attributes[key]
|
101
|
+
end
|
102
|
+
instance_vars = []
|
103
|
+
if ClassMappings.check_for_associations
|
104
|
+
instance_vars = obj.instance_variables.reject{|assoc| ["@attributes","@new_record","@read_only"].include?(assoc)}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
instance_vars.each do |var| # this also picks up the eager loaded associations, because association called "has_many_assoc" has an instance variable called "@has_many_assoc"
|
109
|
+
attr_name = var[1..-1]
|
110
|
+
attr_name.to_camel! if ClassMappings.translate_case
|
111
|
+
new_object[attr_name] = obj.instance_variable_get(var)
|
112
|
+
end
|
113
|
+
new_object
|
114
|
+
rescue Exception => e
|
115
|
+
puts e.message
|
116
|
+
puts e.backtrace
|
117
|
+
raise RUBYAMFException.new(RUBYAMFException.VO_ERROR, e.message)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/lib/revent/as_r.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
require "#{File.dirname(__FILE__)}/amf3/amf3"
|
4
|
+
|
5
|
+
# AS clients are not reliable. For security, we close the connection immediately
|
6
|
+
# if there is any error.
|
7
|
+
module Revent
|
8
|
+
module ASRCon
|
9
|
+
# Called by Flash player
|
10
|
+
CMD_POLICY = '<policy-file-request/>' + "\0"
|
11
|
+
|
12
|
+
TYPE_CALL = 0
|
13
|
+
TYPE_RESULT = 1
|
14
|
+
TYPE_ERROR = 2
|
15
|
+
|
16
|
+
attr_writer :me
|
17
|
+
attr_accessor :property # Something that you want to associate with this connection
|
18
|
+
attr_writer :cons
|
19
|
+
|
20
|
+
def post_init
|
21
|
+
@connected = true
|
22
|
+
@data = ''
|
23
|
+
@dec = RubyAMF::IO::AMFDeserializer.new
|
24
|
+
@enc = RubyAMF::IO::AMFSerializer.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def receive_data(data)
|
28
|
+
@data << data
|
29
|
+
if @data == CMD_POLICY
|
30
|
+
send_data(@me.policy + "\0")
|
31
|
+
close_connection_after_writing
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
if @data.size > @me.max_cmd_length
|
36
|
+
close_connection
|
37
|
+
return
|
38
|
+
end
|
39
|
+
|
40
|
+
while true
|
41
|
+
@dec.reset
|
42
|
+
@dec.stream = @data
|
43
|
+
o = @dec.read_amf3
|
44
|
+
break if o.nil?
|
45
|
+
|
46
|
+
process(o)
|
47
|
+
|
48
|
+
last_size = @data.size
|
49
|
+
@data.slice!(0, @dec.stream_position)
|
50
|
+
break if last_size == @dec.stream_position
|
51
|
+
end
|
52
|
+
rescue
|
53
|
+
close_connection
|
54
|
+
end
|
55
|
+
|
56
|
+
def process(o)
|
57
|
+
type = o[0]
|
58
|
+
cmd = o[1]
|
59
|
+
value = o[2]
|
60
|
+
|
61
|
+
case type
|
62
|
+
when TYPE_CALL
|
63
|
+
@me.on_call(self, cmd, value)
|
64
|
+
when TYPE_RESULT
|
65
|
+
@me.on_result(self, cmd, value)
|
66
|
+
when TYPE_ERROR
|
67
|
+
@me.on_error(self, cmd, value)
|
68
|
+
end
|
69
|
+
rescue => e
|
70
|
+
@me.logger.error(e)
|
71
|
+
close_connection
|
72
|
+
end
|
73
|
+
|
74
|
+
def unbind
|
75
|
+
@connected = false
|
76
|
+
if @cons.nil?
|
77
|
+
@me.on_close
|
78
|
+
else
|
79
|
+
@me.on_close(self)
|
80
|
+
@cons.delete(self)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# ----------------------------------------------------------------------------
|
85
|
+
|
86
|
+
def send_amf3_data(data)
|
87
|
+
@enc.reset
|
88
|
+
@enc.write_amf3(data)
|
89
|
+
send_data(@enc.stream)
|
90
|
+
@enc.reset
|
91
|
+
end
|
92
|
+
|
93
|
+
def connected?
|
94
|
+
@connected
|
95
|
+
end
|
96
|
+
|
97
|
+
# IP of the container.
|
98
|
+
def remote_ip
|
99
|
+
peername = get_peername
|
100
|
+
return @last_ip if peername.nil?
|
101
|
+
|
102
|
+
a = get_peername[2,6].unpack("nC4")
|
103
|
+
a.delete_at(0)
|
104
|
+
@last_ip = a.join('.')
|
105
|
+
end
|
106
|
+
|
107
|
+
def call(cmd, value, close_connection_after_writing = false)
|
108
|
+
send_amf3_data([TYPE_CALL, cmd, value])
|
109
|
+
self.close_connection_after_writing if close_connection_after_writing
|
110
|
+
end
|
111
|
+
|
112
|
+
def result(cmd, value, close_connection_after_writing = false)
|
113
|
+
send_amf3_data([TYPE_RESULT, cmd, value])
|
114
|
+
self.close_connection_after_writing if close_connection_after_writing
|
115
|
+
rescue
|
116
|
+
end
|
117
|
+
|
118
|
+
def error(cmd, value, close_connection_after_writing = false)
|
119
|
+
send_amf3_data([TYPE_ERROR, cmd, value])
|
120
|
+
self.close_connection_after_writing if close_connection_after_writing
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# To make your class a server, just include Revent::RRServer in it.
|
125
|
+
module ASRServer
|
126
|
+
# For security check, normally command cannot be too long
|
127
|
+
DEFAULT_MAX_CMD_LENGTH = 1024
|
128
|
+
|
129
|
+
attr_accessor :logger, :max_cmd_length, :policy
|
130
|
+
|
131
|
+
# Set logger, max_cmd_length, policy if you don't want the default values
|
132
|
+
# before calling this method.
|
133
|
+
def start_server(host, port)
|
134
|
+
@logger = Logger.new(STDOUT) if @logger.nil?
|
135
|
+
@max_cmd_length = DEFAULT_MAX_CMD_LENGTH if @max_cmd_length.nil?
|
136
|
+
if @policy.nil?
|
137
|
+
@policy = <<EOF
|
138
|
+
<cross-domain-policy>
|
139
|
+
<allow-access-from domain="*" to-ports="#{port}" />
|
140
|
+
</cross-domain-policy>
|
141
|
+
EOF
|
142
|
+
end
|
143
|
+
|
144
|
+
@revent_cons = []
|
145
|
+
EventMachine::start_server(host, port, ASRCon) do |con|
|
146
|
+
@revent_cons << con
|
147
|
+
con.me = self
|
148
|
+
con.cons = @revent_cons
|
149
|
+
on_connect(con)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Utilities. You can call remote_ip, call, close directly on each "client", as
|
154
|
+
# if it is an instance of Revent::RRClient.
|
155
|
+
# ----------------------------------------------------------------------------
|
156
|
+
|
157
|
+
def clients
|
158
|
+
@revent_cons
|
159
|
+
end
|
160
|
+
|
161
|
+
# ----------------------------------------------------------------------------
|
162
|
+
|
163
|
+
def on_connect(client)
|
164
|
+
end
|
165
|
+
|
166
|
+
def on_close(client)
|
167
|
+
end
|
168
|
+
|
169
|
+
def on_call(client, cmd, value)
|
170
|
+
end
|
171
|
+
|
172
|
+
def on_result(client, cmd, value)
|
173
|
+
end
|
174
|
+
|
175
|
+
def on_error(client, cmd, value)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
data/lib/revent/r_r.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module Revent
|
6
|
+
# Included by both Revent::RRServer and Revent::RRClient.
|
7
|
+
module RRCon
|
8
|
+
TYPE_CALL = 0
|
9
|
+
TYPE_RESULT = 1
|
10
|
+
TYPE_ERROR = 2
|
11
|
+
|
12
|
+
attr_writer :me
|
13
|
+
attr_accessor :property # Something that you want to associate with this connection
|
14
|
+
attr_writer :cons # Only used for Revent::RRServer
|
15
|
+
|
16
|
+
def post_init
|
17
|
+
@connected = true
|
18
|
+
@data = ''
|
19
|
+
end
|
20
|
+
|
21
|
+
def receive_data(data)
|
22
|
+
@data << data
|
23
|
+
while true
|
24
|
+
io = StringIO.new(@data)
|
25
|
+
o = Marshal.load(io)
|
26
|
+
@data.slice!(0, io.pos)
|
27
|
+
process(o)
|
28
|
+
end
|
29
|
+
rescue
|
30
|
+
end
|
31
|
+
|
32
|
+
def process(o)
|
33
|
+
type = o[0]
|
34
|
+
cmd = o[1]
|
35
|
+
value = o[2]
|
36
|
+
|
37
|
+
case type
|
38
|
+
when TYPE_CALL
|
39
|
+
value = @cons.nil? ? @me.on_call(cmd, value) : @me.on_call(self, cmd, value)
|
40
|
+
o = [TYPE_RESULT, cmd, value]
|
41
|
+
send_data(Marshal.dump(o))
|
42
|
+
when TYPE_RESULT
|
43
|
+
@cons.nil? ? @me.on_result(cmd, value) : @me.on_result(self, cmd, value)
|
44
|
+
when TYPE_ERROR
|
45
|
+
@cons.nil? ? @me.on_error(cmd, value) : @me.on_error(self, cmd, value)
|
46
|
+
end
|
47
|
+
rescue => e
|
48
|
+
o = [TYPE_ERROR, cmd, e]
|
49
|
+
send_data(Marshal.dump(o))
|
50
|
+
end
|
51
|
+
|
52
|
+
def unbind
|
53
|
+
@connected = false
|
54
|
+
if @cons.nil?
|
55
|
+
@me.on_close
|
56
|
+
else
|
57
|
+
@me.on_close(self)
|
58
|
+
@cons.delete(self)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# ----------------------------------------------------------------------------
|
63
|
+
|
64
|
+
def connected?
|
65
|
+
@connected
|
66
|
+
end
|
67
|
+
|
68
|
+
# IP of the container.
|
69
|
+
def remote_ip
|
70
|
+
peername = get_peername
|
71
|
+
return @last_ip if peername.nil?
|
72
|
+
|
73
|
+
a = get_peername[2,6].unpack("nC4")
|
74
|
+
a.delete_at(0)
|
75
|
+
@last_ip = a.join('.')
|
76
|
+
end
|
77
|
+
|
78
|
+
def call(cmd, value)
|
79
|
+
o = [TYPE_CALL, cmd, value]
|
80
|
+
send_data(Marshal.dump(o))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
# To make your class a server, just include Revent::RRServer in it.
|
86
|
+
module RRServer
|
87
|
+
def start_server(host, port)
|
88
|
+
@revent_cons = []
|
89
|
+
EventMachine::start_server(host, port, RRCon) do |con|
|
90
|
+
@revent_cons << con
|
91
|
+
con.me = self
|
92
|
+
con.cons = @revent_cons
|
93
|
+
on_connect(con)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Utilities. You can call remote_ip, call, close directly on each "client", as
|
98
|
+
# if it is an instance of Revent::RRClient.
|
99
|
+
# ----------------------------------------------------------------------------
|
100
|
+
|
101
|
+
def clients
|
102
|
+
@revent_cons
|
103
|
+
end
|
104
|
+
|
105
|
+
# ----------------------------------------------------------------------------
|
106
|
+
|
107
|
+
def on_connect(client)
|
108
|
+
end
|
109
|
+
|
110
|
+
def on_close(client)
|
111
|
+
end
|
112
|
+
|
113
|
+
def on_call(client, cmd, value)
|
114
|
+
end
|
115
|
+
|
116
|
+
def on_result(client, cmd, value)
|
117
|
+
end
|
118
|
+
|
119
|
+
def on_error(client, cmd, value)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# To make your class a client, just include Revent::RRClient in it.
|
124
|
+
module RRClient
|
125
|
+
def connect(host, port)
|
126
|
+
EventMachine::connect(host, port, RRCon) do |con|
|
127
|
+
@revent_con = con
|
128
|
+
con.me = self
|
129
|
+
on_connect
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Utilities ------------------------------------------------------------------
|
134
|
+
|
135
|
+
def property
|
136
|
+
@revent_con.property
|
137
|
+
end
|
138
|
+
|
139
|
+
def property=(value)
|
140
|
+
@revent_con.property = value
|
141
|
+
end
|
142
|
+
|
143
|
+
def remote_ip
|
144
|
+
@revent_con.remote_ip
|
145
|
+
end
|
146
|
+
|
147
|
+
def call(cmd, value)
|
148
|
+
@revent_con.call(cmd, value)
|
149
|
+
end
|
150
|
+
|
151
|
+
def close_connection
|
152
|
+
@revent_con.close_connection
|
153
|
+
end
|
154
|
+
|
155
|
+
def close_connection_after_writing
|
156
|
+
@revent_con.close_connection_after_writing
|
157
|
+
end
|
158
|
+
|
159
|
+
# ----------------------------------------------------------------------------
|
160
|
+
|
161
|
+
def on_connect
|
162
|
+
end
|
163
|
+
|
164
|
+
def on_close
|
165
|
+
end
|
166
|
+
|
167
|
+
def on_call(cmd, value)
|
168
|
+
end
|
169
|
+
|
170
|
+
def on_result(cmd, value)
|
171
|
+
end
|
172
|
+
|
173
|
+
def on_error(cmd, value)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|