Using cfthread to speed up your web service calls
I've recently taken to using the excellent Postmark to send out all the transactional emails from my web sites. For just 0.15 cents per email, you get the benefits of increased deliverability and a great API to track and manage any undelivered mail.
If you like, you can send your emails simply by using Postmark's SMTP servers -- a really quick way to migrate to their system. But the real power comes when you start using their API. I'm not going to go into detail about how to set this up here, but in short you just have to send your message as a JSON object via HTTP Post.
And this is where threads come in. There are some web services you will use -- for instance, payment gateways -- where the returned result is critical to the continuation of the page request. But others -- as well as Postmark, I also send API requests to MailChimp for mailing list subscriptions -- where you really don't need to know the outcome.
Simply set up a new thread using
<cfthread> (you're just firing and forgetting -- you don't need to worry about any joining when the thread completes), and perform your
<cfhttp> magic in there. ColdFusion will get on with doing that in the background -- but in the meantime your user has already received their page without waiting for a webservice call which you never quite know how long will take to peform.
Of course, you can (and should) include error handling logic in your
<cfthread> -- so you've got some idea of what the problem was if, for some reason, the webservice call was unsuccessful. But in most cases, this will be of concern to you and not to your user.
Here is an example of how I've implemented it for Postmark:
<!--- ...page processing, generate the message to be sent... ---> <!--- Fire off the webservice call in a thread ---> <cfthread action="run" name="postmark_#createUUID()#" json="#email.getJson()#" subject="#email.getSubject()#"> <cftry> <!--- Code to call the http request and parse any errors. Returns a struct "result" with keys "code", "status" and "message". ... ---> <cfif result.code neq 200> <!--- Successful webservice call, but Postmark returned an error. Log it. ---> <cflog text="Failed: #subject#; #result.code# #result.status#" file="postmark" /> <cfelse> <!--- Message was accepted by Postmark. Send it. ---> <cflog text="Success: #subject#" file="postmark" /> </cfif> <cfcatch type="any"> <!--- Some other error. Log it. ---> <cflog text="Failed: #subject#; #e.message#" file="postmark" /> </cfcatch> </cftry> </cfthread> <!--- ...now get on with returning a page to the user... --->
Caveat: using cfscript #
There is one issue I ran into, which appears to be related to this bug report: if you're writing your code in cfscript, you would use
new http(); to create your HTTP request. However, under certain circumstances -- it may be the ones described in the bug, or may be slightly different -- this will fail. Luckily -- because I was logging the results of the thread -- I was able to detect it, and found the following error message:
Failed: Website order: 12400; An error occurred when performing a file operation read on file C:\[path\to\website]\WEB-INF\cftags\META-INF\taglib.cftld.
There was a string of several of these over a number of hours, then it went back to normal -- making it very tricky to reproduce the error.
My solution was to rewrite the component using tags rather than script; fingers crossed, it has been behaving itself perfectly ever since.