error handling - not - rails 4: páginas de error personalizadas para 404, 500 y ¿de dónde viene el mensaje de error predeterminado 500?
error 404 not found (4)
📆 Actualización 2018 📆
Nuestra joya exception_handler
ahora se considera la más popular (páginas de error personalizadas de Rails) ↴
exception_handler exception_handler
Cómo funciona
Todas las excepciones de Rails se manejan con config.exceptions_app
. Esto se asigna en los archivos config/application.rb
o config/environments/*.rb
; debe ser una devolución de llamada:
Cuando Rails acierta un error, invoca el middleware ShowExceptions
. Llama a exception_app
y envía la request
completa (incluida la exception
) a exceptions_app
:
exceptions_app
necesita entregar una respuesta . Si no, se carga el failsafe
:
# show_exceptions.rb#L38
def render_exception(env, exception)
wrapper = ExceptionWrapper.new(env, exception)
status = wrapper.status_code
env["action_dispatch.exception"] = wrapper.exception
env["PATH_INFO"] = "/#{status}"
response = @exceptions_app.call(request.env) # => exceptions_app callback
response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
rescue Exception => failsafe_error # => raised if exceptions_app false
$stderr.puts "Error during failsafe response: #{failsafe_error}/n #{failsafe_error.backtrace * "/n "}"
FAILSAFE_RESPONSE
end
El failsafe
se almacena como https://github.com/rails/rails/blob/4-0-stable/actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L18-L22 en la parte superior de ShowExceptions
.
Páginas de error personalizadas
Si desea crear páginas de error personalizadas, debe config.exceptions_app
su propia devolución de llamada en config.exceptions_app
. Esto se puede hacer en la aplicación o con una gema:
Observe cómo se usa el método de call
: así es como funciona una devolución de llamada. Rails ( env
) se invoca cuando la solicitud se recibe de Internet; cuando se produce una excepción, env
se pasa a exceptions_app
.
La calidad de su manejo de excepciones dependerá de cómo administre env
. Esto es importante; hacer referencia a self.routes
no lleva el entorno hacia adelante.
La mejor manera es manejar excepciones con un controlador por separado. Esto le permite manejar la solicitud como si fuera simplemente otra vista, otorgando acceso al layout
y otros componentes ( model
/ email
).
-
Hay dos formas de manejar excepciones:
- Anulando las rutas 404/500
- Invocando un controlador
Nuestra gema fue diseñada alrededor de nuestro controller
, invocada cada vez que se exception
una exception
. Esto le da un control total sobre el proceso de excepción, lo que permite un diseño 100% de marca :
exception_handler es ahora la principal joya de páginas de error personalizadas de producción para Rails.
Mantenido por más de 3 años, es la joya de excepción más simple y poderosa para Rails. Funciona al 100% en Rails 5 y ya se ha descargado más de 70,000 veces.
Joya
La última exception_handler tiene las siguientes actualizaciones:
- Excepciones personalizadas
- "Mapeo" de excepciones (elija qué excepciones manejar)
- Notificaciónes de Correo Electrónico
- Backend del modelo
- Integración de piñones 4+
- Suite de prueba RSpec
- Vistas basadas en la configuración regional
Puedes leer más here .
Gestión de excepciones de Rails
Si no estás interesado en la gema, déjame explicarte el proceso:
Todas las excepciones de Rails se manejan con la config.exceptions_app
llamada config.exceptions_app
. Esto se asigna en los archivos config/application.rb
o config/environments/*.rb
; debe ser una devolución de llamada:
Siempre que su aplicación presente una excepción, se invoca el middleware ShowExceptions
. Este middleware crea la excepción en la request
y la reenvía a la config.exceptions_app
llamada config.exceptions_app
.
Por defecto, config.exceptions_app
apunta a las rutas. Es por esto que Rails viene con 404.html
, 500.html
y 422.html
en la carpeta public
.
Si desea crear páginas de excepción personalizadas , debe anular la config.exceptions_app
llamada config.exceptions_app
, pasar la solicitud errónea a un controlador apropiado, ya sea un controller
o una route
:
[[middleware]]
Las dos formas de administrar esto de manera efectiva son enviar las solicitudes erróneas a las rutas o invocar un controlador.
La forma más simple, y más común, es reenviar la solicitud a las rutas; desafortunadamente, esto ignora la solicitud y le impide detallar las excepciones correctamente.
La mejor manera es invocar un controlador por separado. Esto le permitirá pasar la solicitud completa, lo que le permite guardarla, enviarla por correo electrónico o hacer otras cosas.
-
400/500 errores
Los raíles solo pueden responder con errores HTTP válidos .
Si bien la excepción de la aplicación puede ser diferente, el código de estado devuelto debe ser 40x
o 50x
. Esto está en línea con la especificación HTTP, y se describe here ↴
Esto significa que no importa qué solución de manejo de excepciones use / compile, Rails debe devolver errores 40x
o 50x
al navegador.
En otras palabras, las páginas de error personalizadas tienen poco que ver con el tipo de excepción: más información sobre cómo atrapa y atiende la respuesta del navegador .
De forma predeterminada, Rails lo hace con los 404.html
, 422.html
y 500.html
en la carpeta public
. Si desea manejar el flujo de excepciones usted mismo, necesita eliminar estos archivos y canalizar las solicitudes erróneas a su propia devolución de llamada exceptions_app
.
Esto se puede hacer con las routes
o con un controller
(que explicaré ahora):
1. Rutas
La forma más simple es dejar que las rutas lo manejen.
Este método está inflado y requiere el uso de múltiples acciones. También es difícil manejar las respuestas.
Este tutorial explica:
Esto muestra cómo reemplazar el exceptions_app
con las rutas directamente:
# config/application.rb
config.exceptions_app = self.routes
Aquí está el código que tengo (Ruby 2.0.0, Rails 4.0):
Configuración de la aplicación
#config/application.rb
config.exceptions_app = self.routes
Rutas
#config/routes.rb
if Rails.env.production?
get ''404'', to: ''application#page_not_found''
get ''422'', to: ''application#server_error''
get ''500'', to: ''application#server_error''
end
Controlador de aplicación
#controllers/application_controller.rb
def page_not_found
respond_to do |format|
format.html { render template: ''errors/not_found_error'', layout: ''layouts/application'', status: 404 }
format.all { render nothing: true, status: 404 }
end
end
def server_error
respond_to do |format|
format.html { render template: ''errors/internal_server_error'', layout: ''layouts/error'', status: 500 }
format.all { render nothing: true, status: 500}
end
end
Diseño de errores (totalmente estático, solo para errores del servidor)
#views/layouts/error.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= action_name.titleize %> :: <%= site_name %></title>
<%= csrf_meta_tags %>
<style>
body {
background: #fff;
font-family: Helvetica, Arial, Sans-Serif;
font-size: 14px;
}
.error_container {
display: block;
margin: auto;
margin: 10% auto 0 auto;
width: 40%;
}
.error_container .error {
display: block;
text-align: center;
}
.error_container .error img {
display: block;
margin: 0 auto 25px auto;
}
.error_container .message strong {
font-weight: bold;
color: #f00;
}
</style>
</head>
<body>
<div class="error_container">
<%= yield %>
</div>
</body>
</html>
Vistas de error
#views/errors/not_found_error.html.erb
<div class="error">
<h2>Sorry, this page has moved, or doesn''t exist!</h2>
</div>
#views/errors/internal_server_error.html.erb
<div class="error">
<div class="message">
<strong>Error!</strong>
We''re sorry, but our server is experiencing problems :(
</div>
</div>
Aunque muchos prefieren el método de "rutas" por su simplicidad, no es eficiente ni modular. De hecho, si su aplicación tiene alguna apariencia de orientación a objetos, la descartará rápidamente como un hack.
Una forma mucho más contundente es usar un controlador personalizado para captar la excepción pura. De esta forma, puede construir el flujo de acuerdo con la estructura general de su aplicación:
2. Controlador
La otra opción es enrutar todas las solicitudes a un controlador.
Esto es infinitamente más poderoso ya que le permite tomar la solicitud (excepción) y pasarla a las vistas, mientras se administra en el back-end. Esto permitirá la posibilidad de guardarlo en la base de datos.
Esta gist muestra cómo:
Significa que podemos conectarnos al middleware y pasar toda la solicitud a un controlador.
Si este controlador está respaldado por un modelo y vistas, podemos extraerlo en una gema (que es lo que hicimos). Si quería hacerlo manualmente, aquí le mostramos cómo hacerlo:
-
Config
La belleza de este método es que se engancha directamente en config.exceptions_app
. Esto significa que cualquier excepción se puede manejar de forma nativa, lo que permite una mayor eficiencia. Para asegurarse de que esto funcione, debe colocar el siguiente código en config/application.rb
( exceptions_app
solo funciona en production
- el development
muestra los errores):
#config/application.rb
config.exceptions_app = ->(env) { ExceptionController.action(:show).call(env) }
Para probar, puede establecer las solicitudes "locales" en falso:
#config/environments/development.rb
config.consider_all_requests_local = false # true
-
Controlador
El siguiente paso es agregar un controlador de exception
. Si bien esto se puede manejar en application_controller
, es mucho mejor extraerlo por sí mismo. Observe la llamada desde la application.rb
- ExceptionController.action(:show)
:
#app/controllers/exception_controller.rb
class ExceptionController < ApplicationController
#Response
respond_to :html, :xml, :json
#Dependencies
before_action :status
#Layout
layout :layout_status
####################
# Action #
####################
#Show
def show
respond_with status: @status
end
####################
# Dependencies #
####################
protected
#Info
def status
@exception = env[''action_dispatch.exception'']
@status = ActionDispatch::ExceptionWrapper.new(env, @exception).status_code
@response = ActionDispatch::ExceptionWrapper.rescue_responses[@exception.class.name]
end
#Format
def details
@details ||= {}.tap do |h|
I18n.with_options scope: [:exception, :show, @response], exception_name: @exception.class.name, exception_message: @exception.message do |i18n|
h[:name] = i18n.t "#{@exception.class.name.underscore}.title", default: i18n.t(:title, default: @exception.class.name)
h[:message] = i18n.t "#{@exception.class.name.underscore}.description", default: i18n.t(:description, default: @exception.message)
end
end
end
helper_method :details
####################
# Layout #
####################
private
#Layout
def layout_status
@status.to_s == "404" ? "application" : "error"
end
end
-
Puntos de vista
Hay dos vistas para agregar para que funcione.
El primero es la vista de exception/show
, y el segundo es el layouts/error
. El primero es dar a exception_contoller#show
a view, y el segundo para 500
errores internos del servidor.
#app/views/exception/show.html.erb
<h1><%= details[:name] %></h1>
<p><%= details[:message] %></p>
#app/views/layouts/error.html.erb (for 500 internal server errors)
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Error</title>
<style>
html {
height: 100%;
background: #fff;
}
body {
font-family: Helvetica, Arial, Sans-Serif;
font-size: 14px;
}
.error_container {
display: block;
margin: auto;
margin: 10% auto 0 auto;
width: 40%;
}
.error_container .error {
display: block;
text-align: center;
}
.error_container .error img {
display: block;
margin: 0 auto 15px auto;
}
.error_container .message > * {
display: block;
}
.error_container .message strong {
font-weight: bold;
color: #f00;
}
</style>
</head>
<body>
<div class="error_container"><%= yield %></div>
</body>
</html>
Conclusión
La excepción no importa tanto como el código de error .
Cuando Rails genera una excepción, asigna uno de los códigos de respuesta HTTP
anteriores. Estos permiten que su navegador determine si la solicitud fue exitosa.
Cuando se trata de excepciones, debe asegurarse de que pueda manejar los errores 40*
(que normalmente usarán el mismo diseño que el resto de su aplicación) y los errores 50*
(que necesitarán su propio diseño).
En ambos casos, será mejor que use un controlador de exception
separado, que le permitirá administrar la exception
como un objeto.
Actualmente en producción estoy obteniendo este texto:
500 Internal Server Error
If you are the administrator of this website, then please read this web application''s
log file and/or the web server''s log file to find out what went wrong.
No html en esa página nada.
¿Dónde está este código? No tengo public / 500.html ni nada en ese sentido.
En mis rutas tengo:
get "/404", :to => "errors#error_404"
get "/422", :to => "errors#error_404"
get "/500", :to => "errors#error_500"
get "/505", :to => "errors#error_505"
ErrorsController:
class ErrorsController < ApplicationController
def sub_layout
"left"
end
def error_404
render :status => 404, :formats => [:html], :layout => "white", :sub_layout => "left"
end
def error_422
render :status => 422, :formats => [:html], :layout => "white", :sub_layout => "left"
end
def error_500
render :status => 500, :formats => [:html], :layout => "white", :sub_layout => "left"
end
def error_505
render :status => 505, :formats => [:html], :layout => "white", :sub_layout => "left"
end
end
¿Cómo hacer que cargue mis errores personalizados siempre? En algunos errores, simplemente tira el texto de 2 líneas desde algún lugar del núcleo del riel, ¡quiero que recoja mis páginas de error personalizadas cada vez! ¿cómo? ¡Gracias!
Aquí está la última y rápida solución para mostrar la página 404_error personalizada.
- Agregue las siguientes líneas en development.rb o production.rb según su env .
config.exceptions_app = self.routes
config.consider_all_requests_local = false
- Eliminar todo rm public / {404,500,422} .html
- Cree el archivo 404.html.erb en la carpeta estática de su proyecto de rieles. Puedes agregar tu html personalizado aquí (esto usará el diseño de tu aplicación, así que no te preocupes por el contenido del encabezado y el pie de página)
El error que estás experimentando se está lanzando desde
Esto significa que el código que sus excepciones están siendo rescatadas por ellos mismos están lanzando excepciones. Puede consultar sus registros para ver el texto:
Error during failsafe response:
para identificar de qué se originan realmente las excepciones y así resolver su problema.
Las páginas de error en la aplicación deben ser tan simples como sea posible. La misma recomendación se refiere a su procesamiento. Si su aplicación devuelve 500 código de respuesta HTTP, significa que las cosas ya han empeorado. Y existe la posibilidad de que no pueda mostrar la página de error y mostrarla al usuario.
Idealmente, las páginas de error deberían ser un HTML simple servido directamente por su servidor web sin tocar el servidor de la aplicación.
Hablando de la implementación de Rails de esta idea. Se basa en el uso de la canalización de activos para precompilar las páginas estáticas HTML.
Primero agregue un nuevo tipo de activo (Rails> 4.1):
# config/initializers/assets.rb
Rails.application.config.assets.precompile += %w(404.html 500.html)
Rails.application.config.assets.paths << Rails.root.join(''app/assets/html'')
Rails.application.config.assets.register_mime_type(''text/html'', ''.html'')
Si el motor de plantillas está utilizando (por ejemplo, slim, haml), regístrelo a través del inicializador:
# for Slim
Rails.application.assets.register_engine(''.slim'', Slim::Template)
# for Haml
Rails.application.assets.register_engine(''.haml'', Tilt::HamlTemplate)
Ahora está listo para crear páginas bonitas de error en el directorio app / assets / html utilizando su motor de plantilla favorito y los ayudantes de visualización incorporados en Rails.
Consejos para la producción
En la tubería de activos de producción agrega resumen a los activos compilados y almacena los archivos en la carpeta predeterminada (típicamente compartida / pública / activos en el servidor de producción). Puede usar capistrano para copiar las páginas de error a la raíz del servidor web:
# config/deploy.rb
# Capistrano 3 only
namespace :deploy do
desc ''Copy compiled error pages to public''
task :copy_error_pages do
on roles(:all) do
%w(404 500).each do |page|
page_glob = "#{current_path}/public/#{fetch(:assets_prefix)}/#{page}*.html"
# copy newest asset
asset_file = capture :ruby, %Q{-e "print Dir.glob(''#{page_glob}'').max_by { |file| File.mtime(file) }"}
if asset_file
execute :cp, "#{asset_file} #{current_path}/public/#{page}.html"
else
error "Error #{page} asset does not exist"
end
end
end
end
after :finishing, :copy_error_pages
end
Y lo último. Indique al servidor web que use estos archivos para ciertos códigos de error HTTP (configuración nginx de muestra):
error_page 500 502 503 504 /500.html;
error_page 404 /404.html;
Sprocket 3 Update
Para Sprocket 3 necesitas algo como esto (probado con Rails 5):
# config/environments/production.rb
config.assets.configure do |env|
env.register_transformer ''text/slim'', ''text/html'', Slim::Template
env.register_mime_type ''text/slim'', extensions: [''.html'']
env.register_engine ''.slim'', Slim::Template
end
# config/initializers/assets.rb
Rails.application.config.assets.precompile += %w(404.html 500.html)
Rails.application.config.assets.paths << Rails.root.join(''app/assets/html'')