lib/heel/server.rb in heel-0.6.0 vs lib/heel/server.rb in heel-1.0.0
- old
+ new
@@ -1,261 +1,295 @@
+#--
+# Copyright (c) 2007, 2008 Jeremy Hinegardner
+# All rights reserved. Licensed under the BSD license. See LICENSE for details
+#++
+
require 'heel'
+require 'thin'
require 'ostruct'
require 'launchy'
-require 'tmpdir'
require 'fileutils'
+require 'heel/rackapp'
module Heel
- class Server
+ class Server
- attr_accessor :options
- attr_accessor :parsed_options
+ attr_accessor :options
+ attr_accessor :parsed_options
- attr_reader :stdout
- attr_reader :stderr
- attr_reader :stdin
-
-
- class << self
- # thank you Jamis - from Capistrano
- def home_directory # :nodoc:
- ENV["HOME"] ||
- (ENV["HOMEPATH"] && "#{ENV["HOMEDRIVE"]}#{ENV["HOMEPATH"]}") ||
+ attr_reader :stdout
+ attr_reader :stderr
+ attr_reader :stdin
+
+
+ class << self
+ # thank you Jamis - from Capistrano
+ def home_directory # :nodoc:
+ ENV["HOME"] ||
+ (ENV["HOMEPATH"] && "#{ENV["HOMEDRIVE"]}#{ENV["HOMEPATH"]}") ||
"/"
- end
-
- def kill_existing_proc
- Heel::Server.new.kill_existing_proc
- end
- end
+ end
- def initialize(argv = [])
- argv ||= []
+ def kill_existing_proc
+ Heel::Server.new.kill_existing_proc
+ end
+ end
- set_io
-
- @options = default_options
- @parsed_options = ::OpenStruct.new
- @parser = option_parser
- @error_message = nil
+ def initialize(argv = [])
+ argv ||= []
- begin
- @parser.parse!(argv)
- rescue ::OptionParser::ParseError => pe
- msg = ["#{@parser.program_name}: #{pe}",
+ set_io
+
+ @options = default_options
+ @parsed_options = ::OpenStruct.new
+ @parser = option_parser
+ @error_message = nil
+
+ begin
+ @parser.parse!(argv)
+ rescue ::OptionParser::ParseError => pe
+ msg = ["#{@parser.program_name}: #{pe}",
"Try `#{@parser.program_name} --help` for more information"]
- @error_message = msg.join("\n")
- end
- end
+ @error_message = msg.join("\n")
+ end
+ end
- def default_options
- if @default_options.nil? then
- @default_options = ::OpenStruct.new
- @default_options.show_version = false
- @default_options.show_help = false
- @default_options.address = "127.0.0.1"
- @default_options.port = 4331
- @default_options.document_root = Dir.pwd
- @default_options.daemonize = false
- @default_options.highlighting = true
- @default_options.kill = false
- @default_options.launch_browser = true
- end
- return @default_options
- end
-
- def default_directory
- ENV["HEEL_DEFAULT_DIRECTORY"] || File.join(::Heel::Server.home_directory,".heel")
- end
-
- def pid_file
- File.join(default_directory,"heel.pid")
- end
-
- def log_file
- File.join(default_directory,"heel.log")
- end
+ def default_options
+ if @default_options.nil? then
+ @default_options = ::OpenStruct.new
+ @default_options.show_version = false
+ @default_options.show_help = false
+ @default_options.address = "0.0.0.0"
+ @default_options.port = 4331
+ @default_options.document_root = Dir.pwd
+ @default_options.daemonize = false
+ @default_options.highlighting = true
+ @default_options.kill = false
+ @default_options.launch_browser = true
+ end
+ return @default_options
+ end
- def option_parser
- OptionParser.new do |op|
- op.separator ""
+ def default_directory
+ ENV["HEEL_DEFAULT_DIRECTORY"] || File.join(::Heel::Server.home_directory,".heel")
+ end
- op.on("-a", "--address ADDRESS", "Address to bind to",
+ def pid_file
+ File.join(default_directory,"heel.pid")
+ end
+
+ def log_file
+ File.join(default_directory,"heel.log")
+ end
+
+ def option_parser
+ OptionParser.new do |op|
+ op.separator ""
+
+ op.on("-a", "--address ADDRESS", "Address to bind to",
" (default: #{default_options.address})") do |add|
- @parsed_options.address = add
- end
+ @parsed_options.address = add
+ end
- op.on("-d", "--daemonize", "Run daemonized in the background") do
- @parsed_options.daemonize = true
- end
+ op.on("-d", "--daemonize", "Run daemonized in the background") do
+ raise ::OptionParser::ParseError, "Daemonizing is not supported on windows" if Thin.win?
+ @parsed_options.daemonize = true
+ end
- op.on("-h", "--help", "Display this text") do
- @parsed_options.show_help = true
- end
-
- op.on("-k", "--kill", "Kill an existing daemonized heel process") do
- @parsed_options.kill = true
- end
-
- op.on("--[no-]highlighting", "Turn on or off syntax highlighting",
+ op.on("-h", "--help", "Display this text") do
+ @parsed_options.show_help = true
+ end
+
+ op.on("-k", "--kill", "Kill an existing daemonized heel process") do
+ @parsed_options.kill = true
+ end
+
+ op.on("--[no-]highlighting", "Turn on or off syntax highlighting",
" (default: on)") do |highlighting|
- @parsed_options.highlighting = highlighting
- end
-
- op.on("--[no-]launch-browser", "Turn on or off automatic browser launch",
+ @parsed_options.highlighting = highlighting
+ end
+
+ op.on("--[no-]launch-browser", "Turn on or off automatic browser launch",
" (default: on)") do |l|
- @parsed_options.launch_browser = l
- end
+ @parsed_options.launch_browser = l
+ end
- op.on("-p", "--port PORT", Integer, "Port to bind to",
+ op.on("-p", "--port PORT", Integer, "Port to bind to",
" (default: #{default_options.port})") do |port|
- @parsed_options.port = port
- end
+ @parsed_options.port = port
+ end
- op.on("-r","--root ROOT",
+ op.on("-r","--root ROOT",
"Set the document root"," (default: #{default_options.document_root})") do |document_root|
- @parsed_options.document_root = File.expand_path(document_root)
- raise ::OptionParser::ParseError, "#{@parsed_options.document_root} is not a valid directory" if not File.directory?(@parsed_options.document_root)
- end
+ @parsed_options.document_root = File.expand_path(document_root)
+ raise ::OptionParser::ParseError, "#{@parsed_options.document_root} is not a valid directory" if not File.directory?(@parsed_options.document_root)
+ end
- op.on("-v", "--version", "Show version") do
- @parsed_options.show_version = true
- end
- end
+ op.on("-v", "--version", "Show version") do
+ @parsed_options.show_version = true
end
+ end
+ end
- def merge_options
- options = default_options.marshal_dump
- @parsed_options.marshal_dump.each_pair do |key,value|
- options[key] = value
- end
+ def merge_options
+ options = default_options.marshal_dump
+ @parsed_options.marshal_dump.each_pair do |key,value|
+ options[key] = value
+ end
- @options = OpenStruct.new(options)
+ @options = OpenStruct.new(options)
+ end
+
+ # set the IO objects in a single method call. This is really only for testing
+ # instrumentation
+ def set_io(stdin = $stdin, stdout = $stdout ,setderr = $stderr)
+ @stdin = stdin
+ @stdout = stdout
+ @stderr = stderr
+ end
+
+ # if Version or Help options are set, then output the appropriate information instead of
+ # running the server.
+ def error_version_help_kill
+ if @parsed_options.show_version then
+ @stdout.puts "#{@parser.program_name}: version #{Heel::VERSION}"
+ exit 0
+ elsif @parsed_options.show_help then
+ @stdout.puts @parser.to_s
+ exit 0
+ elsif @error_message then
+ @stdout.puts @error_message
+ exit 1
+ elsif @parsed_options.kill then
+ kill_existing_proc
+ end
+ end
+
+ # kill an already running background heel process
+ def kill_existing_proc
+ if File.exists?(pid_file) then
+ begin
+ pid = open(pid_file).read.to_i
+ @stdout.puts "Sending TERM to process #{pid}"
+ Process.kill("TERM", pid)
+ rescue Errno::ESRCH
+ @stdout.puts "Unable to kill process with pid #{pid}. Process does not exist. Removing stale pid file."
+ File.unlink(pid_file)
+ rescue Errno::EPERM
+ @stdout.puts "Unable to kill process with pid #{pid}. No permissions to kill process."
end
+ else
+ @stdout.puts "No pid file exists, no process to kill"
+ end
+ @stdout.puts "Done."
+ exit 0
+ end
- # set the IO objects in a single method call. This is really only for testing
- # instrumentation
- def set_io(stdin = $stdin, stdout = $stdout ,setderr = $stderr)
- @stdin = stdin
- @stdout = stdout
- @stderr = stderr
+ # setup the directory that heel will use as the location to run from, where its logs will
+ # be stored and its PID file if backgrounded.
+ def setup_heel_dir
+ if not File.exists?(default_directory) then
+ FileUtils.mkdir_p(default_directory)
+ @stdout.puts "Created #{default_directory}"
+ @stdout.puts "heel's PID (#{pid_file}) and log file (#{log_file}) are stored here"
+ end
+ end
+
+ # make sure that if we are daemonizing the process is not running
+ def ensure_not_running
+ if options.daemonize and File.exist?(pid_file) then
+ @stdout.puts "ERROR: PID File #{pid_file} already exists. Heel may already be running."
+ @stdout.puts "ERROR: Check the Log file #{log_file}"
+ @stdout.puts "ERROR: Heel will not start until the .pid file is cleared (`heel --kill' to clean it up)."
+ exit 1
+ end
+ end
+
+ def launch_browser
+ Thread.new do
+ print "Launching your browser"
+ if options.daemonize then
+ puts " at http://#{options.address}:#{options.port}/"
+ else
+ puts "..."
end
+ ::Launchy.open("http://#{options.address}:#{options.port}/")
+ end
+ end
- # if Version or Help options are set, then output the appropriate information instead of
- # running the server.
- def error_version_help_kill
- if @parsed_options.show_version then
- @stdout.puts "#{@parser.program_name}: version #{Heel::VERSION}"
- exit 0
- elsif @parsed_options.show_help then
- @stdout.puts @parser.to_s
- exit 0
- elsif @error_message then
- @stdout.puts @error_message
- exit 1
- elsif @parsed_options.kill then
- kill_existing_proc
- end
+ def thin_server
+ server = Thin::Server.new(options.address, options.port)
+
+ # overload the name of the process so it shows up as heel not thin
+ def server.name
+ "heel (v#{Heel::VERSION})"
+ end
+
+ server.pid_file = pid_file
+ server.log_file = log_file
+
+ app = Heel::RackApp.new({ :document_root => options.document_root,
+ :highlighting => options.highlighting})
+
+ Heel::Logger.log_file = log_file
+ server.app = Rack::Builder.new {
+ use Heel::Logger
+ map "/" do
+ run app
end
-
- # kill an already running background heel process
- def kill_existing_proc
- if File.exists?(pid_file) then
- begin
- pid = open(pid_file).read.to_i
- @stdout.puts "Sending TERM to process #{pid}"
- Process.kill("TERM", pid)
- rescue Errno::ESRCH
- @stdout.puts "Unable to kill process with pid #{pid}. Process does not exist. Removing stale pid file."
- File.unlink(pid_file)
- rescue Errno::EPERM
- @stdout.puts "Unable to kill process with pid #{pid}. No permissions to kill process."
- end
- else
- @stdout.puts "No pid file exists, no process to kill"
- end
- @stdout.puts "Done."
- exit 0
+ map "/heel_css" do
+ run Rack::File.new(Heel::Configuration.data_path( "css" ))
end
-
- # setup the directory that heel will use as the location to run from, where its logs will
- # be stored and its PID file if backgrounded.
- def setup_heel_dir
- if not File.exists?(default_directory) then
- FileUtils.mkdir_p(default_directory)
- @stdout.puts "Created #{default_directory}"
- @stdout.puts "heel's PID (#{pid_file}) and log file (#{log_file}) are stored here"
- end
+ map "/heel_icons" do
+ run Rack::File.new(Heel::Configuration.data_path("famfamfam", "icons"))
end
- # run the heel server with the current options.
- def run
-
- error_version_help_kill
- merge_options
- setup_heel_dir
-
- # capture method/variables into a local context so they can be used inside the Configurator block
- c_address = options.address
- c_port = options.port
- c_document_root = options.document_root
- c_background_me = options.daemonize
- c_default_dir = default_directory
- c_highlighting = options.highlighting
- c_pid_file = pid_file
- c_log_file = log_file
+ }
+
+ server.app = Thin::Stats::Adapter.new(server.app, "/heel_stats")
- stats = ::Mongrel::StatisticsFilter.new(:sample_rate => 1)
- config = ::Mongrel::Configurator.new(:host => options.address, :port => options.port, :pid_file => c_pid_file) do
- if c_background_me then
- if File.exists?(c_pid_file) then
- log "ERROR: PID File #{c_pid_file} already exists. Heel may already be running."
- log "ERROR: Check the Log file #{c_log_file}"
- log "ERROR: Heel will not start until the .pid file is cleared (`heel --kill' to clean it up)."
- exit 1
- end
- daemonize({:cwd => c_default_dir, :log_file => c_log_file})
- end
-
- begin
-
- listener do
- uri "/", :handler => stats
- uri "/", :handler => Heel::DirHandler.new({:document_root => c_document_root,
- :highlighting => c_highlighting })
- uri "/", :handler => Heel::ErrorHandler.new
- uri "/heel_css", :handler => Heel::DirHandler.new({:document_root =>
- File.join(APP_DATA_DIR, "css")})
- uri "/heel_icons", :handler => Heel::DirHandler.new({ :document_root =>
- File.join(APP_DATA_DIR, "famfamfam", "icons")})
- uri "/heel_status", :handler => ::Mongrel::StatusHandler.new(:stats_filter => stats)
- end
- rescue Errno::EADDRINUSE
- log "ERROR: Address (#{c_address}:#{c_port}) is already in use, please check running processes or run `heel --kill'"
- exit 1
- end
- setup_signals
- end
-
- config.run
- config.log "heel running at http://#{options.address}:#{options.port} with document root #{options.document_root}"
-
- if c_background_me then
- config.write_pid_file
+ return server
+ end
+
+ def start_thin_server
+ server = thin_server
+
+ server_thread = Thread.new do
+ begin
+ if options.daemonize then
+ if cpid = fork then
+ # wait for the top child of the server double fork to exit
+ Process.waitpid(cpid)
else
- config.log "Use Ctrl-C to stop."
+ server.daemonize
+ server.start
end
-
- if options.launch_browser then
- config.log "Launching your browser..."
- if c_background_me then
- puts "Launching your browser to http://#{options.address}:#{options.port}/"
- end
- ::Launchy.open("http://#{options.address}:#{options.port}/")
+ else
+ begin
+ server.start
+ rescue RuntimeError
+ $stderr.puts "ERROR: Unable to start server. Heel may already be running. Please check running processes or run `heel --kill'"
+ exit 1
end
-
- config.join
-
+ end
end
-
+ end
end
+
+
+ # run the heel server with the current options.
+ def run
+
+ error_version_help_kill
+ merge_options
+ setup_heel_dir
+ ensure_not_running
+
+ server_thread = start_thin_server
+
+ if options.launch_browser then
+ launch_browser.join
+ end
+ server_thread.join
+ end
+ end
end