Skip to content
This repository has been archived by the owner on Sep 10, 2021. It is now read-only.

Commit

Permalink
Add Rails adapter and command line script
Browse files Browse the repository at this point in the history
  • Loading branch information
macournoyer committed Jan 2, 2008
1 parent 3f05ea8 commit 4baf38c
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 11 deletions.
14 changes: 7 additions & 7 deletions Rakefile
Expand Up @@ -54,16 +54,20 @@ spec = Gem::Specification.new do |s|
s.author = "Marc-Andre Cournoyer"
s.email = 'macournoyer@gmail.com'
s.homepage = 'http://code.macournoyer.com/thin/'
s.executables = %w(thin)

s.required_ruby_version = '>= 1.8.6'

s.add_dependency 'eventmachine', '>= 0.9.0'
s.add_dependency 'rack', '>= 0.2.0'

s.files = %w(README COPYING Rakefile) + Dir.glob("{doc,lib,test,example}/**/*")
s.files = %w(COPYING README Rakefile) +
Dir.glob("{bin,doc,spec,lib,example}/**/*") +
Dir.glob("ext/**/*.{h,c,rb,rl}") +
s.extensions = FileList["ext/**/extconf.rb"].to_a

s.require_path = "lib"
s.bindir = "bin"
end

Rake::GemPackageTask.new(spec) do |p|
Expand Down Expand Up @@ -154,19 +158,15 @@ end
desc 'Deploy on all servers'
task :deploy => %w(deploy:alpha deploy:public)

task :install do
task :install => :compile do
sh %{rake package}
sh %{sudo gem install pkg/#{Thin::NAME}-#{Thin::VERSION::STRING}}
end

task :uninstall => [:clean] do
task :uninstall => :clean do
sh %{sudo gem uninstall #{Thin::NAME}}
end

task :tag do
sh %Q{svn cp . http://code.macournoyer.com/svn/thin/tags/#{Thin::VERSION::STRING} -m "Tagging version #{Thin::VERSION::STRING}"}
end

# == Utilities

def upload(file, to, options={})
Expand Down
79 changes: 79 additions & 0 deletions bin/thin
@@ -0,0 +1,79 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../lib/thin'
require 'optparse'

options = {
:root => Dir.pwd,
:env => 'development',
:host => '0.0.0.0',
:port => 3000,
:timeout => 60,
:log_file => 'log/thin.log',
:pid_file => 'tmp/pids/thin.pid'
}

opts = OptionParser.new do |opts|
opts.banner = "Usage: thin [options] start|stop"

opts.separator ""
opts.separator "Server options:"

opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host| options[:host] = host }
opts.on("-p", "--port PORT", "use PORT (default: 3000)") { |port| options[:port] = port }
opts.on("-e", "--env ENV", "Rails environment (default: development)") { |env| options[:env] = env }
opts.on("-c", "--chdir PATH", "listen on HOST (default: current dir)") { |dir| options[:root] = dir }
opts.on("-d", "--daemonize", "Daemonize") { options[:daemonize] = true }
opts.on("-l", "--log-file FILE", "File to redirect output",
"(default: #{options[:log_file]})") { |file| options[:log_file] = file }
opts.on("-P", "--pid-file FILE", "File to store PID",
"(default: #{options[:pid_file]})") { |file| options[:pid_file] = file }
opts.on("-t", "--timeout SEC", "Request or command timeout in sec",
"(default: #{options[:timeout]})") { |sec| options[:timeout] = sec }
opts.on("-u", "--user NAME", "User to run daemon as (use with -g)") { |user| options[:user] = user }
opts.on("-g", "--group NAME", "Group to run daemon as (use with -u)") { |group| options[:group] = group }

opts.separator ""
opts.separator "Common options:"

opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit
end

opts.on_tail('-v', '--version', "Show version") do
puts Thin::NAME
exit
end

opts.parse! ARGV
end

case ARGV[0]

when 'start'
app = Rack::Adapter::Rails.new(options)
server = Thin::Server.new(options[:host], options[:port], app)

server.pid_file = options[:pid_file]
server.log_file = options[:log_file]
server.timeout = options[:timeout]

if options[:daemonize]
server.change_privilege options[:user], options[:group] if options[:user] && options[:group]
server.daemonize
end

server.start!

when 'stop'
Thin::Server.kill options[:pid_file], options[:timeout]

when nil
puts "Command required"
puts opts
exit 1

else
abort "Invalid command : #{ARGV[0]}"

end
136 changes: 136 additions & 0 deletions lib/rack/adapter/rails.rb
@@ -0,0 +1,136 @@
# This as been submitted to Rack as a patch, tested and everything.
# Bug Christian Neukirchen at chneukirchen@gmail.com to apply the patch!

require 'cgi'

# Adapter to run a Rails app with any supported Rack handler.
# By default it will try to load the Rails application in the
# current directory in the development environment.
# Options:
# root: Root directory of the Rails app
# env: Rails environment to run in (development, production or test)
# Based on http://fuzed.rubyforge.org/ Rails adapter
module Rack
module Adapter
class Rails
def initialize(options={})
@root = options[:root] || Dir.pwd
@env = options[:env] || 'development'

load_application

@file_server = Rack::File.new(::File.join(RAILS_ROOT, "public"))
end

def load_application
ENV['RAILS_ENV'] = @env

require "#{@root}/config/environment"
require 'dispatcher'
end

# TODO refactor this in File#can_serve?(path) ??
def file?(path)
full_path = ::File.join(@file_server.root, Utils.unescape(path))
::File.file?(full_path) && ::File.readable?(full_path)
end

def call(env)
# Serve the file if it's there
return @file_server.call(env) if file?(env['PATH_INFO'])

request = Request.new(env)
response = Response.new

session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
cgi = CGIWrapper.new(request, response)

Dispatcher.dispatch(cgi, session_options, response)

response.finish
end

protected

class CGIWrapper < ::CGI
def initialize(request, response, *args)
@request = request
@response = response
@args = *args
@input = request.body

super *args
end

def header(options = "text/html")
if options.is_a?(String)
@response['Content-Type'] = options unless @response['Content-Type']
else
@response['Content-Length'] = options.delete('Content-Length').to_s if options['Content-Length']

@response['Content-Type'] = options.delete('type') || "text/html"
@response['Content-Type'] += "; charset=" + options.delete('charset') if options['charset']

@response['Content-Language'] = options.delete('language') if options['language']
@response['Expires'] = options.delete('expires') if options['expires']

@response.status = options.delete('Status') if options['Status']

options.each { |k,v| @response[k] = v }

# Convert 'cookie' header to 'Set-Cookie' headers.
# According to http://www.faqs.org/rfcs/rfc2109.html:
# the Set-Cookie response header comprises the token
# Set-Cookie:, followed by a comma-separated list of
# one or more cookies.
if cookie = @response.header.delete('Cookie')
cookies = case cookie
when Array then cookie.collect { |c| c.to_s }.join(', ')
when Hash then cookie.collect { |_, c| c.to_s }.join(', ')
else cookie.to_s
end

cookies << ', ' + @output_cookies.each { |c| c.to_s }.join(', ') if @output_cookies

@response['Set-Cookie'] = cookies
end
end

""
end

def params
@params ||= @request.params
end

def cookies
@request.cookies
end

def query_string
@request.query_string
end

# Used to wrap the normal args variable used inside CGI.
def args
@args
end

# Used to wrap the normal env_table variable used inside CGI.
def env_table
@request.env
end

# Used to wrap the normal stdinput variable used inside CGI.
def stdinput
@input
end

def stdoutput
STDERR.puts "stdoutput should not be used."
@response.body
end
end
end
end
end
11 changes: 7 additions & 4 deletions lib/thin.rb
Expand Up @@ -14,13 +14,13 @@ module Thin
NAME = 'thin'.freeze
SERVER = "#{NAME} #{VERSION::STRING}".freeze

autoload :Logging, 'thin/logging'
autoload :Daemonizable, 'thin/daemonizing'
autoload :Connection, 'thin/connection'
autoload :Server, 'thin/server'
autoload :Request, 'thin/request'
autoload :Daemonizable, 'thin/daemonizing'
autoload :Logging, 'thin/logging'
autoload :Headers, 'thin/headers'
autoload :Request, 'thin/request'
autoload :Response, 'thin/response'
autoload :Server, 'thin/server'
end

require 'rack'
Expand All @@ -29,4 +29,7 @@ module Rack
module Handler
autoload :Thin, 'rack/handler/thin'
end
module Adapter
autoload :Rails, 'rack/adapter/rails'
end
end

0 comments on commit 4baf38c

Please sign in to comment.