Handling Remote API Errors With Application.cfc's OnError() Event Method
I was just reading over on Ray Camden's blog about security concerns that need to be addressed with API calls that access remote ColdFusion components. This got me thinking about my unified API response and how I might go about mixing authorization and exception handling into that equation. If you've read my blog recently, you've see that for remote method calls, I always return a unified API response object with the following keys:
- Success - Boolean to flag success of failure of the request.
- Errors - Array of Property/Error structures.
- Data - Any data that needs to be returned as part of a successful request.
By keeping all of my API responses uniform, it allows my client-side response handlers to remain small and straightforward.
But what about my remote ColdFusion components? If I want to keep this uniform API response and handle security at the same time, do I have truly have to put security in all of my remote methods? This got me thinking about throwing authorization exceptions from ColdFusion. And, that led me to the question the I am answering in this blog post: Is there a way to leverage the OnError() event handler in Application.cfc to return an "non-success" but still uniform API response?
The answer: absolutely!
Now that you've seen the magic in action, let's take a look at the code. The first thing I want to examine is my remote service component:
RemoteService.cfc
<cfcomponent
extends="RemoteProxy"
output="false"
hint="I am test remote-access API class.">
<cffunction
name="GetRandomNumber"
access="remote"
returntype="struct"
returnformat="json"
output="false"
hint="I return a random value.">
<!--- Define the local scope. --->
<cfset var LOCAL = {} />
<!--- Throw an error. --->
<cfthrow
type="NotAuthorized"
message="You are not authorized to make this call."
/>
<!--- Create new API response. --->
<cfset LOCAL.Response = THIS.NewAPIResponse() />
<!--- Create random number. --->
<cfset LOCAL.Response.Data = RandRange( 1, 100 ) />
<!--- Return API Response. --->
<cfreturn LOCAL.Response />
</cffunction>
</cfcomponent>
As you can see, this component extends my core RemoteProxy.cfc (seen farther below) and provides one remote-access method, GetRandomNumber(). This method attempts to get a new unified API response, but my code rudely throws a security error. I am not saying that this is the appropriate place to throw this error - it would actually be better in the remote object's pseudo constructor or in the OnRequestStart() application event method - I'm merely putting it here as a proof of concept.
Now, once this error get's thrown, it has to be handled. That's where the Application.cfc OnError() event handler comes into play:
Application.cfc
<cfcomponent
output="false"
hint="I provide application settings and event handlers.">
<cffunction
name="OnError"
access="public"
returntype="void"
output="true"
hint="I handle uncaught application errors.">
<!--- Define the arguments. --->
<cfargument
name="Exception"
type="any"
required="true"
hint="I am the uncaught exception."
/>
<!--- Define the local scope. --->
<cfset var LOCAL = {} />
<!---
Normally, we would want to return a error header, but
if the request was an API call (remote call), we never
want to return an error. Rather, we want to return an
"unsuccessful" API request.
--->
<cfif REFindNoCase( "\.cfc$", CGI.script_name )>
<!--- Create a new API response object. --->
<cfset CreateObject( "component", "RemoteProxy" ).
ReturnErrorResponse( ARGUMENTS.Exception.Message )
/>
</cfif>
<!--- Return out. --->
<cfreturn />
</cffunction>
</cfcomponent>
As you can see, my OnError() application event handler checks to see if the requested template was a CFC. If it was, then we know that this is a remote-access API request. And as such, it creates a temporary instance of the base RemoteProxy.cfc component and asks it to return a non-success API response.
At this point, our remote service object has thrown an error and our Application.cfc has caught it and asked the RemoteProxy.cfc to return a response. To see how that works, let's now take a look at the RemoteProxy.cfc base class for all remote service objects:
RemoteProxy.cfc
<cfcomponent
output="false"
hint="I provide the core remote proxy API functionality.">
<cffunction
name="NewAPIResponse"
access="public"
returntype="struct"
output="false"
hint="I provide a new default API response object.">
<!--- Define the local scope. --->
<cfset var LOCAL = {} />
<!--- Create a new API response object. --->
<cfset LOCAL.Response = {
Success = true,
Errors = [],
Data = ""
} />
<!--- Return new response object. --->
<cfreturn LOCAL.Response />
</cffunction>
<cffunction
name="ReturnErrorResponse"
access="public"
returntype="void"
output="false"
hint="I return an error with the given message (assuming that an API error was thrown and not caught).">
<!--- Define arguments. --->
<cfargument
name="Error"
type="string"
required="true"
hint="I am the error message."
/>
<!--- Define the local scope. --->
<cfset var LOCAL = {} />
<!--- Create a new API response object. --->
<cfset LOCAL.Response = THIS.NewAPIResponse() />
<!--- Flag it as not successful. --->
<cfset LOCAL.Response.Success = false />
<!--- Set the error message. --->
<cfset LOCAL.Response.Errors[ 1 ] = {
Property = "",
Error = ARGUMENTS.Error
} />
<!---
Create the response string. Check to see if we want
this as WDDX or JSON.
NOTE: We can't really assume this is in the URL, but
for this demo, we will.
--->
<cfif (
StructKeyExists( URL, "returnformat" ) AND
(URL.returnformat EQ "wddx")
)>
<!--- Convert to WDDX. --->
<cfwddx
action="cfml2wddx"
input="#LOCAL.Response#"
output="LOCAL.ResponseString"
/>
<cfelse>
<!---
By default, we are going to return the API
response in JSON string (since that is what
our appliation does by default).
--->
<cfset LOCAL.ResponseString = SerializeJSON(
LOCAL.Response
) />
</cfif>
<!---
Now that we have our API response set up, we need
to update the headers and return the response.
--->
<!---
Set header response code to be 200 - remember, the
whole point of the unified API response is that it
never "fails" unless there is truly a request
exception.
--->
<cfheader
statuscode="200"
statustext="OK"
/>
<!--- Steam binary contact back. --->
<cfcontent
type="text/plain"
variable="#ToBinary( ToBase64( LOCAL.ResponseString ) )#"
/>
<!--- Return out. --->
<cfreturn />
</cffunction>
</cfcomponent>
If you look at the ReturnErrorResponse() method, you'll see that it creates a new API response object and populates it just as any remote method call would do. But, the rest of the method is quite different. When we call a standard remote method, we let ColdFusion implicitly handle the return data type conversion. But, since our implicit work flow has thrown an exception, it is up to us to worry about performing any data type conversions.
As such, we check to see if the user requested a WDDX response format. If so, we convert our response object to WDDX. If not, then we go with our application default which is to return the response as JSON. Once we have the serialized response string, we convert it to binary and literally stream it to the client as the response body.
That's what you saw in the video and I have to say, this is really awesome! Whether or not you agree with how this particular proof of concept is being executed, the true awesomeness here is the discovery that the response of a remote ColdFusion method invocation is, in no way, tied to the actual method invocation itself! That gives us some serious power and flexibility in the way that we handle our remote API requests (including always returning a unified response even in the context of exception).
Anyway, I think that's a pretty sweet way to end the week!
While the HTML page itself is not really important in this post, I am posting it below for reference:
Index.cfm
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Handling Remote API Errors With Application.cfc</title>
<script type="text/javascript" src="jquery-1.3.2.min.js"></script>
<script type="text/javascript">
function GetRandomNumber(){
$.ajax(
{
method: "get",
url: "RemoteService.cfc",
data: {
method: "GetRandomNumber"
},
dataType: "json",
// Success handler.
success: function( objResponse ){
if (objResponse.SUCCESS){
// Populate the random number.
$( "#random-number" ).text( objResponse.DATA );
} else {
// There was an error.
AlertErrors( objResponse.ERRORS );
}
},
// Error handler.
error: function(){
AlertErrors(
[{
PROPERTY: "",
ERROR: "An unexpected error occurred"
}]
);
}
}
);
}
function AlertErrors( arrErrors ){
var strMessage = "Please review the following:\n\n";
// Compile errors into a single message.
$.each(
arrErrors,
function( intI, objError ){
strMessage += (objError.ERROR + "\n");
}
);
// Alert compiled message.
alert( strMessage );
}
// When page loads, grab random number.
$(
function(){
// Hook up refresh link.
$( "#refresh-number" )
.attr( "href", "javascript:void( 0 )" )
.click(
function(){
GetRandomNumber();
return( false );
}
)
;
// Get initial random number.
GetRandomNumber();
}
);
</script>
</head>
<body>
<h1>
Handling Remote API Errors With Application.cfc
</h1>
<p>
Random Number:
<span id="random-number"></span>
. . .
(<a id="refresh-number">refresh</a>)
</p>
</body>
</html>
This discovery makes me want to restructure my API architecture. This is quite exciting!
Want to use code from this post? Check out the license.
Reader Comments
INTERESTING!
I have a few questions.
1. What's the motivation behind standardizing on returning an array of errors instead of a single string or struct?, and behind using both a success flag and an always-present errors collection? Have you had a situation in development where you've returned more than one error?
2. When you say "streaming"... I'm not sure what that means. Is using the cfcontent tag and converting to binary going to do something differently in browsers than if you just returned the normal content output as a document? The only thing I can think of is that it'll reduce the transfer size maybe?
3. Is the reason you set the cfheader manually because onError will return a different status code? I would've thought since the presence of that method overrides the pure handoff of ColdFusion errors that it would return a 200 by default?
4. Is there some strategy behind inheriting from a standardized .cfc file for all of your API CFCs instead of them just passing their natural responses through some app-scope UDF or CFC instance?
5. I noticed you're providing an explicit error handler to your jQuery ajax call on the page's Javascript. What kind of errors generate server responses that will trigger that handler? If you're catching all CF errors in your application and responding intelligently will that ever be called other than during your development ( like if you type a wrong URL or something )? Have you come across $.ajaxSetup yet? It lets you specify global params to all of your subsequent ajax calls, so you can use it to have any of these type of errors be handled implicitly, which can really trim up some of those jQuery ajax calls.
Really great effing post. I've been doing a bastardly amount of jQuery and CFC AJAX lately and it's interesting to see other people facing the same issues. Props.
@David,
Ok, here goes:
A1) I like having a standardized API response because it makes my client-side response handling much more uniform as well. I know that I can always check a Success flag to see if there were any errors; I also know that I can always hand my error collection off to some standard error handler to be processed. As far as having multiple errors returned, I find that this happens all the time when using AJAX to submit a form. The API response can return all the form-related errors.
Also, if I want to do true client-side validation, I will usually create the same format response object so that my error processing (using the established error collection) can be handled by the same methods regardless of server-returned errors, or client-generated errors.
A2)
The CFContent tag can be used to stream back a binary variable as the binary response content. The benefit, in general, in using this technique is that you don't have to deal with white space suppression. If you return your content as variable, you don't have to clear the content buffer or worry about "unnatural" tag structure to make sure no extra white space exists.
Yes, in this demo, you could stream all of this from the OnError() event handler without using the CFContent tag (just let the OnError() method generate content); but, I simply find the CFHeader / CFContent methodology a bit cleaner.
Perhaps its just an emotional choice.
A3)
Yes, you are correct. I just tested this and using OnError() does, in fact, return a 200 error by default. I didn't know that :)
A4)
I like to use the base RemoteProxy.cfc object as I feel that all the API "responsibility" should be in the API. You could easily hand that stuff off to an APPLICATION-scoped object, but I don't think you would get too much re-use out of it as this stuff is API-specific.
Also, because remote-access CFCs are created without "proper" initialization, any access to application objects does require some breaking of encapsulation. This is a "necessary evil" of an API; but, I figure if we don't need to do it, don't do it.
A5)
The error handler in the AJAX post is for http-related errors such as loss of connectivity or request timeouts or when the server itself returns an uncaught exception (such as when the server is rebooting and you get a "Service Not Available" error).
But, to me, that's really the only time that that would get use. Everything else, *should* be returned in my unified API response, with the Success flag set to false. After all, the API request is always successful; the failure is only whether or not the "intent" is carried out.
Yes, I have used ajaxStart() and ajaxStop(). They are definitely cool.
Ben,
I was the one that posed the question to Ray. Thanks for your work on this, between this post and the responses that Ray got, there is a lot to think about and a lot of good approaches to handling security for AJAX calls. I am slowly getting my work to look at AJAX as a viable solution for some tasks we face, but security is an absolute must for any remote call made before I can get them on board with it.
1: Ok. I get that. I'm kind of curious though, because the only example I can think of ( like you mentioned ) is form "validation", where more than one item doesn't meet validation. But then I start thinking that that can never actually happen in an AJAX application ( unless you're purposely not implementing client-side validation, which is much more experience-enhancing in my opinion, even with the speed of XHR requests ), because either the client side validation will catch the issues and the form won't be submitted, OR the user has Javascript disabled, and the form won't be submitted through AJAX / your AJAX API at all. I think.
2. That is FREAKING AWESOME. That is the 2nd coolest solution to whitespace management in ColdFusion I have ever even heard of! Emotional or not, I really like that idea!
3. Thanks for verifying that. So I'm curious. I really like that you make a lot of your organizational and implementational decisions based off of the current issue's "concern" or "responsibility" or what seems natural and appropriate to you etc. So my curiosity is, will you keep that manual 200 header in there now or remove it?
4. When you say "proper" initialization, do you mean that an init method doesn't get called or that the pseudo constructor portion of a .cfc doesn't execute or what? That's really interesting.
5. I was actually talking about $.ajaxSetup( { options } ); Which lets you do this: $.ajaxSetup( { error: AlertErrors } ); Then for every other jQuery ajax call you make, all of these types of errors will automatically be passed to the AlertErrors function, so you never have to include that in your $.ajax( ) calls.
Thanks again Ben. I learn more caveats and get more perspective from your blog than any other I've ever read, son.
One other thing I'd like to comment on. While using CFHEADER to fudge a 500 error that the AJAX code can catch is definitely easier to do (and judging from the other blogs and tutorials I have read, that seems to be the "preferred" method of error management by AJAX coders), it becomes problematic when part of your job is to monitor, and decrease, the number of 500 errors on a given site. As that is part of the mandate we work under, using this approach of passing back a structure that contains a success/fail flag is something we will have to do at my work. So thanks again for this how-to, I will be reading it again a few times to figure out how to best implement this kind of response handling in the future.
@David,
1) To be honest, I don't do a lot of "True" client-side validation. Most of my validation on the client side is simply did you enter *any* value. Other than that, I pass the form back to the server via AJAX and get errors that way. Even so, there are errors that only the server-side can catch such as value uniqueness and last minute changes in the system (ie. something was ok 2 seconds ago, but NOT at the time of form submission).
3) I'll probably remove it. No need to have it there if it's not doing anything; and, if I keep it, I think it will only cloud my understanding later on. I rather know that OnError() prevents a 500 error and just rely on that. So, thanks again for making me test that :)
4) For "proper" initialization, I am only referring to the Init() function convention. The Pseudo constructor always gets called anytime a CFC is instantiated.
5) Ahhh yes, sorry for misreading. I have seen and played with $.ajaxSetup(), but have not really used it in any projects yet. Perhaps in my next one!
@Rob,
That's a good point. I suppose, you can always log the 500 errors as they happen and then return a proper API response. Of course, with things like security authorization and session timeouts, those aren't really 500s that you can *do* anything about as they are natural side-effects of good application design. But, unexpected 500 errors, yeah, we definitely want to work on removing those!
Excellent point Rob.
Ben, I've hit a snag implementing this, and it makes no sense. jQuery is not seeing the full ERRORS array for some reason.
Here is JSON that my CF passes back, it validated on JSONlint:
{"ERRORS":[{"ERRORTYPE":"","ERROR":"Error"}],"SUCCESS":false,"DATA":""}
But in my jQuery, the only thing that it can see from the ERRORS array is ERRORS.ERRORTYPE. It says ERRORS.ERROR is undefined when I toss anything to the console (even the whole response struct).
Any help would be appreciated.
@Rob,
You have to refer to it as an array:
ERRORS[ 1 ].Error
Try that.
Thanks Ben, that did it. I figured it would be something simple... :(
@Rob,
No problem man, glad to help.
Great article!
Do you have any tips as to how I could output AMF content instead of JSON?
I was really excited of being able to apply this neat concept to Flex/AMF but I cannot figure out how to serialize a CF complex object (I'm using a VO that I've mapped in both CF and Flex) in to AMF within the "cfcontent" tag within onError().
Specifically, I cannot find an AMF alternative to SerializeJSON() method you use.
I have been successful at intercepting the result at the onCFCRequest() method in order to create the unified response object... I just can't deal with errors and it's driving me crazy.
Any ideas would be greatly appreciated!
@Doug,
You ask a very tough question! And, one that I don't fully understand enough to answer. It took me like 2 years to figure out how to intercept SOAP based responses ( www.bennadel.com/blog/1774-Intercepting-ColdFusion-As-A-Service-SOAP-And-RESTful-Component-Requests.htm )... AMF, from my understanding, is even more complicated than SOAP.
If I can find out more about AMF, maybe I can start to work on this; but, as I am not much of a FLEX guy, I wouldn't even know where to start.
Has anyone found a solution for serializing to AMF? I'd love to use this method with my Flex application to intercept authentication errors, but not sure how to send out the response.
@Mary Jo,
I've never seen it myself, but I have to believe that it's possible since PHP can serialize to AMF and it doesn't have any of the fancy ColdFusion "endpoints" or whatever they are called. I think that's part of the ZEND framework? As I'm not familiar with FLEX, I really have no experience on this, sorry.