RESTfulCF is a framework for ColdFusion that simplifies the publishing of RESTful API-style interfaces.
The framework was extracted from work at White Label Dating. The goal was to provide an interface to a ColdFusion core to be used by a Ruby on Rails application; rather than giving direct database access and having to maintain two sets of business rules across two different languages, a REST API was built on top of the existing ColdFusion application.
Due to the background of the project, there are a number of "Rails-isms" that have found their way into the framework (such as the way errors are returned, typing for properties in XML resource representations), but in general these are only there to improve interoperability and have certainly not been added just to "make it work like Rails".
REST stands for "REpresentational State Transfer". It is a stateless protocol based around the use of "resources" (think objects), each of which is uniquely addressable (in this case, via a URI); components of the system exchange "representations" of resources (the actual data representing the resource, for example an XML or JSON packet, or an HTML or PDF document).
REST interfaces require a constrained set of well-defined operators; in this case we use HTTP methods:
GET
for reading resources (both singular and collections)POST
for creating new resourcesPUT
for updating existing resourcesDELETE
for deleting existing resources
For a more detailed discussion, see the Wikipedia REST page.
If the system consuming your RESTful application does not support
PUT
andDELETE
requests, these can be simulated by using the special_method
parameter via aPOST
. The arguments should be provided as either aPOST
parameter or in the query string (if both are provided then thePOST
variable takes precedence); pass_method=PUT
or_method=DELETE
as appropriate.
- ColdFusion 8/9
- Wildcard mappings for Search Engine Safe (SES) URLs must be enabled
- MXUnit is required for the unit tests
This framework makes use of the CGI['PATH_INFO']
variable which is populated when a URL such as script.cfm/path_info
is requested; this behaviour is known as "search engine safe URLs" and is not enabled by default on ColdFusion, and the example URL given would normally result in a 404 Not Found
error. To allow ColdFusion to handle these types of requests, edit your web.xml
file and search for the following section:
<!-- begin SES
<servlet-mapping id="coldfusion_mapping_6">
<servlet-name>CfmServlet</servlet-name>
<url-pattern>*.cfml/*</url-pattern>
</servlet-mapping>
...
end SES -->
Simply un-comment out the section and restart ColdFusion.
To make non-GET
requests to a RESTful API (i.e. for creating, updating or deleting resources), you'll need something other than your browser. cURL
is good enough for you command-line people, or if you're a Firefox fan try the Poster plugin.
Simply place the restfulcf
directory either in your webroot or within a custom tag path, or create a mapping called /restfulcf
to the location of the directory.
A very simple sample application is included in restfulcf/sample/helloworld
which will perhaps give a clearer example of the components of a RESTfulCF application. There is a README
file in that directory which fully explains how to use the example; the code is also well commented so it should be simple enough to follow.
The following is a breakdown of the main components you'll need to use to implement a REST service using RESTfulCF.
This is the "entrance point" to a RESTfulCF implementation. It's a custom tag, and a simple implementation would be:
<cfapplication name="my_restful_app">
<cfimport taglib="/location/of/restfulcf/framework/tags/" prefix="restfulcf">
<restfulcf:endpoint
name = application.applicationname,
engine = "path.to.my.Dispatcher"
reload = TRUE
response = "variables.response">
RESTfulCF makes use of the Application
scope, so this must be defined using <cfapplication>
or Application.cfc
before calling the endpoint
custom tag. The call to endpoint
should be from a publicly-accessible script. If you'd rather not play with compile-time imports of custom tag libraries, you can easily use <cfmodule>
instead.
The arguments to the endpoint
custom tag are as follows:
name
-- the unique name of the RESTful implementation (usually the application name)engine
-- the dotted-path to your Dispatcher (a string, not an instance)reload
-- should the engine be reloaded on each request (should beFALSE
in production)response
-- an optional variable to push theResponse
resulting from the request
An implementation of RESTfulCF will contain a component which extends Dispatcher
: this is the file which will define which Resource
s are available (through which Route
s), as well as defining any Authenticator
to use for all calls.
The simplest custom Dispatcher
would be something like:
<cfcomponent extends="restfulcf.framework.core.Dispatcher" output="no">
<cfset variables.controller_path = "path.to.my.controllers">
<cffunction name="init" access="public" returntype="restfulcf.framework.core.Dispatcher" output="no">
<cfset super.init(argumentcollection=arguments)>
<cfset addResource("resource")>
<cfreturn this>
</cffunction>
</cfcomponent>
Here we're simply defining the path to the location of the Controller
components, and adding a resource called "resource".
This function is a helper for creating Route
s mapping URIs to certain controllers and functions. The simplest form is just:
<cfset addResource("resource")>
This will use a controller called Resources.cfc
in the controller_path
set in the Dispatcher
(see above). It will then look inside that controller and auto-map certain functions it finds to routes as follows:
index
->GET /resources
create
->POST /resources
read
->GET /resources/:id
update
->PUT /resources/:id
delete
->DELETE /resources/:id
count
->GET /resources/count
addResource()
can take a number of different arguments to allow for route nesting, aliasing of route names, the path to the actual controller file, whether default routes should be created automatically, and if they should, which ones:
resource
-- unique resource namenesting
-- array or comma-delimited list of route nesting, to allow generation of routes such as/resources/:resource_id/subs/:id
route_alias
-- an alias to use for this resource within the generated routes, so a controller calledMyLongResources
could be aliased toresources
in the generated routes instead of the defaultmy_long_resources
controller
-- used to override the default naming conventions for controllers, mainly to allow for better organisation of controllers (e.g. setting this assub.Resources
would add this path to the end of thecontroller_path
for where to find the controller)create_default_routes
-- should default routes be automatically created (default isTRUE
)methods
-- ifcreate_default_routes
isTRUE
, only creates routes for these functions (or all if not provided)
If you want to use custom (non-default) routes then you should still call addResource()
to define the controller to handle requests for that resource name. You can then add individual routes as follows:
<cfset addResource(name = "resources", create_default_routes = FALSE)>
<cfset variables.routes.addRoute(
createobject("component", "restfulcf.framework.core.Route").init(
verb = "GET",
uri = "/resources/:id",
controller = "resources",
method = "read"
)
)>
A Route
is effectively a mapping of HTTP request method (GET
, POST
etc) and URI pattern to a given controller and function. For example, a route may be defined that maps GET /resources/:id
to path.to.controllers.Resources#read
. All routes are defined in the Dispatcher#init
function and are stored within a RouteCollection
within that Dispatcher
.
The initialisation arguments for a Route
are:
verb
-- the HTTP method (one ofGET
,POST
,PUT
orDELETE
)uri
-- the URI that matches this route, e.g. /users/:idcontroller
-- the controller to use for this route (as defined viaDispatcher#addResource()
)method
-- the name of the function to call in the controller
The URI for a Route
can (and usually does) contain one or more parameter; these names of these are prefixed with a colon :
. For example, the following URI defines a parameter called id
:
/resources/:id
During Route
matching, any parameters are replaced out with a simple regular expression (matching anything except a forward slash /
), so the above URI would match against any of the following:
/resources/1
/resources/qwertyuiop
/resources/user+name
If a Route
matches a requested URI, and it contains one or more parameters, the extracted parameters will be passed to the controller as named arguments (named as the parameters):
<cffunction name="read">
<cfargument name="id">
...
</cffunction>
The idea of a "nested resource" can be provided by using multiple parameters:
/resources/:resource_id/foos/:foo_id/bars/:id
Again, the appropriate parameters are passed as named arguments through to the controller:
<cffunction name="read">
<cfargument name="resource_id">
<cfargument name="foo_id">
<cfargument name="id_id">
...
</cffunction>
A RESTfulCF application will contain one or more components which extend Controller
: these are the core of the application. Each Controller
instance should implement one or more functions which are mapped to a public Route
through the Dispatcher
. The default functions which can automatically be picked up and Route
s created for them are listed in the addResource()
section above.
The base Controller
component is almost completely empty: it contains a simple init()
function, and the HTTP_STATUS_CODES
lookup described below. You can created any functions you choose here: they're hooked up to the actual REST interface via Route
s as defined in the Dispatcher
section above. In general, a function will take in a number of arguments, and return a Resource
of some variety:
<cffunction name="read" access="public" returntype="restfulcf.framework.Resource">
<cfargument name="id">
<cfreturn createobject("component", "path.to.my.resources.Resource").init(
id = arguments.id,
)>
</cffunction>
Each controller contains a lookup structure which maps human-readable HTTP status names to their appropriate status codes. For instance, ok
is mapped to 200
, created
to 201
, not_found
to 404
and so on. These can be used for readability when setting the response status code from within a Controller
:
<cfset arguments['_response'].setStatusCode(this.HTTP_STATUS_CODES['created'])>
See details on the Response
component below for more information.
At the simplest level, a component that extends Resource
is the definition of an individual resource; the available fields within a Resource
are defined by using <cfproperty>
tags. In general, most Resource
files are as simple as this:
<cfcomponent extends="restfulcf.framework.core.Resource" output="no">
<cfproperty name="id" type="numeric" default="0">
<cfproperty name="name" type="string" default="">
<cfproperty name="created_at" type="date" default="1900-01-01 00:00:00">
<cfproperty name="updated_at" type="date" default="1900-01-01 00:00:00">
</cfcomponent>
Each <cfproperty>
tag must have a unique (within this Resource
) name
and a tyoe
; the default
is optional but should be provided. The options for a property's type
are:
numeric
string
date
boolean
All defaults
are plain text: you cannot use a ColdFusion function to populate these (hence the long-hand definition of the timestamps in the example above).
Optionally, numeric
and date
type properties can be given a precision
which define how these fields are formatted in a representation of the resource:
numeric
--integer
,decimal
,float
date
--datetime
(default),date
,time
A numeric
type does not have a precision
default: the property will be output as given. The exception to this rule is any numeric
property named id
or that ends in _id
, which are automatically treated as integer
s unless the precision is given otherwise.
The base Resource
component makes use of the onMissingMethod
function to allow implicit setter and getter methods for any property defined using <cfproperty>
, based on the property name. For the example given above, the following functions will be available for setting and retrieving property values:
getID()
andsetID(number)
getName()
andsetName(string)
getCreated_at()
andsetCreated_at(date)
getUpdated_at()
andsetUpdated_at(date)
As you can see, the function names don't adhere to the standard camel-cased naming convention.
The conversion from Resource
to a transferrable representation of that resource is via the functions named to...()
built in to the base Resource
. The ...
part is replaced by the requested type; the built-ins are xml
, txt
, json
, csv
and html
. Any Resource
can override any of these functions to provide a custom response; a pdf
type is allowed by the framework, but there is no default handler and the toPDF()
function must be implemented on a per-Resource
basis.
The default responses for each representation type are:
toTXT()
andtoJSON()
-- both return a JSON-encoded stringtoHTML()
-- returns a definition list (<dl>...</dl>
)toXML()
-- returns an XML packettoCSV()
-- returns a CSV document, including a header line detailing the names and order of properties
An example XML representation of the above sample Resource
is:
<?xml version="1.0" encoding="UTF-8"?>
<resource>
<created_at type="datetime">{ts '1900-01-01 00:00:00'}</created_at>
<id type="integer">0</id>
<name></name>
<updated_at type="datetime">{ts '1900-01-01 00:00:00'}</updated_at>
</resource>
If you request a response type that a given resource doesn't support, the HTTP response code will be set to 415 Unsupported Media Type
(i.e. your request was understood, but the resource can't be formatted into the required type.)
This component is the first of the "meta-resources" within RESTfulCF, which extends Resource
and so is treated just like any other Resource
instance returned from a controller. A ResourceCollection
is exactly that: a collection of Resource
s. This is generally what will be returned from the index
method in a controller, so that an example request of http://localhost/resources.xml
, where Resources#index
returned a ResourceCollection
, would result in a response similar to the following:
<?xml version="1.0" encoding="UTF-8"?>
<resources type="array">
<resource>
...
</resource>
<resource>
...
</resource>
</resources>
The controller would initialise and add to a ResourceCollection
as follows:
<cfset collection = createobject("component", "restfulcf.framework.core.ResourceCollection").init(name)>
<cfloop array="#resources#" index="resource">
<cfset collection.add(resource)>
</cfloop>
<cfreturn collection>
A ResourceCount
is another "meta-resource", but in this case it simply returns a count. A resource of this type should generally be returned when requesting a count of resources, rather than retrieving all the resources as a ResourceCollection
just to perform a count()
-type function on the collection. An example request of http://localhost/resources/count.xml
, where Resources#count
returned a ResourceCount
, would result in a response similar to the following:
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<count type="integer">12345</count>
</resources>
The controller would initialise a ResourceCount
as follows:
<cfset count = createobject("component", "restfulcf.framework.core.ResourceCount").init(name, count)>
<cfreturn count>
An instance of Response
is is passed, by reference, to each Controller
function request under the argument name _response
. The data in this object is what defines what is returned at the end of the request.
The Response
object contains a number of simple setters/getters which are used throughout the framework, but you will find you'll need to set some from within your controllers. For example, if we're trying to read
a resource (e.g. GET /resources/1
) but the given resource doesn't exist, the setStatusCode()
function should be called to set the response status to 404 Not Found
:
<cffunction name="read">
<cfargument name="id">
<cfif NOT resourceExists(arguments.id)>
<cfset arguments['_response'].setStatusCode(this.HTTP_STATUS_CODES['not_found'])>
<cfreturn>
</cfif>
...
</cffunction>
The following is a quick example of a complete create
function from a sample controller, which makes use of setStatusCode()
, addError()
(for validation errors: also see ErrorCollection
below) and setResponseURI()
(for setting the Location
header of the HTTP response to the URI of the newly created resource) from the Response
object:
<cffunction name="create">
<cfargument name="name">
<cfset var resource = {}>
<!--- validate the name --->
<cfif NOT len(arguments.name)>
<!--- add an error if there's a problem --->
<cfset arguments['_response'].addError("Name must not be empty")>
</cfif>
<!--- if there are any errors, set a fail status and return --->
<cfif arguments['_response'].hasErrors()>
<cfset arguments['_response'].setStatusCode(this.HTTP_STATUS_CODES['unprocessable_entity'])>
<cfreturn>
</cfif>
<!--- still here, so create the new resource --->
<cfset resource.id = saveResource(arguments.name)>
<!--- set the proper response status and URI --->
<cfset arguments['_response'].setStatusCode(this.HTTP_STATUS_CODES['created'])>
<cfset arguments['_response'].setResponseURI("/resources/" & resource.id)>
<!--- and return the resource-ified resource --->
<cfreturn createobject("component", "path.to.my.resources.Resource").init(
id = resource.id,
name = arguments.name
)>
</cffunction>
Another function that you may use from within a controller is setResponseFile()
; If this is set with a local file path (and the file exists) then that file will be sent as the HTTP response. This is useful for serving up pre-existing static files through the REST interface.
A reference to the Response
object is also returned if the response
variable name argument is provided when calling the endpoint.cfm
custom tag.
These components are used within the internals of the framework, but you'll rarely need to concern yourself with them.
This component is a template for locking your REST implementation down using basic authentication. If you wish to use this, create a new component extending this one, override the init()
and isAuthenticated()
functions, and add a line similar to the following to the init()
function of your app's Dispatcher
component:
<cfset setAuthenticator(createobject("component", "path.to.my.Authenticator").init(...))>
This authentication will be used for every request; you cannot simply only protect some resources.
ErrorCollection
extends Resource
, and provides the internal error reporting facility for when validation errors occur.
This component is the internal representation of a request; it's generally not used directly, but is passed to each Controller
function request under the argument name _request
. Through this you can get a handle on the root Dispatcher
instance, the current Route
, plus the current URI and requested response type.
This is simply a collection of all Route
s available to the REST implementation; it uses the findRoute
function to match the HTTP request method and URI to a single route.
A simple response caching system is available within RESTfulCF which caches the response object for a given request (based on the request type and URI). Multiple requests for the same (cached) data will bypass the controller and simply return the relevant Response
.
Caching is only applicable to
GET
requests. If the cache status of the response to a non-GET
request will be ignored and will never be cached (or asked to be retrieved from the cache).
As an example, to use the built-in application
-scoped cache, add the following line to your Dispatcher#init
:
<cfset setCache(createobject("component", "restfulcf.framework.core.cache.ApplicationCache").init())>
Then, in the relevant Controller
function, set that the response can be cached as follows:
<cfset arguments['_response'].setCacheStatus(TRUE)>
The default cache time for this cache is 30 minutes; to change this you can pass a timestamp through to the setCacheStatus()
function. For example, to set the responses to a given action to cache for an hour you'd add the following to the controller function:
<cfset arguments['_response'].setCacheStatus(createtimestamp(0,1,0,0))>
The
ApplicationCache
is not recommended for production use: use it as an example of what you need to do to create your own concrete cache (using memcached or something similar), as described below.
You may create you own cache type by creating a new component that extends restfulcf.framework.core.cache.AbstractCache
and implementing the following functions:
getKey
setKey
deleteKey
Look at the code for restfulcf.framework.core.cache.ApplicationCache
for the arguments to these functions etc.
The HTTP response code will be one of the following:
200 OK
201 Created
401 Unauthorized
-- if a customAuthenticator
is used and correct credentials have not been supplied404 Not Found
-- if either a resource (or controller) can't be found415 Unsupported Media Type
-- if an unsupported representation type was requested422 Unprocessable Entity
-- if a resource validation error occurred500 Internal Server Error
-- something went wrong
The current version has very little in the way of error handling: if something goes wrong, then you'll likely get a standard ColdFusion error page, which won't be take to kindly by any consumer of your REST interface that's expecting a nicely formatted XML packet back. Work on this is nearing completion.
RESTfulCF is released under the MIT license as detailed in the LICENSE file that should be distributed with this library; the source code is freely available.
RESTfulCF was developed by Tim Blair during work on White Label Dating, while employed by Global Personals Ltd. Global Personals Ltd have kindly agreed to the extraction and release of this software under the license terms above.