Class Camping::FastCGI
In: lib/camping/fastcgi.rb
Parent: Object

Camping::FastCGI is a small class for hooking one or more Camping apps up to FastCGI. Generally, you‘ll use this class in your application‘s postamble.

The Smallest Example

 if __FILE__ == $0
   require 'camping/fastcgi'
   Camping::FastCGI.start(YourApp)
 end

This example is stripped down to the basics. The postamble has no database connection. It just loads this class and calls Camping::FastCGI.start.

Now, in Lighttpd or Apache, you can point to your app‘s file, which will be executed, only to discover that your app now speaks the FastCGI protocol.

Here‘s a sample lighttpd.conf (tested with Lighttpd 1.4.11) to serve as example:

  server.port                 = 3044
  server.bind                 = "127.0.0.1"
  server.modules              = ( "mod_fastcgi" )
  server.document-root        = "/var/www/camping/blog/"
  server.errorlog             = "/var/www/camping/blog/error.log"

  #### fastcgi module
  fastcgi.server = ( "/" => (
    "localhost" => (
      "socket" => "/tmp/camping-blog.socket",
      "bin-path" => "/var/www/camping/blog/blog.rb",
      "check-local" => "disable",
      "max-procs" => 1 ) ) )

The file /var/www/camping/blog/blog.rb is the Camping app with the postamble.

Mounting Many Apps

 require 'camping/fastcgi'
 fast = Camping::FastCGI.new
 fast.mount("/blog", Blog)
 fast.mount("/tepee", Tepee)
 fast.mount("/", Index)
 fast.start

Methods

mount   new   serve   start   start  

Constants

CHUNK_SIZE = (4 * 1024)

Public Class methods

Creates a Camping::FastCGI class with empty mounts.

[Source]

    # File lib/camping/fastcgi.rb, line 62
62:     def initialize
63:         @mounts = {}
64:     end

Serve an entire directory of Camping apps. (See code.whytheluckystiff.net/camping/wiki/TheCampingServer.)

Use this method inside your FastCGI dispatcher:

  #!/usr/local/bin/ruby
  require 'rubygems'
  require 'camping/fastcgi'
  Camping::Models::Base.establish_connection :adapter => 'sqlite3', :database => "/path/to/db"
  Camping::FastCGI.serve("/home/why/cvs/camping/examples")

[Source]

     # File lib/camping/fastcgi.rb, line 161
161:     def self.serve(path, index=nil)
162:         require 'camping/reloader'
163:         if File.directory? path
164:             fast = Camping::FastCGI.new
165:             script_load = proc do |script|
166:                 app = Camping::Reloader.new(script)
167:                 fast.mount("/#{app.mount}", app)
168:                 app
169:             end
170:             Dir[File.join(path, '*.rb')].each &script_load
171:             fast.mount("/", index) if index
172: 
173:             fast.start do |dir, app|
174:                  Dir[File.join(path, dir, '*.rb')].each do |script|
175:                      smount = "/" + File.basename(script, '.rb')
176:                      script_load[script] unless @mounts.has_key? smount
177:                  end
178:             end
179:         else
180:             start(Camping::Reloader.new(path))
181:         end
182:     end

A simple single-app starter mechanism

  Camping::FastCGI.start(Blog)

[Source]

     # File lib/camping/fastcgi.rb, line 144
144:     def self.start(app)
145:         cf = Camping::FastCGI.new
146:         cf.mount("/", app)
147:         cf.start
148:     end

Public Instance methods

Mounts a Camping application. The dir being the name of the directory to serve as the application‘s root. The app is a Camping class.

[Source]

    # File lib/camping/fastcgi.rb, line 67
67:     def mount(dir, app)
68:         dir.gsub!(/\/{2,}/, '/')
69:         dir.gsub!(/\/+$/, '')
70:         @mounts[dir] = app
71:     end

Starts the FastCGI main loop.

[Source]

     # File lib/camping/fastcgi.rb, line 74
 74:     def start
 75:         FCGI.each do |req|
 76:             dir, app = nil
 77:             begin
 78:                 root, path = "/"
 79:                 if ENV['FORCE_ROOT'] and ENV['FORCE_ROOT'].to_i == 1
 80:                   path = req.env['REQUEST_URI']
 81:                 else
 82:                   root = req.env['SCRIPT_NAME']
 83:                   path = req.env['PATH_INFO']
 84:                 end
 85: 
 86:                 dir, app = @mounts.max { |a,b| match(path, a[0]) <=> match(path, b[0]) }
 87:                 unless dir and app
 88:                     dir, app = '/', Camping
 89:                 end
 90:                 yield dir, app if block_given?
 91: 
 92:                 req.env['SERVER_SCRIPT_NAME'] = req.env['SCRIPT_NAME']
 93:                 req.env['SERVER_PATH_INFO'] = req.env['PATH_INFO']
 94:                 req.env['SCRIPT_NAME'] = File.join(root, dir)
 95:                 req.env['PATH_INFO'] = path.gsub(/^#{dir}/, '')
 96: 
 97:                 controller = app.run(req.in, req.env)
 98:                 sendfile = nil
 99:                 headers = {}
100:                 controller.headers.each do |k, v|
101:                   if k =~ /^X-SENDFILE$/i and !ENV['SERVER_X_SENDFILE']
102:                     sendfile = v
103:                   else
104:                     headers[k] = v
105:                   end
106:                 end
107: 
108:                 body = controller.body
109:                 controller.body = ""
110:                 controller.headers = headers
111: 
112:                 req.out << controller.to_s
113:                 if sendfile
114:                   File.open(sendfile, "rb") do |f|
115:                     while chunk = f.read(CHUNK_SIZE) and chunk.length > 0
116:                       req.out << chunk
117:                     end
118:                   end
119:                 elsif body.respond_to? :read
120:                   while chunk = body.read(CHUNK_SIZE) and chunk.length > 0
121:                     req.out << chunk
122:                   end
123:                   body.close if body.respond_to? :close
124:                 else
125:                   req.out << body.to_s
126:                 end
127:             rescue Exception => e
128:                 req.out << "Content-Type: text/html\r\n\r\n" +
129:                     "<h1>Camping Problem!</h1>" +
130:                     "<h2><strong>#{root}</strong>#{path}</h2>" + 
131:                     "<h3>#{e.class} #{esc e.message}</h3>" +
132:                     "<ul>" + e.backtrace.map { |bt| "<li>#{esc bt}</li>" }.join + "</ul>" +
133:                     "<hr /><p>#{req.env.inspect}</p>"
134:             ensure
135:                 req.finish
136:             end
137:         end
138:     end

[Validate]