ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
Over the weekend, I read an excellent book on building RESTful web services - the REST API Design Rulebook by Mark Masse. In the book, Masse describes how the various HTTP methods (also known as Verbs) are intended to be used to create, access, update, delete, and augment data within a RESTful web service API. As far as HTTP verbs go, I'm extremely familiar with GET and POST - the two verbs natively supported by the HTTP Form element. PUT and DELETE, on the other hand, are rather new to me. In fact, I'm not even sure if ColdFusion supports them. As such, I wanted to put together a quick experiment to see how PUT and DELETE requests are handled in ColdFusion.
To set the stage for this experiment, I created a simple ColdFusion API that returns a chunk of HTML output that does nothing more that CFDump out the various data scopes within the ColdFusion request:
api.cfm - Our Simple ColdFusion API
<!---
Create a buffer for our output of the way various Scopes are
presented during different HTTP requests.
--->
<cfsavecontent variable="debugOutput">
<cfdump
var="#cgi#"
label="CGI Scope"
show="content_type, request_method"
/>
<br />
<cfdump
var="#getHttpRequestData()#"
label="HTTP Request Data"
/>
<br />
<cfdump
var="Content: #toString( getHttpRequestData().content )#"
label="Content"
/>
<br />
<br />
<cfdump
var="#url#"
label="URL Scope"
/>
<br />
<cfdump
var="#form#"
label="Form Scope"
/>
</cfsavecontent>
<!--- Send HTML output back to the client. --->
<cfcontent
type="text/html; charset=utf-8"
variable="#toBinary( toBase64( debugOutput ) )#"
/>
As you can see, very little is going on here. We're outputting the following request information:
- CGI Scope
- HTTP Request Data
- HTTP Request Body (part of the request data)
- URL Scope
- FORM Scope
When I output the request body, I'm passing it through a call to toString(). I have to do this because any request that comes through with a Content-Type other than "text/*" or "application/x-www-form-urlencoded" will show up as a byte array in the CFDump output.
Ok, simple enough stuff. Now, let's create an HTML page that uses jQuery to make PUT and DELETE requests to this ColdFusion API.
demo.htm - Our First PUT / DELETE Test
<!DOCTYPE html>
<html>
<head>
<title>Checking ColdFusion's Support For PUT And DELETE</title>
</head>
<body>
<h1>
Checking ColdFusion's Support For PUT And DELETE
</h1>
<h2>
PUT Response
</h2>
<div id="putResponse">
<!-- To be populated dynamically. -->
</div>
<h2>
DELETE Response
</h2>
<div id="deleteResponse">
<!-- To be populated dynamically. -->
</div>
<script type="text/javascript" src="../jquery-1.7.1.js"></script>
<script type="text/javascript">
// Make PUT request.
$.ajax({
type: "PUT",
url: "./api.cfm?dataValue1=Sent+in+URL",
data: {
dataValue2: "Sent in Data."
},
dataType: "html",
success: function( content ){
// Inject PUT output.
$( "#putResponse" ).html( content );
}
});
// Make DELETE request.
$.ajax({
type: "DELETE",
url: "./api.cfm?dataValue1=Sent+in+URL",
data: {
dataValue2: "Sent in Data."
},
dataType: "html",
success: function( content ){
// Inject DELETE output.
$( "#deleteResponse" ).html( content );
}
});
</script>
</body>
</html>
Here, I am passing through both query-string and "data" variables to see how ColdFusion parses them on the server. If this were a POST request, query-string variables would go into the URL scope and data variables would go into the FORM scope. PUT and DELETE are handled differently. When we run the above code, we get the following page output:
As you can see, when it comes to PUT and DELETE HTTP requests, ColdFusion does not parse the content body into the FORM scope. It will populate the URL scope based on the incoming query-string; but, as far as the request body, that is left alone.
As a follow-up test, I wanted to try the same thing, except, rather than passing data as an Object / hash, I wanted to post the data as a raw JSON (JavaScript Object Notation) string. In the following code, notice that I am making use of JSON.stringify() when defining my jQuery AJAX request settings.
demo2.htm - PUT / DELETE Using JSON.stringify()
<!DOCTYPE html>
<html>
<head>
<title>Checking ColdFusion's Support For PUT And DELETE</title>
</head>
<body>
<h1>
Checking ColdFusion's Support For PUT And DELETE
</h1>
<h2>
PUT Response (Using JSON.stringify)
</h2>
<div id="putResponse">
<!-- To be populated dynamically. -->
</div>
<h2>
DELETE Response (Using JSON.stringify)
</h2>
<div id="deleteResponse">
<!-- To be populated dynamically. -->
</div>
<script type="text/javascript" src="../jquery-1.7.1.js"></script>
<script type="text/javascript">
// Make PUT request.
$.ajax({
type: "PUT",
url: "./api.cfm?dataValue1=Sent+in+URL",
contentType: "application/json",
data: JSON.stringify({
dataValue2: "Sent in Data."
}),
dataType: "html",
success: function( content ){
// Inject PUT output.
$( "#putResponse" ).html( content );
}
});
// Make DELETE request.
$.ajax({
type: "DELETE",
url: "./api.cfm?dataValue1=Sent+in+URL",
contentType: "application/json",
data: JSON.stringify({
dataValue2: "Sent in Data."
}),
dataType: "html",
success: function( content ){
// Inject DELETE output.
$( "#deleteResponse" ).html( content );
}
});
</script>
</body>
</html>
When you define the "data" property as a string value, jQuery will not apply any further processing to it. This means our JSON string becomes the outgoing request body. And, Since we are now passing a different type of content, we also need to provide the contentType header value, "application/json."
This time, when we run the page, we get the following output:
As you can see, this experiment yields basically the same results: the content body is left unparsed. The nice thing about using JSON as the transport format, however, is that we can now parse the JSON into native ColdFusion objects on the server using deserializeJSON(). This allows a very structured message to be passed from the client to the server (and then ultimately, from the server back to the client).
It looks like ColdFusion has excellent support for the four basic HTTP verbs - GET, POST, PUT, and DELETE. The question then becomes, what is the support for PUT and DELETE on the client? After all, there's no point in creating an API that your client can't use.
While I can't find any exhaustive list on browser support, the story seems to be this: the HTML Form element tends to support GET and POST. AJAX (Asynchronous JavaScript and XML), on the other hand, supports all four verbs - GET, POST, PUT, and DELETE. Again, I can't seem to find any actual list on this; but, people are saying that if the client supports AJAX, then it supports PUT and DLETE (via AJAX).
JSON.stringify() is a different story. While it is supported in all modern browsers, Internet Explorer (IE) didn't support it until IE8. Therefore, if you would like to communicate with your API using JSON packets, you'll probably need to check for browser support and inject a JSON shim if necessary.
NOTE: jQuery provides a jQuery.parseJSON() method for deserializing JSON strings on the client; it does not, however, provide support [yet] for serializing objects into JSON strings.
Anyway, I'm excited that ColdFusion supports all four core HTTP verbs. This just means that I can start creating more meaningful API architectures.
Want to use code from this post? Check out the license.
Reader Comments
You can also perform HEAD requests. This essentially "pings" the server and retrieves ONLY the HTTP headers for the requested page.
I used it in HTML/JS AIR applications when I needed to test for connectivity. Do a HEAD request and flip the networked flag based on the response.
@Andy,
The HEAD request is the one that that I don't really know what to do with, yet. It just seems like a strange verb. Is it supposed to get the HTTP headers only? Or is it supposed to get meta-data about the Resource as well. Confusing.
Is there any real benefit to using PUT or DELETE (yet)?
I could maybe see a request accept page that would handle DELETE differently than the rest (switch on CGI.REQUEST_METHOD), but is PUT really that much different than GET or POST? Maybe PUT inserts, and GET/POST just query for matching values to try to stop bots from inserting non-formatted data?
Meh, maybe I'm just overthinking this...
@Jason,
To be honest, I don't have good answers for that kinds of stuff. And, to the point, that is specifically why I *really* liked the REST API Design Rulebook by Mark Masse - he just cuts through the shoulda/coulda and says, "This is how you do it." More than anything, I just like the direction that it gives, which is to say "A" direction rather than "no" direction.
According to Masse, this is simply the way HTTP was designed to work. And, I suppose, unless I can think of a good reason to not do that, I'd rather just use whatever is considered a "best practice."
So, as someone who has also been frustrated by the kinds of questions that you pose, I guess I just like having a rulebook.
Josh Cyr posted this on Twitter just a little bit ago. Thought it was appropriate.
http://stackoverflow.com/questions/1619152/how-to-create-rest-urls-without-verbs/1619677#1619677
@Ben
According to RFC 2616 ( http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html ) HEAD should be identical to GET without a body. OPTIONS is another interesting method intended to be used to communicate the service options available.
PUT and DELETE can be repeated. The example I recall from a year or two ago was that if you're using PUT to create a page on the site, if for some reason it fails and you end up repeating it several times, you still end up with only one instance of what you were trying to PUT. If you similarly posted, you'd end up with as many instances as were POSTED. What's the word for that, idempotent or something?
It's why with REST, those two are great for the C and D of CRUD respectively, or so I understand it.
I've run into issues with OPTIONS, jQuery, and IIS hosted services. Maybe it was because it was an .ASMX affair as opposed to CF, but I kept getting 403 or 405. Has anyone had any experience with acting on an OPTIONS verb in CFC-land?
I have used the HEAD verb with unit-testing to verify that none of the links on a page (A HREF, LINK HREF, IMG SRC, SCRIPT SRC, etc.) are broken. It gets a lot less information than the full page, but that includes a status code like "200 OK" or "404 Not Found".