I’m working with the ConstantContact API webservice, and ran into a slight problem. The API uses Digest Authentication, and ColdFusion does not support Digest Authentication in cfhttp. I found this out by reading in the live docs that:
The cfhttp tag does not support NTLM or Digest Authentication.
The ConstantContact forums pointed me to a custom tag cfx_http5. But I hate CFX tags. I like to place as few dependencies on the ColdFusion Administrator as possible when I code. CFX tags also tend not to make the trip the first time in server migrations or emergency moves. It might be my own prejudice but, no, there will be no custom tags.
Which leaves me to implementing some Java client, as luck would have it there was ample documentation on doing this. I just needed to use the open source Apache HTTP client. To work with them, I had to add them to the class path, or add an entry in the ColdFusion Administrator class path list. That would have bought me nothing, as I didn’t want to go through the administrator.
This left me looking for a way to dynamically call jar files. If only there was some way to dynamically call jar files in ColdFusion… preferably written by someone with a marsupial pouch…. Ohh wait, Mark Mandel wrote the Open Source project JavaLoader, which allows just that. Hmm, and Mark is a mammal from Australia. Things are looking good.
So I was able to build my http client with Digest Authentication. I added some code that formatted the response like a cfhttp struct, so that if there were other advantages to this method I could just swap out this for cfhttp in a pinch. The code is below in the extended section of this blog post.
I was trying to do 3 things with this post:
- Explain how to use Digest Authentication with ColdFusion
- Show how with ColdFusion Open Source community and ColdFusion’s ability to call Java, there isn’t much you can’t do with it.
- Further the rumor that Mark Mandel is a marsupial.
I hope I’ve done these three things for you.
var paths = arrayNew(1);
var rootPath = GetDirectoryFromPath(GetCurrentTemplatePath());
paths[1] = rootPath & "/jars/commons-httpclient-3.1.jar";
paths[2] = rootPath & "/jars/commons-codec-1.3.jar";
paths[3] = rootPath & "/jars/commons-logging-1.1.1.jar";
//create the loader
variables.loader = createObject("component", "javaloader.JavaLoader").init(paths);
var result = "";
var credentials = loader.create("org.apache.commons.httpclient.UsernamePasswordCredentials");
var HttpClient = loader.create("org.apache.commons.httpclient.HttpClient").init();
var httpGet = loader.create("org.apache.commons.httpclient.methods.GetMethod");
var AuthScope = loader.create("org.apache.commons.httpclient.auth.AuthScope");
var jURL = createObject("java", "java.net.URL").init(arguments.url);
if (len(arguments.username) and len(arguments.password) gt 0){
AuthScope.init(jURL.getHost(), arguments.port, arguments.realm);
credentials.init(arguments.Username, arguments.password);
httpClient.getState().setCredentials(authScope, credentials);
}
httpGet.init(arguments.url);
httpClient.executeMethod(httpGet);
result = convertHttpClientResponseToCFHTTPFormat(httpGet);
httpGet.releaseConnection();
return result;
var result = structNew();
var responseheader = structNew();
responseheader['Status_Code'] = httpGet.getStatusCode();
responseheader['Explanation'] = httpGet.getStatusText();
responseheader['Http_Version'] = httpGet.getEffectiveVersion().toString();
header = httpGet.getStatusLine().toString();
headers = httpGet.getResponseHeaders();
for (i=1; i lte ArrayLen(headers); i=i+1){
responseheader[getToken(headers[i], 1, ":")] = getToken(headers[i], 2, ":");
header = listAppend(header, headers[i], " ");
}
result['Charset'] = httpGet.getResponseCharSet();
result['ErrorDetail'] = "";
result['Filecontent'] = httpGet.getResponseBodyAsString();
result['Header'] = header;
if (structKeyExists(responseheader, 'Content-Type')){
result['Mimetype'] = GetToken(responseheader['Content-Type'], 1, ";");
}
result['Responseheader'] = responseheader;
result['Statuscode'] = responseheader['Status_Code'] & " " & responseheader['Explanation'];
if( not structKeyExists(responseheader, 'Content-Type') OR
FindNoCase("text", responseheader['Content-Type']) OR
FindNoCase("message", responseheader['Content-Type']) OR
FindNoCase("application/octet-stream", responseheader['Content-Type'])
){
result['Text'] = "YES";
}
else{
result['Text'] = "NO";
}
return result;
Terry,
Thanks for sharing. I’ll be filing this one away for later.
Oh, and you are truly a master at spreading rumors!
LikeLike
I always had my suspicions about Mark…
LikeLike
Very nice solution. Who knew Apache could talk with the dead? Maybe only you and the marsupials.
LikeLike
This should go on RiaForge!
LikeLike
@Sami, I kinda rushed it out as a response to CF is dead nonsense today. I might give that a try, but I’m looking at a swamped next few weeks.
Anyone who wants to steal the code and release on Riaforge can be my guest.
LikeLike
Hi Guys
you can do the same thing on windows with the XMLHTTP COM object.
you don’t need to install anything because its part of the OS.
LikeLike
Talk about perfect timing! Decided I wanted to get my work calendar, which is hosted on an OS X Caldav server, into my Google calendar. Trouble was Google doesn’t support remote calendars with authentication so I needed a CF proxy in between. This worked a treat, although for some reason I needed to put the port number in the URL string as well as the port attribute.
Many thanks!
LikeLike
I have to say great timing as well. I’m just starting to do some API programming with Constant Contact in a ColdFusion page. Make sure you use the "realm" argument with "api.constantcontact.com" as the value. Thanks for the post.
LikeLike
You can do a post also, just copy the get function, replacing "get" with "post" and then:
…
<cfargument name="url" type="string" required="TRUE" hint="The url to call." />
<cfargument name="body" type="string" required="TRUE" hint="What to send to the URL" />
….
var AuthScope = loader.create("org.apache.commons.httpclient.auth.AuthScope");
var myStringRequestEntity = loader.create("org.apache.commons.httpclient.methods.StringRequestEntity");
….
httpPost.init(arguments.url);
httpPost.setRequestEntity(myStringRequestEntity.init(arguments.body, "application/atom+xml", "UTF-8"));
….
NOTE: This is for posting an ATOM XML document as a string.
LikeLike
Excellent! I’m about to need a constant contact API wrapper myself. If it’s alright w/you, I’ll use your code as a starting point and release it and the CC api on riaforge once they’re done.
LikeLike
Constant Contact’s API is switching from Digest Authentication to Basic Authentication over HTTPS. Since you don’t indicate the authentication type (digest versus basic), it appears there’s nothing that needs to change in the code. We just need to make sure the URLs have "https" instead of "http." I’ll update you if anything changes after I test this strategy out.
LikeLike
You need to change default="80" to default="443" in order to use https correctly. That, and changing the URL, will do the trick.
LikeLike
This begs the question – WHY don’t Adobe support NTLM in CFHTTP when the functionality is obviously there by using your method?!?!?!?! Dumbfounded!
Thank for the fix BTW
Martin
LikeLike
Terrence,
Please forgive a rookie question. Can you show me how to implement the code written above? I am new to some of this and I can’t seem to get it going.
Thank you.
LikeLike