Default exception doesn't render with correct content type

I just noticed something I think is interesting and wanted to see what
everyone felt about it. Using a quick and dirty scaffold to create a
resource, I found that if I hit a show url, with an invalid id, it
will throw an exception in html instead of xml.

Here is how you repeat this.

$ rails testing_rest
$ cd testing_rest
$ ./script/generate scaffold person name:string
$ rake db:migrate
$ ./script/server

From another window

$ curl -i -H “Accept: application/xml” -H “Content-Type: application/
xml” http://127.0.0.1:3000/people

So far, so good. I was returned an empty array in xml

HTTP/1.1 200 OK

Content-Type: application/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>

Now, I send over a request for a person that doesn’t exist.

$ curl -i -H “Accept: application/xml” -H “Content-Type: application/
xml” http://127.0.0.1:3000/people/1

I get back a full html debug output in html. Yuck!

HTTP/1.1 404 Not Found

Content-Type: text/html; charset=utf-8

I chopped the content for readability, its not what matters.

Ok, so I’m in development mode and it will automatically assume I am
local and spit out the debug. I can handle that, although I still
don’t think it should be in html.

But, if I run db:migrate again under production and start it up (or
modify enviroment.rb), and now hit the external IP of my machine
instead of localhost, I get back the 404 page. Better, but, its still
coming back in html.

$ curl -i -H “Accept: application/xml” -H “Content-Type: application/
xml” http://192.168.1.101:3000/people/1

lication/xml" http://192.168.247.20:3000/people/1
HTTP/1.1 404 Not Found
Connection: close
Date: Fri, 29 Feb 2008 02:24:02 GMT
Set-Cookie:
_testing_rest_session=BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo
%250ASGFzaHsABjoKQHVzZWR7AA%253D%253D–
d1e3f66166f02f6c78f45395f7c0186d7ab9dcd9; path=/
Status: 404 Not Found
Cache-Control: no-cache
Server: Mongrel 1.1.3
Content-Type: text/html; charset=utf-8
Content-Length: 947


My question is, shouldn’t this be returning me xml? I do have a
respond_to block saying that it should spit back xml. Well, the
problem is in the way rails handles uncaught exceptions. It doesn’t
do anything with content-type from what I can see. If there is a
404.html file in public, it will render it as html. If you don’t have
a 404.html file, it will simply send back a head with 404 as the
response. However, it still shows content-type as xml.

So, I guess I have three choices. The first one is to catch that
exception in my action. The second one is to create a plugin. The
third is to just not care that I am sending back html to my xml
clients. I chose the plugin.

What are you all doing? Does it matter that I am sending content-type
html to an xml client? What about rss, javascript, etc…

BTW - here is my plugin. Nothing special I just add the alternate
formats I want to the ALT_FORMATS array. I also have access to a
template to render instead of a static html file in public. Not sure
if that gives me anything great, just how I chose to do it at the
time.

Thoughts??

-Dusty Doris


module MyExceptionHandler
  DEFAULT_CODES = {
    'ActionController::RoutingError' => :not_found,
    'ActionController::UnknownAction' => :not_found,
    'ActiveRecord::RecordNotFound' => :not_found,
    'ActiveRecord::StaleObjectError' => :conflict,
    'ActiveRecord::RecordInvalid' => :unprocessable_entity,
    'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
    'ActionController::MethodNotAllowed' => :method_not_allowed,
    'ActionController::NotImplemented' => :not_implemented,
    'ActionController::InvalidAuthenticityToken'

=> :unprocessable_entity
}

  ALT_FORMATS = [:xml, :json, :js, :rss, :atom, :text]

  def rescue_action_in_public(exception)
    @exception = exception
    respond_to do |format|
      format.html do
        render :template => "exceptions/#{return_code.to_s}",
               :layout   => "exceptions",
               :status   => return_code
      end
      ALT_FORMATS.each do |f|
        format.send(f) { head :status => return_code }
      end
    end
  end

  def rescue_action_locally(exception)
    respond_to do |format|
      format.html do
        add_variables_to_assigns
        @template.instance_variable_set("@exception", exception)
        @template.instance_variable_set("@rescues_path",
          File.dirname(rescues_path("stub")))
        @template.send!(:assign_variables_from_controller)
        @template.instance_variable_set("@contents",

@template.render_file(template_path_for_local_rescue(exception),
false))
response.content_type = Mime::HTML
render_for_file(rescues_path(“layout”),
response_code_for_rescue(exception))
end
ALT_FORMATS.each do |f|
format.send(f) { head :status => return_code }
end
end
end

  private
  def return_code
    DEFAULT_CODES[@exception.class.to_s] || :internal_server_error
  end

end

With a little

require ‘my_exception_handler’
ActionController::Base.send :include, MyExceptionHandler
ActionController::Base.consider_all_requests_local = false

dusty wrote:

require ‘my_exception_handler’
ActionController::Base.send :include, MyExceptionHandler
ActionController::Base.consider_all_requests_local = false

something like the following will also work…

class Application < ActionController::Base
around_filter :catch_exceptions

def catch_exceptions
begin
yield
rescue Exception => e
render :xml => e.message, :status => 500
end
end

end

Thanks for the reply. That would be much easier. The filters are
nice, unfortunately, it doesn’t catch things like
ActionController::RoutingErrors. At least its not for me. It does
catch things like notfound errors within an action though.

dusty wrote:

Thanks for the reply. That would be much easier. The filters are
nice, unfortunately, it doesn’t catch things like
ActionController::RoutingErrors. At least its not for me. It does
catch things like notfound errors within an action though.

You are right, it won’t catch routing errors as they are thrown before
the surround_filters get a chance to party :frowning:

ilan