PDA

View Full Version : How to handle exceptions/bad JSON response with form.submit()?



olivierpons
2 Mar 2010, 2:18 AM
Everything is in the title: it seems everything is coded like the server will always return good JSON (maybe empty result, maybe JSON with "result=false" or whatever but always good JSON). If it's not the case, there's an exception and I still haven't found a proper way to handle it.

Suppose you give work with 2 persons:
- (1) the one who develops the Client side / Web pages + ExtJs
- (2) the one who develops the Server side

The (1) guy has to be able to handle malformed JSON.

Here's my code:


this.getForm().submit({
url:'../_admin/json/laby_edit.php',
params: {
[blah]
},
waitTitle: '[blah]',
waitMsg: '[blah]',
success: function (form, action) {
[blah]
},
failure:function(form, action) {
[blah]
}
});
},


And it seems there's no way to handle and JSON-decode exception.
Any hints?
Any clue?


Thanks a lot!

harley.333
2 Mar 2010, 6:38 AM
You can always take the direct approach and override Ext.decode.



(function() {
var f = Ext.util.JSON.decode;
Ext.util.JSON.decode = function() {
try {
return f.apply(arguments);
} catch (ex) {
// handleException
}
}
Ext.decode = Ext.util.JSON.decode;
)();


Personally, I never addressed this issue from the browser point-of-view. I just made damned sure that guy #2 did his job right! :)

Troy Wolf
2 Mar 2010, 6:46 AM
Personally, I never addressed this issue from the browser point-of-view. I just made damned sure that guy #2 did his job right! :)
That has been pretty much how I've handled this possible issue, too. I don't work in a huge team where UI guys are completely separated from server code guys, but I guess that happens. Still, if the server sends malformed JSON, that's really a problem to fix, not something to "handle" in the UI.

Also consider that no matter what server-side language you use, you'll have a JSON-encoding function or library to help you. It should be "impossible" for these to output malformed JSON. You should be using these utilities rather than hand-generating JSON structures. My opinion anyway. ~o)

prophet
2 Mar 2010, 6:51 AM
I use a global error handler that sends detailed messages back to the server:
Ext.ux.ErrorHandler (http://extjs-ux.org/docs/index.html?class=Ext.ux.ErrorHandler)

olivierpons
2 Mar 2010, 6:58 AM
@harley.333 Thanks for the example, it may work, but...

Believe a stager :) You never ever have to rely on other side's quality. Never.
Never believe that what you will get will be clean JSON datas.
Whether from the server's p.o.v. or the client's p.o.v. .

Check Apache source code to see that from their experience you never have to trust what you get only based on the fact that "you should get it" (hackers and/or memory corruption can cause havoc).

Of course in a ideal world, the server will always send JSON datas.

Thanks again for the hint.

:D

@prophet Thanks, I had seen this solution but the comment

"browser inconsistencies are making this extremely difficult"

afraid me. There's no description of the problems encountered and I don't know where the limits are.

prophet
2 Mar 2010, 7:25 AM
@prophet Thanks, I had seen this solution but the comment

"browser inconsistencies are making this extremely difficult"

afraid me. There's no description of the problems encountered and I don't know where the limits are.

I'd still consider giving it a try.

If you read the code in that error handling class, you'll see that it's pretty flexible.
I doubt that it would cause more problems for you. It's certainly helped me a great deal (including reporting JSON parse errors to the server):



// Sets the window.onerror handler to the ErrorHandler UX
Ext.ux.ErrorHandler.init();
Ext.ux.ErrorHandler.on("error", function(ex) {

Ext.Ajax.request({
url: 'errorhandler.php',
params: {
error: Ext.encode(ex)
},
success: function(resp, options){
var respJson = Ext.decode(resp.responseText);
if (respJson.success == true) {
if (respJson.suggestedAction) {
Ext.MessageBox.alert('Error', 'Suggested Action: '+respJson.suggestedAction);
}
}
}
});
});

harley.333
2 Mar 2010, 8:26 AM
Hey Prophet - That's my class :) I use it everywhere I can, and it works very well. I'm glad that someone else is taking advantage of it.

Oliver, the browser inconsistencies I'm talking about are detailed in that code, and I've addressed most of them. The main problem that I was unable to address is that window.onerror is supported by Firefox and IE, but none of the other browsers.

The ErrorHandler class doesn't fix any errors; it merely makes it easier to deal with them (especially cross-browser). Let me know if the override I provided earlier doesn't help.

Troy Wolf
2 Mar 2010, 12:37 PM
...You never ever have to rely on other side's quality. Never.
Never believe that what you will get will be clean JSON datas.
Whether from the server's p.o.v. or the client's p.o.v. .

Check Apache source code to see that from their experience you never have to trust what you get only based on the fact that "you should get it" (hackers and/or memory corruption can cause havoc).

Of course in a ideal world, the server will always send JSON datas...
.
You are correct of course, but I guess I am not concerned about preventing my javascript-driven browser application from throwing errors when a hacker is messing with it--he gets what he deserves. At least for my applications, I am comfortable with knowing that the JSON will be good JSON unless someone is hacking around against my application or perhaps some kind of rare Apache error that somehow hiccups and truncates my JSON structure. (I've never seen this.)

I'm very willing to be schooled, and obviously harley.333 also feels the need to catch these kind of failure conditions. I'd like to hear real-world examples of your applications making AJAX calls and getting back invalid JSON. What ended up being the cause? Perhaps I need to beef up the robustness of my own browser apps in a similar way as you describe in this thread.

I already, of course, handle "normal" failures including non-200 responses and communications failures caused by network disconnects and non-responsive web servers. ~o)

olivierpons
2 Mar 2010, 11:47 PM
Murphy's law: if it can happen, it will happen.

@prophet:

Thank you very much indeed that's exactly what I needed, and the action to take is sent by the server, so (from my p.o.v.) that's perfectly clean code. It's so rare ;)

@Try Wolf:

Memory corruption?
It's like god: it's not because you've never seen it that is doesn't exist (suspicious joke).

Seriously, my colleague just explained it has happended yesterday (in one of his 170 servers he deals with everyday). The cause was memory corruption, the server sent garbage. A reboot and the memory check failed... that sounds impossible but it's not. That made me think about those kind of stuff. The client on the WebBrowser has to know something bad happened and he can't keep on working.

If you take a look at Apache's code, the book Apache Modules (thanks Nick Kew) it's clearly explained that you never have to rely on the quality of what you get. If you think you should get proper JSON make sure it's proper JSON (= handle errors).
That's a way of clean programming too, even though it seems useless. That's exactly the kind of small things that makes you feel you work with a very, very, very good developer: from my pov, you have to handle all the time all the errors you can think of. Never think this way: "ok I'll handle those possible errors later", because... you will forget to handle them (experience talks :D).

@harley.333 (http://www.extjs.com/forum/member.php?u=118)

Very good job man!
Maybe you could just add the comment you've explained here in the documentation so everyone knows it without having too look into the code itself. That would be nice!
Thanks again!

--

That's unbelievable: it seems it's not implemented (http://code.google.com/p/chromium/issues/detail?id=7771) or buggy (https://bugs.webkit.org/show_bug.cgi?id=8519) on Safari & Chrome >_<

bareflix
10 Aug 2011, 9:11 AM
I already, of course, handle "normal" failures including non-200 responses and communications failures caused by network disconnects and non-responsive web servers. ~o)

Do you have any pointers on how to catch these errors?
I have ext.Direct setup and working, and I set an exception handler with:


Ext.direct.Manager.on('exception', this.directException, this);

The error handler gets called if I get a exception from the server, but not if the server is just down. Firefox knows immediately that the request failed (the machine is up, the web server is down in this case) but I don't know how to catch it in ext to report it.

Troy Wolf
12 Aug 2011, 9:43 AM
Do you have any pointers on how to catch these errors?
I have ext.Direct setup and working, and I set an exception handler with:


Ext.direct.Manager.on('exception', this.directException, this);

The error handler gets called if I get a exception from the server, but not if the server is just down. Firefox knows immediately that the request failed (the machine is up, the web server is down in this case) but I don't know how to catch it in ext to report it.

Sure, below I've pasted some generic Failure and Success handling we use for our Ajax requests. Note that Ext handles Form-based Ajax requests differently from "plain" Ajax requests. I think many get confused by this. The methods below, at least for us, cover over those differences.

The primary part for you to focus on that explains how I 'handle "normal" failures including non-200 responses and communications failures' is the formFailure() method. The "magic" is to detect if you have an action.result object. If so, then your communication succeeded, in which case, I pass things onto my normal form success handler. ELSE, we have a comm failure, and the reason is found in action.response.statusText. I display that reason to my user in a pretty message box with a nice error icon.



/* Initialize the base foo object to namespace our stuff. */
Ext.ns('foo');

/**
* Method to extend any object and/or override objects. Commonly used to customize
* the foo object. Copied from Ext.
*/
foo.apply = function(o, c, defaults){
// no "this" reference for friendly out of scope calls
if(defaults){
foo.apply(o, defaults);
}
if(o && c && typeof c === 'object'){
for(var p in c){
o[p] = c[p];
}
}
return o;
};
foo.applyIf = function(o, c) {
if(o && c && typeof c === 'object'){
for(var p in c){
if(typeof (o[p]) === 'undefined') {
o[p] = c[p];
}
}
}
return o;
};

/* Add some properties and methods to foo, but don't stomp over anything. */
foo.applyIf(foo, {

/**
Generic message display method. Designed to work with Ext.MessageBox, but will use basic
alert() if Ext not available. Pass in simple message string or a config object.
*/
showMsg: function(config) {
/* Defaults. Allow you to pass in only what you want to change. */
config = config || {};
if (typeof config !== 'object') {
config = {msg: config};
}
// Set default max size to size of "parent" window.
var winSize = Ext.getBody().getViewSize();
msgConfig = {
modal: true,
title: '',
icon: 'INFO',
msg: '',
buttons: 'OK',
maxWidth: winSize.width,
maxHeight: winSize.height
};
foo.apply(msgConfig, config);
/* Even if Ext is not included, show an alert. */
if (typeof Ext !== 'object') {
return alert(msgConfig.msg);
}
msgConfig.icon = Ext.MessageBox[msgConfig.icon];
msgConfig.buttons = Ext.MessageBox[msgConfig.buttons];
/* maxWidth is NOT working as advertised, so force it. */
Ext.Msg.maxWidth = msgConfig.maxWidth;
Ext.Msg.show(msgConfig);
},

/*
Generic handler for failed AJAX communications. HTTP Errors and connection failures will
trigger this handler. Our application-controlled errors will hit successHandler. This detects
the failure details so the message differentiates between different failure conditions.
In my testing, statusText may contain:

"Not Found": The URL your app requested does not exist.
"Internal Server Error": Server produced a 500 error.
"transaction aborted": AJAX timeout reached so the browser gave up.
"communication failure": Could not find the service (network, Apache down, etc)

We include a couple of helper methods to handle the difference between form and Ajax failure
responses.
*/

/**
* To be used as Ext Form failure handler.
*/
formFailure: function(frm, action) {
/* Ext forms call their formFailure handler when 'success' = false. So if
* we get a result object, then the server successfully sent a JSON response,
* process it as we would a success. This allows us to utilize the 'errors' object
* to highlight fields that failed server-side validation.
*/
if (action.result) {
foo.formSuccess(frm, action);
} else {
foo.failureHandler(action.response.statusText);
}
},

/**
* To be used as Ext Ajax failure handler.
*/
ajaxFailure: function(response, request) {
foo.failureHandler(response.statusText);
},

failureHandler: function(failureTxt) {
var msg = 'There was a failure while communicating with the server.';
foo.showMsg({
title: failureTxt,
icon: 'ERROR',
msg: msg
});
},

/*
Generic handler for successful Form and AJAX communications.
The Success handlers do not necessarily mean things were a "success" from our
application's point of view. It simply means the HTTP communication was successful and
we got back an object we understand. If the response contains a message, shows the message
with a single OK button. Detect if the message is INFO, WARNING, or ERROR level and show an
appropriate icon in the message box.

Ext's Form and Ajax responses are not the same structure. So here are a couple
helper methods to level the field. The form response includes an already decoded
action.result object. AJAX.request does not--leaving you to decode it from
response.responseText.
*/

/**
* To be used as Ext Form success handler.
*/
formSuccess: function(frm, action, fn) {
var result = action.result;
// Custom attribute to anchor the message box to an element.
if (frm.msgAnim) {
e = frm.getEl();
result.msgAnimEl = e;
}
foo.successHandler(result, fn);
},

/**
* To be used as Ext Ajax success handler.
*/
ajaxSuccess: function(response, request, fn) {
var result = Ext.decode(response.responseText);
foo.successHandler(result, fn);
},

/**
successHandler() expects a result object that contains a 'level' and a 'msg'. Level is used to
show an appropriate icon. Msg is just the text to display.

Example 'result' object from the server:
{"msg":"Tell the user something here.", "success":false, "level":"WARNING"}

The object could also contain result.msgAnimEl to anchor the message box
animation to a specific element.
*/
successHandler: function(result, fn) {
if (result.msg) {
if (!result.level) {
result.level = 'INFO';
}
var msgCfg = {
icon: result.level,
msg: result.msg
};
// Custom attribute to anchor the message box to an element.
if (result.msgAnimEl) {
msgCfg.animEl = result.msgAnimEl;
}
if (fn) { msgCfg.fn = fn; }
this.showMsg(msgCfg);
}
},

/* Ext normal form submit supports an 'errors' object that contains fields that
* failed server-side validation. Ext will mark the fields invalid and provide invalid text.
* When we use AJAX submit instead of built-in form submit, we lose this built-in functionality.
* This method gives you this same functionality. Just return 'errors' in your JSON response as
* documented here: http://www.extjs.com/deploy/dev/docs/?class=Ext.form.Action.Submit
*/
formErrors: function(frm, result) {
if (result.errors) {
var errors = result.errors;
var errMsg, f;
for (var fieldName in errors) {
errMsg = errors[fieldName];
f = frm.findField(fieldName);
f.markInvalid(errMsg);
}
}
}
};