lib/rack/insight/app.rb
require "rack"
require "rack/toolbar"
require "digest/sha1"
require "rack/insight/logging"
require "rack/insight/config"
require "rack/insight/filtered_backtrace"
require "rack/insight/options"
require "rack/insight/magic_insight"
require "rack/insight/default_invocation"
require "rack/insight/panel"
require "rack/insight/panel_app"
require "rack/insight/params_signature"
require "rack/insight/rack_static_bug_avoider"
require "rack/insight/redirect_interceptor"
require "rack/insight/render"
require "rack/insight/path_match_filters"
require "rack/insight/toolbar"
require "rack/insight/enable-button"
require "rack/insight/path-filter"
require "rack/insight/request-recorder"
require "rack/insight/instrumentation/setup"
require "rack/insight/panels-content"
require "rack/insight/panels-header"
module Rack::Insight
class App
include Rack::Insight::Options
include Rack::Insight::Logging
INSIGHT_ROOT = "/__insight__"
INSIGHT_REGEX = %r{^#{INSIGHT_ROOT}}
class SecurityError < StandardError
end
def initialize(app, options = {}, &block)
initialize_options options
@base_app = app
@panels = []
instance_eval(&block) if block_given?
build_normal_stack
build_debug_stack
# TODO: Understand when this would be used
if options[:on_initialize]
options[:on_initialize].call(self)
end
end
attr_accessor :panels
# allow access to configuration settings directly through the app object!
def config
Rack::Insight::Config.config
end
def call(env)
@original_request = Rack::Request.new(env)
@env = env
self.options = @default_options
if insight_active?
Rack::Insight.enable
env["rack-insight.panels"] = []
@debug_stack.call(env)
else
@normal_stack.call(env)
end
end
def reset(new_options=nil)
@env = nil
initialize_options(new_options)
Rack::Insight::Instrumentation::ClassProbe::all_probes.each do |probe|
probe.clear_collectors
end
Rack::Insight::Instrumentation::InstanceProbe::all_probes.each do |probe|
probe.clear_collectors
end
Rack::Insight::Instrumentation::PackageDefinition.clear_collectors
build_debug_stack
end
private
def insight_active?
return (toolbar_requested? && ip_authorized? && password_authorized?)
end
def build_normal_stack
builder = Rack::Builder.new
builder.use EnableButton
builder.run Rack::Cascade.new([ asset_mapped(Rack::Builder.new), @base_app ])
@normal_stack = builder.to_app
end
def build_debug_stack
@panels.clear
builder = Rack::Builder.new
builder.use Toolbar, self
builder.run Rack::Cascade.new([panel_mappings, shortcut_stack(@base_app), collection_stack(@base_app)])
@debug_stack = builder.to_app
end
def panel_mappings
classes = read_option(:panel_classes)
root = INSIGHT_ROOT
insight = self
builder = Rack::Builder.new do
classes.each do |panel_class|
panel_class.panel_mappings.each do |path, app|
map [root, path].join("/") do
run app
end
end
end
map root + "/panels_content" do
run PanelsContent.new(insight)
end
map root + "/panels_header" do
run PanelsHeader.new(insight)
end
end
return asset_mapped(builder)
end
def shortcut_stack(app)
Rack::Builder.app do
use PathFilter
run app
end
end
def collection_stack(app)
classes = read_option(:panel_classes)
insight_id = self.object_id
panels = self.panels
#Builder makes it impossible to access the panels
app = Instrumentation::Setup.new(app)
app = RedirectInterceptor.new(app)
#Reversed? Does it matter?
app = classes.inject(app) do |app, panel_class|
panel = panel_class.new(app)
panels << panel
panel
end
app = RequestRecorder.new(app)
return app
end
def asset_mapped(builder)
path = public_path
builder.map INSIGHT_ROOT do
run Rack::File.new(path)
end
builder.to_app
end
def public_path
::File.expand_path("../../insight/public/__insight__", __FILE__)
end
def toolbar_requested?
@original_request.cookies["rack-insight_enabled"]
end
def ip_authorized?
return true unless options["rack-insight.ip_masks"]
logger.info{ "Checking #{@original_request.ip} against ip_masks" } if verbose(:high)
ip = IPAddr.new(@original_request.ip)
mask = options["rack-insight.ip_masks"].find do |ip_mask|
ip_mask.include?(ip)
end
if mask
logger.info{ "Matched #{mask}" } if verbose(:high)
return true
else
logger.info{ "Matched no masks" } if verbose(:high)
return false
end
end
def password_authorized?
return true unless options["rack-insight.password"]
logger.info{"Checking password"} if verbose(:low)
expected_sha = Digest::SHA1.hexdigest ["rack-insight", options["rack-insight.password"]].join(":")
actual_sha = @original_request.cookies["rack-insight_password"]
logger.info{"Password result: #{actual_sha == expected_sha}"} if verbose(:med)
actual_sha == expected_sha
end
end
end