Why does CErrorHandler always display full exception page for AJAX requests?

Hi,

So when an exception is thrown and later handled by CErrorHandler::handleException(), then if it is a CHttpException (or YII_DEBUG is not true), we have the option of displaying the error using CErrorHandler::errorAction. This is good, because it lets us decide for ourselves if we want to display CHttpExceptions via a custom view or using the full exception display.

However, if the request is an AJAX one, then unconditionally the display of the exception is done in the full display way. Here are lines 178-183 in CErrorHandler:




			if($this->isAjaxRequest())

				$app->displayException($exception);

			else if($exception instanceof CHttpException || !YII_DEBUG)

				$this->render('error',$data);

			else

				$this->render('exception',$data);



This gives the result that we cannot easily choose to display CHttpExceptions using a custom view for AJAX requests, like we can for non-AJAX requests. I am wondering what the reasoning behind this is?

Say for example that in an action I determine that the request was done in an incorrect way, like in the default webapp’s loadModel().

The normal way to handle this is to throw a CHttpException(400, “You should’t attempt this”), and if the request is a non-AJAX one, this will normally be displayed using the errorAction (usually SiteController::actionError).

This is a fine solution since the exception we are throwing is a “controlled one”, and what I mean by that is that it isn’t like a totally unexpected CException/Exception coming from somewhere else, and that shouldn’t have happened under any circumstances. I.e. we wish to display it in a controlled way.

However, this is impossible to do for an AJAX request, because CErrorHandler unconditionally renders the whole exception regardless of its type and/or what YII_DEBUG is set to. I consider this a problem because it would be very neat to do this in the JS code requesting the action:




jQuery.ajax(url, {

	'data': ...


	'success': function(result){

		// Do stuff here on success.

	},


	'error': function(jqXHR){

		// If the server returned a non-success HTTP code (which is usually the case when

		//  we throw CHttpException), this is considered a failure by jQuery, this callback

		//  is used instead of 'success', and we can/could use this to display the returned

		//  error message.

		// This is where the problem lies though, because we always get a huge load of

		//  exception display instead of just the result of a custom view if the exception

		//  thrown was a CHttpException. There is no easy way to display the result of a

		//  custom view for the exception in this case.

		

		// If the readyState is 0 it's most likely due to the user aborting the request. In that case do nothing.

		if(jqXHR.readyState != 0)

		{

			// Display an error message to the user.

		}

	}

});



If CHttpExceptions (or other exceptions when YII_DEBUG is not true) were possible to display using a custom view/action for AJAX requests as well, we would be able to do the above, instead of on the server side having to add code that deals with AJAX/non-AJAX (catch the exception thrown and IF the request is AJAX return a response that the client-side JS code then inspects and determines if it’s an error inside the ‘success’ function in the code example above, and if it is displays the error).

Ok, 'nuf said. Am I missing something obvious here? Doesn’t what I say make sense?

Thanks!

Edit: I can indeed just override some stuff to handle this the way I want, but this has downsides of its own, and until I’m smart enough to see why AJAX requests are handled this specifically by CErrorHandler I’d like to discuss if it would be a good idea to change this.

Edit: MrSoundless kindly pointed out that CApplication::displayException(), which is what is used to output the exception when the request is an AJAX one, only outputs full stack trace and file information when YII_DEBUG is not true. This is accurate, but I argue that it still outputs more info than one might want, but foremost the base "problem" still exists in that you cannot at all control how the exception is rendered, like you can for non-AJAX requests.

I have thought along the same lines, and would also be interested if someone with more knowledge could say something about this.

Just lost 2 hours looking at what is going on with ajax related exceptions and found your post.

I also think that this should be addressed since you have good point on it.

The ajax.error function can receive 3 parameters (xhr, textStatus, errorThrown)…

this way you can check the value of textStatus to see if there are some errors and then use xhr.status and xhr.responseText to display a nice alert.

@mdomba you are right for for ajax.error parameters. Actually I’m using them the same way you described. But having something for xhr.responseText like this:


<h1>CHttpException</h1>

<p>some message....</p>

isn’t acceptable for me. I don’t wanna show users h1 tag with “CHttpException” text. It’s not user friendly.

So I have 2 options:

  1. Replace the responseText with JavaScript

  2. Extend the CErrorHandler

Second option is nicer and I did it like that, but why not use already active option from config instead of overriding a class?


'errorHandler'=>array(

    // use 'site/error' action to display errors

    'errorAction'=>'site/error',

),

Currently name of that option should be ‘errorHandlerForSomeRequests’.

If I added that option shouldn’t that handle all requests? Why separate ajax requests if we already have option which is intentionally added?

I added the exteded ‘class’ to ‘errorHandler’ options, but for me that is not logical. What are your thoughts?

What you are suggesting here is one of the actual problems we are discussing. The full error response text is complex and it’s not possible to get the relevant/user-friendly parts of it in any other way than to add a load of JS code to fetch parts of the error, or do an overly complex (and according to my rationale unneeded) extending of CErrorHandler.

I second what B-Scan says in his reply before this one. Dealing with the text that is output now is very inflexible and not user friendly. We would have to pick out certain parts of the bunch of error information that is output. It would be much better if we could control this like we can for non-AJAX requests as well.

Another point that I think is of great/most value is the fact that when a non-success HTTP response code is given, jQuery will consider this an error and fire the ‘error’ callback instead. Please see the jQuery.ajax() code example I gave in my original post, especially the comments inside the ‘error’ callback. Being able to do things like in this example would be very very useful and this is something we all have to do every day when dealing with AJAX in a nice way.

I see your point…

I just removed the check - http://code.google.com/p/yii/source/detail?r=3386

Please test the code, and let us know if it’s working better this way…

Great! It works much better now. Thank you neighbor ;)

Added a wiki article for this, so that more users understands the difference

http://www.yiiframework.com/wiki/228/display-a-nice-exception-message-on-ajax-requests/

Found this old thread now and just wanted to say thanks mdomba, this change really helps keeping our code clean!