packet 0.1.0

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.
@@ -0,0 +1,35 @@
1
+ EVAL_APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__) + "/.."))
2
+ ["bin","worker","lib"].each { |x| $LOAD_PATH.unshift(EVAL_APP_ROOT + "/#{x}")}
3
+ WORKER_ROOT = EVAL_APP_ROOT + "/worker"
4
+
5
+ require "packet"
6
+ class Foo
7
+ def receive_data p_data
8
+ ask_worker(:no_proxy_worker,:data => p_data, :type => :request)
9
+ end
10
+
11
+ def worker_receive p_data
12
+ send_data "#{p_data[:data]}\n"
13
+ end
14
+
15
+ def show_result p_data
16
+ send_data("#{p_data[:response]}\n")
17
+ end
18
+
19
+ def connection_completed
20
+ end
21
+
22
+ def post_init
23
+ end
24
+
25
+ def wow
26
+ puts "Wow"
27
+ end
28
+ end
29
+
30
+ Packet::Reactor.run do |t_reactor|
31
+ t_reactor.start_server("localhost", 11006,Foo) do |instance|
32
+ instance.wow
33
+ end
34
+ end
35
+
@@ -0,0 +1,48 @@
1
+ # Extends the class object with class and instance accessors for class attributes,
2
+ # just like the native attr* accessors for instance attributes.
3
+ class Class # :nodoc:
4
+ def cattr_reader(*syms)
5
+ syms.flatten.each do |sym|
6
+ next if sym.is_a?(Hash)
7
+ class_eval(<<-EOS, __FILE__, __LINE__)
8
+ unless defined? @@#{sym}
9
+ @@#{sym} = nil
10
+ end
11
+
12
+ def self.#{sym}
13
+ @@#{sym}
14
+ end
15
+
16
+ def #{sym}
17
+ @@#{sym}
18
+ end
19
+ EOS
20
+ end
21
+ end
22
+
23
+ def cattr_writer(*syms)
24
+ options = syms.last.is_a?(Hash) ? syms.pop : {}
25
+ syms.flatten.each do |sym|
26
+ class_eval(<<-EOS, __FILE__, __LINE__)
27
+ unless defined? @@#{sym}
28
+ @@#{sym} = nil
29
+ end
30
+
31
+ def self.#{sym}=(obj)
32
+ @@#{sym} = obj
33
+ end
34
+
35
+ #{"
36
+ def #{sym}=(obj)
37
+ @@#{sym} = obj
38
+ end
39
+ " unless options[:instance_writer] == false }
40
+ EOS
41
+ end
42
+ end
43
+
44
+ def cattr_accessor(*syms)
45
+ cattr_reader(*syms)
46
+ cattr_writer(*syms)
47
+ end
48
+ end
@@ -0,0 +1,61 @@
1
+ # FIXME: write this stupid parser in C.
2
+ class BinParser
3
+ def initialize
4
+ @size = 0
5
+ @data = []
6
+ # 0 => reading length
7
+ # 1 => reading actual data
8
+ @parser_state = 0
9
+ @length_string = ""
10
+ @numeric_length = 0
11
+ end
12
+
13
+ # if you look at it, it could be a suicidal function
14
+ def extract new_data
15
+ if @parser_state == 0
16
+ length_to_read = 9 - @length_string.length
17
+ len_str,remaining = new_data.unpack("a#{length_to_read}a*")
18
+ if len_str.length < length_to_read
19
+ @length_string << len_str
20
+ return
21
+ else
22
+ @length_string << len_str
23
+ @numeric_length = @length_string.to_i
24
+ @parser_state = 1
25
+ if remaining.length < @numeric_length
26
+ @data << remaining
27
+ @numeric_length = @numeric_length - remaining.length
28
+ else
29
+ @data << remaining
30
+ yield(@data.join)
31
+ @data = []
32
+ @parser_state = 0
33
+ @length_string = ""
34
+ @numeric_length = 0
35
+ end
36
+ end
37
+ elsif @parser_state == 1
38
+ pack_data,remaining = new_data.unpack("a#{@numeric_length}a*")
39
+ if pack_data.length < @numeric_length
40
+ @data << pack_data
41
+ @numeric_length = @numeric_length - pack_data.length
42
+ elsif pack_data.length == @numeric_length
43
+ @data << pack_data
44
+ yield(@data.join)
45
+ @data = []
46
+ @parser_state = 0
47
+ @length_string = ""
48
+ @numeric_length = 0
49
+ else
50
+ @data << pack_data
51
+ yield(@data.join)
52
+ @data = []
53
+ @parser_state = 0
54
+ @length_string = ""
55
+ @numeric_length = 0
56
+ extract(remaining)
57
+ end
58
+ end
59
+ end
60
+ end
61
+
@@ -0,0 +1,127 @@
1
+ # BufferedTokenizer - Statefully split input data by a specifiable token
2
+ #
3
+ # Authors:: Tony Arcieri, Martin Emde
4
+ #
5
+ #----------------------------------------------------------------------------
6
+ #
7
+ # Copyright (C) 2006-07 by Tony Arcieri and Martin Emde
8
+ #
9
+ # Distributed under the Ruby license (http://www.ruby-lang.org/en/LICENSE.txt)
10
+ #
11
+ #---------------------------------------------------------------------------
12
+ #
13
+
14
+ # (C)2006 Tony Arcieri, Martin Emde
15
+ # Distributed under the Ruby license (http://www.ruby-lang.org/en/LICENSE.txt)
16
+
17
+ # BufferedTokenizer takes a delimiter upon instantiation, or acts line-based
18
+ # by default. It allows input to be spoon-fed from some outside source which
19
+ # receives arbitrary length datagrams which may-or-may-not contain the token
20
+ # by which entities are delimited.
21
+
22
+ class BufferedTokenizer
23
+ # New BufferedTokenizers will operate on lines delimited by "\n" by default
24
+ # or allow you to specify any delimiter token you so choose, which will then
25
+ # be used by String#split to tokenize the input data
26
+ def initialize(delimiter = "\n", size_limit = nil)
27
+ # Store the specified delimiter
28
+ @delimiter = delimiter
29
+
30
+ # Store the specified size limitation
31
+ @size_limit = size_limit
32
+
33
+ # The input buffer is stored as an array. This is by far the most efficient
34
+ # approach given language constraints (in C a linked list would be a more
35
+ # appropriate data structure). Segments of input data are stored in a list
36
+ # which is only joined when a token is reached, substantially reducing the
37
+ # number of objects required for the operation.
38
+ @input = []
39
+
40
+ # Size of the input buffer
41
+ @input_size = 0
42
+ end
43
+
44
+ # Extract takes an arbitrary string of input data and returns an array of
45
+ # tokenized entities, provided there were any available to extract. This
46
+ # makes for easy processing of datagrams using a pattern like:
47
+ #
48
+ # tokenizer.extract(data).map { |entity| Decode(entity) }.each do ...
49
+ def extract(data)
50
+ # Extract token-delimited entities from the input string with the split command.
51
+ # There's a bit of craftiness here with the -1 parameter. Normally split would
52
+ # behave no differently regardless of if the token lies at the very end of the
53
+ # input buffer or not (i.e. a literal edge case) Specifying -1 forces split to
54
+ # return "" in this case, meaning that the last entry in the list represents a
55
+ # new segment of data where the token has not been encountered
56
+ entities = data.split @delimiter, -1
57
+
58
+ # Check to see if the buffer has exceeded capacity, if we're imposing a limit
59
+ if @size_limit
60
+ raise 'input buffer full' if @input_size + entities.first.size > @size_limit
61
+ @input_size += entities.first.size
62
+ end
63
+
64
+ # Move the first entry in the resulting array into the input buffer. It represents
65
+ # the last segment of a token-delimited entity unless it's the only entry in the list.
66
+ @input << entities.shift
67
+
68
+ # If the resulting array from the split is empty, the token was not encountered
69
+ # (not even at the end of the buffer). Since we've encountered no token-delimited
70
+ # entities this go-around, return an empty array.
71
+ return [] if entities.empty?
72
+
73
+ # At this point, we've hit a token, or potentially multiple tokens. Now we can bring
74
+ # together all the data we've buffered from earlier calls without hitting a token,
75
+ # and add it to our list of discovered entities.
76
+ entities.unshift @input.join
77
+
78
+ =begin
79
+ # Note added by FC, 10Jul07. This paragraph contains a regression. It breaks
80
+ # empty tokens. Think of the empty line that delimits an HTTP header. It will have
81
+ # two "\n" delimiters in a row, and this code mishandles the resulting empty token.
82
+ # It someone figures out how to fix the problem, we can re-enable this code branch.
83
+ # Multi-character token support.
84
+ # Split any tokens that were incomplete on the last iteration buf complete now.
85
+ entities.map! do |e|
86
+ e.split @delimiter, -1
87
+ end
88
+ # Flatten the resulting array. This has the side effect of removing the empty
89
+ # entry at the end that was produced by passing -1 to split. Add it again if
90
+ # necessary.
91
+ if (entities[-1] == [])
92
+ entities.flatten! << []
93
+ else
94
+ entities.flatten!
95
+ end
96
+ =end
97
+
98
+ # Now that we've hit a token, joined the input buffer and added it to the entities
99
+ # list, we can go ahead and clear the input buffer. All of the segments that were
100
+ # stored before the join can now be garbage collected.
101
+ @input.clear
102
+
103
+ # The last entity in the list is not token delimited, however, thanks to the -1
104
+ # passed to split. It represents the beginning of a new list of as-yet-untokenized
105
+ # data, so we add it to the start of the list.
106
+ @input << entities.pop
107
+
108
+ # Set the new input buffer size, provided we're keeping track
109
+ @input_size = @input.first.size if @size_limit
110
+
111
+ # Now we're left with the list of extracted token-delimited entities we wanted
112
+ # in the first place. Hooray!
113
+ entities
114
+ end
115
+
116
+ # Flush the contents of the input buffer, i.e. return the input buffer even though
117
+ # a token has not yet been encountered
118
+ def flush
119
+ buffer = @input.join
120
+ @input.clear
121
+ buffer
122
+ end
123
+
124
+ def empty?
125
+ @input.empty?
126
+ end
127
+ end
@@ -0,0 +1,14 @@
1
+ # class implements a simple callback mechanism for invoking callbacks
2
+ module Packet
3
+ class Callback
4
+ attr_accessor :signature,:stored_proc
5
+ def initialize(&block)
6
+ @signature = Guid.hexdigest
7
+ @stored_proc = block
8
+ end
9
+
10
+ def invoke(*args)
11
+ @stored_proc.call(*args)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,33 @@
1
+ # FIMXE: following class must modify the fd_watchlist thats being monitored by
2
+ # main eventloop.
3
+
4
+ module Packet
5
+ class Connection
6
+ # method gets called when connection to external server is completed
7
+ def connection_completed
8
+
9
+ end
10
+
11
+ # method gets called when external client is disconnected
12
+ def unbind
13
+
14
+ end
15
+
16
+ # method gets called just at the beginning of initializing things.
17
+ def post_init
18
+
19
+ end
20
+
21
+ def send_data
22
+
23
+ end
24
+
25
+ def ask_worker
26
+
27
+ end
28
+
29
+ def receive_data
30
+
31
+ end
32
+ end # end of class Connection
33
+ end # end of module Packet
@@ -0,0 +1,241 @@
1
+ # FIXME: timer implementation can be optimized
2
+ module Packet
3
+ module Core
4
+ def self.included(base_klass)
5
+ base_klass.extend(ClassMethods)
6
+ base_klass.instance_eval do
7
+ # iattr_accessor :connection_callbacks
8
+ @@connection_callbacks ||= {}
9
+ cattr_accessor :connection_callbacks
10
+ attr_accessor :read_ios, :write_ios, :listen_sockets
11
+ attr_accessor :connection_completion_awaited
12
+ attr_accessor :connections
13
+ include CommonMethods
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ def after_connection p_method
19
+ connection_callbacks[:after_connection] ||= []
20
+ connection_callbacks[:after_connection] << p_method
21
+ end
22
+
23
+ def after_unbind p_method
24
+ connection_callbacks[:after_unbind] ||= []
25
+ connection_callbacks[:after_unbind] << p_method
26
+ end
27
+
28
+ def before_unbind p_method
29
+ connection_callbacks[:before_unbind] ||= []
30
+ connection_callbacks[:before_unbind] << p_method
31
+ end
32
+ end # end of module#ClassMethods
33
+
34
+ module CommonMethods
35
+ include NbioHelper
36
+ # method
37
+ def connect(ip,port,t_module,&block)
38
+ t_socket = Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)
39
+ t_sock_addr = Socket.sockaddr_in(port,ip)
40
+ t_socket.setsockopt(Socket::IPPROTO_TCP,Socket::TCP_NODELAY,1)
41
+
42
+ connection_completion_awaited[t_socket.fileno] =
43
+ { :sock_addr => t_sock_addr, :module => t_module,:block => block }
44
+ begin
45
+ t_socket.connect_nonblock(t_sock_addr)
46
+ immediate_complete(t_socket,t_sock_addr,t_module,&block)
47
+ rescue Errno::EINPROGRESS
48
+ write_ios << t_socket
49
+ end
50
+ end
51
+
52
+ def reconnect(server,port,handler)
53
+ raise "invalid handler" unless handler.respond_to?(:connection_completed)
54
+ return handler if connections.keys.include?(handler.connection.fileno)
55
+ connect(server,port,handler)
56
+ end
57
+
58
+ def immediate_complete(t_socket,sock_addr,t_module,&block)
59
+ read_ios << t_socket
60
+ write_ios.delete(t_socket)
61
+ decorate_handler(t_socket,true,sock_addr,t_module,&block)
62
+ connection_completion_awaited.delete(t_socket.fileno)
63
+ end
64
+
65
+ def accept_connection(sock_opts)
66
+ sock_io = sock_opts[:socket]
67
+
68
+ begin
69
+ client_socket,client_sockaddr = sock_io.accept_nonblock
70
+ rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR
71
+ puts "not ready yet"
72
+ return
73
+ end
74
+ read_ios << client_socket
75
+ decorate_handler(client_socket,true,client_sockaddr,sock_opts[:module],&sock_opts[:block])
76
+ end
77
+
78
+ def complete_connection(t_sock,sock_opts)
79
+ actually_connected = true
80
+ begin
81
+ t_sock.connect_nonblock(sock_opts[:sock_addr])
82
+ rescue Errno::EISCONN
83
+ puts "Socket already connected"
84
+ rescue Errno::ECONNREFUSED
85
+ actually_connected = false
86
+ end
87
+
88
+ read_ios << t_sock if actually_connected
89
+ write_ios.delete(t_sock)
90
+ decorate_handler(t_sock,actually_connected,sock_opts[:sock_addr],sock_opts[:module],&sock_opts[:block])
91
+ connection_completion_awaited.delete(t_sock.fileno)
92
+ end
93
+
94
+ # method removes the connection and closes the socket
95
+ def remove_connection(t_sock)
96
+ @read_ios.delete(t_sock)
97
+ @write_ios.delete(t_sock)
98
+ connections.delete(t_sock.fileno)
99
+ t_sock.close
100
+ end
101
+
102
+ def socket_really_connected?(t_sock)
103
+ begin
104
+ t_data = read_data(t_sock)
105
+ return true
106
+ rescue DisconnectError
107
+ return false
108
+ end
109
+ end
110
+
111
+ # method opens a socket for listening
112
+ def start_server(ip,port,t_module,&block)
113
+ t_socket = Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)
114
+ t_socket.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR,true)
115
+ sockaddr = Socket.sockaddr_in(port.to_i,ip)
116
+ t_socket.bind(sockaddr)
117
+ t_socket.listen(50)
118
+ t_socket.setsockopt(Socket::IPPROTO_TCP,Socket::TCP_NODELAY,1)
119
+ listen_sockets[t_socket.fileno] = { :socket => t_socket,:block => block,:module => t_module }
120
+ @read_ios << t_socket
121
+ end
122
+
123
+ # method starts event loop in the process
124
+ def start_reactor
125
+ Signal.trap("TERM") { terminate_me }
126
+ Signal.trap("INT") { shutdown }
127
+ loop do
128
+ check_for_timer_events
129
+ ready_fds = select(@read_ios,@write_ios,nil,0.005)
130
+ next if ready_fds.blank?
131
+ ready_fds = ready_fds.flatten.compact
132
+ ready_fds.each do |t_sock|
133
+ if t_sock.is_a? UNIXSocket
134
+ handle_internal_messages(t_sock)
135
+ else
136
+ handle_external_messages(t_sock)
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ def terminate_me
143
+ exit
144
+ end
145
+
146
+ def shutdown
147
+ exit
148
+ end
149
+
150
+ def handle_internal_messages(t_sock)
151
+ raise "Method should be implemented by concerned classes"
152
+ end
153
+
154
+ def handle_external_messages(t_sock)
155
+ sock_fd = t_sock.fileno
156
+ if sock_opts = listen_sockets[sock_fd]
157
+ accept_connection(sock_opts)
158
+ elsif extern_opts = connection_completion_awaited[sock_fd]
159
+ complete_connection(t_sock,extern_opts)
160
+ else
161
+ read_external_socket(t_sock)
162
+ end
163
+ end
164
+
165
+ def read_external_socket(t_sock)
166
+ handler_instance = connections[t_sock.fileno].instance
167
+ begin
168
+ t_data = read_data(t_sock)
169
+ handler_instance.receive_data(t_data) if handler_instance.respond_to?(:receive_data)
170
+ rescue DisconnectError => sock_error
171
+ handler_instance.unbind if handler_instance.respond_to?(:unbind)
172
+ connections.delete(t_sock.fileno)
173
+ read_ios.delete(t_sock)
174
+ end
175
+ end
176
+
177
+ def add_periodic_timer(interval,&block)
178
+ t_timer = PeriodicEvent.new(interval,&block)
179
+ @timer_hash[t_timer.timer_signature] = t_timer
180
+ return t_timer
181
+ end
182
+
183
+ def add_timer(elapsed_time,&block)
184
+ t_timer = Event.new(elapsed_time,&block)
185
+ @timer_hash[t_timer.timer_signature] = t_timer
186
+ return t_timer
187
+ end
188
+
189
+ def cancel_timer(t_timer)
190
+ @timer_hash.delete(t_timer.timer_signature)
191
+ end
192
+
193
+ def initialize
194
+ @read_ios ||= []
195
+ @write_ios ||= []
196
+ @connection_completion_awaited ||= {}
197
+ @connections ||= {}
198
+ @listen_sockets ||= {}
199
+
200
+ @timer_hash ||= {}
201
+ end
202
+
203
+ def check_for_timer_events
204
+ @timer_hash.each do |key,timer|
205
+ if timer.run_now?
206
+ timer.run
207
+ @timer_hash.delete(key) if !timer.respond_to?(:interval)
208
+ end
209
+ end
210
+ end
211
+
212
+ def initialize_handler(p_module)
213
+ return p_module if(!p_module.is_a?(Class) and !p_module.is_a?(Module))
214
+ handler =
215
+ if(p_module and p_module.is_a?(Class))
216
+ p_module
217
+ else
218
+ Class.new(Connection) { p_module and include p_module }
219
+ end
220
+ return handler.new
221
+ end
222
+
223
+ def decorate_handler(t_socket,actually_connected,sock_addr,t_module,&block)
224
+ handler_instance = initialize_handler(t_module)
225
+ connection_callbacks[:after_connection].each { |t_callback| self.send(t_callback,handler_instance,t_socket)}
226
+ handler_instance.invoke_init unless handler_instance.initialized
227
+ unless actually_connected
228
+ handler_instance.unbind if handler_instance.respond_to?(:unbind)
229
+ return
230
+ end
231
+ t_signature = Guid.hexdigest
232
+ handler_instance.signature = t_signature
233
+ connections[t_socket.fileno] =
234
+ OpenStruct.new( :socket => t_socket, :instance => handler_instance, :signature => t_signature,:sock_addr => sock_addr)
235
+ block.call(handler_instance) if block
236
+ handler_instance.connection_completed if handler_instance.respond_to?(:connection_completed)
237
+ end
238
+
239
+ end # end of module#CommonMethods
240
+ end #end of module#Core
241
+ end #end of module#Packet