Currently in production I'm getting this text:
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.
There isn't any HTML on that page. Where is this code situated? I don't have a public/500.html file.
In my routes I have:
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
How do I make it load my custom errors always? On some errors, it just throws those two lines of text coming somewhere from the Ruby on Rails core. I want it to pickup my custom styled error pages every time!
htaccess, you can simply trigger a 500 directly: RewriteRule ^ - [R=500] . (In fact, any invalid syntax would do the job, eg crash ). You don't need the separate folder, you could make the error conditional in your main .
1. 500 Internal Server Error - The server encountered an unexpected condition which prevented it from fulfilling the request. 2. 404 Not Found - The server has not found anything matching the Request-URI.
The HTTP status code 500 is a generic error response. It means that the server encountered an unexpected condition that prevented it from fulfilling the request. This error is usually returned by the server when no other error code is suitable.
Custom error pages enable you to customize the pages that display when an error occurs. This makes your website appear more professional and also prevents visitors from leaving your site. If a visitor sees a generic error page, they are likely to leave your site.
Our exception_handler
gem can be used for Ruby on Rails custom error pages.
All Ruby on Rails exceptions are handled with config.exceptions_app
. This is assigned in the config/application.rb
or config/environments/*.rb
files - it needs to be a callback:
config.exceptions_app sets the exceptions application invoked by the ShowException middleware when an exception happens. Defaults to ActionDispatch::PublicExceptions.new(Rails.public_path).
Whenever Ruby on Rails hits an error, it invokes the ShowExceptions
middleware. This calls exception_app
and sends the entire request
(including exception
) to the exceptions_app
:
exceptions_app
needs to deliver a response. If not, the failsafe
is loaded:
# 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
The failsafe
is stored as FAILSAFE_RESPONSE
at the top of ShowExceptions
.
If you want to create custom error pages, you need to inject your own callback into config.exceptions_app
. This can either be done in the application or with a gem:
Notice how the call
method is used - this is how a callback works. Ruby on Rails (env
) is invoked when the request is received from the Internet; when an exception is raised, env
is passed to exceptions_app
.
The quality of your exception handling will be dependent on how you manage env
. This is important; referencing self.routes
does not carry the environment forward.
The best way is to handle exceptions with a separate controller. This allows you to handle the request as if it was just another view, granting access to the layout
and other components (model
/ email
).
There are two ways to handle exceptions:
404
/ 500
routesOur gem was designed around our controller
- invoked each time an exception
is raised. This gives full control over the exception process, allowing for 100% branded layout. It works 100% on Ruby on Rails 5.
If you're not interested in the gem, let me explain the process:
All Ruby on Rails exceptions are handled with the config.exceptions_app
callback. This is assigned in the config/application.rb
or config/environments/*.rb
files - it needs to be a callback:
config.exceptions_app sets the exceptions application invoked by the ShowException middleware when an exception happens. Defaults to ActionDispatch::PublicExceptions.new(Rails.public_path).
Whenever an exception is raised by your app, the ShowExceptions
middleware is invoked. This middleware builds the exception into the request
and forwards it to the config.exceptions_app
callback.
By default, config.exceptions_app
points to the routes. This is why Rails comes with 404.html
, 500.html
and 422.html
in the public
folder.
If you want to create custom exception pages, you need to override the config.exceptions_app
callback - passing the erroneous request to an appropriate handler, be it a controller
or route
:
[ middleware ]
The two ways to manage this effectively are either to send the erroneous requests to the routes, or to invoke a controller.
The simplest - and most common - way is to forward the request to the routes; unfortunately, this ignores the request and prevents you from detailing the exceptions properly.
The best way is to invoke a separate controller. This will allow you to pass the entire request, allowing you to save it, email it or do a number of other things.
Rails can only respond with HTTP-valid errors.
Whilst the app's exception may be different, the returned status code should be either 40x
or 50x
. This is in line with the HTTP spec and outlined here.
This means that no matter what exception handling solution you use/build, Ruby on Rails needs to return either 40x
or 50x
errors to the browser.
In other words, custom error pages have little to do with the type of exception - more how you're catching and serving the browser response.
By default, Ruby on Rails does this with 404.html
, 422.html
and 500.html
files in the public
folder. If you want to handle the exception flow yourself, you need to remove these files and channel the erroneous requests to your own exceptions_app
callback.
This can be done with the routes
or with a controller
(which I'll explain now):
The simplest way is to let the routes handle it.
This method is bloated and requires using multiple actions. It is also difficult to manage the responses.
This shows how to replace the exceptions_app
with the routes directly:
# config/application.rb config.exceptions_app = self.routes
Here is the code I have (Ruby 2.0.0 and Ruby on Rails 4.0):
Application configuration
#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
Application Controller
#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
Errors Layout (totally static -- for server errors only)
#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>
Error Views
#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>
While many prefer the "routes" method for its simplicity, it is neither efficient nor modular. Indeed, if your application has any semblance of object orientation, you'll quickly dismiss it as a hack.
A much more resounding way is to use a custom controller to catch the pure exception. This way, you can construct the flow in accordance with your application's overall structure:
The other option is to route all the requests to a controller.
This is infinitely more powerful as it allows you to take the request (exception) and pass it through to the views, whilst managing it in the backend. This will allow for the likes of saving it to the database.
This gist shows how.
It means we can hook into the middleware and pass the entire request to a controller.
If this controller is backed by a model and views, we can extract it into a gem (which is what we did). If you wanted to do it manually, here's how:
Configuration
The beauty of this method is that it hooks directly into config.exceptions_app
. This means any exception can be handled natively, allowing for more efficiency. To make sure this works, you need to put the following code into config/application.rb
(exceptions_app
only works in production
- development
shows the errors):
#config/application.rb config.exceptions_app = ->(env) { ExceptionController.action(:show).call(env) }
To test, you can set the "local" requests to false:
#config/environments/development.rb config.consider_all_requests_local = false # true
Controller
The next step is to add an exception
controller. While this can be handled in application_controller
, it is far better to extract into its own. Notice the call from the 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
Views
There are two views to add to get this working.
The first is the exception/show
view, and second is the layouts/error
. The first is to give the exception_contoller#show
a view, and the second for 500
internal server errors.
#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>
The exception doesn't matter as much as the error code.
When Ruby on Rails raises an exception, it assigns one of the above HTTP response codes. These allow your browser to determine whether the request was successful.
When dealing with exceptions, you need to ensure you're able to handle 40*
errors (which will typically use the same layout as the rest of your application) and the 50*
errors (which will need their own layout).
In both cases, you'll be best using a separate exception
controller, which will allow you to manage the exception
as an object.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With