Sunday 21 October 2012

CFML: Organise yer apps properly

G'day
This is kinda a follow-up / continuation of the article I wrote the other day on how a request/response comes together, from the client browser initiating it, through to ColdFusion servicing it, and back to the client browser again.

It also stems from me opting to rebuild my PC over the weekend (started on Friday... it's pretty much done now: Sunday afternoon), and therefore reinstalling ColdFusion and getting all my apps up and running again. For those that had a laugh about me running Vista before, I've just upgraded to Windows 7, which garners less derision from those who... for some reason give a shit about that sort of thing.



Here's a good opportunity to thank my "shall-remain-nameless" benefactor (you will be reading this, and you know who you are) for putting me in a situation where I can afford to do things like fix my PC and upgrade it. Thanks mate.

And the third reason I'm writing this is because I became annoyed at some advice I read in some framework docs the other day (I'll not name it, but regular readers will be able to work it out).

It was the latter that got me thinking / grumbling, and made me decide to write the previous and this article.

OK, so what am I on about?

Right... your CFM files don't belong in your web root. Don't put them there. And if you're responsible for writing / documenting a framework stop bloody telling people to put the files in the web root! Equally, one should not be mixing one's source code with the application running the code.

There are four significant "moving parts" in a web application (for the purposes of this article):
  1. The web server software, eg: Apache HTTPD
  2. The web root, holding web-browsable files such as welcome files (index.cfm), images, JS and CSS file
  3. The ColdFusion application server (or Railo/OpenBD): the thing that processes the CFML code
  4. The ColdFusion application root: where all the CFML files go.
They're four different things, and they shouldn't be all lumped together, or co-habitate.

Let's back-up a bit. Let's think about a word-processing application, and your documents created by the word-processing application. The application might reside in a directory thus:

C:\Program Files\My Word Processor\

Within that directory, there's a bunch of stuff (300MB in the case of Open Office... well that's a whole office suite I guess), and somewhere in there is myWordProcessor.exe. That's the app.

When you create a document with My Word Processor, do you keep all your docs in the application directory? Of course you don't. That'd be stupid. You've got a "My Documents" (or facsimile thereof) directory elsewhere in the filesystem where all your important & volatile stuff goes. Obviously there's a distinction between where the app lives, and where the data created with the app / used by the app resides. There's a coupla reasons for this, amongst which is that application files don't change much, and don't need special attention like being backed-up regularly and versioned and that sort of thing. Plus the app vendors generally expect their apps directory to not be cluttered up with other miscellany, plus quite often the user permissions on app directories are different from data directories. All good reasons to not mishmash stuff together.

So why then, when I install CF10 do I end up with this:


C:\apps\adobe\ColdFusion\10\cfusion\wwwroot <--- you need to stick files in here for them to be served by CF.


That's bad setup, innit?  Your files should be distinct from your application. yet by default, the CF installer lumps them all in together. Thanks for that.

But - fair dos - if I didn't install the developer version, and opt to use the internal web server, then the CF installer will set its "web root" (more correctly the "CF root", I think) into wherever the web server sticks its files, C:\inetpub\wwwroot or something like that. That's better, but it's still daft.

This is another thing. Even Adobe don't uniformly or coherently distinguish between two rather different notions: the web root (which is where the web server serves stuff from) and the ColdFusion root - similar to the web root, but specific to ColdFusion serving CFML resources. I've worked with CF people who are more "senior" to me that just didn't get that there is a difference between a web server virtual directory and a ColdFusion-mapped directory. They're two different things, for two different purposes. Even after I explained it, I still just got a mostly blank look (which is an indictment of my explanation as much as anything else, I guess!).

On the whole, CFML files are not designed to be web accessible. Well unless you're coding style is like something out of last century I guess. But even then you've probably at least broken your file structure down into header.cfm, main.cfm and footer.cfm (etc). And of those: only main.cfm is actually intended to be web-accessible: you won't expect poeple to browse to header.cfm do you? No. So why have you got it in a web-browsable directory?  If you're using a framework (even yer own one), then almost none of your CFM files are intended to be served by the web server. They're intended to be used to be called by ColdFusion to compose a response to a request. So why then is the default practice to home all CFML files in the web root?  That's stupid. And lazy. And inviting hackery by dramatically increasing the vectors for attack (where anything exposed to the outside world is a potential vector).

So don't do that.

What you should do (and this is just my approach, but it'll be a variation on the theme other people employ) is this.

Web server application:
C:\apps\apache\httpd\2.2\[etc]


ColdFusion server application:
C:\apps\adobe\coldfusion\10\[instance]\[etc]


Web app directory:
D:\webapps\myApp\ <--- ColdFusion root. CF stuff goes here. Not web browsable.
D:\webapps\myApp\www <--- Apache doc root. Web browsable stuff goes here.


Within the www dir, I have:

// Application.cfc
component extends="com.daccfml.helloworld.Application" {

}

<!--- helloWorld.cfm --->
<cfinclude template="/com/daccfml/helloworld/inc/helloWorld.cfm">

Where the HelloWorld.cfm is such a contrived example it's a bit daft, but here it is:

<cfset message = "Hello World">
<cfoutput>
<html>
<head>
    <title>#message#</title>
</head>
<body>
    <h1>#message#</h1>
    <p>#message#</p>
</body>
</html>
</cfoutput>


Note the pathing for the extends and template paths. Within the myApp dir, I have this file system sub-structure: D:\webapps\myApp\com\daccfml\helloworld\, and in there I have all my CFC packages and include files (as per this example), and whatever else I need for the app, including the main Application.cfc, thus:

component {

    this.name = "HelloWorld";
    this.mappings    = {
        "/com/daccfml/helloworld" = getDirectoryFromPath(getCurrentTemplatePath())
    };

}

So that creates the mapping the <cfinclude> then uses.

TBH, I think I have delusions of grandeur in namespacing my helloWorld app with the old "reverse domain" approach to packaging stuff, but I def will have other apps from other vendors in there (eg: ColdBox, FW/1, MXUnit etc), so as I'll be namespacing that stuff out of my way, I do it with my own code too. It could equally have just been D:\webapps\myApp\helloworld\. The point is, it's organised, and it's not in the web root.

Configuring this does require a bit of horsing around upfront, but only a bit.

Firstly one needs to tell ColdFusion to use a different directory for its code. I had no idea how to do this in CF10 (I could do it with my eyes closed - weird approach to doing things, I know - in JRun, but I know stuff-all about Tomcat and fairly content to keep it this way), but Google pointed the way to Ryan Anklam's blog; this entry in particular. What I needed to do is open-up C:\apps\adobe\ColdFusion\10\scratch\runtime\conf\server.xml and modify what was there to say this instead:

<Host name="localhost" autoDeploy="false" unpackWARs="true" appBase="webapps">
    <Valve pattern="%h %l %u %t &quot;%r&quot; %s %b" directory="logs" prefix="localhost_access_log." className="org.apache.catalina.valves.AccessLogValve" suffix=".txt" resolveHosts="false">
    </Valve>
    <Context path="/" docBase="D:/webapps/helloWorld" WorkDir="C:\apps\adobe\ColdFusion\10\scratch\runtime\conf\Catalina\localhost\tmp" aliases="/CFIDE=C:/apps/adobe/ColdFusion/10/scratch/wwwroot/CFIDE,/WEB-INF=C:/apps/adobe/ColdFusion/10/scratch/wwwroot/WEB-INF" ></Context>
</Host>
There's more in that file, but that can be left as is. This stuff is down the bottom (for me, and this was a default CF10 instance install, so probably will be the same for you). Note that my instance name is "scratch". The grey stuff was already there, I've just included it to contextualise things (this wasn't completely clear in Ryan's article), it was just the black line I added, and the highlighted stuff is what I changed from Ryan's example.

When I restarted CF, it was serving files from D:/webapps/helloWorld. And CFAdmin still worked (this is all via the default web server still).

Next I added a new virtual host in Apache:

<VirtualHost *:8080> <!-- I need to reconfig so this is just 80, oops -->
    ServerAdmin adamcameroncoldfusion@gmail.com
    DocumentRoot "D:/webapps/helloWorld/www"
    ServerName helloworld.local
    ErrorLog "logs/helloworld-error.log"
    CustomLog "logs/helloworld-access.log" common
    
    <Directory "D:/webapps/helloWorld/www">
        Order Allow,Deny
        Allow from all
        Options Indexes FollowSymLinks
    </Directory>    
</VirtualHost>

Now I do not know if that's the best way to configure that - I am no expert in Apache by any measure, and I only monkey with things when I'm setting up a new install. I spend a lot of time copying and pasting stuff I know to work, rather than explicitly knowing why it works. This is in [apache dir]\conf\extra\httpd-vhosts.conf, which is only enabled if one un-comments the reference to it in [apache dir]\conf\httpd.conf.

This means that via Apache only that www dir is web browsable. And only Apache will be serving stuff to the general hoi polloi. So none of the D:\webapps\myApp\com\daccfml\helloworld\ is accessible to the outside world, nor will any of the D:\webapps\myApp\org\coldbox stuff be when I get around to sticking that in there too (it was gonna be today, but I'm fed-up of computers now, so I think this will be the last bit of "work" I do today).

And then I do the usual horsing around with WSConfig.exe to tell Apache and ColdFusion to play nice with each other.

People have used the excuse with me that it's just a lot harder to configure things this way, which is why they just slap all their shite in the default location. Please. All the above takes about 5min. And one only has to do it once.

I was speaking with someone about the advice framework vendors give to just slap all their stuff into the web root and this saves doing a CF mapping to access it all. They maintained "it's just easier that way". Well I'm sorry - and I did say this to them - "easier" does not equate to "good" nor does it equate to "responsible advice to give to someone". They questioned why it mattered one way or the other and I pointed out the potential increased security risk of having unnecessary resources exposed to the outside world. Plus it's just sloppy to advocate this. I mean... would you just save all your document files and downloaded attachments and the like on your desktop? No. Why not: it's easier?  Yeah, but it's just dumb and no-one would do this. Jeez... even my folks - in their late 70s, and didn't touch a computer until they were in the mid 60s - don't do that. They know how to organise stuff. Kinda. Well they're not a great example really, now that I think about it, but still: anyone who knows how to use a computer doesn't just slap all their stuff into one directory and go "tada!  Easy!"

I also think people such as Adobe (/ Railo / whatever), framework "vendors", and mouthy busy-bodies who have been around the block a few times (ie: people like me who blather on about stuff) who are the people who other people look to for advice have a certain burden to advise people how do do things in the right way. Not just any old way. So that's why I wrote this. Being mouthy, basically.

If anyone has any thoughts about this, how I can fine-tune things or just wanna offer-up how they go about things if it's at odds with my advice here: please let me/us know. The more info the better.

Righto.

--
Adam