Skin Spider : The Page Equivalent Url

I am a firm believer that most pages in an application should have a URL equivalent. Meaning, you can put some URL in the browser and get most any page in the system. I say "most" any page, as I draw the line at things like multi-part forms and form processing (as I view those as "phases" of a given page, not distinct pages). While this is a rather simple feature of the application, it will become very important for further updates where our redirection-navigation depends on where the user came from - but that's for a future update.

Based on our current application architecture, building the page-equivalent Url is rather simple. All we need to do is use the server name, the ColdFusion template being called, and all related variables in the query string. The server name and the ColdFusion template can be taken directly from the CGI object or the underlying request object (which I will discuss later). The query string can be taken directly from out REQUEST.Attributes object. Remember, REQUEST.Attributes is a unioning of the FORM and the URL scopes. The way we have it set up, the REQUEST.Attributes should encompass every location-defining query string variable (plus other stuff as well).

StructLib.cfc ColdFusion Component

To start with, I updated the ColdFusion user-defined library (UDF) for structures, StructLib.cfc. I added a ToList() method which takes a structure of simple values and converts it into a dual-delimited list. To create a query string type value, you might pass in the URL struct, and "=" and "&" as the respective delimiters.

You will notice that the function needs simple values for the string concatenation of the struct values, but I am not doing any validation (ie. skipping over non-simple values or throwing an error). My theory on validation is as such: if you pass in invalid data, the system is going to break in one way or another. If I don't do any validation in the method, the ColdFusion runtime will throw an error when it tried to do concatenation (such as if the passed in struct had child structs or arrays). On the other hand, if I did do validation, I might throw a custom error explaining that that the struct can only handle simple values.

What's the difference? The only real difference is that if I do error handling / validation, then I can have a more informative error message. But is this worth it? I argue that it is not. By doing my own validation, I am adding the overhead of the validation to the existing validation that ColdFusion already does at run time. This is overkill. If it's going to break, it's going to break. But I don't want to rant about this... it's personal.

PageData.cfc ColdFusion Component

Once we figure out what the page-equivalent Url is, we need a place to store it. The existing REQUEST.PageData object just felt like the natural place for this. It already houses information about the rendered (X)HTML page, why not also give it information about the request location of the page. I am not 100% satisfied with this, but that stems mostly from the fact that I am also not 100% satisfied with the PageData.cfc object itself. But regardless, there is now a private variable for the Url in the PageData.cfc private variables. There is also a GetUrl() and a SetUrl() method.

Application.cfc ColdFusion Component

The setting of the Url into the REQUEST.PageData object takes place during the pre-page processing portion of the Application.cfc ColdFusion component. Specifically, it happens at the end of the OnRequestStart() application event method. As I explained above, I am building the Url based on the underlying request object and the REQUEST.Attributes object.

To go into just a bit more detail on the Url construction, I start off by getting the Url object from the underlying request object (via the Page Context). This does not return a string object; this returns a Java StringBuffer instance that contains the constructed URL parts (ie. HTTP Protocol, domain, port, authentication, ColdFusion template, etc). To this, we need to append the query string values (a function of the StringBuffer). The query string value we get by converting the REQUEST.Attributes struct to a dual delimited list (using the ToList() method discussed earlier). Then, we convert the string buffer to a string to get the actual, full page-equivalent Url.

As I mentioned before, this will not behave nicely on pages that require processing or contain HUGE values in the REQUEST.Attributes struct. But, I am not going to worry about that for the moment.

This does, however, raise an interesting issue; I have created a generic Struct function, ToList(), but I am using it here for a very specialized feature. Not only should I be worried about large text areas, I should also be worried about Url Encoding. Should these be optional arguments in the ToList() function? Or, Should this be a different function all together - perhaps part of the PageData component itself? There is fine balance between being open to extension and being overly complicated (at the risk of not duplicating code). For now, I am going to use the generic function and just hope that Url encoding and large text values (that might go past the allowable limit for Url length) don't cause any problems. Not the best plan of attack, but I don't want premature optimization of a system that isn't throwing errors.

Another thing to notice is that none of the meta-form data is stored in the URL. This is the reason that we strip it out of the FORM scope after we are done using it (after it has been moved into the REQUEST.MetaForm object and before the REQUEST.Attributes object is created). Some people might argue that this is an improper analysis of what a page url is... but I argue that the REQUEST.MetaForm holds information about the "phase" of the page. This should NOT be part of the page url. The url should not indicate anything but initial page data. You wouldn't want to link to the "processing" of a page - that just wouldn't make any sense.

So that's the small update. But this now covers the prerequisites for the next update regarding referential-relocation (as a post-form-processing Url). Hopefully that shouldn't be too complicated.

I am the co-founder and lead engineer at InVision App, Inc — the world's leading prototyping,
collaboration & workflow platform. I also rock out in JavaScript and ColdFusion 24x7 and I dream about
promise resolving asynchronously.