Search code examples
restcoldfusioncoldfusion-10application.cfc

How do I return a status code error from a REST Service while using an OnError handler?


I'm trying to set up some REST Web Services with ColdFusion 10, and if I have an onError handler in place in the Application.cfc the error status code is not returned back to to the Consumer.

Let's consider this Application.cfc

<cfcomponent displayname="ApplicationCFC" output="true" >
    <cfscript>
        this.name = "learnWith";
        this.applicationTimeout = createTimeSpan(1,1,0,0);
        this.sessionManagement = "false";
        this.restsettings.autoregister = true;
        this.restsettings.skipCFCWithError = true;
    </cfscript>

    <cffunction name="onApplicationStart" returntype="boolean" output="true">
        <cfset RestInitApplication(expandPath('/myDir'),"lw")>
        <cfreturn true>
    </cffunction>
</cfcomponent>

Now consider the service, like this:

<cfcomponent rest="true" restpath="/user">
    <cffunction name="authenticate" access="remote" restpath="/login" returntype="String" httpmethod="POST"
            consumes="application/json" produces="application/json">
        <cfset requestData = getHTTPRequestData()>
        <cfset userInfo = deserializeJSON(ToString(requestData.content)) />

        <!--- cfquery to load user data ---->

        <cfif local.dataQuery.recordcount EQ 1>
            <cfset local.userVO = createObject()> <!--- create and populate userVo here --->
            <cfreturn SerializeJSON(local.userVO)>
        <cfelse>
            <!--- user not found, throw 401 error --->
            <cfthrow type="RestError" errorcode="401"  />
        </cfif>
    </cffunction>
</cfcomponent>

This code works perfectly from Postman. If I pass in the proper credentials

{
    "userName": "user",
    "password": "hashedPassword
}

I get a user object back as expected with a 200 response.

Correct Credentials Login

If I pass in fake or invalid credentials:

{
    "userName": "fakeuser",
    "password": "fakePassword
}

I get a 401 error back:

Failed Authentication

That's perfect. However, I want to use a global error handler for the CFC, both for non-REST Service based code and to log possible REST Service errors. If I add this into the Application.cfc

    <cffunction name="onError" returnType="void" output="true">
        <cfargument name="exception" required="true">
        <cfargument name="eventname" type="string" required="true">
        <!--- error logging here --->
        <cfthrow type="RestError" errorcode="401" />
        <!--- or 
            <cfthrow type="#arguments.exception.Cause.Cause.type#" 
                     errorcode="#arguments.exception.Cause.Cause.code#">--->

    </cffunction>

ColdFusion returns a 500 Internal Server error message.

500 Internal Server Error

I experimented with cfheader both in the CFC method and within the onError handler:

<cfheader statusCode = "401" statusText = "RestError">

It'll return a 200 with no body instead of a 401 status:

200 error

I've tried various iterations of this, but am at a loss. Can I use status codes from a ColdFusion rest service while also having an onError handler in place? And if so, how?


Solution

  • I was able to do what I want. Instead of using cfthrow to create an error I can create the response I needed with restSetResponse.

    Inside the cffunction, first make sure to set the return type to void.

    Then instead of returning an object or using cfthrow, you want to create a response object manually and send it back using restSetResponse.

    This solved my issue:

    <cfcomponent rest="true" restpath="/user">
        <cffunction name="authenticate" access="remote" restpath="/login" returntype="void" httpmethod="POST"
                consumes="application/json" produces="application/json">
            <cfset requestData = getHTTPRequestData()>
            <cfset userInfo = deserializeJSON(ToString(requestData.content)) />
    
            <!--- cfquery to load user data ---->
    
            <cfset local.result = structNew()/>
    
            <cfif local.dataQuery.recordcount EQ 1>
                <cfset local.userVO = createObject()> <!--- create and populate userVo here --->
    
                <cfset local.result.status = 200 />
                <cfset local.result.content = SerializeJSON(local.userVO) />
            <cfelse>
                <cfset local.result.status = 401 />
                <cfset local.result.content = SerializeJSON({message: 'User Not Authorized'}) />
            </cfif>
    
            <cfset restSetResponse(local.result) />
        </cffunction>
    </cfcomponent>
    

    Once I did that I was able to control the response back to my consumer without triggering the onError method in the Application.cfc.