Merge branch 'szzh' of http://repository.trustie.net/xianbo/trustie2 into szzh
commit
b52690adc2
@ -0,0 +1,4 @@
|
||||
language: ruby
|
||||
rvm:
|
||||
- 2.1.0
|
||||
- 2.0.0
|
@ -0,0 +1 @@
|
||||
--markup markdown --no-private
|
@ -0,0 +1,3 @@
|
||||
# Changelog
|
||||
|
||||
See https://github.com/charliesome/better_errors/releases
|
@ -0,0 +1,10 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gemspec
|
||||
|
||||
gem "rake"
|
||||
gem "rspec", "2.14.1"
|
||||
gem "binding_of_caller", platforms: :ruby
|
||||
gem "pry", "0.9.12"
|
||||
gem "yard"
|
||||
gem "kramdown"
|
@ -0,0 +1,22 @@
|
||||
Copyright (c) 2014 Charlie Somerville
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@ -0,0 +1,103 @@
|
||||
# Better Errors [](https://rubygems.org/gems/better_errors) [](https://travis-ci.org/charliesome/better_errors) [](https://codeclimate.com/github/charliesome/better_errors)
|
||||
|
||||
Better Errors replaces the standard Rails error page with a much better and more useful error page. It is also usable outside of Rails in any Rack app as Rack middleware.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
* Full stack trace
|
||||
* Source code inspection for all stack frames (with highlighting)
|
||||
* Local and instance variable inspection
|
||||
* Live REPL on every stack frame
|
||||
|
||||
## Installation
|
||||
|
||||
Add this to your Gemfile:
|
||||
|
||||
```ruby
|
||||
group :development do
|
||||
gem "better_errors"
|
||||
end
|
||||
```
|
||||
|
||||
If you would like to use Better Errors' **advanced features** (REPL, local/instance variable inspection, pretty stack frame names), you need to add the [`binding_of_caller`](https://github.com/banister/binding_of_caller) gem by [@banisterfiend](http://twitter.com/banisterfiend) to your Gemfile:
|
||||
|
||||
```ruby
|
||||
gem "binding_of_caller"
|
||||
```
|
||||
|
||||
This is an optional dependency however, and Better Errors will work without it.
|
||||
|
||||
_Note: If you discover that Better Errors isn't working - particularly after upgrading from version 0.5.0 or less - be sure to set `config.consider_all_requests_local = true` in `config/environments/development.rb`._
|
||||
|
||||
## Security
|
||||
|
||||
**NOTE:** It is *critical* you put better\_errors in the **development** section. **Do NOT run better_errors in production, or on Internet facing hosts.**
|
||||
|
||||
You will notice that the only machine that gets the Better Errors page is localhost, which means you get the default error page if you are developing on a remote host (or a virtually remote host, such as a Vagrant box). Obviously, the REPL is not something you want to expose to the public, but there may also be other pieces of sensitive information available in the backtrace.
|
||||
|
||||
To poke selective holes in this security mechanism, you can add a line like this to your startup (for example, on Rails it would be `config/environments/development.rb`)
|
||||
|
||||
```ruby
|
||||
BetterErrors::Middleware.allow_ip! ENV['TRUSTED_IP'] if ENV['TRUSTED_IP']
|
||||
```
|
||||
|
||||
Then run Rails like this:
|
||||
|
||||
```shell
|
||||
TRUSTED_IP=66.68.96.220 rails s
|
||||
```
|
||||
|
||||
Note that the `allow_ip!` is actually backed by a `Set`, so you can add more than one IP address or subnet.
|
||||
|
||||
**Tip:** You can find your apparent IP by hitting the old error page's "Show env dump" and looking at "REMOTE_ADDR".
|
||||
|
||||
**VirtualBox:** If you are using VirtualBox and are accessing the guest from your host's browser, you will need to use `allow_ip!` to see the error page.
|
||||
|
||||
## Usage
|
||||
|
||||
If you're using Rails, there's nothing else you need to do.
|
||||
|
||||
If you're not using Rails, you need to insert `BetterErrors::Middleware` into your middleware stack, and optionally set `BetterErrors.application_root` if you'd like Better Errors to abbreviate filenames within your application.
|
||||
|
||||
Here's an example using Sinatra:
|
||||
|
||||
```ruby
|
||||
require "sinatra"
|
||||
require "better_errors"
|
||||
|
||||
configure :development do
|
||||
use BetterErrors::Middleware
|
||||
BetterErrors.application_root = __dir__
|
||||
end
|
||||
|
||||
get "/" do
|
||||
raise "oops"
|
||||
end
|
||||
```
|
||||
|
||||
### Unicorn, Puma, and other multi-worker servers
|
||||
|
||||
Better Errors works by leaving a lot of context in server process memory. If
|
||||
you're using a web server that runs multiple "workers" it's likely that a second
|
||||
request (as happens when you click on a stack frame) will hit a different
|
||||
worker. That worker won't have the necessary context in memory, and you'll see
|
||||
a `Session Expired` message.
|
||||
|
||||
If this is the case for you, consider turning the number of workers to one (1)
|
||||
in `development`. Another option would be to use Webrick, Mongrel, Thin,
|
||||
or another single-process server as your `rails server`, when you are trying
|
||||
to troubleshoot an issue in development.
|
||||
|
||||
## Get in touch!
|
||||
|
||||
If you're using better_errors, I'd love to hear from you. Drop me a line and tell me what you think!
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork it
|
||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
3. Commit your changes (`git commit -am 'Add some feature'`)
|
||||
4. Push to the branch (`git push origin my-new-feature`)
|
||||
5. Create new Pull Request
|
@ -0,0 +1,13 @@
|
||||
require "bundler/gem_tasks"
|
||||
require "rspec/core/rake_task"
|
||||
|
||||
namespace :test do
|
||||
RSpec::Core::RakeTask.new(:with_binding_of_caller)
|
||||
|
||||
without_task = RSpec::Core::RakeTask.new(:without_binding_of_caller)
|
||||
without_task.ruby_opts = "-I spec -r without_binding_of_caller"
|
||||
|
||||
task :all => [:with_binding_of_caller, :without_binding_of_caller]
|
||||
end
|
||||
|
||||
task :default => "test:all"
|
@ -0,0 +1,27 @@
|
||||
lib = File.expand_path('../lib', __FILE__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require 'better_errors/version'
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "better_errors"
|
||||
s.version = BetterErrors::VERSION
|
||||
s.authors = ["Charlie Somerville"]
|
||||
s.email = ["charlie@charliesomerville.com"]
|
||||
s.description = %q{Provides a better error page for Rails and other Rack apps. Includes source code inspection, a live REPL and local/instance variable inspection for all stack frames.}
|
||||
s.summary = %q{Better error page for Rails and other Rack apps}
|
||||
s.homepage = "https://github.com/charliesome/better_errors"
|
||||
s.license = "MIT"
|
||||
|
||||
s.files = `git ls-files`.split($/)
|
||||
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
||||
s.require_paths = ["lib"]
|
||||
|
||||
s.required_ruby_version = ">= 2.0.0"
|
||||
|
||||
s.add_dependency "erubis", ">= 2.6.6"
|
||||
s.add_dependency "coderay", ">= 1.0.0"
|
||||
|
||||
# optional dependencies:
|
||||
# s.add_dependency "binding_of_caller"
|
||||
# s.add_dependency "pry"
|
||||
end
|
@ -0,0 +1,146 @@
|
||||
require "pp"
|
||||
require "erubis"
|
||||
require "coderay"
|
||||
require "uri"
|
||||
|
||||
require "better_errors/code_formatter"
|
||||
require "better_errors/error_page"
|
||||
require "better_errors/middleware"
|
||||
require "better_errors/raised_exception"
|
||||
require "better_errors/repl"
|
||||
require "better_errors/stack_frame"
|
||||
require "better_errors/version"
|
||||
|
||||
module BetterErrors
|
||||
POSSIBLE_EDITOR_PRESETS = [
|
||||
{ symbols: [:emacs, :emacsclient], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" },
|
||||
{ symbols: [:macvim, :mvim], sniff: /vim/i, url: proc { |file, line| "mvim://open?url=file://#{file}&line=#{line}" } },
|
||||
{ symbols: [:sublime, :subl, :st], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" },
|
||||
{ symbols: [:textmate, :txmt, :tm], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
|
||||
]
|
||||
|
||||
class << self
|
||||
# The path to the root of the application. Better Errors uses this property
|
||||
# to determine if a file in a backtrace should be considered an application
|
||||
# frame. If you are using Better Errors with Rails, you do not need to set
|
||||
# this attribute manually.
|
||||
#
|
||||
# @return [String]
|
||||
attr_accessor :application_root
|
||||
|
||||
# The logger to use when logging exception details and backtraces. If you
|
||||
# are using Better Errors with Rails, you do not need to set this attribute
|
||||
# manually. If this attribute is `nil`, nothing will be logged.
|
||||
#
|
||||
# @return [Logger, nil]
|
||||
attr_accessor :logger
|
||||
|
||||
# @private
|
||||
attr_accessor :binding_of_caller_available
|
||||
|
||||
# @private
|
||||
alias_method :binding_of_caller_available?, :binding_of_caller_available
|
||||
|
||||
# The ignored instance variables.
|
||||
# @return [Array]
|
||||
attr_accessor :ignored_instance_variables
|
||||
end
|
||||
@ignored_instance_variables = []
|
||||
|
||||
# Returns a proc, which when called with a filename and line number argument,
|
||||
# returns a URL to open the filename and line in the selected editor.
|
||||
#
|
||||
# Generates TextMate URLs by default.
|
||||
#
|
||||
# BetterErrors.editor["/some/file", 123]
|
||||
# # => txmt://open?url=file:///some/file&line=123
|
||||
#
|
||||
# @return [Proc]
|
||||
def self.editor
|
||||
@editor
|
||||
end
|
||||
|
||||
# Configures how Better Errors generates open-in-editor URLs.
|
||||
#
|
||||
# @overload BetterErrors.editor=(sym)
|
||||
# Uses one of the preset editor configurations. Valid symbols are:
|
||||
#
|
||||
# * `:textmate`, `:txmt`, `:tm`
|
||||
# * `:sublime`, `:subl`, `:st`
|
||||
# * `:macvim`
|
||||
#
|
||||
# @param [Symbol] sym
|
||||
#
|
||||
# @overload BetterErrors.editor=(str)
|
||||
# Uses `str` as the format string for generating open-in-editor URLs.
|
||||
#
|
||||
# Use `%{file}` and `%{line}` as placeholders for the actual values.
|
||||
#
|
||||
# @example
|
||||
# BetterErrors.editor = "my-editor://open?url=%{file}&line=%{line}"
|
||||
#
|
||||
# @param [String] str
|
||||
#
|
||||
# @overload BetterErrors.editor=(proc)
|
||||
# Uses `proc` to generate open-in-editor URLs. The proc will be called
|
||||
# with `file` and `line` parameters when a URL needs to be generated.
|
||||
#
|
||||
# Your proc should take care to escape `file` appropriately with
|
||||
# `URI.encode_www_form_component` (please note that `URI.escape` is **not**
|
||||
# a suitable substitute.)
|
||||
#
|
||||
# @example
|
||||
# BetterErrors.editor = proc { |file, line|
|
||||
# "my-editor://open?url=#{URI.encode_www_form_component file}&line=#{line}"
|
||||
# }
|
||||
#
|
||||
# @param [Proc] proc
|
||||
#
|
||||
def self.editor=(editor)
|
||||
POSSIBLE_EDITOR_PRESETS.each do |config|
|
||||
if config[:symbols].include?(editor)
|
||||
return self.editor = config[:url]
|
||||
end
|
||||
end
|
||||
|
||||
if editor.is_a? String
|
||||
self.editor = proc { |file, line| editor % { file: URI.encode_www_form_component(file), line: line } }
|
||||
else
|
||||
if editor.respond_to? :call
|
||||
@editor = editor
|
||||
else
|
||||
raise TypeError, "Expected editor to be a valid editor key, a format string or a callable."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Enables experimental Pry support in the inline REPL
|
||||
#
|
||||
# If you encounter problems while using Pry, *please* file a bug report at
|
||||
# https://github.com/charliesome/better_errors/issues
|
||||
def self.use_pry!
|
||||
REPL::PROVIDERS.unshift const: :Pry, impl: "better_errors/repl/pry"
|
||||
end
|
||||
|
||||
# Automatically sniffs a default editor preset based on the EDITOR
|
||||
# environment variable.
|
||||
#
|
||||
# @return [Symbol]
|
||||
def self.default_editor
|
||||
POSSIBLE_EDITOR_PRESETS.detect(-> { {} }) { |config|
|
||||
ENV["EDITOR"] =~ config[:sniff]
|
||||
}[:url] || :textmate
|
||||
end
|
||||
|
||||
BetterErrors.editor = default_editor
|
||||
end
|
||||
|
||||
begin
|
||||
require "binding_of_caller"
|
||||
require "better_errors/exception_extension"
|
||||
BetterErrors.binding_of_caller_available = true
|
||||
rescue LoadError => e
|
||||
BetterErrors.binding_of_caller_available = false
|
||||
end
|
||||
|
||||
require "better_errors/rails" if defined? Rails::Railtie
|
@ -0,0 +1,63 @@
|
||||
module BetterErrors
|
||||
# @private
|
||||
class CodeFormatter
|
||||
require "better_errors/code_formatter/html"
|
||||
require "better_errors/code_formatter/text"
|
||||
|
||||
FILE_TYPES = {
|
||||
".rb" => :ruby,
|
||||
"" => :ruby,
|
||||
".html" => :html,
|
||||
".erb" => :erb,
|
||||
".haml" => :haml
|
||||
}
|
||||
|
||||
attr_reader :filename, :line, :context
|
||||
|
||||
def initialize(filename, line, context = 5)
|
||||
@filename = filename
|
||||
@line = line
|
||||
@context = context
|
||||
end
|
||||
|
||||
def output
|
||||
formatted_code
|
||||
rescue Errno::ENOENT, Errno::EINVAL
|
||||
source_unavailable
|
||||
end
|
||||
|
||||
def formatted_code
|
||||
formatted_lines.join
|
||||
end
|
||||
|
||||
def coderay_scanner
|
||||
ext = File.extname(filename)
|
||||
FILE_TYPES[ext] || :text
|
||||
end
|
||||
|
||||
def each_line_of(lines, &blk)
|
||||
line_range.zip(lines).map { |current_line, str|
|
||||
yield (current_line == line), current_line, str
|
||||
}
|
||||
end
|
||||
|
||||
def highlighted_lines
|
||||
CodeRay.scan(context_lines.join, coderay_scanner).div(wrap: nil).lines
|
||||
end
|
||||
|
||||
def context_lines
|
||||
range = line_range
|
||||
source_lines[(range.begin - 1)..(range.end - 1)] or raise Errno::EINVAL
|
||||
end
|
||||
|
||||
def source_lines
|
||||
@source_lines ||= File.readlines(filename)
|
||||
end
|
||||
|
||||
def line_range
|
||||
min = [line - context, 1].max
|
||||
max = [line + context, source_lines.count].min
|
||||
min..max
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,26 @@
|
||||
module BetterErrors
|
||||
# @private
|
||||
class CodeFormatter::HTML < CodeFormatter
|
||||
def source_unavailable
|
||||
"<p class='unavailable'>Source is not available</p>"
|
||||
end
|
||||
|
||||
def formatted_lines
|
||||
each_line_of(highlighted_lines) { |highlight, current_line, str|
|
||||
class_name = highlight ? "highlight" : ""
|
||||
sprintf '<pre class="%s">%s</pre>', class_name, str
|
||||
}
|
||||
end
|
||||
|
||||
def formatted_nums
|
||||
each_line_of(highlighted_lines) { |highlight, current_line, str|
|
||||
class_name = highlight ? "highlight" : ""
|
||||
sprintf '<span class="%s">%5d</span>', class_name, current_line
|
||||
}
|
||||
end
|
||||
|
||||
def formatted_code
|
||||
%{<div class="code_linenums">#{formatted_nums.join}</div><div class="code">#{super}</div>}
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,14 @@
|
||||
module BetterErrors
|
||||
# @private
|
||||
class CodeFormatter::Text < CodeFormatter
|
||||
def source_unavailable
|
||||
"# Source is not available"
|
||||
end
|
||||
|
||||
def formatted_lines
|
||||
each_line_of(context_lines) { |highlight, current_line, str|
|
||||
sprintf '%s %3d %s', (highlight ? '>' : ' '), current_line, str
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,110 @@
|
||||
require "cgi"
|
||||
require "json"
|
||||
require "securerandom"
|
||||
|
||||
module BetterErrors
|
||||
# @private
|
||||
class ErrorPage
|
||||
def self.template_path(template_name)
|
||||
File.expand_path("../templates/#{template_name}.erb", __FILE__)
|
||||
end
|
||||
|
||||
def self.template(template_name)
|
||||
Erubis::EscapedEruby.new(File.read(template_path(template_name)))
|
||||
end
|
||||
|
||||
attr_reader :exception, :env, :repls
|
||||
|
||||
def initialize(exception, env)
|
||||
@exception = RaisedException.new(exception)
|
||||
@env = env
|
||||
@start_time = Time.now.to_f
|
||||
@repls = []
|
||||
end
|
||||
|
||||
def id
|
||||
@id ||= SecureRandom.hex(8)
|
||||
end
|
||||
|
||||
def render(template_name = "main")
|
||||
self.class.template(template_name).result binding
|
||||
end
|
||||
|
||||
def do_variables(opts)
|
||||
index = opts["index"].to_i
|
||||
@frame = backtrace_frames[index]
|
||||
@var_start_time = Time.now.to_f
|
||||
{ html: render("variable_info") }
|
||||
end
|
||||
|
||||
def do_eval(opts)
|
||||
index = opts["index"].to_i
|
||||
code = opts["source"]
|
||||
|
||||
unless binding = backtrace_frames[index].frame_binding
|
||||
return { error: "REPL unavailable in this stack frame" }
|
||||
end
|
||||
|
||||
result, prompt, prefilled_input =
|
||||
(@repls[index] ||= REPL.provider.new(binding)).send_input(code)
|
||||
|
||||
{ result: result,
|
||||
prompt: prompt,
|
||||
prefilled_input: prefilled_input,
|
||||
highlighted_input: CodeRay.scan(code, :ruby).div(wrap: nil) }
|
||||
end
|
||||
|
||||
def backtrace_frames
|
||||
exception.backtrace
|
||||
end
|
||||
|
||||
def application_frames
|
||||
backtrace_frames.select(&:application?)
|
||||
end
|
||||
|
||||
def first_frame
|
||||
application_frames.first || backtrace_frames.first
|
||||
end
|
||||
|
||||
private
|
||||
def editor_url(frame)
|
||||
BetterErrors.editor[frame.filename, frame.line]
|
||||
end
|
||||
|
||||
def rack_session
|
||||
env['rack.session']
|
||||
end
|
||||
|
||||
def rails_params
|
||||
env['action_dispatch.request.parameters']
|
||||
end
|
||||
|
||||
def uri_prefix
|
||||
env["SCRIPT_NAME"] || ""
|
||||
end
|
||||
|
||||
def request_path
|
||||
env["PATH_INFO"]
|
||||
end
|
||||
|
||||
def html_formatted_code_block(frame)
|
||||
CodeFormatter::HTML.new(frame.filename, frame.line).output
|
||||
end
|
||||
|
||||
def text_formatted_code_block(frame)
|
||||
CodeFormatter::Text.new(frame.filename, frame.line).output
|
||||
end
|
||||
|
||||
def text_heading(char, str)
|
||||
str + "\n" + char*str.size
|
||||
end
|
||||
|
||||
def inspect_value(obj)
|
||||
CGI.escapeHTML(obj.inspect)
|
||||
rescue NoMethodError
|
||||
"<span class='unsupported'>(object doesn't support inspect)</span>"
|
||||
rescue Exception => e
|
||||
"<span class='unsupported'>(exception was raised in inspect)</span>"
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,17 @@
|
||||
module BetterErrors
|
||||
module ExceptionExtension
|
||||
prepend_features Exception
|
||||
|
||||
def set_backtrace(*)
|
||||
if caller_locations.none? { |loc| loc.path == __FILE__ }
|
||||
@__better_errors_bindings_stack = binding.callers.drop(1)
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def __better_errors_bindings_stack
|
||||
@__better_errors_bindings_stack || []
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,141 @@
|
||||
require "json"
|
||||
require "ipaddr"
|
||||
require "set"
|
||||
|
||||
module BetterErrors
|
||||
# Better Errors' error handling middleware. Including this in your middleware
|
||||
# stack will show a Better Errors error page for exceptions raised below this
|
||||
# middleware.
|
||||
#
|
||||
# If you are using Ruby on Rails, you do not need to manually insert this
|
||||
# middleware into your middleware stack.
|
||||
#
|
||||
# @example Sinatra
|
||||
# require "better_errors"
|
||||
#
|
||||
# if development?
|
||||
# use BetterErrors::Middleware
|
||||
# end
|
||||
#
|
||||
# @example Rack
|
||||
# require "better_errors"
|
||||
# if ENV["RACK_ENV"] == "development"
|
||||
# use BetterErrors::Middleware
|
||||
# end
|
||||
#
|
||||
class Middleware
|
||||
# The set of IP addresses that are allowed to access Better Errors.
|
||||
#
|
||||
# Set to `{ "127.0.0.1/8", "::1/128" }` by default.
|
||||
ALLOWED_IPS = Set.new
|
||||
|
||||
# Adds an address to the set of IP addresses allowed to access Better
|
||||
# Errors.
|
||||
def self.allow_ip!(addr)
|
||||
ALLOWED_IPS << IPAddr.new(addr)
|
||||
end
|
||||
|
||||
allow_ip! "127.0.0.0/8"
|
||||
allow_ip! "::1/128" rescue nil # windows ruby doesn't have ipv6 support
|
||||
|
||||
# A new instance of BetterErrors::Middleware
|
||||
#
|
||||
# @param app The Rack app/middleware to wrap with Better Errors
|
||||
# @param handler The error handler to use.
|
||||
def initialize(app, handler = ErrorPage)
|
||||
@app = app
|
||||
@handler = handler
|
||||
end
|
||||
|
||||
# Calls the Better Errors middleware
|
||||
#
|
||||
# @param [Hash] env
|
||||
# @return [Array]
|
||||
def call(env)
|
||||
if allow_ip? env
|
||||
better_errors_call env
|
||||
else
|
||||
@app.call env
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def allow_ip?(env)
|
||||
# REMOTE_ADDR is not in the rack spec, so some application servers do
|
||||
# not provide it.
|
||||
return true unless env["REMOTE_ADDR"] and !env["REMOTE_ADDR"].strip.empty?
|
||||
ip = IPAddr.new env["REMOTE_ADDR"].split("%").first
|
||||
ALLOWED_IPS.any? { |subnet| subnet.include? ip }
|
||||
end
|
||||
|
||||
def better_errors_call(env)
|
||||
case env["PATH_INFO"]
|
||||
when %r{/__better_errors/(?<id>.+?)/(?<method>\w+)\z}
|
||||
internal_call env, $~
|
||||
when %r{/__better_errors/?\z}
|
||||
show_error_page env
|
||||
else
|
||||
protected_app_call env
|
||||
end
|
||||
end
|
||||
|
||||
def protected_app_call(env)
|
||||
@app.call env
|
||||
rescue Exception => ex
|
||||
@error_page = @handler.new ex, env
|
||||
log_exception
|
||||
show_error_page(env, ex)
|
||||
end
|
||||
|
||||
def show_error_page(env, exception=nil)
|
||||
type, content = if @error_page
|
||||
if text?(env)
|
||||
[ 'plain', @error_page.render('text') ]
|
||||
else
|
||||
[ 'html', @error_page.render ]
|
||||
end
|
||||
else
|
||||
[ 'html', no_errors_page ]
|
||||
end
|
||||
|
||||
status_code = 500
|
||||
if defined? ActionDispatch::ExceptionWrapper
|
||||
status_code = ActionDispatch::ExceptionWrapper.new(env, exception).status_code
|
||||
end
|
||||
|
||||
[status_code, { "Content-Type" => "text/#{type}; charset=utf-8" }, [content]]
|
||||
end
|
||||
|
||||
def text?(env)
|
||||
env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" ||
|
||||
!env["HTTP_ACCEPT"].to_s.include?('html')
|
||||
end
|
||||
|
||||
def log_exception
|
||||
return unless BetterErrors.logger
|
||||
|
||||
message = "\n#{@error_page.exception.type} - #{@error_page.exception.message}:\n"
|
||||
@error_page.backtrace_frames.each do |frame|
|
||||
message << " #{frame}\n"
|
||||
end
|
||||
|
||||
BetterErrors.logger.fatal message
|
||||
end
|
||||
|
||||
def internal_call(env, opts)
|
||||
if opts[:id] != @error_page.id
|
||||
return [200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(error: "Session expired")]]
|
||||
end
|
||||
|
||||
env["rack.input"].rewind
|
||||
response = @error_page.send("do_#{opts[:method]}", JSON.parse(env["rack.input"].read))
|
||||
[200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(response)]]
|
||||
end
|
||||
|
||||
def no_errors_page
|
||||
"<h1>No errors</h1><p>No errors have been recorded yet.</p><hr>" +
|
||||
"<code>Better Errors v#{BetterErrors::VERSION}</code>"
|
||||
end
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue