View Full Version : Strategy for handling Server-side Exceptions
harley.333
14 Aug 2007, 8:14 PM
What's the recommended strategy for handling Server-side Exceptions?
For example, I call the Server-side method "GetRecords" and pass an out-of-range parameter. The error is caught and an exception is thrown. The exception is serialized and returned to the browser.
A HttpStatus of 200 is set because communication between server and browser was successful.
If I'm using the Ext grid, my exception screws stuff up (AFAIK). I'd like to have a generic strategy that I can use throughout the application. I attempted to return a custom HttpStatus, but then I couldn't retrieve my serialized exception. What are other developers doing? (I'm using Ext.base, by the way).
Thanks,
Harley
Ronaldo
15 Aug 2007, 11:41 AM
I guess that every exception at the serverside should be handled properly and a clear message should be given to the user.
i.e. if a record is not found: 'The data you asked for does not exist'.
You can easily respond properly by adding an errormessage in your JSON response.
For connection or timeout problems, I guess that you should add a handler in your ajax response handler and show a message too, but you'll have to handle this client side.
harley.333
15 Aug 2007, 5:21 PM
I guess I'm not being clear.
Let's assume my server-side code is already handling all exceptions. The user needs to be notified about most of these exceptions. What are other developers doing to achieve this in a generic fashion?
I'd like to use Ext-base for all my browser-server communication. I'd also like to use the built-in capabilities of the Ext widgets; specifically, the DataProxy classes which handle all communication with the server.
So, let's imagine that I request my server for data. The possible response-paths are:
1. success - Ext already has this handled.
2. failure, due to communication problem - Ext already has this handled.
3. failure, due to server-side exception - what to do...
Let's imagine that I have a generic technique for packaging exceptions and sending them to the client. Obviously, I need the client to handle it. If a widget is also expecting data from the DataProxy, I need to let the widget know that the data isn't coming.
Should I "extend" Ext.data.Connection - what method(s) should I override?
Should I "extend" Ext.data.HttpProxy - what method(s) should I override?
Is there a simpler solution?
Thanks,
Harley
ToNiC
15 Aug 2007, 7:01 PM
This is what I did to handle server response codes
store.on('loadexception',
function(a,conn,resp) {
if (resp.status == '304') {
Ext.Msg.alert('Content has not changed');
}else if(resp.status == '200') {
return; //Do nothing
}else if (resp.status == '401') {
Ext.Msg.alert('Authentication required - You need to Login');
}else if (resp.status == '302') {
errorDialog.body.update('Session Has Expired');
errorDialog.show();
}else if(resp.status == '500') {
errorDialog.body.update(resp.responseText);
errorDialog.show();
}else{
errorDialog.body.update('An uncaught exception has occured');
errorDialog.show();
}
},
errorDialog is an Ext.BasicDialog
store is an Ext.data.Store object
So when an exception on server occurs I send a 500 response header with the reason returned as HTML in the content.
harley.333
16 Aug 2007, 9:20 AM
Thanks Tonic,
So, your strategy is to purposely send an HttpStatusCode of 500 to the client?
I don't like this because the HttpStatusCode should really be 200 - afterall, communication was successful (both sending and receiving).
Another reason I don't like this is because Ext sees that the HttpStatusCode is not 200-300 and discards the responseText property. Now, I cannot report to my user the 'true' reason for the error.
I feel like I'm missing something fundamental here.
Thanks,
Harley
pyrolupus
16 Aug 2007, 10:19 AM
For example, I call the Server-side method "GetRecords" and pass an out-of-range parameter. The error is caught and an exception is thrown. The exception is serialized and returned to the browser.
So, your strategy is to purposely send an HttpStatusCode of 500 to the client?
I don't like this because the HttpStatusCode should really be 200 - afterall, communication was successful (both sending and receiving).
An HttpStatusCode of 500 means that there was an "Internal Server Error." Just because you caught the error doesn't mean it did not occur. Why would you take issue with reporting to the client that an error really did, in fact, occur?
I think you're getting too stuck on the 200 response. You've already intercepted a server error, and now you want to let the client know that there was a problem. Why not use the status code? It seems a lot more elegant to me than trying to extend the classes to include error information that they really shouldn't have to know (specifics) about.
Pyro
hendricd
16 Aug 2007, 11:59 AM
It seems what harley is looking for is an 'application' oriented method of propogating logic, syntax, or other errors back to the client for handling.
Most server side frameworks, ASP, .NET Coldfusion all support custom error page handlers, but the current incarnation of Ext.lib.Ajax does not, as mentioned before, expose those potential reporting assets from the XHR object if the HTTP status is considered 'not-OK'.
That said, Tonic's loadexception handler is the right way to go but HTTP statuses: 304, and 500, etc would not have access to the responseText or XML.
These responses would be even more important for a REST/SOAP(XML Response) interaction as they also rely on headers from the response to fill out the Error-Handling picture.
I have recently completed an enhancement for Ext.lib.Ajax which resolves this issue (and others). You can read more about it here (http://extjs.com/forum/showthread.php?t=10672).
But the important feature provided (at least in this context) is that everything the XHR object has to offer is included in an exception-response (headers, responseText/XML, etc).
Food-for-thot,
harley.333
16 Aug 2007, 9:30 PM
Hey Pyro,
Hendricd's got it correct. I'm concerned with 'logic errors,' not 'server errors.' Sure, the 'errors' occur on the server, but the 'errors' are not Http-communication errors. Therefore, returning a HttpStatus of 500 is wrong. I admit, I'm anal. But all web-servers that I know of actually use HttpStatus 500 for very valid reasons. I don't want to muck up the waters by overloading that status with something different and proprietary.
I don't like Tonic's solution, because he basically told the browser that the web-server fouled up. But this is just not true. In reality, a business rule was broken, and the web-server correctly and properly reported the 'error.' That is a huge difference.
I'd prefer to return a HttpStatus of 200 (meaning, the server got your request and processed it successfully) and I'd also like to return a specific message containing the 'error.'
So, I guess I'm looking for a method of extending an Ext class (I'm not sure which one). I'm thinking that I'd like to intercept the 'success' event handler before the event subscriber(s) get fired. If the responseText contains a 'logic error,' I handle it (pop up a modal dialog, whatever) and stop the event. If the responseText does not contain a 'logic error,' I let the event continue.
Perhaps, it'd be a good idea to add a third event:
success - communication succeeded and everything is great
failure - communication between browser and server is fouled somehow
error - communication succeeded but not everything is great
Obviously, the Ext team cannot possibly anticipate what constitutes as an 'error' (it would necessarily be specific to each application).
Make sense?
Wolfgang
16 Aug 2007, 11:28 PM
@harley.333
success - communication succeeded and everything is great
failure - communication between browser and server is fouled somehow
error - communication succeeded but not everything is great
this all can be done, now.
You can pass "logic" errors back to the client and work with them as you like. They can be encoded in XML, JSON or plain text.
For this, your JSON/XML response must have set the "success" property to false.
The only issue is: if you expect valid JSON or XML in your response and get a server side error, then this answer will _not_ contain valid JSON or XML.
Maybe you want to have a look here where i describe the issue in more detail and offer a workaround:
http://extjs.com/forum/showthread.php?t=10780
Regards
Wolfgang
hendricd
17 Aug 2007, 5:10 AM
Good Morning ~o)
Yes, any response (valid/Error) should be taken in 'application' context AFTER evaluating HTTP status. That way, you could return a full set of response headers, XML/HTML/JSON-formatted Error response as needed.
But the current data-bound nature of the Ext widgets takes an 'optimistic' approach, thus they are very easy to use. ;)
We really need a 'pessimistic' enhancement. Example:
HTTPProxy's loadResponse method proposal:
// private
loadResponse : function(o, success, response){
delete this.activeRequest;
if(!success){ //trap HTTP Problems
this.fireEvent("loadexception", this, o, response);
// this.fireEvent("ajaxexception", this, o, response);// more appropriate really
o.request.callback.call(o.request.scope, null, o.request.arg, false);
return;
}
var result;
var isOK = this.fireEvent("validate", this, response, o);
if(isOK !== false){
try {
result = o.reader.read(response);
this.fireEvent("load", this, o, o.request.arg);
}catch(e){ // raise: let them know the .reader has issues with the response format
this.fireEvent("loadexception", this, o, response, e);
isOK = false;
}
}
o.request.callback.call(o.request.scope, result, o.request.arg, isOK );
},
Thus making this possible:
ds = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
url: 'your-data.php'
}),
reader : reader
});
ds.proxy.on('validate', function(response, options){
if(response.status == 304){ // Altho currently 304 is considered an HTTP exception (wouldn't see it here)
return false; //nothing changed, no problem, .reader can go back to sleep.
}
if(response.responseXML && response.responseXML.child('ErrorResponse'){
Ext.Msg.alert('Houston, we have a problem...');
return false;
}
var json = false;
try{json=Ext.decode(response.responseText)||false;}catch(ex){}
if(json){
if(json['ErrorResponse'] !== undefined){
Ext.Msg.alert('Houston, we have a problem:' +json['ErrorResponse'].description );
return false;
} else if (!Crockfordtests.test(response.responseText)) { // is it safe?
Ext.Msg.alert('JSON Response Format Problem, try again');
return false;
}
}
return true;
});
You get the drift. You're in control.
Animal
17 Aug 2007, 10:05 AM
There's already http://www.extjs.com/deploy/ext/docs/output/Ext.data.Connection.html#event-requestcomplete
But returning false does not veto the calling of the success handler or callback. :-?
harley.333
17 Aug 2007, 11:29 AM
I think hendricd's solution is what I'm after. I've come to the conclusion that there is really no good way of extending Ext's current codebase (I'll probably override methods, but that's not always a good idea).
The "validate" event is what seems to be missing.
if !success
fire "loadexception"
return
valid = validate()
if !valid
fire "dataexception"
return
fire "loadcomplete"
or whatever
Thanks, guys. I think I'm going to steal hendricd's code until Ext supports this concept, and I'll just hope my code is forward-compatible./:)
hendricd
18 Aug 2007, 4:59 AM
There's already http://www.extjs.com/deploy/ext/docs/output/Ext.data.Connection.html#event-requestcomplete
But returning false does not veto the calling of the success handler or callback. :-?
@Animal - yep, your right. See the revised proposal again. It makes sure the 'owner' of the proxy gets a request.callback regardless of what happens (and provides error handling if the onload craps out). Keep in mind, this approach is designed to provide the App-level exception- Handling within the context of a 'data-aware' component like data.Store and to separate HTTP transport issues from Application problems like:
"Customer param was not specified in your request." (httpStatus is 200)
"Withdrawal amount exceeds available funds" (httpStatus is 200)
<xml><ErrorResponse>You not authorized to see this list of transactions, but current balance is $500.00</ErrorResponse></xml> (httpStatus is still 200)
When refreshing a grid every 2 minutes:
httpStatus = 304 (Content has not changed) thats OK, nothing to do then, keep the grid the way it is...
@Harley - I'd love to see the overrides you come up with for a few of these.
Another problem in server-side response with JSON objects, is the eval... that tries eval (execute) any response...
I report it as a bug (http://extjs.com/forum/showthread.php?p=57053), but it isn't a bug, because the server ALWAYS need to reply with a JSON response
dearplato
23 Aug 2007, 6:20 PM
another error-handle sample for Ext.form.Form
form.submit({
url:strURL,
waitMsg:'Data Saving....',
scope:this,
success:function(form, action){Ext.MessageBox.alert('Sucess', 'You are lucky guys !');}
});
form.on({
actioncomplete: function(form, action)
{
//code goes here....
},
beforeaction: function(form, action)
{
//code goes here....
},
actionfailed:function(form, action)
{
var s;
var s1;
var s2;
switch(action.failureType)
{
case "connect":
s1="Server Connection";
s2="Http request URL:" + action.getUrl();
s=String.format("Action [{0}] occurs [{1}] error.<br>{2}<br>Please contact with me.",action.type,s1,s2);
break;
case "server":
s1="Server-Side Validation";
s="Reason:<br />";
for(var property in action.result.errors)
{
s += String.format("FieldID:{0} Message:{1}<br />",property, action.result.errors[property]);
}
break;
default:
s1=action.failureType;
s2="Http request URL:" + action.getUrl();
s=String.format("Action [{0}] occurs [{1}] error.<br>{2}<br>Please contact with Jack.",action.type,s1,s2);
break;
}
Ext.MessageBox.alert(s1,s);
}
});
//server-side validation error returns below:
//{success:false,errors:{field1ID:"balabala", field2ID:"Bob! what's happen?"}}
hendricd
5 Sep 2007, 6:17 AM
Been doing a bit more thinking (and testing) with this concept. And I came up with these overrides to add 'validate' event support at these 3 levels:
Ext.data.Connection
Ext.data.Store (and subs)
Ext.data.Proxy (and subs)
(Good 'ol createInterceptor() does the trick here ;)
First, Ext.Ajax:
Ext.override(Ext.data.Connection,{
request : function(o){
this.events.validate || (this.events.validate= true);
if(this.fireEvent("beforerequest", this, o) !== false){
var p = o.params;
if(typeof p == "function"){
p = p.call(o.scope||window, o);
}
if(typeof p == "object"){
p = Ext.urlEncode(o.params);
}
if(this.extraParams){
var extras = Ext.urlEncode(this.extraParams);
p = p ? (p + '&' + extras) : extras;
}
var url = o.url || this.url;
if(typeof url == 'function'){
url = url.call(o.scope||window, o);
}
if(o.form){
var form = Ext.getDom(o.form);
url = url || form.action;
var enctype = form.getAttribute("enctype");
if(o.isUpload || (enctype && enctype.toLowerCase() == 'multipart/form-data')){
return this.doFormUpload(o, p, url);
}
var f = Ext.lib.Ajax.serializeForm(form);
p = p ? (p + '&' + f) : f;
}
var hs = o.headers;
if(this.defaultHeaders){
hs = Ext.apply(hs || {}, this.defaultHeaders);
if(!o.headers){
o.headers = hs;
}
}
var cb = {
success: this.handleResponse.createInterceptor(
function(){return this.fireEvent.apply(this,["validate"].concat(arguments)) },this),
failure: this.handleFailure,
scope: this,
argument: {options: o},
timeout : this.timeout
};
var method = o.method||this.method||(p ? "POST" : "GET");
if(method == 'GET' && (this.disableCaching && o.disableCaching !== false) || o.disableCaching === true){
url += (url.indexOf('?') != -1 ? '&' : '?') + '_dc=' + (new Date().getTime());
}
if(typeof o.autoAbort == 'boolean'){ // options gets top priority
if(o.autoAbort){
this.abort();
}
}else if(this.autoAbort !== false){
this.abort();
}
if((method == 'GET' && p) || o.xmlData){
url += (url.indexOf('?') != -1 ? '&' : '?') + p;
p = '';
}
this.transId = Ext.lib.Ajax.request(method, url, cb, p, o);
return this.transId;
}else{
Ext.callback(o.callback, o.scope, [o, null, null]);
return null;
}
}
});
and, the data.Store
Ext.override(Ext.data.Store,{
load : function(options){
options = options || {};
this.events.validate || (this.events.validate= true);
if(this.fireEvent("beforeload", this, options) !== false){
this.storeOptions(options);
var p = Ext.apply(options.params || {}, this.baseParams);
if(this.sortInfo && this.remoteSort){
var pn = this.paramNames;
p[pn["sort"]] = this.sortInfo.field;
p[pn["dir"]] = this.sortInfo.direction;
}
this.proxy.load(p, this.reader, this.loadRecords.createInterceptor(
function(){return this.fireEvent.apply(this,["validate"].concat(arguments)) },this)
, this, options);
}
}
});
..and, the HttpProxy
Ext.override(Ext.data.HttpProxy, {
loadResponse : function(o, success, response){
delete this.activeRequest;
this.events.validate || (this.events.validate= true);
if(!success){ //trap HTTP Problems
this.fireEvent("loadexception", this, o, response);
o.request.callback.call(o.request.scope, null, o.request.arg, false);
return;
}
var result;
var isOK = this.fireEvent("validate", this, response, o);
if(isOK !== false){
try {
result = o.reader.read(response);
this.fireEvent("load", this, o, o.request.arg);
}catch(e){ // raise: let them know the .reader has issues with the response format
this.fireEvent("loadexception", this, o, response, e);
isOK = false;
}
}
o.request.callback.call(o.request.scope, result, o.request.arg, !!isOK );
}
});
..and, the MemoryProxy
Ext.override(Ext.data.MemoryProxy,{
load : function(params, reader, callback, scope, arg){
params = params || {};
this.events.validate || (this.events.validate= true);
var result, isOK;
isOK = this.fireEvent("validate", this, this.data, params, reader);
try {
if(isOK){
result = reader.readRecords(this.data);
}
}catch(e){
this.fireEvent("loadexception", this, arg, null, e);
callback.call(scope, null, arg, false);
return;
}
callback.call(scope, result, arg, !!isOK);
}
});
Meaning...
Now you can intercept/validate (globally) on the Ajax.request level with:
//raised on a 'good' HTTP status condition
Ext.Ajax.on('validate',function(response){
console.log([response.getAllResponseHeaders,response.responseText]);
return doYouLikeYouResponse(response); //returning false here prevents the success callback from firing
},Ext.Ajax);
or, when managing a dataset:
ds = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
url: 'your-data.php'
}),
reader : reader
});
//should we load what the proxy offered?
ds.on({'validate': function(results, options, success){
try{
if(success){ //Got a 'good' Ajax HTTP status
if(dataLooksGood(results)){
results = Ext.apply(results,moreStuffObject); //massage the resultset if necessary
}else{
return false;
}
}
}catch(e){return false;}
},
loadexception:function(){ .... } ,
load:function(){ .... }
});
//trap serverside application errors here.
ds.proxy.on({'validate': function(response, options){
if(response.status == 304){ // Altho currently 304 is considered an HTTP exception (wouldn't see it here)
return false; //nothing changed, no problem, .reader can go back to sleep.
}
if(response.responseXML && response.responseXML.child('ErrorResponse'){
Ext.Msg.alert('Houston, we have a problem...');
return false;
}
var json = false;
try{json=Ext.decode(response.responseText)||false;}catch(ex){}
if(json){
if(json['ErrorResponse'] !== undefined){
Ext.Msg.alert('Houston, we have a problem:' +json['ErrorResponse'].description );
return false;
} else if (!Crockfordtests.test(response.responseText)) { // is it safe?
Ext.Msg.alert('JSON Response Format Problem, try again');
return false;
}
}
return true;
},
loadexception:function(){ .... } ,
load:function(){ .... }
});
ds.load({params:{}});
If anything, it makes a great debugging harness. \:D/
infinit
14 Mar 2008, 8:00 PM
very useful to me.
Trinodia
15 Jan 2009, 12:37 AM
Just tried to get this to work on 2.2 but not getting it to work properly. The overrides i have done i have taken from the 2.2 and then added the differences that hendricd has written above.
The validate event happends and i can get the pure response object but im having problems using it after. If i try to decode the response.responseText i get that it is undefined but if i do console.log(response) i can see the obect in the console, click on it to look at its properties and such so the object is there, i just cant seem to use it for some reason.
Any id
Powered by vBulletin® Version 4.1.5 Copyright © 2012 vBulletin Solutions, Inc. All rights reserved.