# What can be done with fewer assumptions is done in vain with more.
# -- William of Ockham (ca. 1285-1349)
#
# Name-space of Innate, just about everything goes in here.
#
# The only exception is Logger::ColorFormatter.
#
module Innate
ROOT = File.expand_path(File.dirname(__FILE__))
unless $LOAD_PATH.any?{|lp| File.expand_path(lp) == ROOT }
$LOAD_PATH.unshift(ROOT)
end
# stdlib
require 'digest/sha1'
require 'digest/sha2'
require 'find'
require 'ipaddr'
require 'logger'
require 'pathname'
require 'pp'
require 'set'
require 'socket'
require 'uri'
# 3rd party
require 'rack'
# innate core
require 'innate/version'
require 'innate/traited'
require 'innate/trinity'
require 'innate/middleware_compiler'
require 'innate/options/dsl'
require 'innate/options/stub'
require 'innate/dynamap'
# innate full
require 'innate/cache'
require 'innate/node'
require 'innate/options'
require 'innate/log'
require 'innate/state'
require 'innate/current'
require 'innate/mock'
require 'innate/adapter'
require 'innate/action'
require 'innate/helper'
require 'innate/view'
require 'innate/session'
require 'innate/session/flash'
require 'innate/route'
extend Trinity
# Contains all the module functions for Innate, we keep them in a module so
# Ramaze can simply use them as well.
module SingletonMethods
PROXY_OPTIONS = { :port => 'adapter.port', :host => 'adapter.host',
:adapter => 'adapter.handler' }
# The method that starts the whole business.
#
# Call Innate.start after you defined your application.
#
# Usually, this is a blocking call and will not return until the adapter
# has finished, which usually happens when you kill the application or hit
# ^C.
#
# We do return if options.started is true, which indicates that all you
# wanted to do is setup the environment and update options.
#
# @example usage
#
# # passing options
# Innate.start :adapter => :mongrel, :mode => :live
#
# # defining custom middleware
# Innate.start do |m|
# m.innate
# end
#
# @return [nil] if options.started is true
# @yield [MiddlewareCompiler]
# @param [Proc] block will be passed to {middleware!}
#
# @option param :host [String] ('0.0.0.0')
# IP address or hostname that we respond to - 0.0.0.0 for all
# @option param :port [Fixnum] (7000)
# Port for the server
# @option param :started [boolean] (false)
# Indicate that calls Innate::start will be ignored
# @option param :adapter [Symbol] (:webrick)
# Web server to run on
# @option param :setup [Array] ([Innate::Cache, Innate::Node])
# Will send ::setup to each element during Innate::start
# @option param :header [Hash] ({'Content-Type' => 'text/html'})
# Headers that will be merged into the response before Node::call
# @option param :trap [String] ('SIGINT')
# Trap this signal to issue shutdown, nil/false to disable trap
# @option param :state [Symbol] (:Fiber)
# Keep state in Thread or Fiber, fall back to Thread if Fiber not available
# @option param :mode [Symbol] (:dev)
# Indicates which default middleware to use, (:dev|:live)
def start(given_options = {}, &block)
root = given_options.delete(:root)
file = given_options.delete(:file)
found_root = go_figure_root(caller, :root => root, :file => file)
Innate.options.roots = [*found_root] if found_root
# Convert some top-level option keys to the internal ones that we use.
PROXY_OPTIONS.each{|k,v| given_options[v] = given_options.delete(k) }
given_options.delete_if{|k,v| v.nil? }
# Merge the user's given options into our existing set, which contains defaults.
options.merge!(given_options)
setup_dependencies
middleware!(options.mode, &block) if block_given?
return if options.started
options.started = true
signal = options.trap
trap(signal){ stop(10) } if signal
start!
end
def start!(mode = options[:mode])
Adapter.start(middleware(mode))
end
def stop(wait = 3)
Log.info("Shutdown within #{wait} seconds")
Timeout.timeout(wait){ teardown_dependencies }
Timeout.timeout(wait){ exit }
ensure
exit!
end
def setup_dependencies
options[:setup].each{|obj| obj.setup if obj.respond_to?(:setup) }
end
def teardown_dependencies
options[:setup].each{|obj| obj.teardown if obj.respond_to?(:teardown) }
end
# Treat Innate like a rack application, pass the rack +env+ and optionally
# the +mode+ the application runs in.
#
# @param [Hash] env rack env
# @param [Symbol] mode indicates the mode of the application
# @default mode options.mode
# @return [Array] with [body, header, status]
# @author manveru
def call(env, mode = options[:mode])
middleware(mode).call(env)
end
def middleware(mode = options[:mode], &block)
options[:middleware_compiler].build(mode, &block)
end
def middleware!(mode = options[:mode], &block)
options[:middleware_compiler].build!(mode, &block)
end
def middleware_recompile(mode = options[:mode])
options[:middleware_compiler]::COMPILED[mode].compile!
end
# @example Innate can be started by:
#
# Innate.start :file => __FILE__
# Innate.start :root => File.dirname(__FILE__)
#
# Either setting will surpress the warning that might show up on startup
# and tells you it couldn't find an explicit root.
#
# In case these options are not passed we will try to figure out a file named
# `start.rb` in the process' working directory and assume it's a valid point.
def go_figure_root(backtrace, options)
if root = options[:root]
root
elsif file = options[:file]
File.dirname(file)
elsif File.file?('start.rb')
Dir.pwd
else
root = File.dirname(backtrace[0][/^(.*?):\d+/, 1])
Log.warn "No explicit root folder found, assuming it is #{root}"
root
end
end
end
extend SingletonMethods
# This sets up the default modes.
# The Proc to use is determined by the value of options.mode.
# The Proc value is passed to setup_middleware if no block is given to
# Innate::start.
#
# A quick overview over the middleware used here:
#
# * Rack::CommonLogger
# Logs a line in Apache common log format or rack.errors.
#
# * Rack::ShowExceptions
# Catches all exceptions raised from the app it wraps. It shows a useful
# backtrace with the sourcefile and clickable context, the whole Rack
# environment and the request data.
# Be careful when you use this on public-facing sites as it could reveal
# information helpful to attackers.
#
# * Rack::ShowStatus
# Catches all empty responses the app it wraps and replaces them with a
# site explaining the error.
# Additional details can be put into rack.showstatus.detail and
# will be shown as HTML. If such details exist, the error page is always
# rendered, even if the reply was not empty.
#
# * Rack::ConditionalGet
# Middleware that enables conditional GET using If-None-Match and
# If-Modified-Since. The application should set either or both of the
# Last-Modified or Etag response headers according to RFC 2616. When
# either of the conditions is met, the response body is set to be zero
# length and the response status is set to 304 Not Modified.
#
# * Rack::Head
# Removes the body of the response for HEAD requests.
#
# * Rack::Reloader
# Pure ruby source reloader, runs on every request with a configurable
# cooldown period.
#
# * Rack::Lint
# Rack::Lint validates your application and the requests and responses
# according to the Rack spec.
#
# Note that `m.innate` takes away most of the boring part and leaves it up to
# you to select your middleware in your application.
#
# `m.innate` expands to:
#
# use Rack::Cascade.new([
# Rack::File.new('public'),
# Innate::Current.new(
# Rack::Cascade.new([
# Innate::Rewrite.new(Innate::DynaMap),
# Innate::Route.new(Innate::DynaMap)])))
#
# @see Rack::MiddlewareCompiler
middleware :dev do |m|
m.apps(Rack::Lint, Rack::Head, Rack::ContentLength, Rack::CommonLogger,
Rack::ShowExceptions, Rack::ShowStatus, Rack::ConditionalGet)
m.use(Rack::Reloader, 2)
m.innate
end
middleware :live do |m|
m.apps(Rack::Head, Rack::ContentLength, Rack::CommonLogger,
Rack::ShowStatus, Rack::ConditionalGet)
m.innate
end
end