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