scgi 0.6.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.
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
+