Context Standard Plugin: Powernap
Powernap is an HTTPPlugin that makes creating and deploying ReST (Representational State Transfer) web services easier than ever.
A greatly simplified but equally as powerful port from the original Powernap project, http://powernap.riaforge.org/, this plugin provides a full-featured approach to creating rest web service endpoints. Perfect for facilitating communication between disparate systems and servicing AJAX request from front-end clients.
This documentation assumes a very cursory understanding of ReST web services. For more information, go here; http://en.wikipedia.org/wiki/Representational_State_Transfer
Resources in Brief
Central to the concept of ReST is that of the Resource. Without delving too deeply into the basics, a resource is essentially the object at the end of an HTTP request. For instance, an HTML page is technically a resource. When you specify http://{somedomain}.com/hello_world.html what you're saying is "Hey, I'm looking for the 'hello_world' resource in the format of 'html'" and as long as that URI points to a valid resource, you should get exactly what you asked for.
Of course from a web services perspective, a static hello_world resource in html format isn't terribly useful. Resources can be much, much more.
When working with the PowernapPlugin, any .cfc can instantly become a resource. It's a simple matter of annotating the right methods in a way so as to allow Powernap to discover them. Of course, for the purposes of organization you're probably going to want to stick to a naming convention for your .cfc's that makes sense and/or place them in a location that plainly indicates they're actual Resources. However to drive this point home, that's not necessary - it's purely optional.
Initialization
Below is a sample snippet of code to get your HTTPContext ready to service ReST requests with the Powernap plugin;
Figure 1.1
As stated previously, the PowernapPlugin is an HTTPPlugin and as such requires you use an HTTPContext in order for it to be able to respond to requests - as you'll see on line 14 of the code snippet above. Cache that context! The above code is purely an example, don't re-create the context on every request unecessarily.
404
HTTP Status codes are another very important piece of the ReST equation. In fact, if you were to refresh the page that contains the figure 1.1 code, you'll get a 404 response indicating that the requested resource was not found. In fact, the .cfm file containing the code was found AND your HTTPContext instance did it's job - when it delegated the request to the PowernapPlugin it attempted to match the request to a registered resource - but couldn't find one so it returned a response with a 404 status code. This is all expected/default behavior because you haven't defined any methods on any .cfc's within your context that are capable of acting as ReST resources.
When testing, you can disable "restful responses" in powernap by provding an optional constructor argument as follows;
Figure 1.2
In figure 1.2, responses are not restful meaning if no matched resource is found, the context will continue processing and return empty contend with a status code of 200. This approach is not recommended when working with actual ReST web services, but can be helpful when developing.
Creating a Resource
Time to create your first resource. Create a CFC anywhere within your Context root. It can be named anything you like. It also does not need to extend any other component or implement any interface. An example is as follows;
Figure 1.3 : Simple CFC
As you can see in figure 1.3, the component we've created is a plain-old CFC. Nothing special. It exposes a simple and self-explanatory method, getArticle(). We're now going to turn this CFC into a resource capable of responding to a request, ReSTfully with two very simple annotations listed in figure 1.4. Also, we're simply going to stub out the logic requried to retrieve a valid article per line 14;
Figure 1.4 : Resource with Annotations
DONE!. You've created your first ReSTful resource. In fact, in order to see how your HTTPContext responds, simply hit the following URL with your browser;
http://{your-host}:{your-port}/index.cfm/article/1234
You should have recieved a response of type application/json containing the following information;
{"COLUMNS":["ID","TITLE","CONTENT"],"DATA":[]}
It's a query object serialized into a JSON stream. The default content-type for all requests and responses is application/json unless otherwise specified.
URI Variables
For those of you who have had the opportunity to play around with the original Powernap API or it's successor the Quicksilver framework, you'll immediately recognize the curly-braces in the URI annotation. For those of you haven't, here's a brief explanation. Any portion of the URI annotation template that is wrapped between curly braces acts as a variable, meaning any value can be a part of the actual request path-information at that "spot". I could have just as easily passed in the following URI to the browser and that too would have mapped to my method;
http://{your-host}:{your-port}/index.cfm/article/asdf
'asdf' of course is not '1234', but our URI pattern indicates that it will still match and map to our resource method. Not only that, but the information that exists in the variables position will be parsed out and bound to our articleId method argument - why, because it has the same name. HOWEVER, If you were to pass in the URI with the 'asdf' value, you will most certainly see an exception. Why? Because our method signature indicates we're expecting a numeric value - 'asdf' is most certainly NOT numeric.
Thankfully (and just like the original Powernap and Quicksilver frameworks), configuration of our URI's is also very flexible, in fact you can specify very specific values via regular expressions. We want to ignore alpha character matches for the articleId URI variable. To do so, our URI can be configured as such;
/**
* @cfcommons:rest:uri /article/{articleId:[0-9]{8}}
*/
There. Not only are we indicating that ONLY numeric values are acceptable for that URI variable, but that it must be exactly 8 digits long. Any URI that does not meet those requirements will not map to our resource method and the plugin will continue to search for other resources that do match. Assuming nothing else does, a 404 response will be returned.
Data Binding
We've already briefly covered how URI variables are bound to method arguments. But HTTP Request information can be sent over in many ways, in form POST parameters, as raw form information in a PUT or POST, or simply as URL query-string variables. Data-binding happens automatically for each of those scenarios. The general rule of thumb is this; if it has a name in the request, it will be mapped to a method argument of the same name. For instance, if you have a form post parameter named firstName, then the value of that form post parameter will automatically be bound to a method argument of the same name. Same goes for URL query-string parameters.
Raw PUT's / POST's
A raw PUT or POST is request information that get's sent without named parameters. This is a totally valid HTTP request. You can bind information from a raw PUT or POST by ensuring that information gets passed over as a JSON object;
Figure 1.5 : JSON raw PUT / POST
The JSON data in figure 1.5 is very simple. The PowernapPlugin will automatically deserialize that JSON into a native CF structure with a key named "mydata". The value of "mydata" will be a native CF array with the elements you see detailed. In order to bind that information to a resource method argument, you simply need to specify an argument of type struct with a name of "mydata".
Request HTTP methods matter
In our figure 1.4 example, we specified an httpMethod of type GET. This configuration option means that if a request with a method other than GET get's handed off to the context, even if the URI matches the template pattern, Powernap will NOT match that to the resource method. It acts as a type of filter. You can specify any number of valid http methods as a comma-delimited list to that annotation;
/** * @cfcommons:rest:httpMethod PUT,POST */
Automatic Response Serialization
Another very useful mechanism ported from the Quicksilver framework, Powernap will make an extra effort to ensure returning a meaningful and ReSTful response from your method is done automatically. We were able to see an instant response back from our figure 1.4 resource because we obeyed one of the conventions that permitted powernap to automatically serialize a valid response - we returned a query AND the default content-type was JSON.
As long as your method returns any one of the following native CF data-types AND the requested format is JSON, responses will be serialized and sent back in the request automatically;
- Query
- Array
- Struct
- String
IF your resource needs to return a more specific representation, never fear - that's entirely possible. Simply return a custom data-type that exposes a method that matches the requested content-type. For instance, if you needed to return an XML representation - then your resource method must return an object instance that exposes a public method named toXML(). This method (of course) must return a data-type of xml. The request then, must have ".xml" appended to the request URI;
http://{your-host}:{your-port}/index.cfm/article/1234.xml
Command Structures and Custom Status Codes
As long as no server-level exeptions occur when executing your resource method, an http status code of 200 will be sent back to the requesting client. However, control over status codes is another important part of managing effective ReST web services. You can at any point return any valid HTTP status code to the requester by ensuring your resource method returns a struct data-type AND that structure must contain a key named "statusCode" with a numeric value;
Figure 1.6 : Custom Status Codes
The code in figure 1.6 produces the same exact result as the first 1.4 figure EXCEPT the status code returned in the response will be a 202.
Securing your Resources
So you don't want just anyone accessing your resource - eh? No worries, applying security to your resources is simple. A 401 status code in a response indicates "unauthorized" to the requesting client - this is known as a basic http authentication challenge. Most clients will understand what to do with this response and neatly provide you, the user, with a dialogue that contains a username and password text entry. Most browsers will comply as well as poster - the Firefox plugin. In order to produce a 401 challenge - you need simply to return a status code of 401 OR throw an exception of type SecurityException. Both of the following examples will accomplish the same result;
Figure 1.7 : HTTP Basic Authentication Security
Figure 1.8 : HTTP Basic Authentication Security
If the request contains correctly encoded HTTP Basic Authentication credentials, then those values will automatically be parsed our and added to the argument collection bound to your target method as "username" and "password" which you can then reference to perform any type of security check that you see fit. This also means that it fits perfectly with the SimpleSecurity plugin as well.