ColdFusion 10 lets you create and publish REST (Representational State Transfer) services that can be consumed by clients over HTTP/HTTPS request.

What is REST

The following URL takes you to the Java Tutorial that provides conceptual information on REST:
http://download.oracle.com/javaee/6/tutorial/doc/gijqy.html

REST and ColdFusion

You can create REST services by defining certain attributes in the tags cfcomponent, cffuntion, and cfargument and publish as REST resources.

  • *Follows HTTP request-response model:* Beyond having HTTP as a medium, the service lets you follow all HTTP norms. The components published as REST services can be consumed over HTTP/HTTPS request. The REST services are identified with URI (Uniform Resource Identifier) and can be accessed from a web page as well as by specifying the URI in the browser's address bar.
  • Supports all HTTP methods : The REST enabled CFCs support the following HTTP methods: GET, POST, PUT, DELETE, HEAD, and OPTIONS.
  • Implicit handling of serialization/deserialization: ColdFusion natively supports JSON and XML serialization/deserialization. So client applications can consume REST services by issuing HTTP/HTTPS request. The response can either be serialized to XML or JSON format.
  • Publish web service as both REST service and WSDL service: You can create and publish the same ColdFusion component as a REST service and WSDL service.

Creating the REST web service

  • You can create and publish a ColdFusion component or any functions in a component as REST resource.
  • To create a CFC as REST web service, specify either of the following in the tag cfcomponent: restPath or rest.
  • In cffunction, set the attribute access to remote for the functions that you have to expose as REST resource.
Example
<cfcomponent rest="true" restpath="restService">
<cffunction name="sayHello" access="remote" returntype="String" httpmethod="GET">
<cfset rest = "Hello World">
<cfreturn rest>
</cffunction>
</cfcomponent>

Registering an application with REST service

 

After you create the CFC you want to REST-enable, specify the folder for registering as web service either using the function restInitAplication or in the ColdFusion Administrator.

Note: Nested REST applications cannot be registered.

Using ColdFusion Administrator (Data & Services > REST Services)

When you specify a folder, all CFCs in that folder or subfolders for which you have specified rest or restPath are registered.

  1. Browse and select the application path or root folder where ColdFusion would search for CFCs.
  2. (Optional) In the Service Mapping section, specify virtual mapping in place of application name.If the folder has an Application.cfc and an application name, then the service is identified with the application name. You can override this by specifying the service mapping. In this case, the service is identified with the service mapping that is provided. If there is no Applicaiton.cfc in the folder, then it is mandatory to specify the Service mapping.If you choose to specify REST folder outside of ColdFusion root, then add one of the following mappings to register the folder:Consider that you have added a mapping for c:\restfolder as \map

<cfset restinit("c:\restfolder","myapp")>
<cfset restinit("\map","myapp")>
  1. (Optional) Specify an application as default REST service. Only one application can be set as default for a server instance. You can change the default application at any time. Check Set the default application and then click Add Service. To remove the service as default, uncheck it.
  2. After you specify the details, click Add Service to register.The Active ColdFusion REST Services section specifies the details of all registered web services.
    After you register, all CFCs are published as RESTful services. On subsequent startups, the registered services automatically get published.

    Note: Refresh the application whenever there is a change in REST-related component in the application.

Accessing the web service

  • Use the tag cfhttpto access the web service as shown in the following example:

<cfhttp
url="http://localhost:8500/rest/RestTest/restService"
method="get"
port="8500"
result="res">
</cfhttp>
Interpreting the URL

The URL provided in the example can be interpreted as follows:

URL component

Description

http://localhost:8500

Base URL which includes the IP address and port of the ColdFusion server.If you deploy ColdFusion as a JEE application, the URL will contain a context root, for example,

http://localhost:8500*/cfusion*

.

rest

Implies that the request sent is a REST request.This default value can be renamed by revising the context path in web.xml available at cfusion/wwroot/WEB-INF and update the same mapping in uriworkermap.properties file found at config\wsconfig\1.

restTest

Application name or service mapping that you have used while registering the service in ColdFusion Administrator. If you do not specify a service mapping in the ColdFusion Administrator, then the application name is taken from Application.cfc.

restService

Rest path you defined in the service. That is, the value of the attribute restPath in the tag cfcomponent.

Providing accept header

The accept header can be provided in the REST URL. The following examples illustrate this:

HTTP Content-type negotiation

The content-type that is returned by a RESTful web service depends on the Accept HTTP header.
Client can set an Accept request header in the following order:

  • Comma-separated list of preferred content types.
  • (Followed by a semi-colon (;) A floating point number in the range 0 through 1 in the format q=0-1. The default value is 1.0 is the least value which indicates that the content type is not acceptable whereas 1 is the maximum value with the highest priority.
    If two types are provided the same priority, then the sequential priority is considered.
Examples

In the following example, client can use both XML and JSON format as no value is specified to indicate a priority in the Accept header:

GET http://adobe.com/stuff
Accept: application/xml, application/json

The request in the following example specifies a priority for returning the content-type:

GET http://adobe.com/stuff
Accept: text/*;q=0.9, */;q=0.1, audio/mpeg, application/xml;q=0.5

The order of precedence for content type is as follows:

  1. audio/mpeg (as no priority is specified)
  2. text/* (0.9 is the highest value)
  3. application/xml
  4. */
    In the following example, though you have specified same priority for both text/* and audio/mpeg, text/* gets precedence because of the sequence.

    GET http://adobe.com/stuff
    Accept: text/*;q=0.9, */;q=0.1, audio/mpeg=0.9, application/xml;q=0.5

     

Specifying subresources

Functions in a REST service can either be a resource function, subresource function, or subresource locator.

Resource function

The functions for which you have not specified the RestPath at function level but specified {{httpMethod.}}In this case, the resource function is invoked when URL of the CFC is accessed.

Subresource function

The functions for which you have specified both resptPath and httpMethod.
Subresource functions are used to create subresources for the resource created by CFC. If the subresource has httpMethod attribute in cffunction, the function can directly handle HTTP requests as shown in the following example.

Example

Employee.cfc

<cfcomponent rest="true" restPath="/hello">
<cffunction name="sayHello" access="remote" returnType="String" httpMethod="GET" restPath="name">
---------
</cffunction>
</cfcomponent>

In this example, httpMethod and restPath are defined. The baseurl/hello/name is a subresource to the URL baseurl/hello.

Subresource locator

If you have not specified the httpMethod attribute, but have specified restPath, you can dynamically resolve the component that handles the request. Any returned object is treated as a resource class instance and used to either handle the request or to further resolve the object that handles the request.

Example

In this example, StudentService.cfc and Student.cfc are the two REST resources. In StudentService.cfc the function getStudent returns a component. In the function, the object of Student is created and the values name and age are set. When the object is specified in the return type, the function defined in the object with the requested httpmethod is invoked.StudentService.cfc can be invoked directly as it has a restPath specified. Student.cfc can only be invoked through StudentService.cfc because the function getStudent in StudentService.cfc is acting as the subresource locator and returns Student.cfc.StudentService.cfc:

<cfcomponent rest="true" restpath="/studentService">

<cffunction name="addStudent" access="remote" returntype="void" httpmethod="POST">
<cfargument name="name" type="string" required="yes" restargsource="Form"/>
<cfargument name="age" type="numeric" required="yes" restargsource="Form"/>
<!--- Adding the student to data base. --->
</cffunction>
<cffunction name="getStudent" access="remote" returntype="student" restpath="{name}-{age}">
<cfargument name="name" type="string" required="yes" restargsource="Path"/>
<cfargument name="age" type="string" required="yes" restargsource="Path"/>
<!--- Create a student object and return the object. This object will handle the request now. --->
<cfset var myobj = createObject("component", "student")>
<cfset myobj.name = name>
<cfset myobj.age = age>
<cfreturn myobj>
</cffunction>
</cfcomponent>

Student.cfc

<cfcomponent>
<cfproperty name="name" type="string"/>
<cfproperty name="age" type="numeric"/>
<!--- Getting the student detail. --->
<cffunction name="getStudent" access="remote" returntype="String" httpmethod="GET" produces="text/xml">
<!--- Retrieve the Student from the DB. --->
<!--- get student from db where name and age matches --->
<cfset st.name = "Thomas">
<cfset st.age = "25">
<cfset st.surname = "Paul">
<cfset str = "<student><name>" & st.name & "</name><age>" & st.age & "</age><surname>" & st.surname & "</surname></student>">
<cfreturn str>
</cffunction>
<!--- Updating the student detail. --->
<cffunction name="updateStudent" access="remote" returntype="void" httpmethod="PUT">
<!--- Retrieve the Student from the DB. --->
<!--- Update the student in DB. --->
<cfset st.name = name>
<cfset st.age = age>
</cffunction>
<!--- Deleting the student. --->
<cffunction name="deleteStudent" access="remote" returntype="String" httpmethod="DELETE">
<!--- Delete the Student from the DB. --->
<!---<cfset st = deleteStudentFromDB(name)>--->
<cfreturn "Student deleted">
</cffunction>
</cfcomponent>

Student.cfm

<!--- adding the student --->
<cfhttp url="http://localhost:8500/rest/RestTest/studentService" method="post" result="res">
<cfhttpparam type="formfield" name="name" value="Thomas" >
<cfhttpparam type="formfield" name="age" value="25" >
</cfhttp>
<cfoutput>Student Added</cfoutput>
</br>
</br>
<!--- fetching the details --->
<cfhttp url="http://localhost:8500/rest/RestTest/studentService/Thomas-25" method="get" result="res">
</cfhttp>
<cfoutput>#xmlformat(res.filecontent)#</cfoutput>
</br>
</br>
<!--- updating the student details --->
<cfhttp url="http://localhost:8500/rest/RestTest/studentService/Thomas-25" method="put" result="res">
</cfhttp>
<cfoutput>Updated the student</cfoutput>
</br>
</br>
<!--- deleting the student --->
<cfhttp url="http://localhost:8500/rest/RestTest/studentService/Thomas-25" method="delete" result="res">
</cfhttp>
<cfoutput>Student Deleted</cfoutput>

HTTP Responses

By default, ColdFusion provides default HTTP success and failure responses to the client as follows:

Success responses

Default Response

Description

200 OK

Sent if the response has body.

204 No Content

Sent if the response does not have body.

Error responses

Default Response

Description

404 Not found

Request URL is not valid.

406 Not Acceptable

No function in the REST service can produce the MIME type requested by the client.

415 Unsupported Media Type Error

A resource is unable to consume the MIME type of client request.

405 Method not allowed

If the client invokes an HTTP method on a valid URI to which the request HTTP method is not bound.This error does not appear if the client requests for HTTP HEAD and OPTIONS methods.If a resource method that can service HEAD requests for that particular URI is not available, but there exists a method that can handle GET, it is invoked and returns the response from that method without the request body. If there is no existing method that can handle OPTIONS, a meaningful automatically generated response is sent along with the Allow header set.

Custom responses

In addition to the default responses available with ColdFusion, you can set custom responses.
For example, assume that you have to provide a success response 201 Created. Such a default response does not exist. You can only have 200 OK or 204 No Content to send. 
In such scenarios, you can create custom responses in either of the following ways:

Send custom success responses using restSetResponse
  • When you define the CFC as REST service, ensure that the returnType is set to void in the cffunction(the function for which you want to send the custom response).For example,

<cffunction name="create" httpMethod="POST" produces="application/xml" returnType="void">
<cffunction>
  • Create a struct for the custom response that you want to send in the cffunctionas shown in the following example:

<cfset response=structNew()>
<cfset response.status=201>
<cfset response.content="<customer id="&id&"><name>"&name&"</name></customer>"> <cfset response.headers=structNew()>
<cfset response.headers.location="http://localhost:8500/rest/CustomerService/customers/123">

 

In this example, you have set 201 as the status of the response, content that you want to send. For example, customer details and the location where you have created the resource for the POSTrequest.

Note: If you do not specify the status in the custom response, 500 Internal server error is sent as the response status.

 

  • Use the function restSetResponseas follows:

    restSetResponse( response );

Send custom error response using cfthrow

Assume that you want to send a custom error response. For example, consider the following:method.service.cfc

<cffunction name="getCustomer" httpMethod="GET" produces="application/xml" restPath="{id}" return="string">
<cfargument name="id" type="numeric" argtype="PathParam">
<!--- Getting the customer. ---><cffunction>

In this case, you have a customer database and you are doing a GET HTTP request on /customers/123.But you find that the customer with the specified ID 123 is not available. So you want to send 404 Resource Not Found response back to the client, which is not possible by default.In this case, you can use cfthrow to send custom error response as follows:

<cfthrow type="RestError" errorcode="404">

Modifications to Application.cfc

The following enhancements have been made to Application.cfc for REST support:

Variable

Description

this.restsettings.cfclocation

To publish the CFCs only in a particular location, provide comma-separated list of directories where the REST CFCs are located. The directory paths can be absolute or relative.If not set, all the CFCs from the application root are published.

this.restsettings.skipCFCWithError

When an error occurs, continue publishing, ignoring the CFC that has caused the exception.If true, the CFC with error is ignored and the rest of the CFCs are published. By default it is false.If set to false, in case of an error, the application itself is not published. But other registered application are published.If an error occurs during application startup, the error is printed in console.Each application has separate log files for logging the issues.

Extending REST Web services

The following conditions apply when you extend the RESTful CFCs:

  • You can define the REST attributes for functions in the base CFC. So all the CFCs that extend from the base CFC inherits the REST attributes.In the following example, CustomerResource.cfc extends BaseCustomerResource.cfc:BaseCustomerResource.cfc

<cfcomponent>
<cffunction name="SayPlainHello" access="remote" produces="text/plain" returntype="string" httpmethod="POST">
<cfreturn "BaseCustomerResource Plain">
</cffunction>
<cffunction name="SayXMLHello" access="remote" produces="text/xml,application/xml" returntype="string" httpmethod="POST">
<cfreturn "BaseCustomerResource XML">
</cffunction>
</cfcomponent>

BaseCustomerResource.cfc has all REST attributes applied to functions within the CFC. After you define BaseCustomerResource.cfc, you can define CustomerService.cfc that extends BaseCustomerResource.cfc as follows:Customerservice.cfc

<cfcomponent rest="true" restpath="/customerservice" extends="BaseCustomerResource">

<cffunction name="SayPlainHello" access="remote" returntype="string">

<!--- Implement the method. --->
<cfreturn "CustomerResource Plain">
</cffunction>

<cffunction name="SayXMLHello" access="remote" returntype="string">

<!--- Implement the method. --->
<cfreturn "CustomerResource XML">
</cffunction>

</cfcomponent>

BaseCustomerResource.cfm

<cfhttp url="http://localhost:8500/rest/RestTest/customerservice" method="post" result="res">
<cfhttpparam type="header" name="accept" value="text/plain" >
</cfhttp>
<cfoutput>#res.filecontent#</cfoutput>
</br>
</br>
<cfhttp url="http://localhost:8500/rest/RestTest/customerservice" method="post" result="res">
<cfhttpparam type="header" name="accept" value="application/xml" >
</cfhttp>
<cfoutput>#res.filecontent#</cfoutput>
</br>
</br>

Except the rest and restPath, no other REST attributes are required within CustomerService.cfc.

  • When you inherit a RESTful CFC, you can choose to override the attributes used in the CFC that you extend. For example, the following CFC extends the BaseCustomerResource.CFC, but the function SayPlainHellooverrides the function in Base CFC.

<cfcomponent rest="true" restPath="/customerservice" extends="BaseCustomerResource">
<cffunction name="SayPlainHello" access="remote" produces="text/plain" returntype="string" httpmethod="PUT">
<cfargument name="username" type="string" argtype="pathparam">
<!--- Implement the method. --->
</cffunction>


<cffunction name="SayXMLHello" access="remote" returntype="string">
<cfargument name="username" type="string">
<!--- Implement the method. --->
</cffuntion>
</cfcomponent>

 

 

 

Note: Even if you override only one attribute in a function, you have to respecify all REST attributes

  • You have to specify the REST attributes (rest/restPath) in the derived CFC to use it as a REST service. Just that it is defined in the base CFC does not work.

REST services and data interchange formats

REST services in ColdFusion can be represented in XML or JSON formats for transmitting over the web. ColdFusion takes care of the serialization/deserialization of the ColdFusion data types used in the services to XML or JSON implicitly.
A function that is REST-enabled can take the following ColdFusion data types as arguments: query, struct, CFC type, array, array of CFCs, XML, string, numeric, boolean, date, and binary (for XML). 
ColdFusion serializes data to pre-defined formats of XML and JSON. Similarly, ColdFusion deserializes the body only if the body is in the format defined by ColdFusion.

XML serialization for REST services
Serialization specifications
  • The Accept header of the request has to be either text/xml or application/xml.
  • There has to be a function in the service that can produce the required MIME types.
  • The function has to return any of the ColdFusion supported data types.
  • Cyclic arrays are not supported. You might see the serialized string published, but not with the expected output as explained in the following example:

<cfset this.arr1 = arrayNew(1)>
<cfset this.arr1[1] = "1">
<cfset this.arr1[2] = this.arr1>
<cfset this.arr1[3] = "3">

When an array is assigned to a variable, in this case, <cfset this.arr12 = this.arr1>, you assign arr1 as the item in the second index of arr1. ColdFusion implicitly creates a new array and copies the data from arr1 to the newly created array. The newly created array is assigned to the second index of arr1. Therefore, both the instances are different, and therefore cyclic dependency is impacted.When you serialize, you get the following output:

<array id="1" size="3">
<item index="1" type="string">1</item>
<item index="2" type="array">
<array id="2" size="1">
<item index="1" type="string">1</item>
</array>
</item>
<item index="3" type="string">3</item>
</array>

As you can observe, the inner array gets truncated after the first element.

Deserialization specifications
  • The content of the request has to be in a pre-defined format specified by ColdFusion (see details in the section Format definition).
  • The content type of the request has to be either text/xml or application/xml.
  • There has to be a function in the service that can consume the MIME type of the request.
  • cfargument cannot have the attributes restArgSource and restArgName specified. That is, you can only send data in the body of the request.
  • cfargument type should be ColdFusion supported data type
  • There can only be one argument that does not specify the restArgSource attribute. The whole body of the request is deserialized to the argument type.
  • Cyclic arrays are not supported.
Format definition
Query
  • The root element of the XML has to be query.
  • Use the attribute ID to handle circular dependency.
  • The following are the valid attribute values: string, date, boolean, numeric, document, query, struct, array, and CFC (if the value is a CFC instance).
Example

In the following example, The root query element has two child elements columnnames and rows. columnnames is the name of the columns in the query. rows can have data in multiple row elements. Here, the order of the column in the row should match the column defined in the columnames. For each column in the row, a type attribute is mandatory. The type attribute can have any of the ColdFusion data types as values.

<query id="1">
<columnnames>
<column name="columnnameone"/>
<column name="columnnametwo"/>
</columnnames>
<rows>
<row>
<column type="string">value one</column>
<column type="string">value two</column>
</row>
<row>
<column type="number">444.0</column>
<column type="string">value four</column>
</row>
</rows>
</query>

Struct

  • The root element of the XML should be struct.
  • Use the attribute ID to handle circular dependency.
  • The struct can have multiple entry child elements. That is, a key-value pair in the Struct instance to be serialized. entry element requires two mandatory attributes name (name of the entry) and type (the type of the value of the entry). The type attribute can have one of the ColdFusion data types as values.
Example
<struct id="1">
<entry name="name" type="string">joe</entry>
<entry name="age" type="string">30</entry>
<entry name="address" type="string">101 some drive, pleasant town, 90010</entry>
<entry name="eyecolor" type="string">blue</entry>
<entry name="income" type="string">50000</entry>
</struct>
CFC Component
  • The root element of the XML has to be component.
  • ID attribute is used to handle circular dependency.
  • The name attribute has to provide the fully qualified name of the CFC from webroot.
  • A component element can contain multiple property child elements.
  • The property elements are the cfproperty values defined in cfcomponent.
  • The name attribute of the property corresponds to the name of the cfproperty defined.
  • type attribute specifies the type of the cfproperty.
  • If, in the component, any property has a null value, then it does not appear in the serialized format. This applies also for deserialization.
Example

Student.cfc

<component id="1" name="testrest.student">
<property name="name" type="string">paul</property>
<property name="age" type="number">444.0</property>
<property name="dob" type="date">478377000000</property>
</component>

In the following example, testrest.student means the CFC Student.cfc is placed in the testrest directory under webroot.

Format for array and array of cfcomponent
  • The root element has to be array.
  • size attribute specifies the length of the array.
  • The type attribute is not mandatory for the array element whereas for item element, it is mandatory.
  • If the array is a cfcomponent array, then the type attribute of array element should have the fully qualified name of the CFC.
  • The array element contains multiple item child elements. Only the non-null items in the array are serialized.
  • The item element has two attributes: index that specifies that index of the item in the array and type.
Example

The following example shows an array containing two struct objects:

<array id="1" size="2">
<item index="0" type="struct">
<struct id="2">
<entry name="name" type="string">joe</entry>
<entry name="age" type="string">30</entry>
<entry name="address" type="string">101 some drive, pleasant town, 90010</entry>
<entry name="eyecolor" type="string">blue</entry>
<entry name="income" type="string">50000</entry>
</struct>
</item>
<item index="1" type="struct">
<struct id="3">
<entry name="name" type="string">paul</entry>
<entry name="age" type="string">25</entry>
<entry name="address" type="string">some other address</entry>
<entry name="eyecolor" type="string">black</entry>
<entry name="income" type="string">40000</entry>
</struct>
</item>
</array>

Example: Array of CFC Serialization

The following example illustrates serializing array of CFC to XML and JSON formats.

  1. Create an array of arrayCFCdefinition.cfc:

    <cfcomponent>
    <cfproperty name="str" type="string"/>
    </cfcomponent>

     

  2. arrayCFC.cfc produces the required array as follows:

    <cfcomponent restpath="arrayOfCFC">
    <cffunction name="func1" access="remote" output="false" returntype="arrayCFCdefinition[]"
    httpmethod="get" produces="text/xml">
    <cfset arrCFC = arraynew(1)>
    <cfloop from=1 to=2 index="i">
    <cfset obj = createObject("component", "arrayCFCdefinition")>
    <cfset obj.str = i>
    <cfset arrayAppend(arrCFC, obj)>
    </cfloop>
    <cfreturn arrCFC>
    </cffunction>
    <cffunction name="func2" access="remote" output="false" returntype="arrayCFCdefinition[]"
    httpmethod="get" produces="text/json">
    <cfset arrCFC = arraynew(1)>
    <cfloop from=1 to=2 index="i">
    <cfset obj = createObject("component", "arrayCFCdefinition")>
    <cfset obj.str = i>
    <cfset arrayAppend(arrCFC, obj)>
    </cfloop>
    <cfreturn arrCFC>
    </cffunction>
    </cfcomponent>

     

  3. Do the following to access the resource:
    • For XML:

      <cfhttp url="http://127.0.0.1:8500/rest/RestTest/arrayOfCFC" method="get" result="res1">
      <cfhttpparam type="header" name="accept" value="text/xml">
      </cfhttp>

       

    • For JSON:

      <cfhttp url="http://127.0.0.1:8500/rest/RestTest/arrayOfCFC" method="get" result="res2">
      <cfhttpparam type="header" name="accept" value="text/json">
      </cfhttp>

       

  4. You receive the following serialized output as response:
    • For XML:

      <array id="1" size="2" type="cfsuite.restservices.restservices.new.ArrayCFCdefinition">
      <item index="1" type="COMPONENT">
      <component id="2" name="cfsuite.restservices.RESTServices.New.arrayCFCdefinition">
      <property name="STR" type="NUMBER">
      1.0
      </property>
      </component>
      </item>
      <item index="2" type="COMPONENT">
      <component id="3" name="cfsuite.restservices.RESTServices.New.arrayCFCdefinition">
      <property name="STR" type="NUMBER">
      2.0
      </property>
      </component>
      </item>
      </array>

       

    • For JSON:

      [{"Str":1},{"Str":2}]

       

Example: Array of CFC: Deserialization

The following example illustrates deserializing array of CFC from XML format.

Note: Deserializing array of CFC is unsupported for JSON.

  1. Create an array of arrayCFCdefinition.cfc:

    <cfcomponent>
    <cfproperty name="str" type="string"/>
    <cffunction name="check" returntype="any">
    <cfreturn this.str>
    </cffunction>
    </cfcomponent>

     

  2. arrayCFC.cfc produces the required array as follows:

    <cfcomponent>
    <cffunction name="func3" access="remote" output="false" returntype="string"
    httpmethod="put" produces="text/xml" consumes="text/xml">
    <cfargument name="arg" type="arrayCFCdefinition[]"/>

    <cfif arg[2].check() eq "2">
    <cfreturn "true">
    <cfelse>
    <cfreturn "false">
    </cfif>
    </cffunction>
    </cfcomponent>

     

  3. Do the following to access the resource for XML:

    <cfhttp url="http://127.0.0.1:8500/rest/RestTest/arrayOfCFC" method="put" result="res3">
    <cfhttpparam type="header" name="content-type" value="text/xml">
    <cfhttpparam type="header" name="accept" value="text/xml">
    <cfhttpparam type="body" value="<ARRAY ID=""1"" SIZE=""2"" TYPE=""cfsuite.restservices.restservices.new.ArrayCFCdefinition""><ITEM INDEX=""1"" TYPE=""COMPONENT""><COMPONENT ID=""2"" NAME=""cfsuite.restservices.restservices.new.ArrayCFCdefinition""><PROPERTY NAME=""STR"" TYPE=""NUMBER"">1.0</PROPERTY></COMPONENT></ITEM><ITEM INDEX=""2"" TYPE=""COMPONENT""><COMPONENT ID=""3"" NAME=""cfsuite.restservices.restservices.new.ArrayCFCdefinition""><PROPERTY NAME=""STR"" TYPE=""NUMBER"">2.0</PROPERTY></COMPONENT></ITEM></ARRAY>">
    </cfhttp>

     

  4. Refer to the function. You are verifying the value of the property of arrayCFC definition.cfc for the second index of the array.
string, boolean, numeric, binary, and date

Specify the values directly in the body of the request.

Handling cyclic dependency

In ColdFusion, cyclic dependency is handled using the ID reference. All ColdFusion complex data types have unique IDs when serialized. If the same object has to be serialized elsewhere, instead of serializing the object again, a reference is made to the already serialized data using its ID. In the following example, the main object is a struct. The struct contains an array of objects. The array has two elements and both the elements are the same instance of a struct. During serialization, the first element in the array is serialized as it is. The ID of the serialized struct is 2. Instead of serializing the second element, as that object is already serialized, IDREF attribute is used to refer to the already serialized struct instance.

<struct id="1">
<entry name="arrayinastruct" type="array">
<array id="2" size="2">
<item index="0" type="struct">
<struct id="3">
<entry name="name" type="string">joe</entry>
<entry name="age" type="string">30</entry>
<entry name="address" type="string">101 some drive, pleasant town, 90010</entry>
<entry name="eyecolor" type="string">blue</entry>
<entry name="income" type="string">50000</entry>
</struct>
</item>
<item index="1" type="struct">
<struct idref="3"/>
</item>
</array>
</entry>
</struct>

 

Note: Object reference is taken care of by ColdFusion at the time of deserialization also.

JSON serialization and REST services
Serialization specifications
  • The content type of the Accept header of the request has to be text/JSON, application/JSON, or text/plain.
  • REST service should have a function that can handle the required MIME types.
  • Function has to return any of the ColdFusion supported data types other than binary.
  • Cyclic behavior is unsupported. But in the case of arrays, you might see the serialized string published, but not with expected output as explained in the following example:

    <cfset this.arr1 = arrayNew(1)>
    <cfset this.arr1[1] = "1">
    <cfset this.arr1[2] = this.arr1>
    <cfset this.arr1[3] = "3">

    When an array is assigned to a variable (in this case) <cfset this.arr12 = this.arr1>, you assign arr1 as the item in the second index of arr1. ColdFusion implicitly creates a new array and copies the data from arr1 to the newly created array. The newly created array is assigned to the second index of arr1. Therefore, both the instances are different, and therefore cyclic dependency is impacted.When you serialize, you get the following output:

    [1,[1],3]

    As you can observe, the inner array gets truncated after the first element.

Deserialization specifications
  • The content of the request is in a predefined format specified by ColdFusion.
  • The content type of the request is text/JSON, application/JSON, or text/plain.
  • A function in the service consumes the MIME type of the request.
  • cfargument does not have the attributes restargsource and restargname specified.
  • cfargument type is ColdFusion supported data type other than binary or CFC definition.
  • Only one argument has no restAargSource attribute specified. The whole body of the request is deserialized to the argument type.
  • Cyclic behavior is unsupported. But in the case of cyclic arrays, you might see the deserialized array published, but not giving expected output.
Format definitions
Format for query

{'COLUMNS':['columnNameOne','columnNameTwo'],'Data':[['value one','value two'],['444.0','value four']]}

Format for struct

{'NAME':'joe','AGE':30,'ADDRESS':'101 Some Drive, Pleasant Town, 90010','EYECOLOR':'blue','INCOME':50000}

Format for component

{'NAME':'Paul','AGE':444.0,'DOB':'July, 27 2011 00:00:00'}

 

Note: Deserialization is unsupported for components.

Format for array

[{'NAME':'joe','AGE':30,'ADDRESS':'101 Some Drive, Pleasant Town, 90010','EYECOLOR':'blue','INCOME':50000},{'NAME':'paul','AGE':25,'ADDRESS':'Some other address','EYECOLOR':'black','INCOME':40000}]

string, boolean, numeric, and date

Specify the values directly in the body of the request. ----

Support for GZip encoding

If the request contains a Content-Encoding header of "gzip" then the request entity (if any) is uncompressed using the gzip algorithm. If the request contains an Accept-Encoding header containing "gzip" and an "If-None-Match" Header, entitytag value is checked: if it contains the -gzip suffix, remove this suffix, otherwise, completely remove the "if-none-match" header.

If the request contains a Accept-Encoding header that contains "gzip", then the response entity (if any) is compressed using gzip and a Content-Encoding header of "gzip" is added to the response. As this filter is active, the resource representation can be compressed. the value "Accept-Encoding" is so added to the Vary header. If any entityTag is used and content may be gzipped, the "-gzip" suffix is added to entitytag value. 

 

Site-level REST application support

In ColdFusion 10, multiple applications cannot have the same name, even if the applications are residing in different hosts. With the enhanced ColdFusion 11 REST feature, you can have multiple applications with the same name but available in different hosts. Also, you can set one default application (containing the REST service) for each virtual host. 

You can register the directory containing the REST service by using any one of the following options:

  • autoregister Application Setting
  • ColdFusion Administrator console
  • ColdFusion Admin API
  • restInitApplication method

Option 1: autoregister Application Setting

A new Application setting autoregister is introduced in ColdFusion 11.

  • You can set the auto register to true if you want to enable the auto registration for the application: 

<cfset this.restsettings.autoregister="true"/>

 

  • Specifying the servicemapping is optional. If servicemapping is not specified, the "this.name"/application name will be taken as default.

<cfset this.restsettings.servicemapping="testmapping"/>

                                                                                                                                     

  • Specify the usehost or hostname, If usehost attribute is set to true, then the host name is parsed from the URL. usehost="True" is the default value for REST service registration. If usehost=true, the host name will be taken from the URL. The host name will be used for registration. If no hosts are specified, that is, usehost="False", all applications without a host name and all requests from any host will be serviced.

<cfset this.restsettings.usehost=true/>

                                                                                                                                                        

  • Explicitly naming the host name will make the host name. If the host name is not mentioned, then the usehost name will be defaulted.

<cfset this.restsettings.host="www.adobe.com"/>

 

  • Set isDefault to true and the application will be made as default app. 

<cfset this.restsettings.isDefault=true/>

 

When the first request comes to the application and if that request is non-REST request, the application will be started and registered. If both usehost and host are not specified, the apps will be registered without host name. 

 

 

Note: If the first request itself is a REST request, then the application will not get started.

 

Option 2: Registering a REST application using ColdFusion Administrator console

  • Use the Administrator console to register a directory containing REST-enabled CF components. Select Adobe ColdFusion 11 Administrator console > Data & Services > REST services.
  • Browse and select the root path (or the application path) where ColdFusion will search for the directory containing a set of REST enabled CF components. 
    (Optional) In the Service Mapping section, specify the virtual mapping in place of application name. If the folder has an Application.cfc file and an application name, use the application name to identify the REST services in the directory. 
    (Optional) Enter the Host Name. This will enable the mapping of the selected application with the host. Each host can have a default application and multiple applications with the same application name can be mapped to different hosts.
  • Set the REST application as a default application by selecting the option Set as default application. By selecting this option, you omit the need to specify the service mapping or the application name in the URI.
  • Click the Add Service button to complete registration of the directory.
  • View the Active ColdFusion REST Services display table to view the list of active service mappings. You can use the same to edit or delete the service mappings in future.


Option 3: Registering a REST application using the ColdFusion Admin API


You can use functions defined in CFIDE.adminapi.extensions CFC to manage a REST application. The functions are:

  • registerRESTService(path[,serviceMapping[,host[,isdef]]]: This function registers the REST application. The root path specifies the directory containing REST-enabled CF component. Optionally, the service mapping for the REST application, host name, and isdefault can be specified.
  • getRESTServices(): This function returns an array of REST services registered with the ColdFusion Administrator.
  • deleteRESTService(rootPath): This function deletes the specified REST application registered with the ColdFusion Administrator.
  • refreshRESTService(rootPath): If you make any changes to the REST-enabled CF component, you can refresh the registered application by calling this function.
  • getDefaultRestService(): Returns the server wide default REST application.
  • getAllDefaultRESTServices: Returns all the default REST services. It is an array of path – host pair.

Option 4: Registering a REST application using the restInitApplication method


You can also register a REST application by calling the method restInitApplication

The syntax is:

restInitApplication(rootPath[,serviceMapping[,options]])

 

The options are an optional argument. In the options struct you can pass the:

  • Host
  • useHost
  • isDefault

For registering by specifying the host explicitly:

<cfset str=structNew()>
<cfset str.host = "www.site1.com:82">
<cfset str.isDefault = "true">
<cfset RestInitApplication("C:\dev\ColdFusion\cf_main\cfusion\wwwroot\withhostAndDefault", "withhostAndDefault", str)>
App registered

For registering by specifying the UseHost attribute: The host name will be extracted from the request URL and will be used for registration.

<cfset str=structNew()>
<cfset str.useHost = "true">
<cfset str.isDefault = "true">
<cfset RestInitApplication("C:\dev\ColdFusion\cf_main\cfusion\wwwroot\withhostAndDefault", "withhostAndDefault", str)>
App registered

 

 


You do not need Administrator privileges to perform the task. The syntax is:

restInitApplication(rootPath[,serviceMapping[,options]])

 

  • In the options you can specify host, useHost and isDefault. The option usage is same as autoRegister feature.
  • If you have already registered the application using the Administrator module, call restInitApplication to refresh the REST service.
  • Use the restDeleteApplication function to delete the REST service. The syntax is restDeleteApplication(rootPath)

Support for pluggable serializer and deserializer

In the application.cfc, you can register your own handler for serializing and deserializing the complex types. If the serializer is not specified, ColdFusion uses the default mechanism for serialization.

Example: If you have a phone directory to be serialized on the following parameters:

  • The main data structure is an Array. 
  • Each element of the array is a struct. 
  • The Struct contains 2 elements. 
  • The first is the name of the contact and the second is the struct which contains the code and the phone number.

Array

Struct
[
name(String],
phoneNo(Struct)
[
code(String),
no(String)
]
]
Struct
[
name(String],
phoneNo(Struct)
[
code(String),
no(String)
]
]

And in this example, you want to serialize only struct in a simple format and want the result as follows:

<Array size="1"> <Item index="1"> <root> <Name>Paul</Name> <PhoneNo> <root> <Code>080</Code> <No>411150326</No> </root> </PhoneNo> </root> </Item> </Array>

 

With the enhanced ColdFusion 11 REST feature, the user can use the plugged-in custom serializer instead of using the default serialization function. The custom serializer has four functions:

  • CanSerialize - Returns a boolean value and takes the "Accept Type" of the request as the argument. You can return true if you want the customserialzer to serialize the data to the passed argument type. 
  • Serialize - The main serialization logic must be implemented in this function. If canSerialize returns "True" for a request, then ColdFusion will use this function to serialize. If canSerialize returns false, then ColdFusion will use the default serialization to serialize.
  • CanDeserialize - Returns a boolean value and takes the "Content Type" of the request as the argument. You can return true if you want the customserialzer to deserialize the data.
  • DeSerialize - The main deserialization logic must be implemented in this function. If canDeSerialize returns "True" for a request, then ColdFusion will use this function to deserialize. If canDeSerialize returns false, then ColdFusion will use the default de-serialization to deserialize.

The below CustomSerializer code sample will help you to achieve the result for the scenario explained above (Customizing serialization/deserialization of phone directory):

<cfcomponent>
<cffunction name="serialize" access="remote" returntype="String">
<cfargument name="arg" type="any" hint="The object to be serialized"/>
<cfargument name="type" type="string"
hint="The accept header of the request in array format. The order of types in the array is according to the priority of the MIME types in the header."/>

<cfset var result = "">
<cfset var key = "">

<cfif arguments.type eq "XML">
<cfif isStruct(arguments.arg)>
<cfset result = "<root>">
<cfloop collection="#arguments.arg#" item="key">
<cfset result = result & "<" & key & ">">
<cfset result = result & serializeXML(arguments.arg[key], true)>
<cfset result = result & "</" & key & ">">
</cfloop>
<cfset result = result & "</root>">
<cfreturn result>
<cfelse>
<!--- SerializeXML is a new function added in ColdFusion 11. This function will serialize the object to XML 
using ColdFusion's default serialization mechanism." --->
<cfreturn serializeXML(arguments.arg)>
</cfif>
<cfelseif arguments.type eq "JSON">
<cfdump var="#arguments.arg.getClass().getName()#" output="console">
<cfif arguments.arg.getClass().getName() eq "java.lang.String">
<cfreturn "test" & arguments.arg>
<cfelse>
<cfreturn serializeJSON(arguments.arg)>
</cfif>
<cfelse>
<!--- Serialize is a new function added in ColdFusion 11. This function will serialize the object to a
a specified type using ColdFusion's default serialization mechanism. --->
<cfreturn serialize(arguments.arg, arguments.type)>
</cfif>

</cffunction>

<cffunction name="canSerialize" access="remote" returntype="boolean">
<cfargument name="type" type="string"/>

<cfif arguments.type eq "XML">
<cfreturn true>
<cfelseif arguments.type eq "JSON">
<cfreturn true>
<cfelse>
<cfreturn false>
</cfif>
</cffunction>

<cffunction name="canDeserialize" access="remote" returntype="boolean">
<cfargument name="type" type="string"/>

<cfif arguments.type eq "XML">
<cfreturn true>
<cfelseif arguments.type eq "JSON">
<cfreturn true>
<cfelse>
<cfreturn false>
</cfif>
</cffunction>

<cffunction name="deserialize" access="remote" returntype="any">
<cfargument name="arg" type="String" hint="The string to be deserialized"/>
<cfargument name="type" type="String" hint="The content-type header of the request."/>

<cfset var xmlDoc = "">
<cfset var result = "">
<cfset var numEntries = "">
<cfset var key = "">
<cfset var value = "">

<cfif arguments.type equals "XML" and isXml(arguments.arg)>
<cfset xmlDoc = xmlParse(arguments.arg)>

<cfif xmlDoc.XmlRoot.XmlName equals "root">
<cfset result = StructNew()>
<cfset numEntries = ArrayLen(xmlDoc.root.XMLChildren)>
<cfloop index="i" from="1" to="#numEntries#">
<cfset key = xmlDoc.root.XMLChildren[i].XmlName>
<cfif ! len(Trim(xmlDoc.root.XMLChildren[i].XMLText))>
<cfset value = deserializeXML(ToString(xmlDoc.root.XMLChildren[i].XMLChildren[1]), true)>
<cfelse>
<cfset value = deserializeXML(xmlDoc.root.XMLChildren[i].XMLText, true)>
</cfif>
<cfset result[key] = value>
</cfloop>
<cfreturn result>
<cfelse>
<cfreturn deserializeXML(arguments.arg, true)>
</cfif>
<cfelse>
<cfreturn deserializeXML(arguments.arg, true)>
</cfif>

</cffunction>

</cfcomponent>

The CustomSerializer that you have implemented can be specified through application.cfc.  

<cfset this.customSerializer="CustomSerializer">

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License  Twitter™ and Facebook posts are not covered under the terms of Creative Commons.

Legal Notices   |   Online Privacy Policy