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.
- data/LICENSE +4 -0
- data/README +251 -0
- data/Rakefile +87 -0
- data/TODO +0 -0
- data/bin/packet_mongrel.rb +215 -0
- data/bin/runner.rb +35 -0
- data/lib/attribute_accessors.rb +48 -0
- data/lib/bin_parser.rb +61 -0
- data/lib/buftok.rb +127 -0
- data/lib/callback.rb +14 -0
- data/lib/connection.rb +33 -0
- data/lib/core.rb +241 -0
- data/lib/cpu_worker.rb +19 -0
- data/lib/deferrable.rb +210 -0
- data/lib/disconnect_error.rb +8 -0
- data/lib/double_keyed_hash.rb +19 -0
- data/lib/event.rb +25 -0
- data/lib/io_worker.rb +6 -0
- data/lib/meta_pimp.rb +66 -0
- data/lib/nbio.rb +81 -0
- data/lib/packet.rb +37 -0
- data/lib/packet_guid.rb +16 -0
- data/lib/packet_master.rb +148 -0
- data/lib/packet_mongrel.rb +214 -0
- data/lib/periodic_event.rb +27 -0
- data/lib/pimp.rb +31 -0
- data/lib/ruby_hacks.rb +125 -0
- data/lib/worker.rb +120 -0
- metadata +75 -0
data/LICENSE
ADDED
data/README
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
Packet is a pure ruby library for writing network applications in Ruby.
|
2
|
+
It follows Evented Model of network programming and implements almost all the
|
3
|
+
features provided by EventMachine.
|
4
|
+
|
5
|
+
It also provides real easy to user UNIX workers for concurrent programming.
|
6
|
+
|
7
|
+
Its best to have some examples going:
|
8
|
+
|
9
|
+
== Examples
|
10
|
+
=== A Simple Echo Server:
|
11
|
+
require "rubygems"
|
12
|
+
require "packet"
|
13
|
+
|
14
|
+
class Foo
|
15
|
+
def receive_data p_data
|
16
|
+
send_data(p_data)
|
17
|
+
end
|
18
|
+
|
19
|
+
def post_init
|
20
|
+
puts "Client connected"
|
21
|
+
end
|
22
|
+
|
23
|
+
def connection_completed
|
24
|
+
puts "Whoa man"
|
25
|
+
end
|
26
|
+
|
27
|
+
def unbind
|
28
|
+
puts "Client Disconnected"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
Packet::Reactor.run do |t_reactor|
|
33
|
+
t_reactor.start_server("localhost",11006,Foo)
|
34
|
+
end
|
35
|
+
|
36
|
+
Those new to network programming with events and callbacks, will note that,
|
37
|
+
each time a new client connects an instance of class Foo is instantiated.
|
38
|
+
When client writes some data to the socket, receive_data method is invoked.
|
39
|
+
|
40
|
+
Although Packet implements an API similar to EventMachine, but it differs
|
41
|
+
slightly because of the fact that, for a packet app, there can be more than one
|
42
|
+
reactor loop running and hence, we don't use Packet.start_server(...).
|
43
|
+
|
44
|
+
=== A Simple Http Client
|
45
|
+
class WikiHandler
|
46
|
+
def receive_data p_data
|
47
|
+
p p_data
|
48
|
+
end
|
49
|
+
|
50
|
+
def post_init
|
51
|
+
end
|
52
|
+
|
53
|
+
def unbind
|
54
|
+
end
|
55
|
+
|
56
|
+
def connection_completed
|
57
|
+
send_data("GET / \r\n")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
Packet::Reactor.run do |t_reactor|
|
62
|
+
t_reactor.connect("en.wikipedia.org",80,WikiHandler)
|
63
|
+
end
|
64
|
+
|
65
|
+
=== Using Callbacks and Deferables
|
66
|
+
Documentation to come.
|
67
|
+
|
68
|
+
=== Using Workers
|
69
|
+
Packet enables you to write simple workers, which will run in
|
70
|
+
different process and gives you nice
|
71
|
+
evented handle for concurrent execution of various tasks.
|
72
|
+
|
73
|
+
When, you are writing a scalable networking application
|
74
|
+
using Event Model of network programming,
|
75
|
+
sometimes when processing of certain events take time,
|
76
|
+
your event loop is stuck there. With green
|
77
|
+
threads, you don't really have a way of paralleling
|
78
|
+
your request processing. Packet library, allows
|
79
|
+
you to write simple workers, for executing long
|
80
|
+
running tasks. You can pass data and callbacks as an
|
81
|
+
argument.
|
82
|
+
|
83
|
+
When you are going to use workers in
|
84
|
+
your application, you need to define
|
85
|
+
constant WORKER_ROOT,
|
86
|
+
which is the directory location, where
|
87
|
+
your workers are located. All the workers defined in that directory
|
88
|
+
will be automatically, picked and forked in a
|
89
|
+
new process when your packet app starts. So, a typical
|
90
|
+
packet_app, that wants to use workers, will look like this:
|
91
|
+
|
92
|
+
packet_app_root
|
93
|
+
|
|
94
|
+
|__ lib
|
95
|
+
|
|
96
|
+
|___ worker
|
97
|
+
|
|
98
|
+
|___ config
|
99
|
+
|
|
100
|
+
|___ log
|
101
|
+
|
102
|
+
You would define WORKER_ROOT = PACKET_APP_ROOT/worker
|
103
|
+
|
104
|
+
All the workers must inherit class Packet::Worker, and hence a
|
105
|
+
general skeleton of worker will look like:
|
106
|
+
|
107
|
+
class FooWorker < Packet::Worker
|
108
|
+
set_worker_name :foo_worker #=> This is necessary.
|
109
|
+
def receive_data p_data
|
110
|
+
end
|
111
|
+
|
112
|
+
def connection_completed
|
113
|
+
end
|
114
|
+
|
115
|
+
def unbind
|
116
|
+
end
|
117
|
+
|
118
|
+
def post_init
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
All the forked workers are connected to master via
|
123
|
+
UNIX sockets, and hence messages passed to workers from master
|
124
|
+
will be available in receive_data method. Also,
|
125
|
+
when you are passing messages to workers, or worker is passing
|
126
|
+
message to master ( in a nutshell, all the internal
|
127
|
+
communication between workers and master ) directly takes
|
128
|
+
place using ruby objects. All the passed ruby objects are
|
129
|
+
dumped and marshalled across unix sockets in a non blocking
|
130
|
+
manner. BinParser class parses dumped binary objects and
|
131
|
+
makes sure, packets received at other end are complete.
|
132
|
+
Usually, you wouldn't need to worry about this little detail.
|
133
|
+
|
134
|
+
Packet provides various ways of interacting with
|
135
|
+
workers. Usually, when a worker is instantiated, a proxy for
|
136
|
+
that worker will also be instantiated at master
|
137
|
+
process. Packet automatically provides a worker proxy(See meta_pimp.rb)
|
138
|
+
for you, but if you need to multiplex/demultiplex
|
139
|
+
requests based on certain criteria, you may as well define your
|
140
|
+
own worker proxies. Code, would like something like this:
|
141
|
+
|
142
|
+
class FooWorker < Packet::Worker
|
143
|
+
set_worker_proxy :foo_handler
|
144
|
+
end
|
145
|
+
|
146
|
+
When you define, :foo_handler as a proxy for
|
147
|
+
this worker, packet is gonna search for FooHandler class and
|
148
|
+
instantiate it when the worker gets started. All
|
149
|
+
the worker proxies must inherit from Packet::Pimp.
|
150
|
+
Have a look at, Packet::MetaPimp,
|
151
|
+
which acts as a meta pimp for all the workers,
|
152
|
+
which don't have a explicit worker proxy defined.
|
153
|
+
|
154
|
+
=== A complete Case :
|
155
|
+
|
156
|
+
Just for kicks, lets write a sample server,
|
157
|
+
which evals whatever clients send to it. But, assuming this 'eval' of
|
158
|
+
client data can be potentially time/cpu
|
159
|
+
consuming ( not to mention dangerous too ), we are gonna ask our eval_worker, to
|
160
|
+
perform eval and return the result to master process, which in
|
161
|
+
turn returns the result to happy client.
|
162
|
+
|
163
|
+
# APP_ROOT/bin/eval_server.rb
|
164
|
+
EVAL_APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__) + "/.."))
|
165
|
+
["bin","worker","lib"].each { |x| $LOAD_PATH.unshift(EVAL_APP_ROOT + "/#{x}")}
|
166
|
+
WORKER_ROOT = EVAL_APP_ROOT + "/worker"
|
167
|
+
|
168
|
+
require "packet"
|
169
|
+
class EvalServer
|
170
|
+
def receive_data p_data
|
171
|
+
ask_worker(:eval_worker,:data => p_data, :type => :request)
|
172
|
+
end
|
173
|
+
|
174
|
+
# will be called, when any worker sends data back to master process
|
175
|
+
# it should be noted that, you may have several instances of eval_server in
|
176
|
+
# your master, for each connected client, but worker_receive will be always
|
177
|
+
# be invoked for the instance, which originally made the request.
|
178
|
+
# If you need fine control, over this behaviour, you can implement a worker proxy
|
179
|
+
# on the lines of meta_pimp class. This API will change in future perhaps, as i
|
180
|
+
# expect, better ideas to come.
|
181
|
+
def worker_receive p_data
|
182
|
+
send_data "#{p_data[:data]}\n"
|
183
|
+
end
|
184
|
+
|
185
|
+
def show_result p_data
|
186
|
+
send_data("#{p_data[:response]}\n")
|
187
|
+
end
|
188
|
+
|
189
|
+
def connection_completed
|
190
|
+
end
|
191
|
+
|
192
|
+
def post_init
|
193
|
+
end
|
194
|
+
|
195
|
+
def wow
|
196
|
+
puts "Wow"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
Packet::Reactor.run do |t_reactor|
|
201
|
+
t_reactor.start_server("localhost", 11006,EvalServer) do |instance|
|
202
|
+
instance.wow
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# APP_ROOT/worker/eval_worker.rb
|
207
|
+
class EvalWorker < Packet::Worker
|
208
|
+
set_worker_name :eval_worker
|
209
|
+
def worker_init
|
210
|
+
p "Starting no proxy worker"
|
211
|
+
end
|
212
|
+
|
213
|
+
def receive_data data_obj
|
214
|
+
eval_data = eval(data_obj[:data])
|
215
|
+
data_obj[:data] = eval_data
|
216
|
+
data_obj[:type] = :response
|
217
|
+
send_data(data_obj)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
=== Passing file descriptors to workers :
|
222
|
+
Packet, allows this style of load
|
223
|
+
distribution as well, you can use method send_fd to
|
224
|
+
pass file descriptors to workers.
|
225
|
+
WARNING: This feature hasn't been tested well.
|
226
|
+
|
227
|
+
=== Disable auto loading of certain workers:
|
228
|
+
Sometimes, you would need to start a
|
229
|
+
worker at runtime and don't want this pre-forking mechanism.
|
230
|
+
Packet, allows this. You just need to define
|
231
|
+
"set_no_auto_load true" in your worker class and worker
|
232
|
+
will not be automatically forked. Although name is a bit misleading perhaps.
|
233
|
+
|
234
|
+
Now, at runtime, you can call start_worker(:foo_worker, options)
|
235
|
+
to start a worker as usual. It should
|
236
|
+
be noted that, forking a worker, which is already
|
237
|
+
forked can be disastrous, since worker names are being
|
238
|
+
used as unique keys that represent a worker.
|
239
|
+
|
240
|
+
== Performance:
|
241
|
+
Although written in pure ruby, packet performs
|
242
|
+
reasonably well. Mongrel, running on top of Packet is a tad
|
243
|
+
slower than Mongrel running on top of EventMachine. More benchmarks coming soon.
|
244
|
+
|
245
|
+
== Credits
|
246
|
+
Francis for awesome EventMachine lib, which has constantly acted as an inspiration.
|
247
|
+
Ezra, for being a early user and porting mongrel to run on top of packet
|
248
|
+
|
249
|
+
|
250
|
+
|
251
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'rake/testtask'
|
7
|
+
require 'spec/rake/spectask'
|
8
|
+
require 'fileutils'
|
9
|
+
def __DIR__
|
10
|
+
File.dirname(__FILE__)
|
11
|
+
end
|
12
|
+
include FileUtils
|
13
|
+
|
14
|
+
NAME = "packet"
|
15
|
+
$LOAD_PATH.unshift __DIR__+'/lib'
|
16
|
+
require 'packet'
|
17
|
+
|
18
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
19
|
+
|
20
|
+
|
21
|
+
@windows = (PLATFORM =~ /win32/)
|
22
|
+
|
23
|
+
SUDO = @windows ? "" : (ENV["SUDO_COMMAND"] || "sudo")
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
desc "Packages up Packet."
|
28
|
+
task :default => [:package]
|
29
|
+
|
30
|
+
task :doc => [:rdoc]
|
31
|
+
|
32
|
+
|
33
|
+
Rake::RDocTask.new do |rdoc|
|
34
|
+
files = ['README', 'LICENSE', 'CHANGELOG',
|
35
|
+
'lib/**/*.rb']
|
36
|
+
rdoc.rdoc_files.add(files)
|
37
|
+
rdoc.main = 'README'
|
38
|
+
rdoc.title = 'Packet Docs'
|
39
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
40
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
41
|
+
end
|
42
|
+
|
43
|
+
spec = Gem::Specification.new do |s|
|
44
|
+
s.name = NAME
|
45
|
+
s.version = Packet::VERSION
|
46
|
+
s.platform = Gem::Platform::RUBY
|
47
|
+
s.has_rdoc = true
|
48
|
+
s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
|
49
|
+
#s.rdoc_options += RDOC_OPTS +
|
50
|
+
# ['--exclude', '^(app|uploads)']
|
51
|
+
s.summary = "Packet, Events... we got em."
|
52
|
+
s.description = s.summary
|
53
|
+
s.author = "Hemant"
|
54
|
+
s.email = 'foo@bar.com'
|
55
|
+
s.homepage = 'http://code.google.com/p/packet/'
|
56
|
+
s.required_ruby_version = '>= 1.8.4'
|
57
|
+
|
58
|
+
s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{bin,spec,lib,examples,script}/**/*")
|
59
|
+
|
60
|
+
s.require_path = "lib"
|
61
|
+
s.bindir = "bin"
|
62
|
+
end
|
63
|
+
|
64
|
+
Rake::GemPackageTask.new(spec) do |p|
|
65
|
+
#p.need_tar = true
|
66
|
+
p.gem_spec = spec
|
67
|
+
end
|
68
|
+
|
69
|
+
task :install do
|
70
|
+
sh %{rake package}
|
71
|
+
sh %{#{SUDO} gem install pkg/#{NAME}-#{Packet::VERSION} --no-rdoc --no-ri}
|
72
|
+
end
|
73
|
+
|
74
|
+
task :uninstall => [:clean] do
|
75
|
+
sh %{#{SUDO} gem uninstall #{NAME}}
|
76
|
+
end
|
77
|
+
|
78
|
+
##############################################################################
|
79
|
+
# SVN
|
80
|
+
##############################################################################
|
81
|
+
|
82
|
+
desc "Add new files to subversion"
|
83
|
+
task :svn_add do
|
84
|
+
system "svn status | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\ /g' | xargs svn add"
|
85
|
+
end
|
86
|
+
|
87
|
+
|
data/TODO
ADDED
File without changes
|
@@ -0,0 +1,215 @@
|
|
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
|
+
PACKET_APP = File.expand_path(File.join(File.dirname(__FILE__) + "/.."))
|
7
|
+
["bin","config","parser","worker","framework","lib","pimp"].each { |x| $LOAD_PATH.unshift(PACKET_APP + "/#{x}")}
|
8
|
+
require "packet"
|
9
|
+
require 'packet'
|
10
|
+
|
11
|
+
module Mongrel
|
12
|
+
class MongrelProtocol < Packet::Connection
|
13
|
+
def post_init
|
14
|
+
@parser = HttpParser.new
|
15
|
+
@params = HttpParams.new
|
16
|
+
@nparsed = 0
|
17
|
+
@request = nil
|
18
|
+
@request_len = nil
|
19
|
+
@linebuffer = ''
|
20
|
+
end
|
21
|
+
|
22
|
+
def receive_data data
|
23
|
+
@linebuffer << data
|
24
|
+
@nparsed = @parser.execute(@params, @linebuffer, @nparsed) unless @parser.finished?
|
25
|
+
if @parser.finished?
|
26
|
+
if @request_len.nil?
|
27
|
+
@request_len = @params[::Mongrel::Const::CONTENT_LENGTH].to_i
|
28
|
+
script_name, path_info, handlers = ::Mongrel::HttpServer::Instance.classifier.resolve(@params[::Mongrel::Const::REQUEST_PATH])
|
29
|
+
if handlers
|
30
|
+
@params[::Mongrel::Const::PATH_INFO] = path_info
|
31
|
+
@params[::Mongrel::Const::SCRIPT_NAME] = script_name
|
32
|
+
@params[::Mongrel::Const::REMOTE_ADDR] = @params[::Mongrel::Const::HTTP_X_FORWARDED_FOR] #|| ::Socket.unpack_sockaddr_in(get_peername)[1]
|
33
|
+
@notifiers = handlers.select { |h| h.request_notify }
|
34
|
+
end
|
35
|
+
if @request_len > ::Mongrel::Const::MAX_BODY
|
36
|
+
new_buffer = Tempfile.new(::Mongrel::Const::MONGREL_TMP_BASE)
|
37
|
+
new_buffer.binmode
|
38
|
+
new_buffer << @linebuffer[@nparsed..-1]
|
39
|
+
@linebuffer = new_buffer
|
40
|
+
else
|
41
|
+
@linebuffer = StringIO.new(@linebuffer[@nparsed..-1])
|
42
|
+
@linebuffer.pos = @linebuffer.length
|
43
|
+
end
|
44
|
+
end
|
45
|
+
if @linebuffer.length >= @request_len
|
46
|
+
@linebuffer.rewind
|
47
|
+
::Mongrel::HttpServer::Instance.process_http_request(@params,@linebuffer,self)
|
48
|
+
@linebuffer.delete if Tempfile === @linebuffer
|
49
|
+
end
|
50
|
+
elsif @linebuffer.length > ::Mongrel::Const::MAX_HEADER
|
51
|
+
close_connection
|
52
|
+
raise ::Mongrel::HttpParserError.new("HEADER is longer than allowed, aborting client early.")
|
53
|
+
end
|
54
|
+
rescue ::Mongrel::HttpParserError
|
55
|
+
if $mongrel_debug_client
|
56
|
+
STDERR.puts "#{Time.now}: BAD CLIENT (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #$!"
|
57
|
+
STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
|
58
|
+
end
|
59
|
+
close_connection
|
60
|
+
rescue Exception => e
|
61
|
+
close_connection
|
62
|
+
raise e
|
63
|
+
end
|
64
|
+
|
65
|
+
def write data
|
66
|
+
send_data data
|
67
|
+
end
|
68
|
+
|
69
|
+
def closed?
|
70
|
+
false
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
class HttpServer
|
76
|
+
def initialize(host, port, num_processors=950, x=0, y=nil) # Deal with Mongrel 1.0.1 or earlier, as well as later.
|
77
|
+
@socket = nil
|
78
|
+
@classifier = URIClassifier.new
|
79
|
+
@host = host
|
80
|
+
@port = port
|
81
|
+
@workers = ThreadGroup.new
|
82
|
+
if y
|
83
|
+
@throttle = x
|
84
|
+
@timeout = y || 60
|
85
|
+
else
|
86
|
+
@timeout = x
|
87
|
+
end
|
88
|
+
@num_processors = num_processors #num_processors is pointless for evented....
|
89
|
+
@death_time = 60
|
90
|
+
self.class.const_set(:Instance,self)
|
91
|
+
end
|
92
|
+
|
93
|
+
def run
|
94
|
+
trap('INT') { raise StopServer }
|
95
|
+
trap('TERM') { raise StopServer }
|
96
|
+
@acceptor = Thread.new do
|
97
|
+
Packet::Reactor.run do |t_reactor|
|
98
|
+
begin
|
99
|
+
t_reactor.start_server(@host,@port,MongrelProtocol)
|
100
|
+
rescue StopServer
|
101
|
+
t_reactor.start_server
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def process_http_request(params,linebuffer,client)
|
108
|
+
if not params[Const::REQUEST_PATH]
|
109
|
+
uri = URI.parse(params[Const::REQUEST_URI])
|
110
|
+
params[Const::REQUEST_PATH] = uri.request_uri
|
111
|
+
end
|
112
|
+
|
113
|
+
raise "No REQUEST PATH" if not params[Const::REQUEST_PATH]
|
114
|
+
|
115
|
+
script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH])
|
116
|
+
|
117
|
+
if handlers
|
118
|
+
notifiers = handlers.select { |h| h.request_notify }
|
119
|
+
request = HttpRequest.new(params, linebuffer, notifiers)
|
120
|
+
|
121
|
+
# request is good so far, continue processing the response
|
122
|
+
response = HttpResponse.new(client)
|
123
|
+
|
124
|
+
# Process each handler in registered order until we run out or one finalizes the response.
|
125
|
+
dispatch_to_handlers(handlers,request,response)
|
126
|
+
|
127
|
+
# And finally, if nobody closed the response off, we finalize it.
|
128
|
+
unless response.done
|
129
|
+
response.finished
|
130
|
+
else
|
131
|
+
response.close_connection
|
132
|
+
end
|
133
|
+
else
|
134
|
+
# Didn't find it, return a stock 404 response.
|
135
|
+
client.send_data(Const::ERROR_404_RESPONSE)
|
136
|
+
client.close_connection
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def dispatch_to_handlers(handlers,request,response)
|
141
|
+
handlers.each do |handler|
|
142
|
+
handler.process(request, response)
|
143
|
+
break if response.done
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class HttpRequest
|
149
|
+
def initialize(params, linebuffer, dispatchers)
|
150
|
+
@params = params
|
151
|
+
@dispatchers = dispatchers
|
152
|
+
@body = linebuffer
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class HttpResponse
|
157
|
+
def send_file(path, small_file = false)
|
158
|
+
File.open(path, "rb") do |f|
|
159
|
+
while chunk = f.read(Const::CHUNK_SIZE) and chunk.length > 0
|
160
|
+
begin
|
161
|
+
write(chunk)
|
162
|
+
rescue Object => exc
|
163
|
+
break
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
@body_sent = true
|
168
|
+
end
|
169
|
+
|
170
|
+
def write(data)
|
171
|
+
@socket.send_data data
|
172
|
+
end
|
173
|
+
|
174
|
+
def close_connection_after_writing
|
175
|
+
@socket.close_connection
|
176
|
+
end
|
177
|
+
|
178
|
+
def socket_error(details)
|
179
|
+
@socket.close_connection
|
180
|
+
done = true
|
181
|
+
raise details
|
182
|
+
end
|
183
|
+
|
184
|
+
def finished
|
185
|
+
send_status
|
186
|
+
send_header
|
187
|
+
send_body
|
188
|
+
@socket.close_connection
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
class Configurator
|
193
|
+
# This version fixes a bug in the regular Mongrel version by adding
|
194
|
+
# initialization of groups.
|
195
|
+
def change_privilege(user, group)
|
196
|
+
if user and group
|
197
|
+
log "Initializing groups for {#user}:{#group}."
|
198
|
+
Process.initgroups(user,Etc.getgrnam(group).gid)
|
199
|
+
end
|
200
|
+
|
201
|
+
if group
|
202
|
+
log "Changing group to #{group}."
|
203
|
+
Process::GID.change_privilege(Etc.getgrnam(group).gid)
|
204
|
+
end
|
205
|
+
|
206
|
+
if user
|
207
|
+
log "Changing user to #{user}."
|
208
|
+
Process::UID.change_privilege(Etc.getpwnam(user).uid)
|
209
|
+
end
|
210
|
+
rescue Errno::EPERM
|
211
|
+
log "FAILED to change user:group #{user}:#{group}: #$!"
|
212
|
+
exit 1
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|