[ColdBox SEEK 3.8.1] restful api call from angularjs $http.post() does not work

has anyone had problems with using angulars http shortcut method $http.post(’/index.cfm/api/address’, formdata.address);

Apparently jQuery.post() differs from the shortcut method $http.post() in that the latter tranmits the data using Content-Type: application/json whereas the other uses x-www-form-urlencoded
So i’m making my ajax request and angular does successfully post the data, but as you can in the screenshot it never finishes the request.

CAUTION: request is not finished yet!

In FusionReactor I can the request coming in and finishing successfully.

So where is the response? The api should return json data.

The same request using Relaxer works, and I know its using x-www-form-urlencoded as the Content-Type. But is it about the angular request that coldfusion or coldbox does not like?

HTTP spy on google chrome doesn’t actually track an ajax request so i can’t see what is being sent or received.

In Fusion Reactor I see a status 200 OK. See screenshot:

and the HEADERS:

These are the headers as shown by chrome dev tools:

and the PREVIEW and RESPONSE tabs in dev tools are just blank.

the TIMING:

I’m working on angularjs app and coldbox as backend for rest calls. Never experienced any issues.

Have you tried to make a post request to the url outside of angularjs?

What headers are sent as response?

Have you confirmed that ColdBox is doing what you expect with the request? Forget the response (or lack thereof) issue for a moment: is the request actually working before that point? In the code that’s supposed to run when the “api/address” pattern is routed, is that working? For example, in RESTful form, a POST is often used to create a resource. So let’s assume that your api/address route executes the createAddress() method on an Address.cfc handler. Is this method (whatever it is within your code) doing what you expect up until the point that you try to return data? Does a new address get created? A log entry? Whatever other logic you have in place for this route?

If the answer to that is yes, then please share what you can regarding how you are handling the response from ColdBox. You said it’s supposed to return JSON data. How are you doing that?

And I second Trop’s suggestion. Get something like Postman for Chrome that will let you create a POST request without the angularJS layer. If it still doesn’t work at that point, there might be something happening during the ColdFusion request that’s creating an issue. But at least replicating it apart from the extra layer of JS will probably help diagnose the issue a bit faster.

Finally, if you’re still having this issue, it would be helpful if you could post some code, specifically:

  • The route definition you are using to handle this url pattern in ColdBox
  • The handler function which is executed for this route
  • How you’re returning data as JSON

Hitting directly at http://local.intranet-api/paag/address results in
Error Type: SES.405 : 405
Error Messages: Invalid HTTP method: GET

Posting with POSTMAN in chrome works. I get my expected json results.

Posting with Relaxer works. I get my expected json results.

In my Module Config I have the following routes defined:

routes = [ // root/default { pattern="/", handler="readmeHandler", action="index" }, // geocode new address { pattern="/address", handler="geocoderHandler", action={ POST="new" } }, // get geocode totals { pattern="/geocode/totals", handler="geocoderHandler", action={ GET="totals" } }, // Convention Route { pattern="/:handler/:action?"} ];

My handler:

`
function new(event,rc,prc){

prc.results.data = paagGeocoder.geocodeSingleAddress(rc);

}
`

My base handler:

`
component {

public void function preHandler(event,rc,prc){

prc.results = {};
prc.results[“status”] = “OK”;
prc.results[“data”] = {};
prc.results[“message”] = “”;

event.paramValue(“format”,“json”);

}

public void function postHandler(event,rc,prc){

renderResults(event,prc.results);

}

// Any process level error that inheriting handlers do not catch will be caught
// by this onError.
public any function onError(event,faultAction,exception,eventArguments){

prc.results.status = “FAIL”;
prc.results.message = ARGUMENTS.exception.message;
prc.results.data = ARGUMENTS.exception.tagContext;

// TODO: log error here
// Use LogBox Appenders, ie: db, trace, email

// pass the results
renderResults(ARGUMENTS.event, prc.results);

}

private any function renderResults(event,results){

// return if PROXY
if( ARGUMENTS.event.isProxyRequest() ){
return ARGUMENTS[“results”];
}

// NOTE: Config/Routes.cfm has extensions settings turned on
// if a detected extension is not part of the allowed extensions
// then coldbox will thrown an invalid event error, this is when
// onError kicks in.
switch( event.getValue(‘format’) ) {
case “jsonp”:
if(event.valueExists( name=“callback”, private=false )){
event.renderData( data=ARGUMENTS[“results”], type=“jsonp”, jsoncallback=event.getValue(‘callback’) );
}else{
var errorStruct = {status=“FAIL”, message=“MISSING_CALLBACK”};
event.renderData( data=errorStruct, type=“jsonp”, jsoncallback=“fail” );
}
break;

default:
event.renderData( data=ARGUMENTS[“results”], type=event.getValue(‘format’) );
break;
}

}

}
`

Does it work from Angular if you remove the isProxyRequest() check?

Here is my short version of the angularjs app. Of course, the domain will not resolve, but you can atleast see how I’m coding the app. Which is very basic. Plunker - Starter Template AngularJS

So the request is made and then just sits in that “CAUTION: request is not finished yet!” state.

Are your servers by chance behind a load balancer, firewall, or security appliance that might abort connections for any reason? If CF thinks it’s responding, does an HTTP sniffer such as Ethereal (Wireshark) show all the packets coming back to complete the HTTP response?

Thanks!

~Brad

ColdBox Platform Evangelist
Ortus Solutions, Corp

E-mail: brad@coldbox.org
ColdBox Platform: http://www.coldbox.org
Blog: http://www.codersrevolution.com

What happens if you simplify your ColdBox setup (i.e., don’t extend the base handler) and just directly render the data as JSON from your target controller?

Joel, removing proxy check and base handler did not change anything.

Brad, Its my own dev computer without load balancing. I have microsoft’s System Center Endpoint Protection turned on which . I don’t even know how to turn that off to quickly test it. And currently I don’t have any firewall (but i am protected by the company’s internet firewall).

My GET request to get the totals works ok. and If I change my address request to allow GET method it works. My angular service had to change too:

this.geocodeSingleAddress = function(address){ // returns google api response includes coordinates // return $http.post(apiUrlBase + '/address',address); return $http.get(apiUrlBase + '/address?address='+address.address+'&city='+address.city+'&state='+address.state+'&zip='+address.zip); };

So coldfusion does not like the POST method of type application/json.

Ah, now I see what you’re saying. Yeah, I’m getting unexpected results when I use ContentType: application/json. Have you tried overriding the headers used by post() ?

I did what this suggests: http://www.bennadel.com/blog/2617-parsing-angularjs-request-data-on-the-server-using-coldfusion.htm

I added this to onRequestStart in Application.cfc

`
try {
// deserialize application/json type requests
if( getHTTPRequestData().headers[“Content-Type”] eq “application/json” ){
requestBody = toString( getHttpRequestData().content );

if ( len( requestBody ) ) {

structAppend( form, deserializeJson( requestBody ) );

}
}
}
catch(any error) {}
`

In dev tools I can see the request payload is

  1. {“address”:“9584 California Ave”,“city”:“Riverside”,“state”:“CA”,“zip”:“92503”}
    But rc.address is not defined, and the form scope is empty.

I do remember trying changing the content-type to x-www-form-urlencoded but that didn’t work. So I looked into it again and all I needed to do is serialize the data before sending request. Changing the content-type to x-www-… was not enough. jQuery’s $.param did the trick.

This works:

this.geocodeSingleAddress = function(address){ // returns google api response includes coordinates // return $http.post(apiUrlBase + '/address',address); return $http({ method: "POST", url: apiUrlBase + '/address', data: $.param(address), headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); };

Working code (still uses local domain): Plunker - Starter Template AngularJS
the default content type for post, and delete can be set in the configuration block on your angular module.

coldfusion by default does not support application/json - in order to work with that content type you have to deserialize the content yourself before processing. Ben talks about it here. http://www.bennadel.com/blog/2617-parsing-angularjs-request-data-on-the-server-using-coldfusion.htm

What Ben is talking about there is how Anguular JS is sending the information, because Angular JS is using the restful API calls, this information is sent across in the headers and is not a form. Therefore what Ben is telling you, for you to get at this inforamtion you need to access the header of the request to get that information.

This is standard for any Restful API request to any server, regardless whether it is ColdFusion or PHP and applies to the likes of using Facebook requests and Google Requests from their services as well.

I am almost 10% positive that ColdBox handles this, it just doesn’t deserialize the Json, which as you noted you have to do.

Rather than add the following to the Application.cfc, you would be best to write an interceptor that listens for the preRequest event and do this in there. Adding stuff like this to the Application.cfc should be extreme last resort. And don’t attach it to the form, when doing this as an interceptor, you would be better sticking it straight into the RequestContext (RC) or even the PRC.

try {
// deserialize application/json type requests
if( getHTTPRequestData().headers[“Content-Type”] eq “application/json” ){
requestBody = toString( getHttpRequestData().content );

if ( len( requestBody ) ) {

structAppend( form, deserializeJson( requestBody ) );

}
}
}
catch(any error) {}

Anyways, Andrew Scott you are correct. Interceptor method is much better. I also found this GIST ColdBox JSON Interceptor to support json http requests · GitHub that does exactly the same thing.

Lesson learned.

Can anyone submit to forgebox on behalf of someone else?