Model-Glue Short URLs on the Cheap

I've blogged before about short, SES style URLs and what I use. (To summarize - I'd either recommend URL Rewriting in Apache or IIRF for IIS.) What if you can't use a web server side solution? You're left with using a solution I used for BlogCFC, namely appending values after the file name and using CGI.PATH_INFO to parse it. Here is a simple example.

CGI.PATH_INFO is a CGI variable that will represent any information found after the filename in the URL. Look at the URL in the browser. Notice the index.cfm is followed by a / and then various bit of information. What I did for BlogCFC was create a specific pattern, and then wrote some code to parse that pattern and load various groups of blog entries (or A blog entry) based on the value. I could have also done something a bit more generic of the form:

/name1/var1/name2/var2

In this pattern, I've changed name1=var1&name2=var2 to a simpler list of name/value pairs. We can use the same pattern with Model-Glue, but since Model-Glue uses an event value, we need to preface it with the event to run. So the pattern I will use is:

/event/name1/var1/name2/var2

I opened up my Application.cfm file and added the following code:

<!--- get the info ---> <cfif len(trim(cgi.path_info))> <!--- From Michael Dinowitz ---> <cfset urlVars=reReplaceNoCase(trim(cgi.path_info), '.+\.cfm/? *', '')> <cfif urlVars is not "" and urlVars is not "/"> <!--- remove first / ---> <cfif left(urlVars, 1) is "/"> <cfset urlVars = right(urlVars, len(urlVars)-1)> </cfif> <!--- Event is first item ---> <cfset url.event = listFirst(urlVars, "/")> <!--- strip it off ---> <cfset urlVars = listRest(urlVars, "/")> <!--- now build name/val pairs ---> <cfloop index="x" from="1" to="#listlen(urlVars,"/")#" step="2"> <cfset name = listGetAt(urlVars, x, "/")> <cfif listLen(urlVars, "/") gte x+1> <cfset value = listGetAt(urlVars, x+1, "/")> <cfelse> <cfset value = ""> </cfif> <cfset url[name] = value> </cfloop> </cfif> </cfif>

I'm not going to pick over this too much as you can see it is just string parsing. Basically I get the information after the /, grab the event as the first item, and if anything else is left than it is treated as name/value pairs. Also note that I support a name without a value. This would be useful for passing a "flag" type setting.

As a practical example, instead of using this URL:

/index.cfm?event=loadArticle&id=5

I can use this:

/index.cfm/loadArticle/id/5

For print format, I could use this:

/index.cfm/loadArticle/id/5/print

And to make it even nicer, I could include the title of the article in the URL. This would be used to help search engines, but have zero uses in the controllers:

/index.cfm/loadArticle/id/5/Dharma-Controls-All-Feed-The-Swan

Again - let me be clear - your best solution would be to use URL Rewriting, but this is an alternative you can use if that is not an option.

Archived Comments

Comment 1 by Greg Nilsen posted on 11/7/2006 at 8:38 PM

That's more or less what I've been doing for almost a year now over at my site, though I don't think my code is quite as long. I like doing it that way because it's very flexible.

Comment 2 by Michael posted on 11/7/2006 at 9:53 PM

I recall there being an issue with IIS lockdown blocking these requests due to the period in what it initially believes is the directory name. Possibly addressed in newer versions (allowing a single period in a directory but rejecting two), I really don't know, but if anyone runs into a problem handling this request it may be relevant.

Comment 3 by Greg Nilsen posted on 11/7/2006 at 10:25 PM

Michael,

I've never run into that problem with IIS, but I am running my development on an WindowsXP platform. Maybe it was an issue in older versions, but shouldn't be if you're up-to-date.

Comment 4 by Michael posted on 11/7/2006 at 11:13 PM

Just wondering about the 2nd cfif:

<cfif urlVars is not "" and urlVars is not "">

It looks like the same test twice.. ?

Comment 5 by Rob Gonda posted on 11/7/2006 at 11:46 PM

Despite the fact that IIRF is free, I would advice to go with ISAPI Rewrite. It's well worth the few $$. One of the main features that IIRF didn't have was the [U] directive -- unmangle logs files, meaning, log the request prior to the rewrite ... which was essential for me.

Comment 6 by Raymond Camden posted on 11/7/2006 at 11:54 PM

Um, it was a test Michael. ;)

The second condition was meant to be "/". Going to fix now. Thanks!

Comment 7 by Raymond Camden posted on 11/7/2006 at 11:58 PM

In the release notes, I do see a U directive Rob. So maybe he has added that in?

Comment 8 by Rob Gonda posted on 11/8/2006 at 12:15 AM

It may/probably have been added. I remember chatting with him when you first blogged about it and he said it wasn't difficult to add. Good to know; danke,

Comment 9 by Sam Farmer posted on 11/8/2006 at 12:51 AM

Very nice Ray. Got it implemented already! Love the use of the step attribute in cfloop.

Comment 10 by Greg Nilsen posted on 11/8/2006 at 4:16 AM

Ok, now that I'm home I could get at my code. I've posted what I've been using on my blog here:

http://further.gregnilsen.c...

Comment 11 by Sean Corfield posted on 11/9/2006 at 9:56 AM

I posted the code for how I do SES URLs on my (Fusebox) site back in September last year:

http://corfield.org/entry/S...

It works with any framework (or non-framework) because it's included in Application.cfm (or Application.cfm).

Comment 12 by Chris posted on 12/2/2006 at 10:46 PM

I tried implementing the code into my Application.cfm file but it doesn't do anything. Am I missing something?

Comment 13 by Raymond Camden posted on 12/2/2006 at 10:53 PM

Chris - what do you see? You should also cfdump url and see if it is getting populated correctly.

Comment 14 by Chris posted on 12/3/2006 at 6:33 PM

Ray,

When I do the dump, it is getting populated. Do you call the url like this: #viewstate.getValue("myself")#theevent

Comment 15 by Raymond Camden posted on 12/4/2006 at 2:44 AM

No, you need to make the URLs yourself. Unless you make a helper function for it.

Comment 16 by Ross posted on 12/24/2007 at 3:27 AM

I tried using this code. It worked mostly. Where I have trouble is in the modelglue.xml file. When an event fires and I point to another event it seems to just the events together.

For example,
The link reads "index.cfm/page.login". The user clicks it and goes to the login form.

The login form submits to "/index.cfm/action.login" and fires an event "user.login", if successful the result from that event does "page.index". The resulting URL looks like "http://localhost/index.cfm/index.cfm?event=page.index"

Any idea how to properly redirect in the result?

Comment 17 by Raymond Camden posted on 12/24/2007 at 6:27 PM

You said your first link was to index.cfm/page.login. It needs to be /index.cfm/page.login. Did you use that?

Comment 18 by Ross posted on 12/24/2007 at 8:06 PM

Thanks Ray, that was part of my problem.

There is one other thing. In a result, depending on success or failure, I fire another event. When I use "redirect=true" (<result name="success" do="page.index" redirect="true" />), the URL gets messed up again to http://localhost/index.cfm/index.cfm?event=page.index

It works if I leave the redirect part out. I have tried different things in the "do" (/index.cfm/page.index, /page.index) part but I get errors.

Is this just the way this works?

Comment 19 by Sean Corfield posted on 12/25/2007 at 12:29 AM

@Ross, yes, redirects use regular URLs. That's how it's built inside Model-Glue. Since people are unlikely to bookmark the result of a form post (which is when you mostly use redirect="true"), I don't see that as a big problem.