- Open your ColdFusion9/stubs/ folder.
- Make a call to the webservice using createObject:
someWs = createObject("webservice", "http://addressToWsdlFile/?wsdl"); - Inspect the stubs folder and find the subfolder which is newly created, open it.
- There should be a whole subfolder structure which contains eventually some .class files. Copy the root (normaly this is something like a top level domain: com or net or such)
- Paste the folder structure to folder which is noted as a class path folder, or make a new folder and put this in the ColdFusion administrator (Server Settings > Java and JVM > ColdFusion Class Path) or in your server.cfc class path settings
- Restart ColdFusion (do not forget this!)
- Now you can create one of the objects needed in the webservice using:
someObject = createObject("java", "com.test.testapp.testObj);
(the dot notated path is corresponding with the copied folder structure to the class files) - The objects should have all the getters and setters. (Use writeDump() (or cfdump) to see all the properties of the classes). So use someObject.setSomeValue("xxx"); to populate.
- Call the method with the generated object:
result = someWs.someMethod(someObject); - Done!
Showing posts with label web service. Show all posts
Showing posts with label web service. Show all posts
Friday, November 2, 2012
Initiating web service objects using class files
If you want to call a webservice which uses more complex arguments then it's easier (or even best practice) to use class files in stead of recreating the objects by yourself. Here is how:
Monday, June 18, 2012
Using different axis types in webservices (like unsigned int)
This is just a reminder for myself:
I read a great article about using the different datatypes that are not supported in ColdFusion (and not through JavaCast()). The way to go is to use the underlying axis object:
duration = CreateObject("java", "org.apache.axis.types.UnsignedInt").init(60);
In the example the variable "duration" is of type "UnsignedInt" and has the value 60. You can also do this in XML (see my previous post), but this is more elegant in my opinion.
I read a great article about using the different datatypes that are not supported in ColdFusion (and not through JavaCast()). The way to go is to use the underlying axis object:
duration = CreateObject("java", "org.apache.axis.types.UnsignedInt").init(60);
In the example the variable "duration" is of type "UnsignedInt" and has the value 60. You can also do this in XML (see my previous post), but this is more elegant in my opinion.
Friday, June 15, 2012
Question: how to throw an error in a ColdFusion web service?
I've been trying to solve this promblem for some time now, but it seems there is no simple solution for this:
When you look at your webservice WSDL you have three operation parts: input, output and fault. The fault type of a generated ColdFusion WSDL is "CFCInvocationException". This is fine for most purposes, but not in my case. For example: I want to validate the input of the web service call to see if everything is correct before executing the method. Let's say you have a service to order something, then you want to authenticate the user, check the amount, and see if the product is available. If something is not correct then I want to return a fault.
In the service you can set the HTTP status code to 500 by using getPageContext().getResponse().setstatus(500);
But I still want to return a fault code and a fault message. If I generate this and return it in the body it will be returned in the SOAP output part, and not in de fault part. If I use "throw" to return the error, then it will be added to the "CFCInvocationException" and not my own fault type.
throw (message="some",type="urn:errorType",errorCode="999");
Does anyone have a solution to return a valid custom web service error (that is defined in the service WSDL) that will not be overrided by the ColdFusion fault handler?
UPDATE:
I haven't yet looked into CF11 so I don't know if this is fixed in that version, but I read a solution which isn't very elegant, but works. In short: you have to give the webservices their own application.cfc and use the onError function to catch the ColdFusion generated exception and replace the content by soap XML. A sample is described here: http://laksmatee.blogspot.nl/2014/02/half-baked-soap-support-in-coldfusion-9.html
When you look at your webservice WSDL you have three operation parts: input, output and fault. The fault type of a generated ColdFusion WSDL is "CFCInvocationException". This is fine for most purposes, but not in my case. For example: I want to validate the input of the web service call to see if everything is correct before executing the method. Let's say you have a service to order something, then you want to authenticate the user, check the amount, and see if the product is available. If something is not correct then I want to return a fault.
In the service you can set the HTTP status code to 500 by using getPageContext().getResponse().setstatus(500);
But I still want to return a fault code and a fault message. If I generate this and return it in the body it will be returned in the SOAP output part, and not in de fault part. If I use "throw" to return the error, then it will be added to the "CFCInvocationException" and not my own fault type.
throw (message="some",type="urn:errorType",errorCode="999");
Does anyone have a solution to return a valid custom web service error (that is defined in the service WSDL) that will not be overrided by the ColdFusion fault handler?
UPDATE:
I haven't yet looked into CF11 so I don't know if this is fixed in that version, but I read a solution which isn't very elegant, but works. In short: you have to give the webservices their own application.cfc and use the onError function to catch the ColdFusion generated exception and replace the content by soap XML. A sample is described here: http://laksmatee.blogspot.nl/2014/02/half-baked-soap-support-in-coldfusion-9.html
Returning a complex object with specfic data types in a ColdFusion web service
Here is a folow-up on my previous post about returing an integer in a webservice. Most webservices return more then one value. In this example I will return a structure with a firstname and lastname:
<cfcomponent output="false">
<cffunction name="giveMeAPerson" output="false" access="remote" returnType="person">
<cfset var stReturn = createObject("component","person") />
<cfset stReturn.firstName = "John" />
<cfset stReturn.lastName = "Doe" />
<cfset stReturn.age = "30" />
<cfreturn stReturn />
</cffunction>
</cfcomponent>
The returnType="person" points to person.cfc which is a value object with three properties: firstName, lastName and age with the according data type (string and numeric). (If the person.cfc is in a different folder than the web service cfc, then you must use the dot delimited path in the returnType field.)
If you want to use a specific data type for every struct key you will have to use XML. If you want the web service consumers to be able to fully use the different data types you have to specify the data types in the WSDL file. Save the generated WSDL to a file and point to this file in your component (see my previous post for the details on this). Next, edit the WSDL where the returnType is specified. Here I will change the data type "double" of the property "age" to "int". Next I will set the returntype of the function "giveMeAPerson" to "any".
Now it is time to construct the XML that has to be returned:
var XmlResponse = XmlNew();
XmlResponse.xmlRoot = XmlElemNew(XmlResponse,"urn:pointerToURN","giveMeAPersonResponse");
XmlResponse.xmlRoot.XmlAttributes['xsi:type'] = "giveMeAPersonResponse";
XmlResponse.xmlRoot.XmlChildren[1] = XmlElemNew(XmlResponse,"urn:customUrn","firstName");
XmlResponse.xmlRoot.XmlChildren[2] = XmlElemNew(XmlResponse,"lastName");
XmlResponse.xmlRoot.XmlChildren[3] = XmlElemNew(XmlResponse,"age");
XmlResponse.xmlRoot.firstName.XmlAttributes['xsi:type'] = "firstname";
XmlResponse.xmlRoot.lastname.XmlAttributes['xsi:type'] = "xsi:string";
XmlResponse.xmlRoot.age.XmlAttributes['xsi:type'] = "xsi:int";
XmlResponse.xmlRoot. firstName .XmlText = "John";
XmlResponse.xmlRoot.lastname.XmlText = "Doe";
XmlResponse.xmlRoot.age = JavaCast("int",30);
Then return the XmlResponse.
Note: In this example I used a custom data type for firstName. I did this to show you what to change to implement this. The custom data type must be defined in the WSDL under the given URN.
The conversion to int and string is not necessary, if you describe the type in the WSDL then ColdFusion will do the conversion.
<cfcomponent output="false">
<cffunction name="giveMeAPerson" output="false" access="remote" returnType="person">
<cfset var stReturn = createObject("component","person") />
<cfset stReturn.firstName = "John" />
<cfset stReturn.lastName = "Doe" />
<cfset stReturn.age = "30" />
<cfreturn stReturn />
</cffunction>
</cfcomponent>
The returnType="person" points to person.cfc which is a value object with three properties: firstName, lastName and age with the according data type (string and numeric). (If the person.cfc is in a different folder than the web service cfc, then you must use the dot delimited path in the returnType field.)
If you want to use a specific data type for every struct key you will have to use XML. If you want the web service consumers to be able to fully use the different data types you have to specify the data types in the WSDL file. Save the generated WSDL to a file and point to this file in your component (see my previous post for the details on this). Next, edit the WSDL where the returnType is specified. Here I will change the data type "double" of the property "age" to "int". Next I will set the returntype of the function "giveMeAPerson" to "any".
Now it is time to construct the XML that has to be returned:
var XmlResponse = XmlNew();
XmlResponse.xmlRoot = XmlElemNew(XmlResponse,"urn:pointerToURN","giveMeAPersonResponse");
XmlResponse.xmlRoot.XmlAttributes['xsi:type'] = "giveMeAPersonResponse";
XmlResponse.xmlRoot.XmlChildren[1] = XmlElemNew(XmlResponse,"urn:customUrn","firstName");
XmlResponse.xmlRoot.XmlChildren[2] = XmlElemNew(XmlResponse,"lastName");
XmlResponse.xmlRoot.XmlChildren[3] = XmlElemNew(XmlResponse,"age");
XmlResponse.xmlRoot.firstName.XmlAttributes['xsi:type'] = "firstname";
XmlResponse.xmlRoot.lastname.XmlAttributes['xsi:type'] = "xsi:string";
XmlResponse.xmlRoot.age.XmlAttributes['xsi:type'] = "xsi:int";
XmlResponse.xmlRoot. firstName .XmlText = "John";
XmlResponse.xmlRoot.lastname.XmlText = "Doe";
XmlResponse.xmlRoot.age = JavaCast("int",30);
Then return the XmlResponse.
Note: In this example I used a custom data type for firstName. I did this to show you what to change to implement this. The custom data type must be defined in the WSDL under the given URN.
The conversion to int and string is not necessary, if you describe the type in the WSDL then ColdFusion will do the conversion.
Returning an integer (or other data types) with a ColdFusion web service
ColdFusion is a webserver language which is easy to learn and makes it easy to quickly achieve complex operations. This is all very nice, but can get in the way if you want to do something in a specific way. This problem arises when you have to deliver a webservice that returns a specific data type.
Here is a webservice which returns a number:
<cfcomponent output="false">
<cffunction name="giveMeANumber" output="false" access="remote" returnType="numeric">
<cfreturn RandRange(0,10) />
</cffunction>
</cfcomponent>
If you call the web service in a different language than ColdFusion, or in a specific web service tool (like SoapUI) you will see that the returned value will be something like "5.0". This will be no problem for ColdFusion, but this may come as a problem for PHP or .Net. That's why I want the service to return "5" in stead of "5.0".
If you look at the webservice WSDL you will see that the webservice returns a double:
Save the whole WSDL content to a text file and be sure that it is XML compliant (ie. no leading spaces before the XML tag). Now you can change the data type of the return value:
Next, change the component tag to use the external wsdlfile:
<cfcomponent wsdlfile="myExternalwsdlfile.wsdl" style="document" output="false">
If you point to an external WSDL file you have to set style="document".
Next, set the returntype of the function to "any", this is not required for int (double or int are both numeric), but it is best practice because it will be required for other data type.
<cffunction name="giveMeANumber" output="false" access="remote" returnType="any">
Next, use Javacast around the return value to set the right data type, in this case: <cfreturn JavaCast("int",RandRange(0,10)) />
To see which data type in ColdFusion will be returned with which data type (and if it may cause problems, see the ColdFusion reference "Data conversions between ColdFusion and WSDL data types".
Here is a webservice which returns a number:
<cfcomponent output="false">
<cffunction name="giveMeANumber" output="false" access="remote" returnType="numeric">
<cfreturn RandRange(0,10) />
</cffunction>
</cfcomponent>
If you call the web service in a different language than ColdFusion, or in a specific web service tool (like SoapUI) you will see that the returned value will be something like "5.0". This will be no problem for ColdFusion, but this may come as a problem for PHP or .Net. That's why I want the service to return "5" in stead of "5.0".
If you look at the webservice WSDL you will see that the webservice returns a double:
<wsdl:message name="giveMeANumberResponse">
<wsdl:part name="giveMeANumberReturn" type="xsd:double"/>
</wsdl:message>
Save the whole WSDL content to a text file and be sure that it is XML compliant (ie. no leading spaces before the XML tag). Now you can change the data type of the return value:
<wsdl:message name="giveMeANumberResponse">
<wsdl:part name="giveMeANumberReturn" type="xsd:int"/>
</wsdl:message>
Next, change the component tag to use the external wsdlfile:
<cfcomponent wsdlfile="myExternalwsdlfile.wsdl" style="document" output="false">
If you point to an external WSDL file you have to set style="document".
Next, set the returntype of the function to "any", this is not required for int (double or int are both numeric), but it is best practice because it will be required for other data type.
<cffunction name="giveMeANumber" output="false" access="remote" returnType="any">
Next, use Javacast around the return value to set the right data type, in this case: <cfreturn JavaCast("int",RandRange(0,10)) />
To see which data type in ColdFusion will be returned with which data type (and if it may cause problems, see the ColdFusion reference "Data conversions between ColdFusion and WSDL data types".
Wednesday, May 30, 2012
Coldfusion, SOAP headers and custom namespaces
Some webservices require you to add specific headers to your request (i.e. for authentication). In the CFML reference you can find a function AddSOAPRequestHeader which can do this for you. The problem is that the documentation is not always very clear about HOW you should use this.
My problem was that the header should use some custom namespaces and some default namespaces, here is how you should do this.
My SOAP header should look like this:
<soapenv:header>
<ws:authenticationheader>
<username>[username here]</username>
<password>[password here]</password>
</ws:authenticationheader>
</soapenv:header>
XmlHeader = XmlNew();
XmlHeader.xmlRoot = XmlElemNew(XmlHeader,"http://www.url-to-ns","ws:AuthenticationHeader");
XmlHeader.xmlRoot.XmlChildren[1] = XmlElemNew(XmlHeader,"","username");
XmlHeader.xmlRoot.XmlChildren[2] = XmlElemNew(XmlHeader,"","password");
XmlHeader.xmlRoot.username.XmlText = "[username here];
XmlHeader.xmlRoot. username .XmlAttributes['xsi:type'] = "xsd:string";
XmlHeader.xmlRoot.Password.XmlText = "[password here]";
XmlHeader.xmlRoot.Password.XmlAttributes['xsi:type'] = "xsd:string";
AddSOAPRequestHeader(varnameOfWS," http://www.url-to-ns ","ws:AuthenticationHeader",XmlHeader,false);
result = varnameOfWS.doSomething();
From the CF documentation:
Here is the soap header using soap_req = getSOAPRequest(varnameOfWS);
<soapenv:Header>
The namespace definition is valid for the web service which was my goal. I would rather see the definition in the soapenv:Envelope so if anyone knows how to do this please let me know! :)
My problem was that the header should use some custom namespaces and some default namespaces, here is how you should do this.
My SOAP header should look like this:
<soapenv:header>
<ws:authenticationheader>
<username>[username here]</username>
<password>[password here]</password>
</ws:authenticationheader>
</soapenv:header>
There is a namespace "ws" defined in the soapenv:Envelope pointing to a custom namespace. It is still (as far as I could research) impossible to define this in the soapenv. Second best is to define this in the header itself.
XmlHeader = XmlNew();
XmlHeader.xmlRoot = XmlElemNew(XmlHeader,"http://www.url-to-ns","ws:AuthenticationHeader");
XmlHeader.xmlRoot.XmlChildren[1] = XmlElemNew(XmlHeader,"","username");
XmlHeader.xmlRoot.XmlChildren[2] = XmlElemNew(XmlHeader,"","password");
XmlHeader.xmlRoot.username.XmlText = "[username here];
XmlHeader.xmlRoot. username .XmlAttributes['xsi:type'] = "xsd:string";
XmlHeader.xmlRoot.Password.XmlText = "[password here]";
XmlHeader.xmlRoot.Password.XmlAttributes['xsi:type'] = "xsd:string";
AddSOAPRequestHeader(varnameOfWS," http://www.url-to-ns ","ws:AuthenticationHeader",XmlHeader,false);
result = varnameOfWS.doSomething();
From the CF documentation:
XmlElemNew(xmlObj[, namespace], childName)The trick is to add the namespace alias to childName parameter using the format "[alias]:childname", the namespace alias then is automatically added.
Here is the soap header using soap_req = getSOAPRequest(varnameOfWS);
<soapenv:Header>
<ws:AuthenticationHeader
soapenv:actor=""
soapenv:mustUnderstand="0"
xmlns:ws="http://www.url-to-ns">
<username xmlns="" xsi:type="xsd:string">[username here]</username>
<password xmlns="" xsi:type="xsd:string">[password here]</password>
</ws:AuthenticationHeader>
</soapenv:Header>
The namespace definition is valid for the web service which was my goal. I would rather see the definition in the soapenv:Envelope so if anyone knows how to do this please let me know! :)
Friday, June 11, 2010
Connecting to sites which use SSL
If you want to connect to a site which uses https you might get a connection error like "peer not authenticated". To solve this problem you need to import the SSL certificate to your ColdFusion Java keystore. There are a few steps to do this depending on your situation.
If you have a keystore which contains the certificate (which was my case) then you need to export this to a certificate file using the keytool application.
Keytool can be found in your ColdFusion root \runtime\jre\bin\keytool.exe
keytool.exe -export -alias aliasOfKey -v -keystore keystoreFile -file myCertificate.crt
If keytool asks for a password and you don't have ony you can try the default password "changeit".
This exports the certificate with the given alias from the given keystore file to a crt file.
If you don't know the alias of the certificate then you can list the contents of the keystore file using:
keytool.exe -list -v -keystore keystoreFile
You can also open the site in a webbrowser and double click on the key icon. This will view the SSL certificate. Click the details tab. In this window you can save the certificate. Choose BASE64 for encoding and save it to a known location.
Next you need to copy the crt file to the security folder in ColdFusion. This is located in: ColdFusion root runtime\jre\lib\security
In this folder is also the cacerts file which is the keystore for ColdFusion.
Now you need to add the crt file to the keystore:
keytool.exe -import -keystore cacerts -alias aliasOfKey -file myCertificate.crt
And you are done!
If you have a keystore which contains the certificate (which was my case) then you need to export this to a certificate file using the keytool application.
Keytool can be found in your ColdFusion root \runtime\jre\bin\keytool.exe
keytool.exe -export -alias aliasOfKey -v -keystore keystoreFile -file myCertificate.crt
If keytool asks for a password and you don't have ony you can try the default password "changeit".
This exports the certificate with the given alias from the given keystore file to a crt file.
If you don't know the alias of the certificate then you can list the contents of the keystore file using:
keytool.exe -list -v -keystore keystoreFile
You can also open the site in a webbrowser and double click on the key icon. This will view the SSL certificate. Click the details tab. In this window you can save the certificate. Choose BASE64 for encoding and save it to a known location.
Next you need to copy the crt file to the security folder in ColdFusion. This is located in: ColdFusion root runtime\jre\lib\security
In this folder is also the cacerts file which is the keystore for ColdFusion.
Now you need to add the crt file to the keystore:
keytool.exe -import -keystore cacerts -alias aliasOfKey -file myCertificate.crt
And you are done!
Monday, May 17, 2010
Connecting to webservices which require login using cookies
U stumbled upon a problem connecting a ColdFusion site to an external application using a webservice. The normal howtos are insufficient because none of them describe connection to a webservice that requires a login AND use multiple requests. Here is a description how it is done for a webservice which has a separate method for the login and the other functions:
Step one: initiating the webservice
Nothing special here:
ws_securityService = createobject('webservice','http://www.somesite.com/someSecurityservice.asmx?WSDL');
ws_someOtherService = createobject('webservice','http://www.somesite.com/someOtherService.asmx?WSDL');
Step two: maintaining the connection
If you visit a website in a browser it will recognize if you did a previous request. If you login on a page it will remember you so you can load your profile page and so on. By default every request by ColdFusion is a new one, nothing from previous request are remembered, that is why you need to tell ColdFusion to do otherwise:
ws_securityService.setMaintainSession(true);
ws_someOtherService.setMaintainSession(true);
Step three: logging in
In this case the authentication is in a few steps: if you initialize the login the method returns a string which you need to to do a login, this is simplified because there is no need to go in depth here.
challenge = ws_securityService.InitializeLogin(serverUserName); //function returns a key required by the login method
loginsucces = ws_securityService.Login(response); //the real login method
Step four: reading the cookies
After a successful login the webservice will set a cookie with a key to see if the user is authenticated. So this is required by all the other webservices.
server_cookies = ws_securityService._getCall().getMessageContext().getProperty("Cookie"); //this returns an array with cookies
Step five: decoding the cookies
Because I use the underlying Java to add the cookies to a request I need to convert this from the typeless ColdFusion to Java.
Because there will be a conversion error you have to explicitly cast the cookie array by the following code:
String = CreateObject("java", "java.lang.String"); //Get a Java cast string
Array = CreateObject("java", "java.lang.reflect.Array"); //Get a Java cast array
Cookies = Array.newInstance(String.getClass(), arrayLen(server_cookies)); //create the cookie array
Now you need to fill this newly created array with the ColdFusion cookie array:
<cfloop from="1" index="i" to="#arrayLen(server_cookies)#">
<cfset #urldecode(server_cookies[i])#")="" array.set(cookies,="" i-1,=""><!--- ColdFusion arrays start at 1, Java (and all the other programming languages) start at 0 --->
</cfloop>
Step six: adding the cookies to all your other webservices
ws_someOtherService._setProperty("Cookie", cookies); //this adds the cookie to your webservice call
Before, you already set the MaintainSession to true so with every request to this webservice the cookies will be added.
Step seven: call the other webservices
Actually: you are done, now you can call the other webservice which require authentication.
someResult = ws_someOtherService.doSomething(someArgument);
If you want to call another authenticated webservice you just have to do the folowing things:
I hope this helps you and save you some frustrating hours.
Step one: initiating the webservice
Nothing special here:
ws_securityService = createobject('webservice','http://www.somesite.com/someSecurityservice.asmx?WSDL');
ws_someOtherService = createobject('webservice','http://www.somesite.com/someOtherService.asmx?WSDL');
Step two: maintaining the connection
If you visit a website in a browser it will recognize if you did a previous request. If you login on a page it will remember you so you can load your profile page and so on. By default every request by ColdFusion is a new one, nothing from previous request are remembered, that is why you need to tell ColdFusion to do otherwise:
ws_securityService.setMaintainSession(true);
ws_someOtherService.setMaintainSession(true);
Step three: logging in
In this case the authentication is in a few steps: if you initialize the login the method returns a string which you need to to do a login, this is simplified because there is no need to go in depth here.
challenge = ws_securityService.InitializeLogin(serverUserName); //function returns a key required by the login method
loginsucces = ws_securityService.Login(response); //the real login method
Step four: reading the cookies
After a successful login the webservice will set a cookie with a key to see if the user is authenticated. So this is required by all the other webservices.
server_cookies = ws_securityService._getCall().getMessageContext().getProperty("Cookie"); //this returns an array with cookies
Step five: decoding the cookies
Because I use the underlying Java to add the cookies to a request I need to convert this from the typeless ColdFusion to Java.
Because there will be a conversion error you have to explicitly cast the cookie array by the following code:
String = CreateObject("java", "java.lang.String"); //Get a Java cast string
Array = CreateObject("java", "java.lang.reflect.Array"); //Get a Java cast array
Cookies = Array.newInstance(String.getClass(), arrayLen(server_cookies)); //create the cookie array
Now you need to fill this newly created array with the ColdFusion cookie array:
<cfloop from="1" index="i" to="#arrayLen(server_cookies)#">
<cfset #urldecode(server_cookies[i])#")="" array.set(cookies,="" i-1,=""><!--- ColdFusion arrays start at 1, Java (and all the other programming languages) start at 0 --->
</cfloop>
Step six: adding the cookies to all your other webservices
ws_someOtherService._setProperty("Cookie", cookies); //this adds the cookie to your webservice call
Before, you already set the MaintainSession to true so with every request to this webservice the cookies will be added.
Step seven: call the other webservices
Actually: you are done, now you can call the other webservice which require authentication.
someResult = ws_someOtherService.doSomething(someArgument);
If you want to call another authenticated webservice you just have to do the folowing things:
- create the webservice to a variable (step one)
- set maintain connection (step two)
- add the cookie to the webservice (step six)
I hope this helps you and save you some frustrating hours.
Subscribe to:
Posts (Atom)