58 votes

Pages d'erreur personnalisées pour 404, 500 mais d'où vient le message d'erreur 500 par défaut ?

Actuellement en production, je reçois ce texte :

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.

Il n'y a pas de HTML sur cette page. Où se trouve ce code ? Je n'ai pas de public/500.html fichier.

Dans mes itinéraires, j'ai :

  get "/404", :to => "errors#error_404"
  get "/422", :to => "errors#error_404"
  get "/500", :to => "errors#error_500"
  get "/505", :to => "errors#error_505"

ErreursContrôleur :

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

Comment faire pour qu'il charge toujours mes erreurs personnalisées ? Sur certaines erreurs, il jette juste ces deux lignes de texte provenant du noyau de Ruby on Rails. Je veux qu'il récupère mes pages d'erreurs personnalisées à chaque fois !

2 votes

Cet article contient une explication concise de l'endroit où s'affiche cette page d'erreur Rails, sous la rubrique "inconvénients" : mattbrictson.com/dynamic-rails-error-pages . Ainsi, si la page d'erreur contient des erreurs, Rails le reconnaît et affiche l'erreur en texte clair que l'on voit ici. Ce n'est pas aussi détaillé que certains des exemples ci-dessous, mais cela peut être une solution rapide pour certaines personnes !

0 votes

Je suis également confronté au même problème. Rubytastic, pouvez-vous m'aider à résoudre ce problème ?

63voto

Rich Peck Points 26701

Notre site exception_handler gem peut être utilisé pour les pages d'erreur personnalisées de Ruby on Rails.

Comment cela fonctionne

Toutes les exceptions de Ruby on Rails sont traitées avec config.exceptions_app . Elle est attribuée dans le config/application.rb o config/environments/*.rb fichiers - il doit s'agir d'un rappel :

config.exceptions_app définit l'application d'exception invoquée par le middleware ShowException lorsqu'une exception se produit. La valeur par défaut est ActionDispatch::PublicExceptions.new(Rails.public_path).

Chaque fois que Ruby on Rails rencontre une erreur, il invoque la fonction ShowExceptions intergiciel. Celui-ci appelle exception_app et envoie l'ensemble du request (y compris exception ) à la exceptions_app :

Middleware-Powered Exceptions

exceptions_app doit fournir une réponse . Dans le cas contraire, le failsafe est chargé :

  # 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 est enregistré comme FAILSAFE_RESPONSE au sommet de ShowExceptions .


Pages d'erreurs personnalisées

Si vous souhaitez créer des pages d'erreur personnalisées, vous devez injecter votre propre callback dans le fichier config.exceptions_app . Cela peut être fait dans l'application ou avec une gemme :

Code screenshot

Remarquez comment le call est utilisée - c'est ainsi qu'un callback fonctionne. Ruby on Rails ( env ) est invoqué lorsque la demande est reçue de l'Internet ; lorsqu'une exception est levée, env est transmis à exceptions_app .

La qualité de votre traitement des exceptions dépendra de la manière dont vous gérez env . C'est important ; le référencement self.routes fait no faire progresser l'environnement.

Le meilleur moyen est de gérer les exceptions avec un contrôleur séparé. Cela vous permet de traiter la requête comme s'il s'agissait d'une autre vue, en accordant l'accès à l'objet de l'exception. layout et d'autres composants ( model / email ).


Il y a deux des moyens de traiter les exceptions :

  1. Remplacement de 404 / 500 itinéraires
  2. Appeler un contrôleur

Notre joyau a été conçu autour de notre controller - invoqué chaque fois qu'un exception est relevé. Cela donne complet contrôle sur le processus d'exception, permettant Mise en page 100 % personnalisée . Il fonctionne à 100% sur Ruby on Rails 5.


Gérer les exceptions de Ruby on Rails

Si le joyau ne vous intéresse pas, laissez-moi vous expliquer le processus :

Toutes les exceptions de Ruby on Rails sont gérées par la fonction config.exceptions_app callback. Celle-ci est assignée dans le config/application.rb o config/environments/*.rb fichiers - il doit s'agir d'un rappel :

config.exceptions_app définit l'application d'exception invoquée par le middleware ShowException lorsqu'une exception se produit. La valeur par défaut est ActionDispatch::PublicExceptions.new(Rails.public_path).

Chaque fois qu'une exception est levée par votre application, la fonction ShowExceptions est invoqué. Cet intergiciel intègre l'exception dans le fichier request et le transmet à la config.exceptions_app le rappel.

Par défaut, config.exceptions_app pointe vers les routes. C'est pourquoi Rails est livré avec 404.html , 500.html y 422.html en el public dossier.

Si vous voulez créer personnalisé vous devez remplacer les pages d'exception config.exceptions_app callback - en transmettant la demande erronée à un gestionnaire approprié, qu'il s'agisse d'une controller o route :

[ intergiciel ]

Les deux façons de gérer efficacement ce problème sont soit d'envoyer les demandes erronées vers les routes, soit d'invoquer un contrôleur.

La méthode la plus simple - et la plus courante - consiste à transmettre la demande aux routes ; malheureusement, cette méthode ignore la demande et vous empêche de détailler correctement les exceptions.

La meilleure solution consiste à invoquer un contrôleur distinct. Cela vous permettra de transmettre l'intégralité de la requête, ce qui vous permettra de l'enregistrer, de l'envoyer par courrier électronique ou de faire un certain nombre d'autres choses.


Erreurs 400 / 500

Rails peut sólo répondre par des erreurs HTTP-valides .

Alors que l'application exception peut être différent, le code d'état renvoyé debe être soit 40x o 50x . Ceci est conforme à la spécification HTTP et à la description de l'opération. aquí .

Cela signifie que, quelle que soit la solution de traitement des exceptions que vous utilisez/construisez, Ruby on Rails besoins pour retourner soit 40x o 50x au navigateur.

En d'autres termes, les pages d'erreur personnalisées n'ont pas grand-chose à voir avec le système de gestion de l'information. type d'exception - plus comment vous attrapez et servez le réponse du navigateur .

Par défaut, Ruby on Rails le fait avec 404.html , 422.html y 500.html dans le public dossier. Si vous souhaitez gérer vous-même le flux d'exceptions, vous devez supprimer ces fichiers et acheminer les demandes erronées vers votre propre système de gestion des exceptions. exceptions_app le rappel.

Cela peut être fait avec le routes ou avec un controller (que je vais expliquer maintenant) :


1. Routes

Le plus simple est de laisser les routes s'en charger.

Cette méthode est gonflée et nécessite l'utilisation de plusieurs actions. Il est également difficile de gérer les réponses.

Cela montre comment remplacer le exceptions_app avec les routes directement :

# config/application.rb
config.exceptions_app = self.routes

Voici le code dont je dispose (Ruby 2.0.0 et Ruby on Rails 4.0) :

Configuration de l'application

#config/application.rb
config.exceptions_app = self.routes

Routes

#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

Contrôleur d'application

#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

Erreurs Mise en page (totalement statique -- pour les erreurs de serveur uniquement)

#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>

Vues d'erreurs

#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>

Si beaucoup préfèrent la méthode des "routes" pour sa simplicité, elle n'est ni efficace ni modulaire. En effet, si votre application a un semblant d'orientation objet, vous la rejetterez rapidement comme un hack.

A beaucoup Le moyen le plus sûr est d'utiliser un contrôleur personnalisé pour attraper l'exception pure. De cette façon, vous pouvez construire le flux en fonction de la structure globale de votre application :


2. Contrôleur

L'autre option consiste à acheminer toutes les demandes vers un contrôleur.

C'est infiniment plus puissant car cela vous permet de prendre la requête (exception) et de la transmettre aux vues, tout en la gérant dans le backend. Cela permet par exemple de l'enregistrer dans la base de données.

Ce site Gist montre comment.

Cela signifie que nous pouvons nous accrocher à l'intergiciel et passer l'ensemble de la demande à un contrôleur.

Si ce contrôleur est soutenu par un modèle et des vues, nous pouvons l'extraire dans une gemme (ce que nous avons fait). Si vous voulez le faire manuellement, voici comment :


Configuration

La beauté de cette méthode est qu'elle s'accroche directement à config.exceptions_app . Cela signifie que toute exception peut être traitée de manière native, ce qui permet une plus grande efficacité. Pour s'assurer que cela fonctionne, vous devez mettre le code suivant dans le fichier config/application.rb ( exceptions_app ne fonctionne que dans production - development montre les erreurs) :

#config/application.rb
config.exceptions_app = ->(env) { ExceptionController.action(:show).call(env) }

Pour tester, vous pouvez mettre les requêtes "local" à false :

#config/environments/development.rb
config.consider_all_requests_local  = false # true

Contrôleur

L'étape suivante consiste à ajouter un exception contrôleur. Bien que cela puisse être géré dans application_controller il est de loin préférable de l'extraire à part. Remarquez l'appel du 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

Vues

Il y a deux vues à ajouter pour que cela fonctionne.

Le premier est le exception/show et le second est la vue layouts/error . La première consiste à donner à l exception_contoller#show une vue, et la seconde pour 500 erreurs internes du serveur.

#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>

Conclusion

El exception n'a pas autant d'importance que le code d'erreur .

Lorsque Ruby on Rails lève une exception, il attribue l'un des codes de réponse HTTP ci-dessus. Ces codes permettent à votre navigateur de déterminer si la requête a abouti.

Lorsque vous traitez des exceptions, vous devez vous assurer que vous êtes en mesure de gérer 40* (qui utilisera généralement la même mise en page que le reste de votre application) et l'onglet 50* (qui auront besoin de leur propre mise en page).

Dans les deux cas, il est préférable d'utiliser un système séparé de exception qui vous permettra de gérer le exception comme un objet.

11 votes

@RichardPeck beau travail, publicité bizarre cependant. Comment faites-vous pour avoir un message <title> personnalisé ou au moins une page non trouvée ? Merci

0 votes

Je n'avais pas l'intention de faire de la publicité, mais merci ! Que voulez-vous dire à propos du <title> message - où voulez-vous le mettre ?

48 votes

Pour info, on parle de vous sur meta. meta.stackoverflow.com/questions/406788

49voto

vipulnsward Points 364

L'erreur que vous rencontrez est déclenchée à partir de

https://github.com/rails/rails/blob/4-0-stable/actionpack/lib/action_dispatch/middleware/show_exceptions.rb#L18-L22

Cela signifie que le code par lequel vos exceptions sont sauvées lève lui-même des exceptions. Vous pouvez vérifier vos journaux pour le texte :

Error during failsafe response:

pour identifier l'origine réelle des exceptions et résoudre ainsi votre problème.

1 votes

En d'autres termes, n'essayez pas d'ajouter un 1/0 dans le fichier application_controller.rb . Cela lèvera une exception à la fois sur le contrôleur d'erreurs et sur votre contrôleur actuel, et bien sûr vous ne pourrez pas voir votre page d'erreur :).

0 votes

@vladCovaliov Puis-je vous demander ce que vous entendez par essayer de ajouter un 1/0 sur ?

0 votes

@iBug une erreur ou exception intentionnelle à des fins de débogage, dans ce cas une division par zéro.

18voto

Darkside Points 173

Les pages d'erreur de l'application doivent être aussi simples que possible. La même recommandation concerne leur rendu. Si votre application renvoie un code de réponse HTTP 500, cela signifie que les choses ont déjà mal tourné. Et il y a une chance que vous ne puissiez pas rendre une page d'erreur et l'afficher à l'utilisateur.

Idéalement, les pages d'erreur devraient être des pages HTML simples servies directement par votre serveur Web sans passer par le serveur d'application.

En parlant de la mise en œuvre de cette idée par Ruby on Rails. Elle repose sur l'utilisation d'un pipeline d'actifs pour précompiler les pages statiques HTML.

Tout d'abord, ajoutez un nouveau type d'actif (Ruby on 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 un moteur de templating est utilisé (par exemple, Slim et Haml ), enregistrez-le via un initialisateur :

# For Slim
Rails.application.assets.register_engine('.slim', Slim::Template)
# For Haml
Rails.application.assets.register_engine('.haml', Tilt::HamlTemplate)

Vous êtes maintenant prêt à créer de jolies pages d'erreur dans le répertoire app/assets/html en utilisant votre moteur de modèles préféré et les aides de visualisation intégrées de Ruby on Rails.

Conseils pour la production

Sur le pipeline d'actifs de production, le digest est ajouté aux actifs compilés et les fichiers sont stockés dans le dossier par défaut (généralement shared/public/assets sur le serveur de production). (généralement shared/public/assets sur le serveur de production). Vous pouvez utiliser capistrano pour copier les pages d'erreur vers le serveur web Root :

# 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

Et la dernière chose. Dites au serveur web d'utiliser ces fichiers pour certains codes d'erreur HTTP (exemple de configuration nginx) :

error_page 500 502 503 504 /500.html;
error_page 404 /404.html;

Mise à jour du pignon 3

Pour Sprocket 3, vous avez besoin de quelque chose comme ceci (testé avec Ruby on 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')

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X