Copy link to clipboard
Copied
I am using CF REST Services and we would like to send back a detailed error struct when a requestor submits data that does not pass our internal validation check. I am using setRestResponse to manipulate the status code (which works fine on all accounts). However when someone fails validation I would like to send a struct back to the user with a 400 or a 401. My research lead me to putting the struct in the "content" part of my return struct to setRestResponse however when I do that, I get the correct statuscode but not the JSON struct of the error detail.
One thing Im observing is my api seems to be returning HTML, even though I have set the "produces" attribute of my function to "application/json". I have also set the function to void since we are using setRestResponse. Can you help me figure out how to send back an error detail struct with a 400 error? What exactly needs to happen to make the API respond with JSON beyond what Ive done already?
Ive included a snippet of my code.
UPDATE!
I figured out my issue. I had to change the returntype of "putRequest" to "struct" and now everything works dandy!!
Thank you so much for sticking with me and helping me get to the bottom of it. restSetResponse() should get more attention. Its a very simple way to do this that would encourage more to get involved with CF REST Services.
Regards!!!!
Copy link to clipboard
Copied
Below is my code
<cfset response = structNew()/>
<cfif IsDefined('val_check_fail')>
<!---The val_check returned an error. Stop processing and return a REST error--->
<cfscript>
error_struct = StructNew();
error_struct.errorText = val_check;
error_struct.errorCode = 3254;
out_struct = serializeJSON(error_struct);
</cfscript>
<cfset response.status = 400/>
<cfset response.content = #out_struct#/>
<Cfset pyld = response/>
<cfelse>
<cfscript>
ins_payload = invoke(val_obj, 'insert_payload', {value_string:#insert_value_list#,col_string:#insert_list#});
</cfscript>
<cfscript>
success_struct = StructNew();
success_struct.message = "Validated and Accepted For: " & #arguments.messageID#;
out_struct = serializeJSON(success_struct);
</cfscript>
<cfset response.status =200>
<cfset response.content = #out_struct#>
<cfset pyld = response/>
</cfif>
<cfscript>
restSetResponse( pyld );
</cfscript>
Copy link to clipboard
Copied
I tried to understand the issue, but two things confuse me. Firstly, you mention "arguments.messageID", suggesting we're in a function. But I see no var-scoped variables. Where are we? If in a function, could you share the code of the entire function?
Secondly, it's unclear to me why so many variables are defined. I expected something like
<cfset var response = structNew()/>
<cfset var out_struct = "">
<!--- Scope necessary. Variables scope assumed --->
<cfif IsDefined('variables.val_check_fail')>
<!---The val_check returned an error. Stop processing and return a REST error--->
<cfscript>
var error_struct = StructNew();
error_struct.errorText = val_check;
error_struct.errorCode = 3254;
out_struct = serializeJSON(error_struct);
</cfscript>
<cfset response.status = 400/>
<cfelse>
<cfscript>
var ins_payload = invoke(val_obj, 'insert_payload', {value_string:#insert_value_list#,col_string:#insert_list#});
var success_struct = StructNew();
success_struct.message = "Validated and Accepted For: " & arguments.messageID;
out_struct = serializeJSON(success_struct);
</cfscript>
<cfset response.status =200>
</cfif>
<cfset response.content = out_struct>
<cfset restSetResponse(response)>
Copy link to clipboard
Copied
Thanks so much for taking a look!
Much of that code is not germaine to my issue. I should proably have left that out. Thanks for the note about scoping, I made that change.
That said my problem is specfically here (all othter parts of this function work fine, and yes, it is a REST function using returntype="void"). If the validation check (variables.val_check_vail) fails, I want to send a 4XX statuscode error and an error payload. When I do this for a 2XX statuscode, I can successfully send the payload structure with it. When I do this for a 4XX this seems to somehow be overridden and the server returns HTML (despite my setting produces="application/JSON") with the generic 4XX error, and no payload structure. Could this be the webserver overriding ColdFusion on errors and not success statuscodes? If that is the case, how do you override it if your function is set to returntype="void". Can I send a structure back in the "content" variable when using a 4XX statuscode and restSetResponse()?
<cfif IsDefined('variables.val_check_fail')>
<!---The val_check returned an error. Stop processing and return a REST error--->
<cfscript>
error_struct = StructNew();
error_struct.errorText = val_check;
error_struct.errorCode = 3254;
out_struct = serializeJSON(error_struct);
</cfscript>
<cfset response.status = 400/>
<cfset response.content = #out_struct#/>
<Cfset pyld = response/>
<cfelse>
<cfscript>
success_struct = StructNew();
success_struct.message = "Validated and Accepted For: " & #arguments.messageID#;
out_struct = serializeJSON(success_struct);
</cfscript>
<cfset response.status =200>
<cfset response.content = #out_struct#>
<cfset pyld = response/>
</cfif>
<cfscript>
restSetResponse( pyld );
</cfscript>
Copy link to clipboard
Copied
Here is my entire function for reference.
<cffunction name="putRequest" access="remote" produces="application/JSON" returntype="void" httpmethod="POST" restpath="/sendRequest" >
<cfargument name="messageID" required="true" restargsource="query" restargname="messageID"/>
<cfargument name="message" required="true" restargsource="query" restargname="message"/>
<cfscript>
msg = Trim(arguments.message);
dec_obj = CreateObject('component', 'cfc.encryption');
val_obj = CreateObject('component', 'cfc.system');
dec_dt = invoke(dec_obj, 'dec_data', {data:#msg#});
</cfscript>
<cfset dec_msg = DeserializeJSON(dec_dt)>
<cfset keyCount = StructCount(dec_msg)>
<!---The below section should be included in a loop of the JSON or an interrogation of the struct.--->
<!---Send the key/value pair to the validation library--->
<cfset insert_list ="">
<cfset insert_value_list = "">
<cfset l_cnt = 0>
<cfloop collection="#dec_msg#" item="key">
<cfscript>
val_check = invoke(val_obj, 'validation_library', {key:#key#, value:#dec_msg[key]#});
</cfscript>
<cfif val_check IS NOT "Pass">
<cfset val_check_fail = 1>
<cfbreak>
</cfif>
<cfset l_cnt = l_cnt + 1>
<cfif l_cnt IS keyCount>
<cfset insert_list = insert_list & key/>
<cfset insert_value_list = insert_value_list & "'" & dec_msg[key] & "'"/>
<cfelse>
<cfset insert_list = insert_list & key & ","/>
<cfset insert_value_list = insert_value_list & "'" & dec_msg[key] & "',"/>
</cfif>
</cfloop>
<cfset response = structNew()/>
<cfif IsDefined('val_check_fail')>
<!---The val_check returned an error. Stop processing and return a REST error--->
<cfscript>
error_struct = StructNew();
error_struct.errorText = val_check;
error_struct.errorCode = 3254;
out_struct = serializeJSON(error_struct);
</cfscript>
<cfset response.status = 400/>
<cfset response.content = #out_struct#/>
<Cfset pyld = response/>
<cfelse>
<cfscript>
ins_payload = invoke(val_obj, 'insert_payload', {value_string:#insert_value_list#,col_string:#insert_list#});
</cfscript>
<cfscript>
success_struct = StructNew();
success_struct.message = "Validated and Accepted For: " & #arguments.messageID#;
out_struct = serializeJSON(success_struct);
</cfscript>
<cfset response.status =200>
<cfset response.content = #out_struct#>
<cfset pyld = response/>
</cfif>
<cfscript>
restSetResponse( pyld );
</cfscript>
</cffunction>
Copy link to clipboard
Copied
Oh, indeed. I have been able to reproduce the behaviour. The response goes to tags and XML format all of a sudden.
Does it help when you include JSON content-type in the onRESTRequest event-handler in Application.cfc?
<!--- Application.cfc --->
component output="false"
{
this.name = "RestTestApp";
this.applicationTimeout = createTimespan(0,1,0,0);
this.sessionTimeout = createTimespan(0,0,20,0);
public any function onRESTRequest(string cfcname, string method, struct args) {
cfheader( name="Content-Type", value="application/json" );
}
}
Copy link to clipboard
Copied
If all else fails, then you can force a JSON response as follows:
restSetResponse( pyld );
with the line
return pyld;
3. In the onRESTRequest event-handler in Application.cfc, do something like
public any function onRESTRequest (string cfcname, string method, struct args) {
var RESTResponse = invoke(arguments.cfcname, arguments.method, arguments.args);
if (not isJson(RESTResponse)) {
RESTResponse = serializeJson(RESTResponse);
}
return RESTResponse;
}
Copy link to clipboard
Copied
Thanks again!
I think Ive made some headway, however Im getting a new error.
For background I went ahead and used the second reccomendation (which incidentally created the same condition that the first suggestion did). I tried the first stuggestion, and would up getting a 204/No Content error (my code is was suppsoed to surface a 200). Then I employed the section suggestion and got the same condition. So after employing these changes, I can no longer affect the status or the content variable in my response struct. However for some reason it seems like I just need to do something else and this will work. Trying to figure out what it is. For reference at the end of this function Im building a struct called "response" which has two keys. response.status which is the status code Id like to return, and response.content which contains either a success or an error struct.
Can you think of anything I should be doing differently?
Copy link to clipboard
Copied
UPDATE!
I figured out my issue. I had to change the returntype of "putRequest" to "struct" and now everything works dandy!!
Thank you so much for sticking with me and helping me get to the bottom of it. restSetResponse() should get more attention. Its a very simple way to do this that would encourage more to get involved with CF REST Services.
Regards!!!!
Copy link to clipboard
Copied
No worries. That "struct" return-type was a good catch by the way. My suggestion,
'Give the function putRequest a return-type: "string"'
was a blunder.
Perhaps a return-type of "any" would have been better, to cater for JSON, XML, struct, and so on. This is where ColdFusion achieves power from being weakly-typed. 😉
Copy link to clipboard
Copied
Just to follow up to this.
I have implemented things to the point where I now return the following:
{"STATUS":"400","CONTENT":{"ERRORCODE":"HRSIDVC12","ERRORTEXT":"startDate Key must be a date later than today."}}
The issue is, I no longer seem to be able to affect the HTTP Statuscode being returned by sending back STATUS like this. Is there something Im missing about how to have CF direct IIS to send back a specific HTTP Response?
Best,
Gerry
Copy link to clipboard
Copied
You could, in the page that creates the response to the client, simply include something like:
if(RESTResponse.status eq 400) {
cfheader(statuscode = 400, statustext = "Bad Request");
}
Copy link to clipboard
Copied
Could it be that IIS is some how not allowing me to do this? I have gone so far as to simply set up a test call from a page (this is a REST service), and I still cant seem to affect the statuscode return. Ive used CFHEADER in this way to do it and still IIS surfaces its own error.
Copy link to clipboard
Copied
This is just a shot in the dark, but you may be able to tell IIS not to do that using web.config.
https://docs.microsoft.com/en-us/iis/configuration/system.webserver/httperrors/
Dave Watts, Eidolon LLC
Copy link to clipboard
Copied
Could it be that IIS is some how not allowing me to do this? I have gone so far as to simply set up a test call from a page (this is a REST service), and I still cant seem to affect the statuscode return. Ive used CFHEADER in this way to do it and still IIS surfaces its own error.
By @Gerry5C74
I suspect you're setting the header in content, rather than in the page that goes to IIS. That is, your header code is simply lumped with content that is passed to the servlet that is converted into the response that goes to IIS.
To correct the situation, you have to set the header in the final response going to IIS. You should be able to do this in the onRESTRequest event-handler.