Guide to Bulding Web Applications

Overview

This document should serve as a list of do's and dont's to web programmers everywhere. I've seen many mistakes in my time, and hopefully this will allow you to learn from them without having to make them yourself.

I've yet to see a resource which combines all these crucial points into one document. Be wary of what you read in books, as they will lead you to take many shortcuts. Remember, the author's objective is to sell a book, not to make you a responsible web programmer!

Front End Tips

Use a template engine for your frontend.

Using a templating engine allows you to write XHTML code for an audience of web designers. Designers may not know the code which your business logic is composed of, and this allows you to have someone (if not you) change the frontend later.

Be sure the template engine you choose will not break an XML validator. Frequently, the best way to do this is to pick a templating engine which uses comments for its control flow and variable output.

Alternatives exist, including making specialized tags and entities to represent control flow and variable output. Generally this will make your program more dependent on the templating engine and the languages it supports. Other specialized features will do the same. For instance, using Smarty will marry you to PHP forever.

This is almost a prerequisite for internationalization of a large site. The alternative is to fork the business logic when you start changing the presentation, and this could be the start of a maintenance nightmare.

As a good example of a HTML templating engine, check out Perl's HTML::Template module on CPAN. Its functionality is enough to get the job done, and another engine could easily be written to parse the same templates.

Systems to avoid would include ASP, PHP, JSP, and pretty much anything with an acronym ending in a P. These all mix the business logic into the presentation, making it difficult for people who aren't cross-disciplinary to work on the code.

Use XHTML, not HTML.

XHTML, when used properly, can be backwardly compatible with HTML. The difference between the two is that using XHTML, you can use a variety of existing tools to validate and transform your document.

For instance, if you wanted to quickly take every definition-list term in the document and add (deprecated) font tags, you could do so using an XSL stylesheet.

HTML can be converted to XHTML by including a proper DTD, such as those found here. You must also make sure each tag has a closing tag, which you can abbreviate like <br /> (notice the space before the slash, necessary for some older browsers). In some places, this will not work, and you will have to explicitly include an opening and closing tag (<script> is an example). Make sure all attributes are included in quotation marks (such as <script src="script.js">), and that the document includes one top-level element, the <html> tag.

Keep the rest of the world in mind.

Aside from using a templating system, there are many other things that should be kept in mind, if there's a chance that an application will be internationalized. Cultural differences, different calendars, different number formatting, different units, different text direction, and different character sets all need to be taken into account for a user to feel at home.

In general, examine every piece of information which will be visible to the user. Is that information likely to change in an international environment? If the answer is yes, you should probably spend some time thinking about localization.

Java takes the lead concerning localization, having features built into the API, and even having a special toolkit to make the process easier. At the very least, a language with UTF-8 support will make life easier when trying to localize.

Read, understand, and worship Jakob Nielsen.

Jakob Nielsen is the guru of website useability. This man, arguably more than any other, understands what it takes to make your information salient to users of this medium. His experience comes from experimentation: using eye-motion cameras, administering surveys, etc.

His website, http://www.useit.com is a repository of useful tips relating to making your front-end accomplish its goals. He gives a holistic view of the web experience, relating to a number of satellite topics, such as HTML email newsletters, Flash animations, and PDF files.

Business Logic Tips

Validate on the server first, and the client second.

Taking data that hasn't been validated as being in the correct format is as irresponsible and dangerous as telling your kids to take candy from strangers. At best it causes your users inconvenience as your program returns a 500 error.

At worst, however, it can be bad for your job and a terminal illness for your business. Failing to validate at the data's entry point into the program, and then using the data to construct a query in an irresponsible manner, can allow a malicious user to take advantage of the permissions given to the account via which you are executing that query. If that account allows the dropping of tables or deletion of rows, you could lose all your data in one fell swoop.

Writing Javascript validation code as your only line of defense is roughly equivalent to telling your kids they should only take candy from strangers who aren't wearing a red hat. The very first thing a malicious user will do is to take control of the query string themselves, circumventing Javascript altogether. Think of Javascript validation as providing a convenience for your users, so that they don't necessarily have to take another trip to the server. Therefore, write this only after having written validation code on the server, which is a necessity.

Pages should handle their own form submissions.

This allows pages to validate the contents of their fields, and serves to encapsulate the scope of a code change. If this rule is not followed, generally two pages have to be changed with the change of one form field. Not only that, but the submission will have to be recreated in some form or another when bouncing the request back to the initial page. Otherwise, the page will have no method of telling the user why their input was not accepted!

Generally, to go to the next page in a sequence of pages, the web application should use the Status and Location headers to force the browser to redirect. If you are using NPH or some type of server module, you will be forced to set the status in a different way.

This helps make the spaghetti a little more manageable.

Know your encodings.

For instance, what's wrong with the following bit of XHTML?

<a href="http://speakers.com/?spkr=1&amp=2&sub=3">click here</a>

If you didn't guess that the amp query string parameter would translate into a literal ampersand (directing you to http://speakers.com/?spkr=1&=2&sub=3), you should work on learning your encodings better. All attributes which you produce programatically should be XHTML-encoded. You should also make an easy way for yourself to produce URIs and URLs.

Don't use your middle-tier language in such a way as to marry you to a database vendor.

The most notable example is PHP, where in order to switch from one database to another, you have to alter all occurences of the connection, execution, and disconnection functions. For instance, switching from MySQL to PostgreSQL would require you to change all occurrences of the mysql_query function to pg_query.

This can be circumvented by writing wrapper functions for all these calls, or using a global find/replace utility, but the fact of the matter is that it encourages irresponsible use. Therefore, be wary.

Session tracking tough, but vital to the sanity of a web programmer.

HTTP is a stateless protocol, meaning that each request the server makes is free of the context of any other request. By default, at least. Unfortunately, in order to accomplish a simple task, such as putting things in a shopping cart on one page and checking out with them on another, state information is needed.

Lots of people think of cookies as an evil mechanism designed to infringe upon their privacy. Many don't realize that cookies are just one convenient mechanism used in session tracking. Cookies can be circumvented by automatically inserting a hidden form field before the </form> tags on each page, just before output. You would also need to rewrite all links referring to local resources so that they include a similar query string parameter.

Unfortunately, due to some restrictions in Microsoft Internet Explorer, you cannot use the HTTP referer to carry a session variable, as it seems to get lost on a POST request. Otherwise there would be yet another way to circumvent cookies (although an inefficient one).

The idea is that the session variable will contain a value unique to the specific user of your site. You may then look things up on your server using this value (for instance, their shopping cart in the above example). Though not required, a language supporting serialization (such as Perl, using the Storable module) makes life easier.

Whether you accomplish session tracking by one mechanism or another, you should keep a bare minimum of information on the client's machine. This will lower the amount of bandwidth required to communicate with the client on each request/response. It will also prevent you from manually having to carry variables across multiple pages.

It's also important to keep the session variable secure, as its contents may be highly sensitive (credit card numbers, for instance). You should store a login and password within each session, and write your accessor routine in such a way as to fail accessing the session contents unless the proper credentials are provided. This will disallow someone gaining access to the user's browser history or cookie jar and trying to access previously entered information.

A less secure alternative is to make the session variable a key which is extremely difficult to guess, probably on the basis of the process number, time, and/or a very large random number. This still poses a security threat as the session variable can be stored by the browser in a publicly accessible way.

There are three basic kinds of authentication: something you know (password, PIN code, secret handshake), something you have (door key, physical ticket into a concert, signet ring), and something you are (biometrics). Good security uses at least two different authentication types: an ATM card and a PIN code, computer access using both a password and a fingerprint reader, a security badge that includes a picture that a guard looks at.
-- Bruce Schneier

In two-factor authentication, a session variable should be considered something you have. While this is good enough for systems like Java Server Pages, it is better to be safe than to be sorry, especially in today's liablous society. Information stewardship is not a role to be taken lightly.

Use strict, option explicit, turn on all warnings and obey them.

Regardless of the programming language you are using, cranking up the strictness will definitely help you convert runtime bugs into compile-time bugs. Compile-time bugs are bugs that you can catch in development, whereas runtime bugs inevitably slip through once you've actually done deployment. No QA department can catch them all (case in point, Microsoft, with $63B in the bank, still has bugs crawling out of their operating system software).

Crank up the warnings, too. At the very least, you can choose to ignore them, but it's much, much better to understand them and make them go away.

Back End Tips

If the database has row-level permission support, utilize it.

Row-level permissions will keep you out of trouble by allowing you to be lax when writing your queries. By keeping a list of users in the login table, you can be ensured that someone cannot alter a request to the server in order to retrieve someone else's information.

Managing permissions at the database is probably the simplest way to ensure that data does not get into the wrong hands. This allows your queries to forego otherwise very complicated error checking. Some types of error checking will turn one query into multiple queries, control structures, etc.

Unfortunately, only some databases support this (such as PostgreSQL with the set session authorization command). If you are using a database which does not, this is diametrically opposed to using a connection pool.

Load your queries against your persistent data store (frequently SQL) from disk, and prepare them.

By loading your queries from disk, you can easily survey each and every query that may change with a revision in the structure of your persistant store. You can also use other programs to load/test/profile these queries without having to cut and paste them from your business logic. Lastly, a DBA does not need to know the details of your business logic in order to optimize your query.

Aside from that, if you're using a relational database as your persistent store, prepare your queries once and then cache the statement handles. This allows you to refer to the query by name wherever you wish, without incurring the penalty of having the database reparse the SQL.

Prepared statements also have the tendency to remind your code which types are required for which parameters, making error messages more to-the-point.

Connection pooling speeds response time greatly.

Where applicable, pooling your connection to a database (i.e. reusing a connection across several requests) can speed up your application response time dramatically.

Many people don't realize it, but establishing a connection to a database is a painfully slow operation. The database typically needs to load the entire set of authentication and permission tables in order to log you in. This can take longer than the rest of your queries combined, in a small application!

Store sessions in a DBM or the file system, not the database.

A session should be looked up using a single key. Frequently, relational databases store things in a b-tree on disk and it requires O( log N ) time to look up a row.

DBM stores the row in a hash-format. The lookup time for the session will then be O( 1 ), constant. Storing on disk and looking up the session based on filename is also quick, although with a DBM file the sessions will maintain physical proximity to one another on the disk platter, making access that much quicker.

Some RDBMS' optimize this down significantly, but as of August, 2003, none can do a random single-key lookup as fast as Sleepy Cat's DBM. As it is very rare to do a set operation with business data based on a session key, DBM makes an excellent candidate for storing sessions.