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,16 @@
1
+ module Packet
2
+ class Guid
3
+ def self.hexdigest
4
+ values = [
5
+ rand(0x0010000),
6
+ rand(0x0010000),
7
+ rand(0x0010000),
8
+ rand(0x0010000),
9
+ rand(0x0010000),
10
+ rand(0x1000000),
11
+ rand(0x1000000),
12
+ ]
13
+ "%04x%04x%04x%04x%04x%06x%06x" % values
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,148 @@
1
+ # FIXME: Some code is duplicated between worker class and this Reactor class, that can be fixed
2
+ # with help of creation of Connection class and enabling automatic inheritance of that class and
3
+ # mixing in of methods from that class.
4
+ module Packet
5
+ class Reactor
6
+ include Core
7
+ attr_accessor :fd_writers, :msg_writers,:msg_reader
8
+ attr_accessor :live_workers
9
+ after_connection :provide_workers
10
+
11
+ def self.run
12
+ master_reactor_instance = new
13
+ master_reactor_instance.live_workers = DoubleKeyedHash.new
14
+ yield(master_reactor_instance)
15
+ master_reactor_instance.load_workers
16
+ master_reactor_instance.start_reactor
17
+ end # end of run method
18
+
19
+ def provide_workers(handler_instance,t_sock)
20
+ class << handler_instance
21
+ extend Forwardable
22
+ attr_accessor :workers,:connection,:reactor, :initialized,:signature
23
+ include NbioHelper
24
+ def send_data p_data
25
+ begin
26
+ write_data(p_data,connection)
27
+ rescue Errno::EPIPE
28
+ # probably a callback, when there is a error in writing to the socket
29
+ end
30
+ end
31
+ def invoke_init
32
+ @initialized = true
33
+ post_init
34
+ end
35
+
36
+ def close_connection
37
+ unbind
38
+ reactor.remove_connection(connection)
39
+ end
40
+
41
+ def close_connection_after_writing
42
+ connection.flush
43
+ unbind
44
+ reactor.remove_connection(connection)
45
+ end
46
+
47
+ def ask_worker(*args)
48
+ worker_name = args.shift
49
+ data_options = *args
50
+ data_options[:client_signature] = connection.fileno
51
+ workers[worker_name].send_request(data_options)
52
+ end
53
+
54
+ def send_object p_object
55
+ dump_object(p_object,connection)
56
+ end
57
+ def_delegators :@reactor, :start_server, :connect, :add_periodic_timer, :add_timer, :cancel_timer,:reconnect, :start_worker
58
+ end
59
+ handler_instance.workers = @live_workers
60
+ handler_instance.connection = t_sock
61
+ handler_instance.reactor = self
62
+ end
63
+
64
+ # FIXME: right now, each worker is tied to its connection and this can be problematic
65
+ # what if a worker wants to return results in a async manner
66
+ def handle_internal_messages(t_sock)
67
+ sock_fd = t_sock.fileno
68
+ worker_instance = @live_workers[sock_fd]
69
+ begin
70
+ raw_data = read_data(t_sock)
71
+ # t_data = Marshal.load(raw_data)
72
+ worker_instance.receive_data(raw_data) if worker_instance.respond_to?(:receive_data)
73
+ rescue DisconnectError => sock_error
74
+ read_ios.delete(t_sock)
75
+ end
76
+ end
77
+
78
+ # method loads workers in new processes
79
+ # FIXME: this method can be fixed, so as worker code can be actually, required
80
+ # only in forked process and hence saving upon the memory involved
81
+ # where worker is actually required in master as well as in worker.
82
+ def load_workers
83
+
84
+ if defined?(WORKER_ROOT)
85
+ worker_root = WORKER_ROOT
86
+ else
87
+ worker_root = "#{PACKET_APP}/worker"
88
+ end
89
+ t_workers = Dir["#{worker_root}/**/*.rb"]
90
+ return if t_workers.blank?
91
+ t_workers.each do |b_worker|
92
+ worker_name = File.basename(b_worker,".rb")
93
+ require worker_name
94
+ worker_klass = Object.const_get(worker_name.classify)
95
+ next if worker_klass.no_auto_load
96
+ fork_and_load(worker_klass)
97
+ end
98
+
99
+ # FIXME: easiest and yet perhaps a bit ugly, its just to make sure that from each
100
+ # worker proxy one can access other workers
101
+ @live_workers.each do |key,worker_instance|
102
+ worker_instance.workers = @live_workers
103
+ end
104
+ end
105
+
106
+ def start_worker(worker_name,options = {})
107
+ require worker_name.to_s
108
+ worker_klass = Object.const_get(worker_name.classify)
109
+ fork_and_load(worker_klass,options)
110
+ end
111
+
112
+ # method forks given worker file in a new process
113
+ def fork_and_load(worker_klass,worker_options = { })
114
+ t_worker_name = worker_klass.worker_name
115
+ worker_pimp = worker_klass.worker_proxy.to_s
116
+
117
+ # socket from which master process is going to read
118
+ master_read_end,worker_write_end = UNIXSocket.pair(Socket::SOCK_STREAM)
119
+ # socket to which master process is going to write
120
+ worker_read_end,master_write_end = UNIXSocket.pair(Socket::SOCK_STREAM)
121
+ worker_read_fd,master_write_fd = UNIXSocket.pair
122
+
123
+ if((pid = fork()).nil?)
124
+ $0 = "ruby #{worker_klass.worker_name}"
125
+ master_write_end.close
126
+ master_read_end.close
127
+ master_write_fd.close
128
+ # master_write_end.close if master_write_end
129
+ worker_klass.start_worker(:write_end => worker_write_end,:read_end => worker_read_end,:read_fd => worker_read_fd,:options => worker_options)
130
+ end
131
+ Process.detach(pid)
132
+
133
+ unless worker_pimp.blank?
134
+ require worker_pimp
135
+ pimp_klass = Object.const_get(worker_pimp.classify)
136
+ @live_workers[t_worker_name,master_read_end.fileno] = pimp_klass.new(master_write_end,pid,self)
137
+ else
138
+ @live_workers[t_worker_name,master_read_end.fileno] = Packet::MetaPimp.new(master_write_end,pid,self)
139
+ end
140
+
141
+ worker_read_end.close
142
+ worker_write_end.close
143
+ worker_read_fd.close
144
+ read_ios << master_read_end
145
+ end # end of fork_and_load method
146
+
147
+ end # end of Reactor class
148
+ end # end of Packet module
@@ -0,0 +1,214 @@
1
+ # This module rewrites pieces of the very good Mongrel web server in
2
+ # order to change it from a threaded application to an event based
3
+ # application running inside an Packet event loop. It should
4
+ # be compatible with the existing Mongrel handlers for Rails,
5
+ # Camping, Nitro, etc....
6
+
7
+ require 'packet'
8
+ require 'mongrel'
9
+
10
+ module Mongrel
11
+ class MongrelProtocol < Packet::Connection
12
+ def post_init
13
+ @parser = HttpParser.new
14
+ @params = HttpParams.new
15
+ @nparsed = 0
16
+ @request = nil
17
+ @request_len = nil
18
+ @linebuffer = ''
19
+ end
20
+
21
+ def receive_data data
22
+ @linebuffer << data
23
+ @nparsed = @parser.execute(@params, @linebuffer, @nparsed) unless @parser.finished?
24
+ if @parser.finished?
25
+ if @request_len.nil?
26
+ @request_len = @params[::Mongrel::Const::CONTENT_LENGTH].to_i
27
+ script_name, path_info, handlers = ::Mongrel::HttpServer::Instance.classifier.resolve(@params[::Mongrel::Const::REQUEST_PATH])
28
+ if handlers
29
+ @params[::Mongrel::Const::PATH_INFO] = path_info
30
+ @params[::Mongrel::Const::SCRIPT_NAME] = script_name
31
+ @params[::Mongrel::Const::REMOTE_ADDR] = @params[::Mongrel::Const::HTTP_X_FORWARDED_FOR] #|| ::Socket.unpack_sockaddr_in(get_peername)[1]
32
+ @notifiers = handlers.select { |h| h.request_notify }
33
+ end
34
+ if @request_len > ::Mongrel::Const::MAX_BODY
35
+ new_buffer = Tempfile.new(::Mongrel::Const::MONGREL_TMP_BASE)
36
+ new_buffer.binmode
37
+ new_buffer << @linebuffer[@nparsed..-1]
38
+ @linebuffer = new_buffer
39
+ else
40
+ @linebuffer = StringIO.new(@linebuffer[@nparsed..-1])
41
+ @linebuffer.pos = @linebuffer.length
42
+ end
43
+ end
44
+ if @linebuffer.length >= @request_len
45
+ @linebuffer.rewind
46
+ ::Mongrel::HttpServer::Instance.process_http_request(@params,@linebuffer,self)
47
+ @linebuffer.delete if Tempfile === @linebuffer
48
+ end
49
+ elsif @linebuffer.length > ::Mongrel::Const::MAX_HEADER
50
+ close_connection
51
+ raise ::Mongrel::HttpParserError.new("HEADER is longer than allowed, aborting client early.")
52
+ end
53
+ rescue ::Mongrel::HttpParserError
54
+ if $mongrel_debug_client
55
+ STDERR.puts "#{Time.now}: BAD CLIENT (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #$!"
56
+ STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
57
+ end
58
+ close_connection
59
+ rescue Exception => e
60
+ close_connection
61
+ raise e
62
+ end
63
+
64
+ def write data
65
+ send_data data
66
+ end
67
+
68
+ def closed?
69
+ false
70
+ end
71
+
72
+ end
73
+
74
+ class HttpServer
75
+ def initialize(host, port, num_processors=950, x=0, y=nil) # Deal with Mongrel 1.0.1 or earlier, as well as later.
76
+ @socket = nil
77
+ @classifier = URIClassifier.new
78
+ @host = host
79
+ @port = port
80
+ @workers = ThreadGroup.new
81
+ if y
82
+ @throttle = x
83
+ @timeout = y || 60
84
+ else
85
+ @timeout = x
86
+ end
87
+ @num_processors = num_processors #num_processors is pointless for evented....
88
+ @death_time = 60
89
+ self.class.const_set(:Instance,self)
90
+ end
91
+
92
+ def run
93
+ trap('INT') { raise StopServer }
94
+ trap('TERM') { raise StopServer }
95
+ @acceptor = Thread.new do
96
+ Packet::Reactor.run do |t_reactor|
97
+ begin
98
+ t_reactor.start_server(@host,@port,MongrelProtocol)
99
+ rescue StopServer
100
+ t_reactor.start_server
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ def process_http_request(params,linebuffer,client)
107
+ if not params[Const::REQUEST_PATH]
108
+ uri = URI.parse(params[Const::REQUEST_URI])
109
+ params[Const::REQUEST_PATH] = uri.request_uri
110
+ end
111
+
112
+ raise "No REQUEST PATH" if not params[Const::REQUEST_PATH]
113
+
114
+ script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH])
115
+
116
+ if handlers
117
+ notifiers = handlers.select { |h| h.request_notify }
118
+ request = HttpRequest.new(params, linebuffer, notifiers)
119
+
120
+ # request is good so far, continue processing the response
121
+ response = HttpResponse.new(client)
122
+
123
+ # Process each handler in registered order until we run out or one finalizes the response.
124
+ dispatch_to_handlers(handlers,request,response)
125
+
126
+ # And finally, if nobody closed the response off, we finalize it.
127
+ unless response.done
128
+ response.finished
129
+ else
130
+ response.close_connection
131
+ end
132
+ else
133
+ # Didn't find it, return a stock 404 response.
134
+ client.send_data(Const::ERROR_404_RESPONSE)
135
+ client.close_connection
136
+ end
137
+ end
138
+
139
+ def dispatch_to_handlers(handlers,request,response)
140
+ handlers.each do |handler|
141
+ handler.process(request, response)
142
+ break if response.done
143
+ end
144
+ end
145
+ end
146
+
147
+ class HttpRequest
148
+ def initialize(params, linebuffer, dispatchers)
149
+ @params = params
150
+ @dispatchers = dispatchers
151
+ @body = linebuffer
152
+ end
153
+ end
154
+
155
+ class HttpResponse
156
+ def send_file(path, small_file = false)
157
+ File.open(path, "rb") do |f|
158
+ while chunk = f.read(Const::CHUNK_SIZE) and chunk.length > 0
159
+ begin
160
+ write(chunk)
161
+ rescue Object => exc
162
+ break
163
+ end
164
+ end
165
+ end
166
+ @body_sent = true
167
+ end
168
+
169
+ def write(data)
170
+ @socket.send_data data
171
+ end
172
+
173
+ def close_connection_after_writing
174
+ @socket.close_connection
175
+ end
176
+
177
+ def socket_error(details)
178
+ @socket.close_connection
179
+ done = true
180
+ raise details
181
+ end
182
+
183
+ def finished
184
+ send_status
185
+ send_header
186
+ send_body
187
+ @socket.close_connection
188
+ end
189
+ end
190
+
191
+ class Configurator
192
+ # This version fixes a bug in the regular Mongrel version by adding
193
+ # initialization of groups.
194
+ def change_privilege(user, group)
195
+ if user and group
196
+ log "Initializing groups for {#user}:{#group}."
197
+ Process.initgroups(user,Etc.getgrnam(group).gid)
198
+ end
199
+
200
+ if group
201
+ log "Changing group to #{group}."
202
+ Process::GID.change_privilege(Etc.getgrnam(group).gid)
203
+ end
204
+
205
+ if user
206
+ log "Changing user to #{user}."
207
+ Process::UID.change_privilege(Etc.getpwnam(user).uid)
208
+ end
209
+ rescue Errno::EPERM
210
+ log "FAILED to change user:group #{user}:#{group}: #$!"
211
+ exit 1
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,27 @@
1
+ module Packet
2
+ class PeriodicEvent
3
+ attr_accessor :block, :timer_signature, :interval, :cancel_flag
4
+ def initialize(interval, &block)
5
+ @cancel_flag = false
6
+ @timer_signature = Guid.hexdigest
7
+ @block = block
8
+ @scheduled_time = Time.now + interval
9
+ @interval = interval
10
+ end
11
+
12
+ def run_now?
13
+ return true if @scheduled_time <= Time.now
14
+ return false
15
+ end
16
+
17
+ def cancel
18
+ @cancel_flag = true
19
+ end
20
+
21
+ def run
22
+ @scheduled_time += @interval
23
+ @block.call
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ module Packet
2
+ class Pimp
3
+ include NbioHelper
4
+ extend Forwardable
5
+ iattr_accessor :pimp_name
6
+ attr_accessor :lifeline, :pid, :signature
7
+ attr_accessor :fd_write_end
8
+ attr_accessor :workers, :reactor
9
+
10
+ def initialize(lifeline_socket,worker_pid,p_reactor)
11
+ @lifeline = lifeline_socket
12
+ @pid = worker_pid
13
+ @reactor = p_reactor
14
+ @signature = Guid.hexdigest
15
+ pimp_init if self.respond_to?(:pimp_init)
16
+ end
17
+
18
+ # encode the data, before writing to the socket
19
+ def send_data p_data
20
+ dump_object(p_data,@lifeline)
21
+ end
22
+
23
+ def send_fd sock_fd
24
+ @fd_write_end.send_io(sock_fd)
25
+ end
26
+
27
+ alias_method :do_work, :send_data
28
+ def_delegators :@reactor, :connections
29
+ end
30
+ end
31
+
@@ -0,0 +1,125 @@
1
+ # FIXME: ultimate goal should be remove dependence from activesupport gem altogether.
2
+
3
+ class Object
4
+ def deep_copy
5
+ Marshal.load(Marshal.dump(self))
6
+ end
7
+
8
+ def nothing?
9
+ if respond_to?(:empty?) && respond_to?(:strip)
10
+ empty? or strip.empty?
11
+ elsif respond_to?(:empty?)
12
+ empty?
13
+ elsif respond_to?(:zero?)
14
+ zero?
15
+ else
16
+ !self
17
+ end
18
+ end
19
+
20
+ def clean!
21
+ if respond_to?(:empty?) && respond_to?(:strip)
22
+ return nil if empty?
23
+ (strip.empty?) ? nil : (self.strip)
24
+ elsif respond_to?(:empty?)
25
+ empty? ? nil : self
26
+ else
27
+ self
28
+ end
29
+ end
30
+
31
+ def blank?
32
+ if respond_to?(:empty?) && respond_to?(:strip)
33
+ empty? or strip.empty?
34
+ elsif respond_to?(:empty?)
35
+ empty?
36
+ else
37
+ !self
38
+ end
39
+ end
40
+
41
+ def self.metaclass; class << self; self; end; end
42
+
43
+ def self.iattr_accessor *args
44
+
45
+ metaclass.instance_eval do
46
+ attr_accessor *args
47
+ args.each do |attr|
48
+ define_method("set_#{attr}") do |b_value|
49
+ self.send("#{attr}=",b_value)
50
+ end
51
+ end
52
+ end
53
+
54
+ args.each do |attr|
55
+ class_eval do
56
+ define_method(attr) do
57
+ self.class.send(attr)
58
+ end
59
+ define_method("#{attr}=") do |b_value|
60
+ self.class.send("#{attr}=",b_value)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+
68
+ class NilClass #:nodoc:
69
+ def blank?
70
+ true
71
+ end
72
+ end
73
+
74
+ class FalseClass #:nodoc:
75
+ def blank?
76
+ true
77
+ end
78
+ end
79
+
80
+ class TrueClass #:nodoc:
81
+ def blank?
82
+ false
83
+ end
84
+ end
85
+
86
+ class Array #:nodoc:
87
+ alias_method :blank?, :empty?
88
+ def clean!
89
+ (empty?) ? nil : self
90
+ end
91
+
92
+ def extract_options!
93
+ last.is_a?(Hash) ? pop : {}
94
+ end
95
+ end
96
+
97
+ class Hash #:nodoc:
98
+ alias_method :blank?, :empty?
99
+ def clean!
100
+ (empty?) ? nil : self
101
+ end
102
+ end
103
+
104
+ class String #:nodoc:
105
+ def blank?
106
+ empty? || strip.empty?
107
+ end
108
+
109
+ def clean!
110
+ return nil if empty?
111
+ t_val = self.strip
112
+ (t_val.empty?) ? nil : t_val
113
+ end
114
+
115
+ def classify
116
+ word_parts = split('_')
117
+ return word_parts.map { |x| x.capitalize}.join
118
+ end
119
+ end
120
+
121
+ class Numeric #:nodoc:
122
+ def blank?
123
+ false
124
+ end
125
+ end