2010-01-09

PHP 102: A Portable Apache+PHP Environment for Debian/Ubuntu

The very first thing you need to do to develop for PHP is to setup a development server with PHP installed, of course. But maybe you're somewhat new to PHP and Apache is a huge, mind numbing universe of options. All you really want is the ability to run PHP and map hostnames to code, right? Almost always that's my goal, anyway.

Well, I've done all the work for you. Congrats! Actually, I did it mostly for myself and I'm sharing it with you! Yay for us!

What You Need
  • A computer with Ubuntu installed and working; I'm using 9.10 as I write this
  • An Internet Connection
Scope
We are going to setup a new, portable Apache server instance on Ubuntu 9.10 that listens on all of your computer's IP addresses and maps the hostnames being requested from that computer to directories on your hard disk. This means that you can add websites without restarting or reconfiguring Apache in any way. You just create a new directory and Apache figures it out for you. Brilliant, no?

What do I mean by portable? I mean every configuration file, content and scripts you need to run that entire Apache instance is in one folder that is not part of the operating system. You can copy that folder to any other Ubuntu/Debian box and execute the script to start it without any modifications of any kind. All you need is to install Apache2, PHP5 and some other modules. You aren't even using the init script that Ubuntu provides (because theirs sucks; it even says so in the comments).

Preliminary Setup
You need to install Apache, PHP and xdebug and then configure things. Open a terminal (Applications->Accessories->Terminal) and run these commands in this order:
  • sudo apt-get install php5 libapache2-mod-php5 php5-xdebug # Install Apache/PHP
  • sudo update-rc.d -f apache2 remove # Disable Apache Service
  • sudo /etc/init.d/apache2 stop # Stop Apache Service
  • sudo a2enmod php5 # Enable PHP5
  • sudo a2enmod vhost_alias # Enable Virtualhost Aliasing
Environment Preparation
Now that we have installed, configured and disabled the system-wide Apache instance we need to extract my handy Apache environment and set it up.

Open a terminal (Applications -> Accessories -> Terminal) and run the following commands:
  • # Download the file to your home, extract it to /srv and name it something handy
  • cd
  • wget http://www.webaugur.com/wares/files/apache2-portable-latest.tar.gz
  • pushd /srv
  • sudo tar xvzf ~/apache2-portable-latest.tar.gz
  • sudo mv apache2-portable* development
  • pushd development
  • # Run this script as whichever user account is going to be developing code
  • bin/fixperms
  • # start the service
  • sudo etc/init.d/apache2 start
  • # Next we want to symlink it into the init system so it starts up at boot
  • sudo ln -s /srv/development/etc/init.d/apache2 /etc/rc2.d/S91apache2-development
You should now be able to open http://localhost/ in your browser and see your phpinfo() output.

Adding Content
I have prepopulated a simple test script into www/localhost/public/index.php so you can see if its working right away.

The way it works is very simple. If you point a hostname (either in DNS or in your /etc/hosts file) to any of your computer's IP addresses Apache will serve up content from www/hostname/public. So, lets say you point www.ilikeapache.foo to your computer. You will place your content into www/www.ilikeapache.foo/public for Apache to find it. You can symlink many hostnames to a common directory as needed. You might create a link called www/ilikeapache.foo that points to www.ilikeapache.foo, for example. Thus serving up the same content for both host names. Much less ugly than slaving over hot config files into the wee hours of the morning. Simply amazing if you have hundreds of hosts pointing to one server.

What you Get
Here's where you may get a little lost if you're new to all of this. And I'll show you how to use some of this stuff in future articles. So just hang tight if you're not sure how all of this works.

By default, I have configured full on debugging of every kind. So don't go putting this up on a high traffic site as-is. I've done it and it'll run the box out of RAM and swap in under an hour and come crashing to the ground.

Here's a breakdown of useful directories and files in the default config:
  • var/log/apache2/access_log - Access log with hostname in the first column
  • var/log/apache2/error_log - Regular error log file
  • var/log/php.log - PHP Error Log (tail -f var/log/php.log to see errors in realtime)
  • var/log/xdebug - PHP xdebug stack traces and cachegrind (memory profiling) dumps
  • var/spool/mail/ - All outgoing email is intercepted (using bin/mailtrap as the "sendmail" program) and dropped here, one email per (timestamped) file. Files are stored in a format that can be piped directly into sendmail for real delivery.
  • lib/cgi-bin - If you create this directory it will be a common /cgi-bin for all hostnames on this Apache instance.
  • etc/apache2/include/php5.conf - a symlink that can pointed between debug and production PHP5 config files for quickly moving an instance between production and development.
  • etc/apache2/sites-templates/ - Examples of how to manually configure a hostname in this setup; both for HTTP and HTTPS.
  • etc/apache2/run - PID and lock files for this Apache instance
Why Oh Why
Some questions and answers I expect you'll have.
  1. Q: Why do I place content in a hostname/public subdirectory?
    A: You should never put your application code in a public directory. Zend Framework uses the convention of having a directory named "public" that maps URLs into your application code. I use ZF quite a lot and therefore followed this convention.
  2. Q: Can I run more than one instance of Apache like this?
    A: Absolutely, this is why I wrote my own init script. You can have as many Apache instances as you have IP addresses to give them. Modify etc/apache2/sites-available/mass-vhost.conf to Listen on a specific IP address instead of all IPs. You can have one instance on multiple IPs, as well. Just make sure none of the instances are listening on all IPs (or each other's IPs) or you'll have port conflicts.
  3. Q: How do I move my code and apache instance to another server?
    A: Run the setup and preparation steps above on the new host and instead of downloading my copy of the apache-portable just copy your entire directory containing your instance and content from /srv on the current machine to /srv on the new machine.
Troubleshooting and Corrections
If you run into troubles check the var/log/apache2/error_log for relevant problems. If you get completely stumped maybe I missed a step. Feel free to email me with a description of your problem and I'll try to take a look.

Background
I've been using web servers since 1994. I started using NCSA HTTPD and later switched to Apache as it become popular. I was even once part of the first team to port Apache to Windows back in the late 90s. Neat, huh? (No, Windows sucks. It was anything other than neat but it was a challenge.) I've written modules in C and I even modified the indexer module to support XML back when the XML specs were first being drafted (but my changes admittedly weren't generic enough and someone else did a better job later on). I've been using Apache since the beginning and I'm continually amazed at its capabilities.

Labels: , ,

2009-11-23

PHP 102: Whitespace Matters!

Lets start at the beginning. Lets start with creating a blank PHP script. What's one of the most important things to remember when creating a new PHP script? I'll tell ya: Whitespace Matters!

You must keep track of your whitespace in PHP scripts. Why? HTTP headers cannot be set if you have already "printed" any characters. Stray spaces before and after segments will, among other things, prevent you from setting HTTP headers (e.g. session cookies, redirects, and so on).

This is something I run into over and over and over and over in modifying carelessly written code. Sometimes this is the work of a well intentioned developer trying to make code "more readable". Worse, this is a problem you won't catch until its too late. You can code for months or years on a project and never notice any problems. Until one day everything explodes for no obvious reason. There are huge, massive projects out there that still haven't completely learned this lesson.

Opening Demarcation
If your script contains only PHP code then it must begin with <?php at the very beginning of the file. No spaces. No new lines. <?php should be the very first characters in your new file. Don't use asptags <%, don't use shorttags <? or any other cutesy business.

Closing Demarcation
If your script contains only code and no embedded HTML markup then it must never include a ?> closing tag. Never. Ever. Yes, of course it'll work. Don't do it. Omitting the closing ?> ensures that all trailing whitespace will be in a code segment and not your output buffer.

Good Example
<?php
function helloWorld(){
return 'Hello World!';
}

echo helloWorld();



Bad Example



<?php
function helloWorld(){
return 'Hello World!';
}

echo helloWorld();

?>

Labels: , ,

2009-06-04

php-imap extension == evil

The Problem

The PHP IMAP extension relies on University of Washington's libc-client IMAP library which appears to be written by kindergarteners. After some review of the source code, UW's IMAP client library appears to have extensive, serious security and stability problems at very fundamental levels.

I'm seeing segfaults (buffer overflows) performing simple operations like fetching attachments. Some attachments work fine but others fail; larger files especially. There are bug reports and CVE entries related to similar issues. However, upgrading to the supposed "fixed" versions of everything does not make any difference for my specific issue. It does fix some other issues related to this. (i.e. the exact same programming errors that appear to affect thousands of lines of code in the UW client.)

The Solution

Use one of the many native IMAP class library. My choice would be Zend_Mail as we use Zend Framework extensively here at work. Zend_Mail supports a number of other mail protocols in addition to IMAP. MIME type support is likely much better than other options. The other logical choice would be Pear Net_IMAP.

Labels: , , ,

2009-04-09

MySQL Master-Master-Slave(s) Database Replication

I have watched the technology sessions and slideshows explaining how many large websites, such as Google or Facebook, setup their MySQL Databases. But none have ever gone into the details of how to configure MySQL in such a way. So I had to tackle that myself. I am documenting the process here so its hopefully useful to others (and myself when I forget in 6 months).

Overview of the resources involved
Data Integrity Concerns
The very first thing you need to consider is that MySQL technically does not support multiple masters. This is significant because it means that there is no conflict resolution if you write data to both masters. If you happen to change the same record on both masters they wipe each other's changes out. MySQL replication works simply by replaying queries from the master on the slave server.

The safest solution is to simply never write data to both masters. Although, there are strategies that allow you to safely write to multiple masters. For example, limit writes to specific tables on each master server. In my case I use the secondary master as a replication source and hot-spare in case I need to take down the primary master for maintenance. Using the secondary master as a replication source takes the burden of slave replication off your primary database server.

Master Configuration
We have two masters. They are identified as 1 and 2. (Slaves are 11, 12, etc.)

The very first thing you should do is to run /usr/bin/mysql_secure_installation on each of your masters to set MySQL's root password and other security settings.

Master 1
Add the following to the [mysqld] section of my.cnf making the appropriate changes and restart mysqld:
# primary master server id
server-id=1
auto_increment_offset=1
# total number of master servers
auto_increment_increment=2
# local slave replication options
log-bin=master1-bin
log-slave-updates
# remote master replication options
master-host=master2.yourdomain.com
master-port=3306
master-user=replica
master-password=replic8
master-connect-retry=10

Master 2
Add the following to the [mysqld] section of my.cnf making the appropriate changes and restart mysqld:
# secondary master server id
server-id=2
auto_increment_offset=2
# total number of master servers
auto_increment_increment=2
# local slave replication options
log-bin=master2-bin
log-slave-updates
# remote master replication options
master-host=master1.yourdomain.com
master-port=3306
master-user=replica
master-password=replic8
master-connect-retry=10

Create Replication Accounts
On both master servers run the following query as root:
mysql> GRANT REPLICATION SLAVE ON *.* TO 'replica'@'%' IDENTIFIED BY 'replic8';

Dump/Load Existing Data and start Replication

On Master 1
Prevent writing to the database.
mysql> FLUSH TABLES WITH READ LOCK;
mysql> SHOW MASTER STATUS\G
*************************** 1. row ***************************
File: master1-bin.000001
Position: 254
Binlog_Do_DB:
Binlog_Ignore_DB:
1 row in set (0.20 sec)

Make note of the position and file name. You must have these later. From a terminal you now need to dump the database for loading onto the slaves.
bash> mysqldump -A -u root -p > master1.sql

On Master 2
Load the data from Master 1 onto Master 2.
bash> mysql -h master2.yourdomain.com -u root -p < master1.sql
Enable Master 2 as a slave to Master 1 (refer to master 1's show master status above for MASTER_LOG* values)
mysql> CHANGE MASTER TO
MASTER_HOST='master1.yourdomain.com',
MASTER_USER='replica',
MASTER_PASSWORD='replic8',
MASTER_LOG_FILE='
master1-bin.000001',
MASTER_LOG_POS=254;
mysql> START SLAVE;

Get the Log Info for Master 2
mysql> SHOW MASTER STATUS\G
*************************** 1. row ***************************
File: master2-bin.000005
Position: 12314580
Binlog_Do_DB:
Binlog_Ignore_DB:
1 row in set (0.00 sec)

On Master 1
Enable Master 1 as a slave to Master 2 (refer to master 2's show master status above for MASTER_LOG* values)
mysql> CHANGE MASTER TO
MASTER_HOST='master2.yourdomain.com',
MASTER_USER='replica',
MASTER_PASSWORD='replic8',
MASTER_LOG_FILE='
master2-bin.000005',
MASTER_LOG_POS=
12314580;
mysql> START SLAVE;

Master-Master Setup is complete
If all you wanted was a master-master setup then you are finished. Any query executed on master 1 will also be executed on master 2.

Adding Slaves to your Master-Master

On Each Slave Follow this Procedure

Add the following to the [mysqld] section of my.cnf changing the server-id and master settings as appropriate and restart mysqld:
# this slave's server-id
server-id=11
# replicate from master 2
master-host=master2.yourdomain.com
master-port=3306
master-user=replica
master-password=replic8
master-connect-retry=10

Load the data dump from Master 1, if needed:
bash> mysql -h slave11.yourdomain.com -u root -p < master1.sql
Start Replication from Master 2 to each slave (refer to master 2's show master status above for MASTER_LOG* values)
mysql> CHANGE MASTER TO
MASTER_HOST='master2.yourdomain.com',
MASTER_USER='replica',
MASTER_PASSWORD='replic8',
MASTER_LOG_FILE='
master2-bin.000005',
MASTER_LOG_POS=
12314580;
mysql> START SLAVE;

Master-Master-Slave Setup is Complete
Congratulations, you are done with the server configuration! You may verify this by inserting or updating records on Master 1 and then verifying that the change is made on Master 1 and all of your slaves.

Client Access Considerations
To take full advantage of this configuration your client applications will need to be written in such a way that they perform all critical writes (that is, writing data that you want to keep) on Master 1. Also consider that some or all reads immediately following a write may need to come from the master. This is because it can take a few seconds for the slaves to synchronize with Master 1. This is what we call a "dirty" read. Clients performing only reads or creating temporary tables for use with those reads can access your pool of slaves and never touch the master.

Examples
  • Generate query-intense reports on dedicated slaves.
  • A portal page that doesn't need up-to-the-second data.
  • Statistics calculations can be performed on the slaves.
Enforcing Safe Practices
You would be well advised to GRANT only SELECT privileges to clients accessing your tables via a slave. This way a rogue client application cannot manipulate upstream data on the slave. Any changes made on the slave would not be known to any other slave nor the masters. This could be a devastatingly bad situation to find yourself in. Please think through these issues before you implement your clients.
You almost certainly will want to allow clients to create temporary tables, though.


Advanced Stuff
MySQL doesn't require slave tables to have the same schema as masters, either. This allows you to do interesting things like create your master database tables as InnoDB tables with full referential integrity and foreign keys while creating your slave tables as high performance MyISAM tables. You can create different indexing strategies on the slaves to fine-tune specific slaves to specific tasks such as report generation.

Labels: , ,

2008-07-20

Web Development Resources

I have been working on a web development resources listing for tools and services I use every day.

It can be found at: http://sites.google.com/a/webaugur.com/web-development

Labels: , , ,

2006-06-18

Football Browser?

Now there's a clever way to promote Firefox adoption... The world is filled with Football (i.e., Soccer) fans. So Google and Nike have created a community website called Joga.com dedicated to Football. But this website has a twist.

Joga works with IE somewhat. IE users can chat and do some things. More interestingly, IE users are presented with "What if your browser became obsessed with Football" advertising.

The advertisements take you to page with a screenshot of Joga Companion which only works with Firefox. Joga Companion feeds scoring and "goal alerts" in realtime for the team(s) you are monitoring. You get little popup alerts in the usual Firefox popup fashion. And there's also a video feed system built-in that allows you to watch clips of the action. It themes Firefox according to the flag of the country you are currently monitoring.


Joga.com as viewed in Internet Explorer
Boring old Internet Explorer at Joga.com


Joga.com as viewed in Mozilla Firefox with Joga Companion
New Firefox Hotness at Joga.com

Labels: