scgi 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/COPYING +21 -0
  2. data/README +331 -0
  3. data/bin/scgi_ctrl +132 -0
  4. data/lib/RailsSCGIProcessor.rb +28 -0
  5. data/lib/scgi.rb +272 -0
  6. metadata +49 -0
data/COPYING ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2007 Jeremy Evans
2
+ Copyright (c) 2004 Zed A. Shaw
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,331 @@
1
+ = ruby-scgi
2
+
3
+ ruby-scgi is a small Ruby script for running Ruby on Rails (and possibly other
4
+ web applications) for high-speed deployment of your applications in production.
5
+ It is intended as a replacement for the ancient FastCGI code base and bring
6
+ some big advantages to Rails deployment for production operations.
7
+
8
+ SCGI (Simple Common Gateway Interface) is a project to replace CGI and FastCGI
9
+ with a simpler protocol to both implement and manage. It was written by Neil
10
+ Schemenauer and adopted by many Python developers as a hosting option.
11
+
12
+ ruby-scgi is distributed as a gem, and can be installed with:
13
+
14
+ sudo gem install scgi
15
+
16
+ Feedback/Bugs/Support Requests should be handled through RubyForge at
17
+ http://rubyforge.org/projects/scgi/.
18
+
19
+ The RDoc is available at http://code.jeremyevans.net/doc/ruby-scgi/.
20
+ Subversion access is available at svn://code.jeremyevans.net/ruby-scgi/.
21
+
22
+ == Advantages
23
+
24
+ * Simultaneous support for Apache1, Apache2, and lighttpd on OSX and most
25
+ Linux/BSD systems.
26
+ * Same performance as FastCGI and better performance than other methods.
27
+ * Simple to install, run, and configure.
28
+ * Supports both single-port and multi-port clustering on most systems for that
29
+ extra boost in concurrency.
30
+ * Supports both command line and config file configuration.
31
+ * Gives out limited status information to help manage your Rails application's
32
+ resources.
33
+ * Makes it easy to manage your production deployment, and you can even run your
34
+ application in development mode exactly the same way as with script/server
35
+ for extra testing efficiency.
36
+ * You can set a maximum concurrent connections limit, which causes any
37
+ connections over the limit to get redirected to a /busy.html file. This can
38
+ help keep your site alive under heavy load.
39
+ * Simple to configure with your web server. Even if you use clustering you'll
40
+ be able to manage your webserver and Rails application independently.
41
+ * Reasonable defaults for almost everything based on user feedback.
42
+ * Completely free code licensed under Rails's own license.
43
+ * No external dependencies other than Ruby
44
+ * The core implementation and the command line tools are easily extensible for
45
+ other Ruby web frameworks.
46
+
47
+ == Comparison With FastCGI
48
+
49
+ SCGI and FastCGI have similar goals: To keep Ruby running between requests and
50
+ process the requests as fast as possible. The difference is that SCGI is much
51
+ simpler and easier to implement so there's less chance to get it wrong.
52
+
53
+ Specifically, ruby-scgi is written in pure Ruby so it doesn't leak memory,
54
+ runs everywhere, and is easy to install (no compilers needed).
55
+
56
+ One thing that SCGI doesn't support is using UNIX Domain sockets in addition to
57
+ TCP/IP sockets. This isn't really needed, but it is handy in a shared hosting
58
+ situation where you don't want others connecting to your processes or if you
59
+ have to request open ports. Sorry, no UNIX Domain sockets in SCGI.
60
+
61
+ == Comparison With WEBrick
62
+
63
+ In theory WEBrick should be able to run just as fast as SRR. They are both
64
+ written in pure Ruby. They both do similar processing (although WEBrick's are
65
+ a little more complicated). They both return about the same amount of data.
66
+
67
+ In practice WEBrick in production mode runs much slower than SRR in production
68
+ mode. The (dis)advantage (depending on your point of view) is that you have to
69
+ manage your webserver differently than you manage your application.
70
+
71
+ == Comparison With CGI
72
+
73
+ CGI is where every time a request comes in for rails the whole Ruby on Rails
74
+ framework is loaded. This is very slow, but it's easy to install.
75
+
76
+ An alternative is to use the cgi2scgi program distributed with the SCGI source
77
+ available from http://www.mems-exchange.org/software/scgi/ along with the
78
+ Apache modules. This program basically is a small little C program that runs
79
+ quickly as a CGI, but passes it's requests to your SRR backend. It's not all
80
+ that fast, but if you're stuck with cgi-bin only access then this might be just
81
+ the way to go. Since SCGI runs over TCP/IP you can even host your SRR on a
82
+ totally different machine with this.
83
+
84
+ == Running and Configuration
85
+
86
+ If you want to start ruby-scgi with the default configuration, just run:
87
+
88
+ scgi_ctrl -d /path/to/rails/app start
89
+
90
+ To stop the application:
91
+
92
+ scgi_ctrl -d /path/to/rails/app stop
93
+
94
+ To restart the application:
95
+
96
+ scgi_ctrl -d /path/to/rails/app start # start actually restarts
97
+
98
+ Note that restarting/stopping is controlled by a pid file (the location is
99
+ configurable). If the pidfile exists, it is read and the pids in it are
100
+ killed. If restarting, new processes are forked after the existing processes
101
+ are killed.
102
+
103
+ To see the possible and default configuration options, just run the program
104
+ without any arguments:
105
+
106
+ scgi_ctrl [option=value, ...] (start|stop)
107
+ Options:
108
+ -b, --bind IP address to bind to [127.0.0.1]
109
+ -c, --config Location of config file [config/scgi.yaml]
110
+ -d, --directory Working directory [.]
111
+ -e, --environment Environment (for Rails) [production]
112
+ -f, --fork Number of listners on each port [1]
113
+ -l, --logfile Location of log file [log/scgi.log]
114
+ -m, --maxconns Maximum number of concurrent users [2**30-1]
115
+ -n, --number Number of ports to bind to [1]
116
+ -p, --port Starting port to bind to [9999]
117
+ -P, --pidfile Location of pid file [log/scgi.pid]
118
+ -r, --processor Type of processor to use [Rails]
119
+
120
+ Note that the -d (--directory) option changes the working directory of the
121
+ process, so the -c, -l, and -P options are relative to that.
122
+
123
+ Here's a longer explanation of the options:
124
+
125
+ -b, --bind IP address to bind to [127.0.0.1]
126
+
127
+ This is the TCP/IP networking sockets to bind to. It defaults to the
128
+ loopback address because generally the web application runs on the same
129
+ physical server as the web server. If this is not the case, change it to an
130
+ externally available IP, and make sure to lock down access to the port via a
131
+ firewall.
132
+
133
+ -c, --config Location of config file [config/scgi.yaml]
134
+
135
+ This is the configuration file for ruby-scgi. It is recommended that you use
136
+ this instead of the command line configuration, as it saves typing. This
137
+ path is relative to the working directory, so if it is not inside the working
138
+ directory, make sure you specify an absolute path. Also, note that this is
139
+ the only option that is not configurable from the configuration file.
140
+
141
+ -d, --directory Working directory [.]
142
+
143
+ This is the working directory of the process. It should generally be the
144
+ path to the root of the web application. Alternatively, you can change to
145
+ the root of the web application before hand and then not use this option.
146
+
147
+ -e, --environment Environment (for Rails) [production]
148
+
149
+ This is the only option that is Rails-specific, allowing you to specify the
150
+ Rails environment on the command line. It defaults to production because
151
+ that is the general use case for ruby-scgi.
152
+
153
+ -f, --fork Number of listners on each port [1]
154
+
155
+ This enables single-port clustering of processes, so there are multiple
156
+ processes listening on each port. This can simplify configuration of the
157
+ webserver, since only a single port need be specified, and can also eliminate
158
+ the need for a proxy such as pound or pen to handle this for you. It
159
+ defaults to one process per port. Try single port clustering first, and if
160
+ it is not stable, switch to multiple port clustering. It is possible to use
161
+ both as once.
162
+
163
+ -l, --logfile Location of log file [log/scgi.log]
164
+
165
+ This is the location of the log file, relative to the working directory.
166
+ ruby-scgi doesn't log all that much (starts, shutdowns, bad requests, other
167
+ errors, and status info when sent SIGUSR2).
168
+
169
+ -m, --maxconns Maximum number of concurrent users [2**30-1]
170
+
171
+ The maximum number of concurrent connections. If more connections that this
172
+ are sent to the server, it redirects them to the /busy.html file.
173
+
174
+ -n, --number Number of ports to bind to [1]
175
+
176
+ This enables multi-port clustering. Multi-port clustering listens on
177
+ multiple ports starting with the port specified (so port, port+1, port+2,
178
+ ...). This makes webserver configuration a little more difficult, and might
179
+ also require a separate proxy such as pound or pen, so you should try
180
+ single-port clustering first. You can run both at once if you want.
181
+
182
+ -p, --port Starting port to bind to [9999]
183
+
184
+ This is the starting (or only) port that ruby-scgi will use. If multi-port
185
+ clustering is used, all ports will be greater than this one.
186
+
187
+ -P, --pidfile Location of pid file [log/scgi.pid]
188
+
189
+ This is the pid file, relative to the working directory. The pid file is
190
+ necessary, as it is what is used to specify which pids to kill when stopping
191
+ or restarting. If incorrect information is in the pid file, the processes
192
+ won't be stopped when they should be, and you will probably won't be able
193
+ to start new processes (because the ports will still be in use).
194
+
195
+ -r, --processor Type of processor to use [Rails]
196
+
197
+ This is the type of processor to use, it defaults to Rails, as that is the
198
+ only one currently supported. Adding other processers is fairly easy, just
199
+ make the processor is in a file named XXXXXSCGIProcessor.rb (where XXXXX is
200
+ in the name of the processor), and that file is located in ruby's library
201
+ path (the RUBYLIB environment variable). See RailsSCGIProcessor.rb for an
202
+ example.
203
+
204
+ Each of the options can also be specified in the config file as a symbol. An
205
+ example config file would be:
206
+
207
+ ---
208
+ :port: 4000
209
+ :fork: 3
210
+
211
+ This sets up a single-port cluster on port 4000 with 3 listening processes.
212
+
213
+ == Example configurations
214
+
215
+ Note that ruby-scgi is only tested on Lighttpd. Also, note that Lighttpd
216
+ 1.4.16 has a bug which breaks redirects using server.error-handler-404, so
217
+ either use mod_magnet, wait for 1.4.17, or apply the patch in ticket
218
+ 1270 on Lighttpd's Trac.
219
+
220
+ Lighttpd:
221
+
222
+ server.modules = ( ... "mod_scgi" ... )
223
+ server.error-handler-404 = "/dispatch.scgi"
224
+
225
+ # For Single Process or Single-Port Clustering
226
+ scgi.server = ( "dispatch.scgi" => (
227
+ "server1" => (
228
+ "host" => "127.0.0.1",
229
+ "port" => 9999,
230
+ "check-local" => "disable",
231
+ "disable-time" => 0)
232
+ ))
233
+
234
+ # For Multi-Port Clustering
235
+ scgi.server = ( "dispatch.scgi" => (
236
+ "server1" => (
237
+ "host" => "127.0.0.1",
238
+ "port" => 9997,
239
+ "check-local" => "disable",
240
+ "disable-time" => 0),
241
+ "server2" => (
242
+ "host" => "127.0.0.1",
243
+ "port" => 9998,
244
+ "check-local" => "disable",
245
+ "disable-time" => 0),
246
+ "server3" => (
247
+ "host" => "127.0.0.1",
248
+ "port" => 9999,
249
+ "check-local" => "disable",
250
+ "disable-time" => 0)
251
+ ))
252
+
253
+ Apache:
254
+
255
+ <VirtualHost your-ip:80>
256
+ AddDefaultCharset utf-8
257
+ ServerName www.yourdomain
258
+ DocumentRoot /your-switchtower-root/current/public
259
+ ErrorDocument 500 /500.html
260
+ ErrorDocument 404 /404.html
261
+ # handle all requests throug SCGI
262
+ SCGIMount / 127.0.0.1:9999
263
+ # matches locations with a dot following at least one more characters,
264
+ # that is, things like *,html, *.css, *.js, which should be delivered
265
+ # directly from the filesystem
266
+ <LocationMatch \..+$>
267
+ # don't handle those with SCGI
268
+ SCGIHandler Off
269
+ </LocationMatch>
270
+ <Directory /your-switchtower-root/current/public/>
271
+ Options +FollowSymLinks
272
+ Order allow,deny
273
+ allow from all
274
+ </Directory>
275
+ </VirtualHost>
276
+
277
+ == Security
278
+
279
+ Alright, listen up. I'm not gonna have people trying to take me to court
280
+ because they think I didn't tell them about security problems. Here are the
281
+ main attack vectors you should be aware of when running this:
282
+
283
+ * POSIX signals are bad if you're in a shared hosting setup that configures all
284
+ processes to run as a common user like *nobody*. If your provider does this
285
+ then you should use something else or find a better provider.
286
+ * The config file, pid file, and log file and directories should have
287
+ appropriate permissions. The config file should ideally be readable but not
288
+ writable by the user running scgi_ctrl. The pid and log file directory
289
+ should be writable by the user running scgi_ctrl and by no other user. If
290
+ the config file is writable by a non-trusted user, they could potentially
291
+ run arbitrary code, and they could certainly open arbitrary ports and or
292
+ attempt denial of service. If the pid file is writable by a non-trusted
293
+ user, it could cause arbitrary processes to be killed by the user running
294
+ scgi_ctrl
295
+ * Never run scgi_ctrl as root. If you don't know why you should read up about
296
+ the unix security model before deploying any more software.
297
+
298
+ == Changes from previous versions
299
+
300
+ * Single-port clustering is back
301
+ * scgi_ctrl is fully configurable on the command line
302
+ * Clustering and processing are now built into scgi_ctrl
303
+ * DRb, Win32, and throttling are no longer supported
304
+ * Soft reconfiguration is no longer supported (no SIGUSR1)
305
+ * Restarting via SIGHUP is no longer supported (SIGHUP is the same as SIGINT)
306
+ * The only commands available to scgi_ctrl are start and stop
307
+
308
+ == FAQ
309
+
310
+ Q: Have you been living under a rock for the last two years? Mongrel/Nginx is
311
+ the new hotness!
312
+
313
+ A: Well, aren't you snotty. You can certainly use Mongrel if you want. The
314
+ memory/performance differences are small, and it is probably better maintained.
315
+ ruby-scgi may have simpler clustering, and may be useful for certain legacy
316
+ setups. Also, it works well and it's been working for me for the last few
317
+ years, so I haven't felt the need to change.
318
+
319
+ Q: Does it work with Capistrano yet?
320
+
321
+ A: I haven't tried. If you have luck, let me know.
322
+
323
+ Q: Is there an easy way to reload? I don't want to take the whole thing down
324
+ just to deploy new code.
325
+
326
+ A: scgi_ctrl always uses SIGINT to stop processes, which allows existing
327
+ clients to finish. Restarting may have problems if a lot of clients are
328
+ connected and the processes can't shutdown before the new listening socket is
329
+ bound. This hasn't been a problem for me yet, but it is a possible race
330
+ condition. In case it affects you, you may have to run scgi_ctrl start again
331
+ if you get an error binding to a port.
@@ -0,0 +1,132 @@
1
+ #!/usr/local/bin/ruby
2
+ require 'getoptlong'
3
+ require 'yaml'
4
+ SCGI_DEFAULT_CONFIG = {:pidfile=>'log/scgi.pid', :number=>1, :port=>9999,
5
+ :processor=>'Rails', :fork=>1, :logfile=>'log/scgi.log', :maxconns=>2**30-1,
6
+ :bind=>'127.0.0.1', :command=>''}
7
+ SCGI_CONFIG = {}
8
+
9
+ def config_file_options(filename)
10
+ begin
11
+ config = YAML.load(File.read(filename))
12
+ config.is_a?(Hash) ? config : Hash.new
13
+ rescue
14
+ Hash.new
15
+ end
16
+ end
17
+
18
+ def get_processor(string)
19
+ raise NameError, "#{string} is not a valid processor!" unless string =~ /\A[A-Z][A-ZA-z]*\z/
20
+ processor = "#{string}SCGIProcessor"
21
+ require processor
22
+ eval(processor)
23
+ end
24
+
25
+ def parse_options
26
+ config = {:config => 'config/scgi.yaml'}
27
+ opts = GetoptLong.new(
28
+ [ '--bind', '-b', GetoptLong::REQUIRED_ARGUMENT ],
29
+ [ '--config', '-c', GetoptLong::REQUIRED_ARGUMENT ],
30
+ [ '--directory', '-d', GetoptLong::REQUIRED_ARGUMENT ],
31
+ [ '--environment', '-e', GetoptLong::REQUIRED_ARGUMENT ],
32
+ [ '--fork', '-f', GetoptLong::REQUIRED_ARGUMENT ],
33
+ [ '--logfile', '-l', GetoptLong::REQUIRED_ARGUMENT ],
34
+ [ '--maxconns', '-m', GetoptLong::REQUIRED_ARGUMENT ],
35
+ [ '--number', '-n', GetoptLong::REQUIRED_ARGUMENT ],
36
+ [ '--port', '-p', GetoptLong::REQUIRED_ARGUMENT ],
37
+ [ '--pidfile', '-P', GetoptLong::REQUIRED_ARGUMENT ],
38
+ [ '--processor', '-r', GetoptLong::REQUIRED_ARGUMENT ]
39
+ )
40
+ opts.each do |opt, arg|
41
+ case opt
42
+ when '--bind'
43
+ config[:bind] = arg
44
+ when '--config'
45
+ config[:config] = arg
46
+ when '--directory'
47
+ Dir.chdir(arg)
48
+ when '--environment'
49
+ config[:environment] = arg
50
+ when '--fork'
51
+ config[:fork] = arg.to_i
52
+ when '--logfile'
53
+ config[:logile] = arg
54
+ when '--maxconns'
55
+ config[:maxconns] = arg.to_i
56
+ when '--number'
57
+ config[:number] = arg.to_i
58
+ when '--port'
59
+ config[:port] = arg.to_i
60
+ when '--pidfile'
61
+ config[:pidfile] = arg
62
+ when '--processor'
63
+ config[:processor] = arg
64
+ end
65
+ end
66
+
67
+ # Configuration Precedence: Command Line > Config File > Default
68
+ SCGI_CONFIG.merge!(SCGI_DEFAULT_CONFIG)
69
+ SCGI_CONFIG.merge!(config_file_options(config[:config]))
70
+ SCGI_CONFIG.merge!(config)
71
+ end
72
+
73
+ def process(command)
74
+ case command
75
+ when 'start'
76
+ stop
77
+ start
78
+ when 'stop'
79
+ stop
80
+ else
81
+ puts usage
82
+ end
83
+ end
84
+
85
+ def start
86
+ processor = get_processor(SCGI_CONFIG[:processor]).new(SCGI_CONFIG)
87
+ pids = []
88
+ SCGI_CONFIG[:number].times do |i|
89
+ socket = TCPServer.new(SCGI_CONFIG[:bind], port = SCGI_CONFIG[:port]+i)
90
+ SCGI_CONFIG[:fork].times do
91
+ if pid = fork
92
+ pids << pid
93
+ else
94
+ $0 = "scgi-#{SCGI_CONFIG[:processor]} dir:#{Dir.pwd} port:#{port}"
95
+ return processor.listen(socket)
96
+ end
97
+ end
98
+ end
99
+ File.open(SCGI_CONFIG[:pidfile], 'wb'){|file| file.print("#{pids.join(' ')}")}
100
+ end
101
+
102
+ def stop
103
+ if File.file?(SCGI_CONFIG[:pidfile])
104
+ pids = nil
105
+ File.open(SCGI_CONFIG[:pidfile], 'rb'){|f| pids = f.read.split.collect{|x| x.to_i if x.to_i > 0}.compact}
106
+ if pids.length > 0
107
+ Process.kill("INT", *pids) rescue return nil
108
+ File.delete(SCGI_CONFIG[:pidfile])
109
+ end
110
+ end
111
+ end
112
+
113
+ def usage
114
+ <<-END
115
+ scgi_ctrl [option=value, ...] (start|stop)
116
+ Options:
117
+ -b, --bind IP address to bind to [127.0.0.1]
118
+ -c, --config Location of config file [config/scgi.yaml]
119
+ -d, --directory Working directory [.]
120
+ -e, --environment Environment (for Rails) [production]
121
+ -f, --fork Number of listners on each port [1]
122
+ -l, --logfile Location of log file [log/scgi.log]
123
+ -m, --maxconns Maximum number of concurrent users [2**30-1]
124
+ -n, --number Number of ports to bind to [1]
125
+ -p, --port Starting port to bind to [9999]
126
+ -P, --pidfile Location of pid file [log/scgi.pid]
127
+ -r, --processor Type of processor to use [Rails]
128
+ END
129
+ end
130
+
131
+ parse_options
132
+ process(ARGV[0])
@@ -0,0 +1,28 @@
1
+ #!/usr/local/bin/ruby
2
+ require 'scgi'
3
+
4
+ # This SCGI::Processor subclass hooks the SCGI request into Ruby on Rails.
5
+ class RailsSCGIProcessor < SCGI::Processor
6
+ # Initialzes Rails with the appropriate environment and settings
7
+ def initialize(settings)
8
+ ENV["RAILS_ENV"] = settings[:environment] || 'production'
9
+ require "config/environment"
10
+ ActiveRecord::Base.threaded_connections = false
11
+ require 'dispatcher'
12
+ super(settings)
13
+ @guard = Mutex.new
14
+ end
15
+
16
+ # Submits requests to Rails in a single threaded fashion
17
+ def process_request(request, body, socket)
18
+ return if socket.closed?
19
+ cgi = SCGI::CGIFixed.new(request, body, socket)
20
+ begin
21
+ @guard.synchronize{Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)}
22
+ rescue IOError
23
+ @log.error("received IOError #$! when handling client. Your web server doesn't like me.")
24
+ rescue Object => rails_error
25
+ @log.error("calling Dispatcher.dispatch", rails_error)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,272 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ require 'stringio'
4
+ require 'socket'
5
+ require 'cgi'
6
+ require 'monitor'
7
+ require 'singleton'
8
+
9
+ module SCGI
10
+ # A factory that makes Log objects, making sure that one Log is associated
11
+ # with each log file.
12
+ class LogFactory < Monitor
13
+ include Singleton
14
+
15
+ def initialize
16
+ super()
17
+ @@logs = {}
18
+ end
19
+
20
+ def create(file)
21
+ synchronize{@@logs[file] ||= Log.new(file)}
22
+ end
23
+ end
24
+
25
+ # A simple Log class that has an info and error method for output
26
+ # messages to a log file. The main thing this logger does is
27
+ # include the process ID in the logs so that you can see which child
28
+ # is creating each message.
29
+ class Log < Monitor
30
+ def initialize(file)
31
+ super()
32
+ @out = open(file, "a+")
33
+ @out.sync = true
34
+ @pid = Process.pid
35
+ @info = "[INF][#{@pid}] "
36
+ @error = "[ERR][#{@pid}] "
37
+ end
38
+
39
+ def info(msg)
40
+ synchronize{@out.print("#{@info}#{msg}\n")}
41
+ end
42
+
43
+ # If an exception is given then it will print the exception and a stack trace.
44
+ def error(msg, exc=nil)
45
+ return synchronize{@out.print("#{@error}#{msg}\n")} unless exc
46
+ synchronize{@out.print("#{@error}#{msg}: #{exc}\n#{exc.backtrace.join("\n")}\n")}
47
+ end
48
+ end
49
+
50
+ # Modifies CGI so that we can use it. Main thing it does is expose
51
+ # the stdinput and stdoutput so SCGI::Processor can connect them to
52
+ # the right sources. It also exposes the env_table so that SCGI::Processor
53
+ # and hook the SCGI parameters into the environment table.
54
+ class CGIFixed < ::CGI
55
+ public :env_table
56
+ attr_reader :args, :env_table
57
+
58
+ def initialize(params, data, out, *args)
59
+ @env_table = params
60
+ @args = *args
61
+ @input = StringIO.new(data)
62
+ @out = out
63
+ super(*args)
64
+ end
65
+
66
+ def stdinput
67
+ @input
68
+ end
69
+
70
+ def stdoutput
71
+ @out
72
+ end
73
+ end
74
+
75
+ # This is the complete guts of the SCGI system. It is designed so that
76
+ # people can take it and implement it for their own systems, not just
77
+ # Ruby on Rails. This implementation is not complete since you must
78
+ # create your own that implements the process_request method.
79
+ #
80
+ # The SCGI protocol only works with TCP/IP sockets and not domain sockets.
81
+ # It might be useful for shared hosting people to have domain sockets, but
82
+ # they aren't supported in Apache, and in lighttpd they're unreliable.
83
+ # Also, domain sockets don't work so well on Windows.
84
+ class Processor < Monitor
85
+ attr_reader :settings
86
+
87
+ def initialize(settings = {})
88
+ @total_conns = 0
89
+ @shutdown = false
90
+ @dead = false
91
+ @threads = Queue.new
92
+ @settings = settings
93
+ @log = LogFactory.instance.create(settings[:logfile])
94
+ @host = settings[:bind]
95
+ @port = settings[:port]
96
+ @maxconns = settings[:maxconns]
97
+ super()
98
+ setup_signals
99
+ end
100
+
101
+ # Starts the SCGI::Processor having it listen on either the
102
+ # given socket or listening to a new socket on the @host/@port
103
+ # configured. The option to give listen a socket is there so
104
+ # that others can create one socket, and then fork several processors
105
+ # to listen to it.
106
+ #
107
+ # This function does not return until a shutdown.
108
+ def listen(socket = nil)
109
+ @log.info("Started listening on #{@host}:#{@port} at #{@started = Time.now}")
110
+ @socket = socket || TCPServer.new(@host, @port)
111
+
112
+ # we also need a small collector thread that does nothing
113
+ # but pull threads off the thread queue and joins them
114
+ @collector = Thread.new do
115
+ while t = @threads.shift
116
+ collect_thread(t)
117
+ @total_conns += 1
118
+ end
119
+ end
120
+
121
+ thread = Thread.new do
122
+ loop do
123
+ handle_client(@socket.accept)
124
+ break if @shutdown and @threads.length <= 0
125
+ end
126
+ end
127
+
128
+ # and then collect the listener thread which blocks until it exits
129
+ collect_thread(thread)
130
+
131
+ @socket.close unless @socket.closed?
132
+ @dead = true
133
+ @log.info("Exited accept loop. Shutdown complete.")
134
+ end
135
+
136
+ def collect_thread(thread)
137
+ begin
138
+ thread.join
139
+ rescue Interrupt
140
+ @log.info("Shutting down from SIGINT.")
141
+ rescue IOError
142
+ @log.error("received IOError #$!. Web server may possibly be configured wrong.")
143
+ rescue Object
144
+ @log.error("Collecting thread", $!)
145
+ end
146
+ end
147
+
148
+ # Internal function that handles a new client connection.
149
+ # It spawns a thread to handle the client and registers it in the
150
+ # @threads queue. A collector thread is responsible for joining these
151
+ # and clearing them out. This design is needed because Ruby's GC
152
+ # doesn't seem to deal with threads as well as others believe.
153
+ #
154
+ # Depending on how your system works, you may need to synchronize
155
+ # inside your process_request implementation. scgi_rails.rb
156
+ # does this so that Rails will run as if it were single threaded.
157
+ #
158
+ # It also handles calculating the current and total connections,
159
+ # and deals with the graceful shutdown. The important part
160
+ # of graceful shutdown is that new requests get redirected to
161
+ # the /busy.html file.
162
+ #
163
+ def handle_client(socket)
164
+ # ruby's GC seems to do weird things if we don't assign the thread to a local variable
165
+ @threads << Thread.new do
166
+ begin
167
+ len = ""
168
+ # we only read 10 bytes of the length. any request longer than this is invalid
169
+ while len.length <= 10
170
+ c = socket.read(1)
171
+ break if c == ':' # found the terminal, len now has a length in it so read the payload
172
+ len << c
173
+ end
174
+
175
+ # we should now either have a payload length to get
176
+ payload = socket.read(len.to_i)
177
+ if socket.read(1) == ','
178
+ read_header(socket, payload)
179
+ else
180
+ @log.error("Malformed request, does not end with ','")
181
+ end
182
+ rescue Object
183
+ @log.error("Handling client", $!)
184
+ ensure
185
+ # no matter what we have to put this thread on the bad list
186
+ socket.close if not socket.closed?
187
+ end
188
+ end
189
+ end
190
+
191
+ # Does three jobs: reads and parses the SCGI netstring header,
192
+ # reads any content off the socket, and then either calls process_request
193
+ # or immediately returns a redirect to /busy.html for some connections.
194
+ #
195
+ # The browser/connection that will be redirected to /busy.html if
196
+ # either SCGI::Processor is in the middle of a shutdown, or if the
197
+ # number of connections is over the @maxconns. This redirect is
198
+ # immediate and doesn't run inside the interpreter, so it will happen with
199
+ # much less processing and help keep your system responsive.
200
+ def read_header(socket, payload)
201
+ return if socket.closed?
202
+ request = Hash[*(payload.split("\0"))]
203
+ if request["CONTENT_LENGTH"]
204
+ length = request["CONTENT_LENGTH"].to_i
205
+ body = length > 0 ? socket.read(length) : ''
206
+
207
+ if @shutdown or @threads.length > @maxconns
208
+ socket.write("Location: /busy.html\r\nCache-control: no-cache, must-revalidate\r\nExpires: Mon, 26 Jul 1997 05:00:00 GMT\r\nStatus: 307 Temporary Redirect\r\n\r\n")
209
+ else
210
+ process_request(request, body, socket)
211
+ end
212
+ end
213
+ end
214
+
215
+ # You must implement this yourself. The request is a Hash
216
+ # of the CGI parameters from the webserver. The body is the
217
+ # raw CGI body. The socket is where you write your results
218
+ # (properly HTTP formatted) back to the webserver.
219
+ def process_request(request, body, socket)
220
+ raise "You must implement process_request"
221
+ end
222
+
223
+ # Sets up the POSIX signals:
224
+ #
225
+ # * TERM -- Forced shutdown.
226
+ # * INT -- Graceful shutdown.
227
+ # * HUP -- Graceful shutdown.
228
+ # * USR2 -- Dumps status info to the logs. Super ugly.
229
+ def setup_signals
230
+ trap("TERM") { @log.info("SIGTERM, forced shutdown."); shutdown(force=true) }
231
+ trap("INT") { @log.info("SIGINT, graceful shutdown started."); shutdown }
232
+ trap("HUP") { @log.info("SIGHUP, graceful shutdown started."); shutdown }
233
+ trap("USR2") { @log.info(status_info) }
234
+ end
235
+
236
+ # Returns a Hash with status information. This is used
237
+ # when dumping data to the logs
238
+ def status_info
239
+ {
240
+ :time => Time.now, :pid => Process.pid, :settings => @settings,
241
+ :environment => @settings[:environment], :started => @started,
242
+ :max_conns => @maxconns, :conns => @threads.length, :systimes => Process.times,
243
+ :shutdown => @shutdown, :dead => @dead, :total_conns => @total_conns
244
+ }.inspect
245
+ end
246
+
247
+ # When called it will set the @shutdown flag indicating to the
248
+ # SCGI::Processor.listen function that all new connections should
249
+ # be set to /busy.html, and all current connections should be
250
+ # "valved" off. Once all the current connections are gone the
251
+ # SCGI::Processor.listen function will exit.
252
+ #
253
+ # Use the force=true parameter to force an immediate shutdown.
254
+ # This is done by closing the listening socket, so it's rather
255
+ # violent.
256
+ def shutdown(force = false)
257
+ synchronize do
258
+ @shutdown = true
259
+
260
+ if @threads.length == 0
261
+ @log.info("Immediate shutdown since nobody is connected.")
262
+ @socket.close
263
+ elsif force
264
+ @log.info("Forcing shutdown. You may see exceptions.")
265
+ @socket.close
266
+ else
267
+ @log.info("Shutdown requested. Beginning graceful shutdown with #{@threads.length} connected.")
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: scgi
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.6.0
7
+ date: 2007-08-13 00:00:00 -07:00
8
+ summary: Simple support for using SCGI in ruby apps, such as Rails
9
+ require_paths:
10
+ - lib
11
+ email: code@jeremyevans.net
12
+ homepage:
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Jeremy Evans
31
+ files:
32
+ - COPYING
33
+ - README
34
+ - lib/scgi.rb
35
+ - lib/RailsSCGIProcessor.rb
36
+ test_files: []
37
+
38
+ rdoc_options: []
39
+
40
+ extra_rdoc_files: []
41
+
42
+ executables:
43
+ - scgi_ctrl
44
+ extensions: []
45
+
46
+ requirements: []
47
+
48
+ dependencies: []
49
+