richpeck / exception_handler

Ruby on Rails Custom Error Pages
509 stars 70 forks source link

Ironically localhost:3000/404 still gives me the old error page; also, cant redirect 500 errors to new 404 page =[ #62

Closed jonathanphilippou closed 6 years ago

jonathanphilippou commented 6 years ago

localhost:3000/404 gives the standard error page. I've been struggling to change this in routes.rb, maybe someone can tell me what I'm doing wrong.

screen shot 2018-03-20 at 10 25 37 pm

Every other page gives me my 404 as expected (i'm very pleased overall, besides the exception ive mentioned above):

screen shot 2018-03-20 at 10 26 12 pm

But also, I don't get my new error page with 500 errors. I get the follow:

screen shot 2018-03-20 at 10 31 38 pm

How can i make it so that 500 goes to the same page as 400?

richpeck commented 6 years ago

Can you share your repository?

In terms of the 404 error page, I think it's because you may have your /public/404.html page still intact. If you remove that, it should work.

Rich

jonathanphilippou commented 6 years ago

Hey @richpeck

I can't share the repo bc it's for work, but I tried removing /public/404.html and it worked! However, I still get the 500 page i showed above. I tried deleting the /public/500.html. What else do you think needs to be done?

richpeck commented 6 years ago

That error you're seeing is basically the "failsafe" for Rails, meaning that it hit a 500 error, tried to show the "exception" page (provided by ExceptionHandler) and it failed again.

The issue is typically because you're trying to use exactly the same layout as your main app / 404 errors page (hence it will be using something like the same variables or similar). If this is the case, you need to limit any db calls when using the 500 error layout.

elalemanyo commented 6 years ago

Hi, I have also something similar, maybe someone can help me. ruby "2.3.3" gem 'rails', '~> 5.0.6' gem 'exception_handler', '~>0.7.7.0'

I add also config.exception_handler at development.rb:

config.exception_handler = {
  dev: true,
  layouts: {
    '404' => 'exception',
    '500' => 'exception'
  },
}

When I force a 500 error (raise ....), than I see the new error page, but why when I call just /404 I don't see also the new error page? I get just 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.

I see on the logs that is trying to use the application.html.erb, this layout is doing some db calls and that is producing a 500 error, should not use the same layout as the 500? And why is not at least showing the 500 new error page?

I hope someone understand the problem.

Thanks!

richpeck commented 6 years ago

Hi, thanks for the message.

You are correct in your assumption, that the "default" 500 error page is raised by the "application" layout's DB calls etc. In terms of why that layout is showing, you will be better doing the following:

config.exception_handler = {
  dev: true,
  layouts: {
    404 => 'exception',
    500 => 'exception'
  },
}

The "layouts" hash uses integers not strings.

I see on the logs that is trying to use the application.html.erb

If you could show me the logs for this call, I'll be in a much better position to diagnose the error.

--

Looking forward to getting this sorted for you. And thanks again for trying the gem!

elalemanyo commented 6 years ago

Hi, Thanks for your fast answer. The problem was using strings and not integers 😤

Is there a way to make that /500 the 500 new error page render? Now all those static error pages paths are now 404. And how can I test the error pages without triggering an error such the one I want to test?

Thanks for this great gem!

richpeck commented 6 years ago

Hmmmm I don't think so.

The behavior you're reporting is actually how it should work (/404 doesn't exist so it goes to a 404 page).

The way I generally test it is to make an obvious error in a method (.each do), which will then trigger the 500 error page. This will give me the ability to change it to my tastes as required.

Other than that, I guess I could put some functionality into the system but might be hacking it them.

elalemanyo commented 6 years ago

I agree, that the behavior is the way it should work, but we are used to have all those urls (/404, /500 ...). What do you think if for testing we add those routes and we just raise there the http error code? Is any easy why to do something like this?

elalemanyo commented 6 years ago

I add the routes and build a small controller to raise the HTTP Errors, but is not working 😔 The controller try do something like this to show the error pages:

raise ActionController::RoutingError.new('Internal Server Error')

But this is not working. Is there a way I can do that?

Thanks!

richpeck commented 6 years ago

Do you have a repo I could look at? Thanks

elalemanyo commented 6 years ago

Sorry, the repo is private. But what I did is really simple.

routes.rb:

get '/500', to: 'p/http_errors#internal_server_error', as: :internal_server_error

http_errors_controller.rb:

class P::HttpErrorsController < P::BaseController
  def internal_server_error
    raise ActionController::RoutingError.new('Internal Server Error')
  end
end

Should work like this? you think it have something to do with my repo?

Thanks!

richpeck commented 6 years ago

What happens if you do this...

# config/routes.rb
get '500', controller: 'p/http_errors', action: :internal_server_error, as: :internal_server_error

# config/application.rb
config.exception_handler = { dev: true } 

# app/p/controllers/http_errors_controller.rb
class P::HttpErrorsController < P::BaseController
  def internal_server_error
    raise "Internal Server Error"
  end
end

The errors that Rails raises are all mapped to different "HTTP" errors. Any that are not mapped will default to the 500 server error:

500_server_error

elalemanyo commented 6 years ago

Ok, now is almost working: routes.rb

# HTTP Error Pages
#
#
get '401', controller: 'p/http_errors', action: :unauthorized, as: :unauthorized
get '404', controller: 'p/http_errors', action: :not_found, as: :not_found
get '406', controller: 'p/http_errors', action: :not_acceptable, as: :not_acceptable
get '422', controller: 'p/http_errors', action: :unprocessable_entity, as: :unprocessable_entity
get '500', controller: 'p/http_errors', action: :internal_server_error, as: :internal_server_error

http_errors_controller.rb

class P::HttpErrorsController < P::BaseController

  def unauthorized
    raise ActionController::Unauthorized
  end

  def not_found
    raise ActiveRecord::RecordNotFound
  end

  def not_acceptable
    raise ActionController::UnknownFormat
  end

  def unprocessable_entity
    raise ActionController::InvalidAuthenticityToken
  end

  def internal_server_error
    raise ActionController::InternalServerError
  end
end

401 is not working, I need to add it but I am not sure how. And maybe find a better way to raise 500. Can you help me with that?

I think that maybe adding a setting to show error pages even when dev config is false would be a good idea.

For example for development environment, you want to see the standard rails error pages, but is nice to see the pretty ones when you use the static urls (404, 500...), so you can see them without needing to change the global config. What do you think?

BTW: Thanks for all your help and this great gem!

richpeck commented 6 years ago
# config/routes.rb
errors =  Rack::Utils::HTTP_STATUS_CODES.slice( 401, 404, 406, 422, 500 ) # for some reason slice! won't work
errors.each do |k,v|
  value  = v.gsub(" ", "").underscore
  get k.to_s, to: 'application#http_errors', as: value.to_sym, code: value.to_s # get '500', to: 'p/http_errors#show', as: :internal_server_error, code: "internal_server_error"
end
# config/application.rb
config.action_dispatch.rescue_responses.merge!(
   'ApplicationController::NotAuthorized'   => :unauthorized,  
   'ActionController::UnknownFormat'        => :not_acceptable,
   'ActionController::InternalServerError'  => :internal_server_error
) 
# app/controllers/application_controller.rb
class P::HttpErrorsController < ActionController::Base

  def http_errors
    raise Rails.application.config.action_dispatch.rescue_responses.key(params[:code].to_sym).constantize
  end

end

As explained in this StackOverflow answer, the big problem is that Rails doesn't handle authorization (or lack of thereof) by default, meaning that if you're trying to invoke a class (which in your case doesn't exist), you will need to create it.

--

For my solution, Rails comes with its own hash of error codes, which means that rather than explicitly defining each error code you're trying to load, you'll be better just slicing this "master" hash.

This has the added benefit of ensuring that you're always 100% compliant with the Rails core.

After this, all you need to do is route all requests to a single controller view (don't repeat yourself), which could actually be put into the "application" controller (which I've demonstrated above). This gives you ultimate flexibility in managing which error codes are raised etc.

Rich

elalemanyo commented 6 years ago

Is not working, but good Idea. For the moment looks like this:

# config/routes.rb
{
  401 => :unauthorized,
  404 => :not_found,
  406 => :not_acceptable,
  422 => :unprocessable_entity,
  500 => :internal_server_error
}.each do |k,v|
  get k.to_s, controller: 'p/http_errors', action: :show, as: v, code: v
end
# config/application.rb
config.action_dispatch.rescue_responses.merge!(
 'ApplicationController::NotAuthorized'       => :unauthorized,
 'ActionController::RecordNotFound'           => :not_found,
 'ActionController::UnknownFormat'            => :not_acceptable,
 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
 'ActionController::InternalServerError'      => :internal_server_error
)
# app/controllers/p/http_errors_controller.rb
class P::HttpErrorsController < P::BaseController
  def show
    raise Rails.application.config.action_dispatch.rescue_responses.key( params[:code].to_s )
  end
end

And calling for example 422 give me this error:

Started GET "/422" for ::1 at 2018-04-12 14:15:53 +0200
Processing by P::HttpErrorsController#show as HTML
Parameters: {"code"=>:unprocessable_entity}
Completed 500 Internal Server Error in 38ms (ActiveRecord: 9.1ms)
richpeck commented 6 years ago

Does the log have any further details about what might be causing the 500 error?

elalemanyo commented 6 years ago

No 😞

richpeck commented 6 years ago

I'll have a look this evening

elalemanyo commented 6 years ago

@richpeck Did you have time to test it?

Thanks!

richpeck commented 6 years ago

Not yet - had to sort something out. Will test it tonight.

richpeck commented 6 years ago

Works fine for me...

untitled

elalemanyo commented 6 years ago

Can you show me how did you implemented?

Thanks!

richpeck commented 6 years ago

I just copied & pasted the code you had and the code I had.

elalemanyo commented 6 years ago

Ok, I test it again and now works. The only one still not working is 401:

raise ActionController::Unauthorized

This still giving 500 Error page, because ActionController::Unauthorized do not exist -> errors_rb.html

richpeck commented 6 years ago

There is a way around this - using custom exceptions:

untitled

Perhaps if you defined something like the following:

#config/application.rb
config.action_dispatch.rescue_responses = { 
  'ActiveRecord::DangerousAttributeError' => :unauthorized
}

This way, you're able to invoke that particular class and it will be treated as a 401 error.

elalemanyo commented 6 years ago

Still not working... did you try and works for you? Would be nice to have those routes working on development always, like this you can check http pages design without setting dev mode.

richpeck commented 6 years ago

No I didn't test

elalemanyo commented 6 years ago

Would be possible to have routes for each error page, but with http status 200?

richpeck commented 6 years ago

/500 that you can access?

elalemanyo commented 6 years ago

yes. Just a route /500 where you get the HTTP Error page but with 200 code.

richpeck commented 6 years ago

This is coming to the next version -

untitled

elalemanyo commented 6 years ago

Thanks!