A follow-up to my adventures in web app infrastructure entry. I'm not using CGI::Application::Session anymore; I decided I don't want to resort to any weird persistence schemes because of what they might do down the road. (Ie how do I persist a database connection? Especially if that connection is not directly under my control, but under the control of some other object I'm using?)
Instead of using persistence, I'm going to have a mod_fastcgi interface to all the functionality. As I understand it thus far, mod_fastcgi just opens a single instance of your script and feeds requests into it. Essentially your script becomes a little server.
This gains me a lot: I can open a single database connection at the beginning of the fcgi script, or perform any other time-consuming initializations, and then reuse this instance data when I start servicing requests. In fact I decided to tuck all this top-level instance data away in an App class.
I looked into mod_perl, but it seemed much more intrusive. In order to open a persistent database connection, people recommend using Apache::DBI. Well I don't want Apache::DBI appearing everywhere in my code. What if I want to port the web app to a different web server? Moreoever, it would require modifying Tangram itself, which opens a DBI connection behind the scenes.
Too intrusive, and too complicated. mod_fastcgi gets huge points for simplicity.
At the same time, I wanted to retain a conventional cgi interface to things. In particular I've had problems getting mod_fastcgi to run under cygwin apache, and that would make it a lot harder to test changes locally. (Which I always, always set up before undertaking any serious development.) So I wrote a CgiApp class that inherits from CGI::Application and manages an instance of my App class. I can even use this CgiApp class in my mod_fastcgi interface script, by creating a new instance of the CgiApp class on every request, and handing it the FCGI request object & the single instance of the App.
This App class, now, makes no reference to "run modes" or cookies or sessions or anything else web-specific, so it could be used in a lot of other contexts. One of the first things I'm going to do is write a command line interface which uses it. The benefits of such a thing are huge. If you've ever found yourself debugging (gag) via a cgi interface, you know what I'm talking about. When you're stepping through code by refreshing your browser there's something wrong.
A command-line interface also makes automated testing a breeze, and keeps power users (like the developer himself or herself) happy :).
I'm currently working on a small web app; the details are unimportant and uninteresting. What is important & interesting is that it is the pilot for a number of my ideas on how to write a web app via a bunch of Perl modules that act as infrastructure: Tangram, Class::Tangram::Generator, XML::Simple, CGI::Application, and CGI::Application::Session. The goal is basically to write as little code as possible and still get the job done. And, of course, to separate code from presentation details.
Tangram is working like a charm; the impedance mismatch between my objects and the database is effectively taken care of. Aside from building a connection string I don't really worry about the database. When I need to add extra logic to classes, I inherit from Class::Tangram, and objects of my class automatically spring to life as they are pulled from the database.
XML::Simple handles half of the object / presentation impedance mismatch. With the right settings, XMLOut() turns my objects into very clean xml that is passed through an xsl stylesheet to become an xhtml page.
CGI::Application handles flow between different "run modes," which basically correspond to the screens a user will see. With it I have a single entry point--a cgi script--that instantiates and runs a CGI::Application object. This object switches between run modes and handles persistence via CGI::Application::Session.
There are some small things I haven't yet figured out. E.g., I don't know how I will elegantly get post data back into objects. That's the other half of the object / presentation mismatch.
There are also some performance issues. I'm not obsessing about them right now because, as Knuth famously noted, "Premature optimization is the root of all evil." They are tractable & I will solve them when the time comes.
With objects at the top of the object hierarchy, bringing them over in full from the database side and then pushing them through the page will be pretty wasteful...often, you just need to display data in the first few layers. So I need to figure out a pattern for pruning object hierarchies, and it needs to be part of the database infrastructure. Also, right now I'm not persisting the database connection, so a new one is made on every page access. That's slowing things down a bit.
If you are frequently editing xml in emacs do yourself a huge favor and get nxml mode by James Clark, author of the expat xml parser. Look for the latest tarball in this directory. It has everything you'd expect: very granular syntax highlighting (not turned on by default), auto tag completion, auto indentation that you can run over your whole document, schema and dtd validation, etc. After only a few days I can't imagine living without it.
So I'm developing a web app using cygwin perl and iis, when I encounter a scary error message like the following...
That's right, perl itself just died on a fork() here. Something to do with dlls replacing each other at the same base address upon load. The solution is to use Jason Tishler's rebase utility, which you can download here. Just exit out of everything cygwin until you're left at a cygwin bash prompt, no other processes running, and run rebaseall.
Watch out: cygwin postgresql 7.4.2 is built against cygserver rather than cygipc. I upgraded from postgresql 7.4.1-3 and everything was smashed to little tiny bits, Signal 12s were getting sent to everything including initdb. So I downgraded.
Of course, you would never want to use the cygipc method in a production environment. It's simple to set up basically because it wouldn't be very secure as a network daemon (no suid down to a postgres user). But, who uses cygwin as a production environment anyway? I just like having a local database server in case I do offline development.
Something to watch out for when doing proprietary development: marketing people like to change the name of the product. If you haven't planned for this possibility, you're probably in for a nightmare, especially if your codebase is big...weeks of muttering sed incantations over your source files.
The solution I've come up with is the following. Always, always pick an internal codename for your project that has absolutely nothing to do with what your product does, or who it does it for. Refer to the project by its codename in all source comments, source filenames, etc.
Now abstract out the product-specific parts. Write a skin class that retrieves the product name, chooses logos, and determines general look and feel. Leverage inheritance: override this base skin class for each product.
If you have multiple products which all behave slightly differently, use inheritance again to specialize for a specific product.
As for filesystem layout, demote product-specific code into leaf directories. E.g., if you have a subsystem "renderer" then stuff general renderer code into src/renderer, and stuff code specific to the acme product into src/renderer/acme.
Subversion as you may know is the heir apparent to cvs, and is delivering me from years of fear and trepidation because it actually does filesystem versioning. With subversion I don't have to spend days thinking about my source tree layout before that initial import, or obsessing about what to name a particular file. Which, I can tell you, I've done an awful lot of as a cvs user.
However this bug is bad. Basically, subversion does not implement "true moves;" rather, currently move = copy + delete. (Copy node to new location, delete node at old location.) A consequence of this is that doing filesystem restructuring on a branch results in chaos when you try to merge that branch back to the trunk, especially if any of the moved files have since changed on the trunk.
And right now, I need to do just that. Sadness. Well, at least I decided to investigate subversion's filesystem merging before plunging ahead with this.
In the past I've been frustrated by incompatibilities between the ssh keys generated by PuTTY and those by OpenSSH. For a while I kept around two different sets of keys. Today, however, I discovered that puttygen can import your OpenSSH private key. Then you save the private key again in PuTTY format, which lets you load it in Pageant, and there you go.
We ran into a little problem today when we were trying to set up TortoiseSVN to use PuTTY's plink, so we could access our subversion repository over an ssh tunnel using the svn+ssh mechanism. Of course we had Pageant running, a key loaded, and all that good stuff. We pointed TortoiseSVN to plink via Settings -> Network -> SSH client. On checkout, a plink command window would open and just sit there.
Eventually discovered that plink needed the username passed in, so SSH client should be set to something like
You could also use plink's -load option to specify a saved PuTTY session to open, if you had more complicated settings.
And I thought cygwin textmode mounts were cute. Well, at least they seemed to work: cygwin cvs would checkout text (with crlf conversion) and bin (without crlf conversion) files just fine. My windows build tools wouldn't die when run on this sandbox. And I could do all those neat unixy things in that same sandbox. Turns out this nice behavior is the result of a nasty hack in cygwin cvs.
Apparently, cygwin cvs opens all files in binmode, regardless of whether it's on a textmode or binmode mount. Then it examines the mount. If it's textmode and the file is a text file (doesn't have -kb set) it does it's own crlf conversion then and there.
Now I'm trying to migrate to subversion, which doesn't yet have this same nasty hack, and doesn't even have an official cygwin package yet. Subversion must do a textmode open on every file, because even binary files are getting crlf conversion via the cygwin filesystem layer. (As I write this I realize I still don't understand fully. It is amazing how much confusion can arise from such a stupid f'ing little problem.)
Here's a few more tips on keeping your freebsd ports up-to-date. If you've ever upgraded a port manually via "make deinstall && make reinstall" you may have gotten an error message like "libfoo.so.5 not found." This is because another port was linked with the old version of the foo port.
So, do you have to manually track down all dependencies of a port whenever you upgrade it? No. The solution is to use portupgrade. Install portupgrade from ports/sysutils. Then rather than manually upgrading port foo, run
The -r argument recurses over foo's dependencies and brings them up to date if necessary.
To see what ports are installed, and their version numbers, use the package database. Type "pkg_info."