Ruby/Rack/Rails FastCGI
Using Rails with FastCGI is supposed to be easy. You just need a “start” script (usually saved as public/dispatch.fcgi, although this is not a real requirement).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
require_relative '../config/environment'
class Rack::PathInfoRewriter
def initialize(app)
@app = app
end
def call(env)
env.delete('SCRIPT_NAME')
parts = env['REQUEST_URI'].split('?')
env['PATH_INFO'] = parts[0]
env['QUERY_STRING'] = parts[1].to_s
@app.call(env)
end
end
Rack::Handler::FastCGI.run Rack::PathInfoRewriter.new(APPNAME::Application)
|
You’ll have to add ‘fcgi’ to your Gemfile.
I use the following run script with runit:
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/bin/sh
exec 2>&1
export RAILS_ENV=production
export LANG=en_US.UTF-8
cd /srv/www/users/www-user/MyApp
export HOME=~www-user
exec /usr/bin/spawn-fcgi -s /var/run/lighttpd2-MyApp-www-user.sock -n -u www-user -U www-data -l -- \
/srv/www/users/www-user/.rvm/bin/ruby-1.9.3-p194 /srv/www/users/www-user/MyApp/public/dispatch.fcgi
|
EPIPE bug
If you’re using unix sockets for your application, you might see this (or similar) from time to time:
~/.rvm/gems/ruby-1.9.3-p194/gems/rack-1.4.1/lib/rack/handler/fastcgi.rb:93:in `flush': Broken pipe (Errno::EPIPE)
This happens if the client closes the connection to the web server, and the web server closes the connection to the FastCGI backend.
The problem is that ruby terminates (this is not an “exception”, so you cannot catch it); a good sysadmin will use runit (or similar), so it will be restarted, but as long as it is down, connections to the backend won’t get accepted, resulting in 500 Internal Server errors or similar.
To fix this bug, we need to change two things:
ruby-fcgi
My version is fcgi-0.8.8 from 2010… I changed the CHECK_STREAM_ERROR in fcgi-0.8.8/ext/fcgi/fcgi.c (whitespace fail – mixing tabs and spaces):
1 2 3 |
- errno = err;\
- rb_sys_fail(NULL);\
+ rb_raise(eFCGIStreamError, "unknown error (syscall error)");\
|
Rebuild and install it (shell in fcgi-0.8.8/ext/fcgi):
1 |
make && make install
|
Now we get a real exception we can catch:
rack-fastcgi
rack-1.4.1/lib/rack/handler/fastcgi.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
--- fastcgi.rb.old 2012-09-11 23:15:40.436000011 +0000
+++ fastcgi.rb 2012-09-11 22:52:07.380000011 +0000
@@ -25,7 +25,11 @@
STDIN.reopen(TCPServer.new(options[:Host], options[:Port]))
end
FCGI.each { |request|
- serve request, app
+ begin
+ serve request, app
+ rescue StandardError => e
+ STDOUT.print "#{e}\n#{e.backtrace.join("\n")}\n"
+ end
}
end
|
The other parts of the handler already handle exceptions (closing the request and so on).
Now you still get the errors in the log, but ruby doesn’t crash anymore.