packet 0.1.0

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