Monday, January 18, 2010

Setting up Apache, PHP, MySQL, PHPMyAdmin, and Filezilla Server on Windows XP

Need to set up a Windows web server? You're in luck! Today we're going to set up more server components than you can shake a frozen iguana at! (Or, for Latin-prejudiced grammar Nazis, "more server components than at which you can shake a frozen iguana!")

The Disclaimer


I just finished figuring all of this out myself. That's good, because it means I know how to talk to a beginner. But I don't pretend to be an expert. If anybody spots any glaring security holes or other blunders, let me know; otherwise, just be aware that these are only the basics, coming from a relative n00b in all things server-related.

The Goal


The goal today is to set up a web server on Windows where you can upload your PHP code, serve it with Apache, tie it in to a MySQL database, and administer MySQL with PHPMyAdmin.

(Update August 15, 2010 - see "Visualizing our example setup" for help understanding how these pieces work together.)

Let's start by setting up Apache.

A Bunch of Downloads


Here's what you need to download (along with the version numbers I'm using):
  • Apache Web Server (v 2.2.14) - get the Win32 binary (msi installer) from the official site. You can decide between the one that comes with SSL and the one that comes without - either will work for this tutorial.
  • PHP (v 5.3.1)- we'll download the Windows binary here. This is the PHP interpreter; when Apache needs to serve a PHP script, it will hand it off to the PHP interpreter to deal with the PHP code before it sends HTML to the client. Get the zip file labeled "VC6 x86 Thread Safe".
  • MySQL (5.1.42) - Go here and get the "Windows (x86, 32-bit), MSI Installer" - (I got the file named "mysql-5.1.42-win32.msi" - not sure how this is different from the 'mysql-essential' version.)
  • PHPMyAdmin (3.2.4) - get the zip file here.
  • phpmyadmin.sql - tables for some of PHPMyAdmin's advanced features (more on this later)
  • FileZilla Server (0.9.34) - download exe file here.

Setting up Apache


Run the executable you downloaded above. Other than setting your network and server name (I used 'localhost') and your administrator email address, you can just click "Next" through the whole installation. Choose "Typical" when asked for a setup type.



When setup is done, Apache is installed as a service on your machine. (If you right-click My Computer and choose Manage, then click > Services and Applications > Services, you can see all the services running on your machine.)

You can verify that this was successful by opening a browser and going to http://localhost. You should see this:



...which is the file index.html, located in Apache's htdocs folder.

Now, if you look in your task bar, you should see an icon that looks like this:



That's a status indicator that says "Apache is running!" You can click it to stop Apache:



...and you should see the status change.



Installing PHP


Extract the PHP zip file to your c:\ drive and rename the folder from "c:\php-5.3.1-Win32-VC6-x86" (or whatever) to simply "c:\php."

Now, to keep things clean so that you don't have to drop anything PHP-related into the Windows folder, you'll need to add c:\php to the Windows system path. Essentially, that means that when Apache says "I need to run php.exe," Windows will know that c:\php is one of the places where it can look for that.

To add something to the Windows path, right-click My Computer and choose Properties, then go to the Advanced tab.



Click on the "Environment Variables" button near the bottom, then scroll down in the bottom panel until you see "Path."



Click to highlight it, then click "Edit."



Go to the end of that line and add (without the quotes) "C:\php;", then click OK. Restart your machine so that this change will take effect.

Configuring PHP


Once you've rebooted, browse to c:\php. Copy either php.ini-production or php.ini-development, depending on the purpose of this server setup[1], to simply c:\php.ini. Then copy that file to c:\php.ini-original before you start making any changes, so you can always go back and see what you did by diffing your current config with the original one. (WinMerge is a good Windows-based program to do that.)

Now that you've got a backup, open php.ini in a text editor and make the following changes:

  • Optionally, set short_open_tag = On. This is not considered best practice, but it allows you to start PHP code blocks with just <? instead of <?PHP. It's convenient, and may be needed if your existing code uses tags like this.
  • Set extension_dir = "C:/php/ext"
  • Uncomment the 'extension=' line(s) for your preferred database adapter(s), if you've got existing code. For this example, uncomment extension=php_mysqli.dll (to match the config we'll use for PHPMyAdmin later on).
  • Set up your time zone to be used in any PHP date functions. For example, I set date.timezone = "America/New_York". (I know, it's weird to use a string like that. See the manual for more.)

Configuring Apache


Now browse to the Apache directory, and go to the "conf" subdirectory. Here, the main configuration file is called "httpd.conf". Just as before, we want to have a backup of it before we make any changes. Fortunately, Apache comes with backup config files, located in the "original" subfolder. Leave those alone for future comparison with your modified versions.



Now, open up httpd.conf in a text editor and let's make some changes.

  • Set "DocumentRoot" to point to the folder where you want to keep your web pages and scripts. (The default setting points to Apache's 'htdocs' subfolder.) For example, you could set it to DocumentRoot 'C:/www'.
  • Set <directory> to point to the same folder. Like this: <directory 'C/www'>
  • Set DirectoryIndex index.php index.html. This controls which file will be displayed if someone requests a folder. For example, if they browse to www.yoursite.com/foo, with this setting, the server will first look in the foo folder for 'index.php.' If it finds it, it will display it; otherwise, it will proceed to look for 'index.html.' You can add more alternatives to the end of the line or change the order as you like.
  • Set EnableSendfile Off to prevent some page requests from hanging. (This is a Windows-specific configuration that took me a few days to to learn about)
  • Finally, add the following lines to the end of the file

#Enable PHP processing
LoadModule php5_module "c:/php/php5apache2_2.dll"
#.php files should be processed as PHP
AddType application/x-httpd-php .php
#Where to find php.ini
PHPiniDir "C:\php"

Whew! That's it for Apache and PHP. Restart Apache, and create a simple page called 'index.html' in your designated web root folder (like "C:\www", or whatever you chose). Now if you visit http://localhost, you should see it.



Now create a simple php script called 'index.php'. With the DirectoryIndex setting I used above, it will now take precedence over 'index.html' and be displayed when you reload.



Installing MySQL


This is the easiest part yet. Run the installer and use all the default options.

Note: The "Typical" installation option puts the database data in "C:\Documents and Settings\All Users\Application Data\MySQL\MySQL Server 5.1". If you ever need to completely remove MySQL and reinstall, if you don't delete this folder along with the program's folder, you'll reinstall and open your database to find that it already has data in it. Spooky.

Setting Up PHPMyAdmin


This is also pretty simple. For the most basic case, all you need to do is this:

  • Unzip the phpmyadmin file into your web root folder
  • Rename its folder to simply 'phpmyadmin' (so that you can browse to 'localhost/phpmyadmin' to see it)
  • Create a config file in that folder, called config.inc.php

A very simple config file (taken from the docs) looks like this:

<?php
$cfg['blowfish_secret'] = 'gtqw0282bg7130jhga7d'; // change to random string of your choice
$i=0;
$i++;
$cfg['Servers'][$i]['auth_type'] = 'cookie';
?>

That's it! PHPMyAdmin should work.

Making PHPMyAdmin better


If you've got previous experience with XAMPP, you'll be accustomed to some nice little configuration features for PHPMyAdmin. For example, you can bookmark queries, and have your foreign keys display as hyperlinks to the associated record.

Fortunately, we can all get those features working, too. First, add the following lines to config.inc.php:

$cfg['Servers'][$i]['pmadb'] = 'phpmyadmin';
$cfg['Servers'][$i]['bookmarktable'] = 'pma_bookmark';
$cfg['Servers'][$i]['relation'] = 'pma_relation';
$cfg['Servers'][$i]['table_info'] = 'pma_table_info';
$cfg['Servers'][$i]['table_coords'] = 'pma_table_coords';
$cfg['Servers'][$i]['pdf_pages'] = 'pma_pdf_pages';
$cfg['Servers'][$i]['column_info'] = 'pma_column_info';
$cfg['Servers'][$i]['history'] = 'pma_history';
$cfg['Servers'][$i]['designer_coords'] = 'pma_designer_coords';

All of these little features depend on having a database called 'phpmyadmin', with tables named 'pma_bookmark,' etc.

Take a look at the sql file I provided. If you create the database 'phpmyadmin' and then import this file, it will set up the tables you need for the fancy features.

One last tweak that you can take or leave: I hate having too much pagination on my large tables, so I set PHPMyAdmin to display 500 records at a time by adding this line to the config file:

/*Personal Tweaks */
$cfg['MaxRows'] = 500; /* Show 500 rows of a table by default */

Setting up FileZilla Server


Once again, run through the installation process, accepting all the default settings. When you're done, Filezilla Server should launch, and you'll have a status window / control panel that looks like this:



Now you need to set up at least one user. Start by clicking "Edit > Users."



Take a look at this image, then I'll explain it a bit.



Over on the right, you've got a list of users. Here, I've created two. Everything to the left of there applies to the currently-highlighted user. So your first step is to create one, and after that you can manage settings.

At a minimum, you need to:

  • Enable the account
  • Set a password (well, you COULD skip this, but don't)
  • Click on "shared folders" to the left and add at least one folder that this user will have access to, as well as what type of access: read-only, write, etc. A developer will probably need full access to the web root folder.

You can tweak all kinds of other settings, like connection timeout and bandwidth limits, etc etc, but this takes care of the basic setup.

As long as the machine this is running on is online and doesn't have port 21 blocked in its firewall, you should be able to get on another computer, open an FTP client, put in your new server's IP address and the username and password you just created in Filezilla Server, and connect. You should see the folder you gave that user access to.

Done!


That's it! If you have any questions, read the documentation for that component, or check on serverfault - it's like stackoverflow, but for system administrators.

[1] The difference between the development and production versions of this file are mostly related to error reporting and performance profiling (although having error reporting turned on for your production server could expose sensitive security information). Some of the errors and warnings the development version will give you include stuff like "this isn't best practice" or "this code will be deprecated in future versions of PHP." They are great things to learn from your development setup, but you don't want your production server wasting its effort noticing them.
If you're curious to learn more, compare the two files. A diff program like Winmerge (which is free) will let you quickly see the differences between the two, and reading the documentation about those settings will teach you what they're for.

Thursday, January 14, 2010

Firefox Offline Mode

You know what's horrible? Firefox's "Offline Mode."

I've been tinkering with Apache and with some web pages, and I keep being surprised by weird stuff, like:

- I don't see my page edits when I reload
- A page displays just fine, even though the server is no longer installed

Then I realize that Firefox, whose whole job in life is to get online, is offline. It has, invisibly and without my asking, decided not to reload the page, even though I'm pressing Control+F5, which means "no for real, reload everything on this page, throw away your cache, I want to see the most current stuff possible."

I don't know what this feature is for, but I need a way to turn it off.

Adding fields to a web form: a job for closures!

In my last post, I explained how closures work. Via their magic, I can make a function to do this:

nextNumber(); //returns 1
nextNumber(); //returns 2
nextNumber(); //returns 3

The most concise way to create this in Javascript would be:

var nextNumber = (function(){var i=1; return function() {return i++;}})();

Of course, that's not very readable. See my previous post for a step-by-step introduction to closures.

Putting closures to use


One place where this trick is really handy is when you want to dynamically add fields to a web form. You need each new field to have a unique name and ID, but pulling new IDs from a database is overkill.

Say you've got a form where users can order Viking helmets. For each helmet they order, they need to specify a color and size.

Suppose your inputs look like this:

<label for="helmet1color">Helmet 1 Color</label>
<input id="helmet1color" name="helmet[1][color]"/>
<label for="helmet1size">helmet 1 Size</label>
<input id="helmet1size" name="helmet[1][size]"/>

Ideally, you'd like to add as many new inputs as a customer wants. The more helmets they order, the sooner you can afford to buy that beet farm!

So you make a Javascript function to generate new inputs:

function newHelmetInputs(i){
var inputSet = '';            
inputSet += '<label for="helmet'+ i +'color">Helmet '+ i +' Color</label>'
inputSet += '<input id="helmet'+ i +'color" name="helmet['+ i +'][color]"/>'
inputSet += '<label for="helmet'+ i +'size">Helmet '+ i +' Size</label>'
inputSet += '<input id="helmet'+ i +'size" name="helmet['+ i +'][size]"/>'
return inputSet;
}

But of course you've got to tell it whether you need inputs for helmet #2 or #15. Which is where a closure function comes in: it can return a new number every time it's called. Like this:

i = nextNumber();
newInputs = newHelmetInputs(i);

Slap those inputs onto the page, and you're done! Well, almost.

Completing the Picture


To show exactly how this works, I've got to get into some specifics. For my form, I'm doing the following:

1) Using jQuery on the page
2) Validating the form prior to submission with jQuery's Validate plugin
3) Processing the form submission with PHP

With that in mind, here's how I'd add the new inputs: just after the set of helmet inputs, I'd add button a link or button that says "More Pointy Helmets!", and use the following jQuery to capture the click event:

$('#addhelmet').click(function(){
var i = nextNumber();
var newInputs = newHelmetInputs(i);
$(this).before(newInputs);
$('#helmet' + i + 'size').rules('add', {digits: true});
});

That takes care of adding the inputs themselves, and adding a validation rule to make sure the size is given in digits.

Finally, you may have noticed that the names I gave the inputs are written like arrays. If you're using PHP, you can process those as arrays after the form is submitted. This means that no matter how many fields are added, you'll handle them the same way. For example, if you're generating an email that lists all the helmets in this order, you could do this:

foreach ($_POST['helmet'] as $key=>$val){
  $emailbody .= 'Helmet ' . $key . ' Color: ' . $val['color'];
  $emailbody .= 'Helmet ' . $key . ' Size: ' . $val['size'];
  $emailbody .= '
"
}

Voila! Whether the user wants a solitary helmet or enough to outfit his entire shuffleboard league, this form can handle it.