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