I know, I know, I'm like a decade behind everyone else, but I JUST started playing around with web services. I have just never had to invoke or provide one before and so never experimented with it. I had kind of a rocky start with this example, but after some Googling and some Adobe documentation, I got it off the ground.
My first hurdle was getting my application to allow ColdFusion Components (CFCs) to be called directly. My application uses the OnRequest() event method in the Application.cfc ColdFusion component and as you may or may not know, doing so blocks the use of web services and flash remoting. To get around this, first I created a directory for my web services at "/resources/webservices/". Then, I applied a dirty little trick I picked this up from Ray Camden as explained in the first issue of the Fusion Authority Quarterly; I destroyed the OnRequest() event method for web service calls:
Launch code in new window » Download code as text file »
While I think Ray only recommended the OnRequest() event method removal, my OnRequestEnd() event method was causing issues (via the CFFlush tag) so I had to remove it as well. I placed this at the very end of the OnRequestStart() event method. Since that event method fires before the OnRequest() event method, it is able to successfully alter the pre-page processing event chain. Now, all calls to ColdFusion components (or any other files for that matter) located inside the "resources/webservices/" directory can be accessed directly. Thanks Ray!
Then, I started my first ColdFusion component with remote access for use with web services. Stuff quickly started to break and I had no idea what was going on. I started getting errors like this:
Web service operation "GetCompliment" with parameters {Gender={}} could not be found.
But that was working a second ago! It just had a different name. Wait, how come it worked for this method and not that one? Wait, that method worked a second ago and now that I added an argument it no longer works! What gives!!!!
This was all very frustrating. Then I read that by calling the ColdFusion component build something called a "Stub" file. I am not 100% clear on how this all works, but supposedly this stub file defines what the web service does and how it works using the WSDL standard. The tricky part is that this stub file is basically only written once per file. If you go to alter the web service you will quickly find out that stuff breaks because the stub file is now out dated.
Apparently, there is something you can do in the ColdFusion Administrator to fix this (do a manual refresh or something), but I don't like going to the Admin for stuff. That is lame. I want to do it programmatically. Thanks to B. Prucell, I was able to rebuild the stub file using the ColdFusion Service Factory.
Now, since I don't really know what I am doing, I figured this is something I would want to build into the web services so that I could easily rebuild any web service stub file. To accomplish this, I created a base web service component that provides a default Initialization method and RebuildStubFile method:
Launch code in new window » Download code as text file »
As you can see, I do some password protection; I don't want just ANYBODY rebuilding my stub files. Of course, this should probably not be remote access, but like I said, I don't really know what I am doing yet. This is my first web service experience. The thinking here though is that once I get this stub file built, I am very rarely going to change the BaseWebService component so even if the stub file is out dated, the RebuildStubFile() method will mostly likely be defined and available for use.
Ok, so now that I have my base web service ColdFusion component, I could allow my Fun web service to extend it:
Launch code in new window » Download code as text file »
This web service component has one method, GetCompliment(). This web service takes an optional "Gender" argument and returns a gender specific or non-specific compliment. This presented a new problem. Everything worked fine and dandy when I tried to call the web service using the gender argument:
Launch code in new window » Download code as text file »
However, if I tried to invoke this web service without the argument:
Launch code in new window » Download code as text file »
... it totally crashed and burned and give me this error:
Web service operation "GetCompliment" with parameters {} could not be found. <br>The error occurred on line 45.
After some hair pulling, some head pounding, and a good amount of Googling, I finally came across a posting by Steven Erat over at Talking-Tree. Apparently, for a web service you have to pass in optional arguments but tell the web service to omit them:
Launch code in new window » Download code as text file »
Notice that this time, my CFInvokeArgument tag uses the Omit attribute (and sets it to true). This invokes the web service and I guess does not pass in the argument, or I guess it passes it in (to jive with the WSDL document) but is just never used? No idea. All I know is that it works.
If you want to give it a go, the Fun.cfc web service is available at:
http://www.bennadel.com/resources/webservices/Fun.cfc?wsdl
Download Code Snippet ZIP File
Comments (15) | Post Comment | Ask Ben | Permalink | Other Searches | Print Page
Ben,
It's probably just me, because I haven't yet found a need to do a CF webservice. The one service I ever provided was in .NET, for a desktop CMS. But, that is beside the point.
What I don't understand is why you had to go through so much trouble? For instance, why did you have to delete the OnRequestXXX() methods? Why provide them in the first place? It seems like a lot to go through... (and that is what I'm most intersted in)
The other comment I had was about the need to provide an optional argument. I know its not your fault, so don't take this as jab at you =). But, that just seems quite silly. <cfinvokeargument name="Gender" value="" omit="true" /> .... I mean, all that typing/clutter to accomplish nothing except omitting the argument that shouldn't have to be there in the first place! =)
It reminds me of the Java program that does absolutely nothing (as opposed to say, the CF program that does nothing). It's quite long, but it works well! =)
Posted by Sam on Dec 12, 2006 at 10:58 AM
A large application I work on uses web services to communicate with its CFCs which reside on a physically separate application server. Yes I am in hell. CF makes working with web services pretty easy, but there are a number of hair-pulling pieces such as using optional arguments and other issues like trying to debug errors without file names and line numbers. I ended up creating a web service wrapper that solves most of these issues for me. If you end up coming across a funky error that you can't figure out, shoot me an email - chances are I've come across it and fudged my way through it.
Posted by Rob Pilic on Dec 12, 2006 at 10:59 AM
@Rob,
Thanks for the offer. I am sure I will hit you up with some stumps :)
@Sam,
As far as the OnRequest() and the OnRequestEnd() event method, I provide them for other parts of the application. My OnRequest() event performs some security checks. My OnRequestEnd() performs some post page processing and logging type stuff. The problem is that these are not very web-service friendly. So, instead of changing the way I architect my application, I merely delete them if it's a web service call.
And, as far as the "omit" for the optional argument, I am sorry if I was not clear on this fact. Yes, it seems silly to include an argument that you are omitting. The problem is, though, that if you simply do not send the argument (leave out the CFInvokeArgument tag itself), the web service will crash. It says it cannot find the matching web service definition or something. You have to include the "omitted" argument so that ColdFusion can match the web service request to the WSDL definition. At least, that is what I think is going on. But I agree, it seems very silly.
Posted by Ben Nadel on Dec 12, 2006 at 1:35 PM
Ben,
Instead of destroying the onrequestend and onrequest, you might want to check out sean's post about extending the application.cfc. This might be a better approach. Let us know.
http://www.corfield.org/blog/index.cfm/do/blog.entry/entry/Extending_Your_Root_Applicationcfc
Posted by tony petruzzi on Dec 12, 2006 at 3:16 PM
Tony,
While Sean's solution is definitely a nice, elegant one, I cannot use it. I have no application mappings in my applications and therefore cannot extend applicaition.cfc files that are in different directories.
Thanks for the link though. As I said, this is my first web service so I am looking to learn all I can.
Posted by Ben Nadel on Dec 12, 2006 at 3:29 PM
Looks like you had a lot of fun with this! Since this article is catch all for a variety of webservice issues, here's a recent cftalk thread with suggestion for how to deal with webservers returning the WSDL in compressed format:
http://www.houseoffusion.com/groups/CF-Talk/thread.cfm/threadid:49353
Posted by Steven Erat on Dec 14, 2006 at 10:24 PM
Steven,
Thanks for the tip :) That's some good stuff.
Posted by Ben Nadel on Dec 15, 2006 at 7:02 AM
I am trying to do a simple web service in cold fusion and I am getting errors? Any ideas? This is the code?
<cfobject type="JAVA"
action="Create"
name="factory"
class="coldfusion.server.ServiceFactory">
<cfset RpcService = factory.XmlRpcService>
<cfset RpcService.refreshWebService("https://www.devcallnow.com/WebService/OneCallNow.asmx?wsdl")>
<cfinvoke webservice="https://www.devcallnow.com/WebService/OneCallNow.asmx?wsdl" method="Login" returnvariable="LoginToken">
<cfinvokeargument name="Service" value="1" omit="true" >
<cfinvokeargument name="GroupKey" value="2222" omit="true" >
<cfinvokeargument name="Pin" value="3333" omit="true" >
</cfinvoke>
Posted by Dave on Oct 17, 2007 at 2:11 PM
@Dave,
What errors are you getting?
-Ben
Posted by Ben Nadel on Oct 19, 2007 at 8:37 AM
I kept getting not found..But thanks to the help of Paul from cf-talk this is what was discovered. I'm still not sure what all of it means.
Just to follow up on this, it seems that the webservice David was trying to consume using wsdl doesn't fit into the wsdl way of doing things in that it attempts to set one of the invoking arguments and pass that back as well as a complex object.
To get around this, I've told him to invoke the service using cfhttp instead of cfinvoke and parse the resulting XML to get the data he needs...
<cfsavecontent variable="xmlrequest"><soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Login xmlns="https://www.onecallnow.com/WebService/">
<Service>1</Service>
<GroupKey>1111111</GroupKey>
<Pin>1111</Pin>
<LoginToken></LoginToken>
</Login>
</soap:Body>
</soap:Envelope>
</cfsavecontent>
<cfhttp method="post"
url="http://www.devcallnow.com/WebService/OneCallNow.asmx">
<cfhttpparam type="xml" value="#xmlrequest#">
</cfhttp>
<cfset SOAPXML = XMLParse(cfhttp.Filecontent.toString())>
<cfset response =
XMLParse(toString(SOAPXML["soap:Envelope"]["soap:Body"]["XmlChildren"][1]))>
<cfdump var="#response#">
<cfoutput>#response.LoginResponse.LoginToken#</cfoutput>
Posted by Dave on Oct 19, 2007 at 9:17 AM
Hi Ben & everyone reading this excellent article!
Once again you saved the day, Ben!
Just slightly modified the snipped to look like this:
ReFindNoCase("CMS/CFC",GetDirectoryFromPath(cgi.SCRIPT_NAME))...
(since I'm storing all my CFC's in that directory.)
Then off course, we have to keep in mind that onRequestStart won't be available to any cfc, but modifying the snippet further will probably handle this to make the method available (or not) for just some cfc's
Thank you!
Posted by Gov on Dec 28, 2007 at 6:04 AM
# if (ExpandPath( GetDirectoryFromPath( ARGUMENTS.TargetPage ) ) EQ APPLICATION.ServiceFactory.GetConfig().GetFullPaths().WebServices){
#
# // Delete the on request event handler.
# StructDelete( THIS, "OnRequest" );
#
# // Delete the on request end handler.
# StructDelete( THIS, "OnRequestEnd" );
#
# }
Hi Ben,
where would u put this code snippet ?in onrequeststart?
-Roman
Posted by Roman on Feb 15, 2008 at 1:55 PM
@Roman,
Put that in the OnRequest() method.
Posted by Ben Nadel on Feb 15, 2008 at 2:07 PM
i got kind lost.
u r using StructDelete( THIS, "OnRequest" ); in onrequest?
+ if u use onrequest u will have to call explicitly file cgi.SCRIPT_NAME.
Posted by Roman on Feb 15, 2008 at 2:12 PM
@Roman,
I am sorry, I made a mistake. Put it in the OnRequestStart() method. That way, it can delete the "OnRequest" key before it has been executed.
Posted by Ben Nadel on Feb 15, 2008 at 2:28 PM