• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
0

ColdFusion REST Services not allowing a struct to be sent back in "content" when status code is 4xx

New Here ,
Aug 11, 2021 Aug 11, 2021

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.

Views

868

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Community Beginner , Aug 17, 2021 Aug 17, 2021

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!!!!

Votes

Translate

Translate
New Here ,
Aug 11, 2021 Aug 11, 2021

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>

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 13, 2021 Aug 13, 2021

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)>

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Aug 16, 2021 Aug 16, 2021

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>

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Aug 16, 2021 Aug 16, 2021

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>

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 16, 2021 Aug 16, 2021

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" );
    } 

}
  

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 17, 2021 Aug 17, 2021

Copy link to clipboard

Copied

If all else fails, then you can force a JSON response as follows:

  1. Give the function putRequest a return-type: "string"
  2. Replace the the line 
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;   
}  

 

 

 

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Aug 17, 2021 Aug 17, 2021

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?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Aug 17, 2021 Aug 17, 2021

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!!!!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 17, 2021 Aug 17, 2021

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. 😉

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Aug 26, 2021 Aug 26, 2021

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 04, 2021 Sep 04, 2021

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");
}

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Sep 07, 2021 Sep 07, 2021

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.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 07, 2021 Sep 07, 2021

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

Dave Watts, Eidolon LLC

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 08, 2021 Sep 08, 2021

Copy link to clipboard

Copied

LATEST
quote

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.

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Resources
Documentation