Reducing HTTP Requests
In doing some optimizing for phpVMS, I noticed I have a lot of HTTP requests:
As you can see, there are 27 HTTP requests, 15 of them JavaScript files. This means, the browser has to reach out 15 times to grab individual JS files. This seems like a straight forward problem, if we can just condense those 15 files into one, then we can almost half the number of total requests.
So we can do this manually – copy and paste each file, into one master file. The problem is, when you update one file, you have to go and search around in your other files, and figure out exactly where to update, or just redo the entire condensing process. It’s a pain. but these seems like a good place to write some code to do it.
As part of Codon now, I’ve included a class called CodonCondenser. It’s just a basic class that will condense requested files into one file. I set out some requirements:
- It’s a class, so we can reuse it
- Use it for any file types
- Have a cached file of our condensed batch, so we don’t need to dynamically condense it every time, which could be expense.
- Be able to add/remove files at will, and it will handle that
Since we have some basic requirements, let’s plan how it’ll work:
- Set our options, the path to the files, the URL to the files since our function will return the URL to the generated file, set the file type, and a cache
- Pass an array, with the list of our files
- Generate a MD5 hash of the array we’ve passed, this will be our file name. If the array changes, as in we add files, or remove files, the hash will change, so we satisfy requirement #4… OR… use a filename that we generate, or pass in.
- Check this file name, see if it exists; if it does, see if the file age is older than we want (satisfying #3). If it’s not too old, then use this file
- If it’s too old, or doesn’t exist, then generate a new condensed file and return the URL to it.
So let’s start:
[php]
class CodonCondenser
{
public $path;
public $url;
public $timeout = 24;
public $file_ext = ”;
public $filename;
}
[/php]
Our basic class, with the settings. Next is our function to set these options:
[php]
public function SetOptions($path, $url, $file_ext, $timeout=24)
{
$this->path = $path;
$this->url = $url;
$this->timeout = $timeout;
$this->file_ext = $file_ext;
}
[/php]
We pass the $path, which is the absolute path to the files, and $url, which is the path to where the file is will be publicly accessible.
$file_ext is the extension of the condensed file (js, css, htm, etc), and $timeout is the time that the condensed file is considered ‘fresh’. Passing this as blank ($timeout=’’), will disable the time check.
Next is our function to check the cached version. We’ll return true or false if the file passed is valid:
[php]
protected function getCachedFile()
{
if(!file_exists($this->path.’/’.$this->filename))
{
return false;
}
# Check if the version that exists
# is older than the timeout we have alloted
# Value of "" skips the time check
if($this->timeout == ”)
{
if ((time() – @filemtime($file)) > ($this->timeout*3600))
{
# It is older, so delete it
@unlink($this->path.’/’.$file);
return false;
}
}
# The cache file is ok
return true;
}
[/php]
So what we are checking, if the file doesn’t exist, then return false. Next, if the timeout value is blank, just skip the check, and only base it on whether the condensed file exists or not (as we saw the option up above). Otherwise, we check the modification time of the file, and if the current time – the time file created (in seconds), is greater than our timeout value, then return false. If we have 0 (zero) as a timeout value, it will generate a new file every time.
Note that the function is protected, so we can’t access it directly, and instead, go through the main function:
[php]
public function GetCondensedFile($files, $filename=”)
{
if($filename != ”)
{
$this->filename = $filename;
}
else
{
$this->filename = md5(implode(”,$files));
$this->filename .= ‘.’.$this->file_ext;
}
}
[/php]
First, here we are checking if we passed an optional filename. I use the filename option, depending on whether it’s being used in the admin area, or in the front-end client area. I’ll explain this later.
Otherwise, we will build a filename, we check for the cached file, using the function above:
[php]
# Check if we’ve already made this condensed cache file
# If we have, then just give the URL of that file
if($this->getCachedFile() == true)
{
return $this->url.’/’.$this->filename;
}
[/php]
If the getCachedFile() returns true, that mean’s that the cached file is okay, and we exit and just return the full URL path to the condensed file. If it’s not condensed, then we build our condensed file:
[php]
$fp = fopen($this->path.’/’.$this->filename, ‘w’);
foreach($files as $file)
{
fwrite($fp, file_get_contents($this->path.’/’.$file));
}
fclose($fp);
return $this->url.’/’.$this->filename;
[/php]
This is pretty straightforward, we just open the condensed file, and write every file into it, then return the full URL path to the file. So this is how we’ll use it:
[php]
$condenser->SetOptions(‘/var/www/lib/js’, ‘http://mysite.com/lib/js’, ‘js’);
$files = array( ‘jquery.min.js’, ‘jquery-ui.js’,
‘jquery.dimensions.pack.js’,
‘jquery.form.js’, ‘jqModal.js’,
‘jquery.bigiframe.js’,
‘jquery.sparklines.js’,
‘jquery.autocomplete.js’,
‘jquery.tablesorter.pack.js’,
‘jquery.tablesorter.pager.js’,
‘jquery.metadata.js’, ‘jquery.impromptu.js’,
‘jquery.listen-min.js’, ‘nicEdit.js’);
$url = $condenser->GetCondensedFile($files);
[/php]
And then to link the Javascript into our page:
[javascript]
<script type="text/javascript" src="<?php echo $url?>"></script>
[/javascript]
Our result in the end:
14 less requests! Almost half the number of requests. Woohoo! While it’s a basic class, and we can certainly expand it by adding features such as compression (such as implementing JSMin-PHP into), to minify and pack the JS. Be careful if your JS files are already packed if you re-pack them, it could cause errors. I use files that have already been packed, and merge them together this way.
You can also use this for HTML files, CSS, any other text files which are all brought in together in separate requests.
Click to download the full script.