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