How to Schedule Planned Maintenance Using RewriteEngine

… or how I set my sites up to go black tomorrow in protest of SOPA and PIPA. You might not see this prior to that blackout, but it has useful applications for other things—essentially any time you want to do a redirect that has a specific schedule.

If you are running your sites on an Apache server with the rewrite module, scheduling a temporary redirect to a maintenance page is pretty simple:

# MAINTENANCE REDIRECT 
RewriteEngine on
RewriteCond %{TIME} >20120118000001 [NC]
RewriteCond %{TIME} <20120118235959 [NC]
RewriteCond %{REQUEST_URI} !/maintenance.html$ [NC]
RewriteRule ^(.*)$ /maintenance.html [R=307,L]

To explain the code:

Line 2: RewriteEngine on
Turns on the RewriteEngine, if it isn’t already.
Line 3: RewriteCond %{TIME} >20120118000001 [NC]
Checks to see if the date is greater than a specific time. In this case 2012-01-18 00:00:01.
Line 4: RewriteCond %{TIME} <20120118235959 [NC]
Checks to see if the date is less than a specific time. In this case 2012-01-18 23:59:59.
Line 5: RewriteCond %{REQUEST_URI} !/maintenance.html$ [NC]
Checks to be sure that you’re not trying to access the maintenance page itself, otherwise we’d end up in a redirect loop.
Line 6: RewriteRule ^(.*)$ /maintenance.html [R=307,L]
If all of the above conditions are true, it does a temporary (307) redirect to your maintenance page.

Here’s the code again, with the variables you need to modify italicized.

# MAINTENANCE REDIRECT 
RewriteEngine on
RewriteCond %{TIME} >20120118000001 [NC]
RewriteCond %{TIME} <20120118235959 [NC]
RewriteCond %{REQUEST_URI} !/maintenance.html$ [NC]
RewriteRule ^(.*)$ /maintenance.html [R=307,L]

Drop it in your .htaccess file, and you should be good to go.

What time value should I use?

We’re talking server time, so your time will be based on what your server setting is. Mine is set to EST, although it gets funky about Daylight Savings Time sometimes, so it might be off by an hour. If you know your server is running 3 hours slower than your timezone, adjust your times by 3 hours.

Then, figure out your date and time in the format YYYY-MM-DD HH:MM:SS and remove anything that isn’t a number: YYYYMMDDHHMMSS.

Why are you doing a redirect instead of rewrite?

I like to make it very clear to search engines that we’re on a new page and it is only temporary, so no caching should happen. If a search engine hits my homepage and I’ve rewritten the URI, the search engine thinks that my maintenance content should be indexed, which I definitely do not want to happen. Instead, it knows that there was a redirect.

You want the redirect to be temporary (307) so that the search engines know to continue to request the original URI in the future, and not the maintenance page.

What is this SOPA and PIPA stuff?

The American legislative bodies are both currently considering bills that will allow easy censorship on the Web in the name of protecting copyright and intellectual property. A good analogy is that the bills are kind of like trying to kill a squirrel with an atomic bomb. As someone who is both a Web developer and creative professional, I strongly believe that this bill will hinder creative and economic progress as well as the dissemination of information.

If you’re reading this on Jan 17, or any day after Jan 18, you can learn more on Wikipedia, but it will be blacked out on Jan 18. Of course, your favorite search engine will surely return plenty of hits as well.

Why printf Isn’t Working: PHP String Handling

At one point in time, I spent hours troubleshooting why my argument swapping/repeated placeholders in sprintf/printf statements weren’t working properly. I referred to the PHP docs, the internet, cursed the fact that I was doing back-end coding again, etc., and finally gave up and just repeated my variables using normal, un-numbered conversion specifications. Not once did I see anything that answered my question: “Why am I getting a ‘Too few arguments’ warning when using numbered placeholders?”

So, why am I getting a ‘Too few arguments’ warning when using numbered placeholders?

I (and probably you) was/were/are using a double-quoted string.

The issue here is with how PHP handles the String type. PHP is pretty forgiving about a lot of things, and in many cases single-quoted strings and double-quoted strings work just as well as the other. But there are some very important differences, especially when it comes to special characters, conversion specifications and variables.

If you’re having this same problem, change to using single quotes. Voilà, your problem is probably fixed, as long as you didn’t forget to escape any single quotes within your string.

A little more information on strings

The number one thing to remember when working with single-quoted strings in PHP: the only parsed character is the escaped single quote [\'].

Double-quoted strings will parse many escape sequences, and most importantly will expand variables.

If you’re a visual learner, refer to this example:

print '\nHello $username';
print "\nHello $username";

The above code will produce the following (assuming $username has been set to “World”):

\nHello $username
Hello World

So why is this an issue with printf?

I haven’t actually found a source to explain this or verify my postulate. But, I’d bet money on it, and I’m not a betting woman.

Double-quoted strings expand the conversion specification as a variable before printf even gets a chance to evaluate the code. Visually speaking:

$s = "shoes";
$numchucks = "a";
$noun = "wood";
$verb = "chuck";
printf("How much %1$s would %2$s %1$s-%3$s %3$s...",$noun,$numchucks,$verb);

The above code results in a “Too few variables” warning, and nothing prints because printf is actually getting a string that looks like: “How much %1shoes would %2shoes %1shoes-%3shoes %3shoes... In other words, you’re passing it five valid conversion specifications but only four arguments. If you added in the proper number of arguments, like this:

printf("How much %1$s would %2$s %1$s-%3$s %3$s...",$noun,$numchucks,$verb,$noun,$verb);

The resulting output would be:

How much woodhoes would    ahoes chuckhoes-woodhoes chuckoes…

For those readers who are familiar with only the most basic of conversion specs, %2s is perfectly valid. It reads like: “format as a string [s] at least two characters long [2], using the default padding character [a space] and default alignment [left].” So, as you see with ‘a’, it pads the left of the value with a space (exaggerated in above example with three spaces).

In the above examples, we end up with a (somewhat) sensibly formatted string because our interpreted variable $s started with the letter ‘s’, a valid conversion specification character. You will end up with even weirder results if $s begins with an invalid conversion spec character. Take $s="random"; for example. printf tries to use ‘r’ but as it’s not a valid spec character it just leaves out the conversion specifications

How much andom would andom andom-andom andom…

Even more descriptive is if $s doesn’t exist:

How much would…

So, for future reference, if you want to use numbered conversion specifications, here’s the proper way:

printf('How much %1$s would %2$s %1$s-%3$s %3$s...',$noun,$numchucks,$verb);

And you know, I’ve really always wondered—

How much wood would a wood-chuck chuck…

…if a wood-chuck could chuck wood.

Server Ignoring PHP 301 Header: IIS Known Bug

The Web is ever changing, and this article is relatively ancient having been published 9 years ago. It is likely out of date or even blatantly incorrect in relation to modern better practices, so proceed at your own risk.

This is a little more back-end/server-side than front-end, but I happened upon an absolutely aggravating bug found with IIS/the FastCGI module when moving a client over to a new site structure. Like any good development company, we wanted to make sure the old site structure URLs redirected permanently (301 Redirect) to the correct new URLs so that off-site links continued to work. Unfortunately, I ran into some roadblocks setting it up.

The Problem and Roadblocks

This is a large site; something on the order of 2500 pages needed redirects for the new structure. Or at least, 2500 URLs. Many of the pages were dynamically generated from a CMS with redirects already set up to resolve SEO-friendly URLs to the server-based variable-laden ones. The fastest and most convenient—not to mention maintainable and scaleable—method of doing this seemed to be writing a quick PHP script (as it is the scripting language used by the site and CMS) to map the old URLs to the new. We’d simply redirect all requests to that script and then let it handle the mapings with a header statement:

header("Location: ".$newURL,true,301);

Trivial, quick, workable. At least on Apache. I traced my headers though, and IIS was sending a 302 (Temporary) redirect despite the explicit 301 call. I threw out some very naughty words at that point.

Roadblock #1: Using a Microsoft Server

Ok, so, maybe I’m being a little flippant. I suppose there’s nothing wrong with IIS. It’s just that I, personally, prefer LAMP development if I have to do back-end/server-based stuff.

But facetiousness aside, IIS really is the cause of our problems in this case. Ultimately, two issues were affecting what should have been a simple issue. Specifically, a bug in the IIS FastCGI module and a size limit on web.config files.

Roadblock #1.1: IIS/FastCGI and PHP 301 Redirects

Two dirty words: “Known Bug.” Essentially, IIS/FastCGI module somehow lose track of the fact that you said “I want a 301 redirect” and handles it as a 302. Every. Single. Time.

So, our quick, easy, maintainable, scalable script idea goes out the window.

Roadblock #2: web.config filesize limit

Evidently there is a max size for your web.config filesizes. Don’t ask me what it is though: I can’t figure that out. My Google-fu isn’t strong enough to find anything about it, evidently.

I found this out when moving to Redirect Mapping Plan B: Spit Out web.config Redirects for Every URL Mapping In One File. That, my friends, spit out a wonderful 500: Internal Server Error error when I uploaded my new web.config file.

Guess it was time for a Plan C.

Our Workaround

Yuck.

I don’t even want to mention it. It’s depressing. Hopefully if you run into this, you can do something better.

We wrote a script that generated the URLs we needed and the mapping to the new URLs. Then, we formatted them for web.config redirects (everything to this point was actually part of Plan B. Moving on:) and made individual web.config files for each. Individual. Folder. under the old structure. This meant leaving old, useless folders online with nothing by web.config files in them. Like I said. Yuck.

Why not use another language, like ASP to get around the bug?,” you ask. In theory, that would work. But it would also take more time. See, we needed to interface with the CMS to get the correct URLs, so not only would we have to author the quick mapping script, but we’d have to write a more involved script to actually retrieve the correct URLs from the PHP-based CMS, and that was determined to be a non-option. (Hey, it all comes down to business: time==money && faster_workaround == less_time == more_money_per_time.)

Will This Ever Be Fixed?

According to user ruslany (Microsoft employee?) on the IIS forums:

This is a bug in IIS FastCGI module. It will be fixed in Windows 7 RTM. We are also looking into possible ways for making this fix available for IIS 7.

So, short answer, no, not really, unless you upgrade. Maybe they’ll get around to fixing it in IIS 7 with a patch. Maybe. They’re looking. He says. Here’s the full thread.

As for the max web.config file size, it actually makes a lot of sense, so I can’t imagine them fixing changing it. It was just frustrating to hit that unknown amount after the other bug. If anyone reading this happens to know that mysterious byte size cap, though, please let me know. It’d be useful info in the future, I suppose, and I am too lazy to experiment to determine the answer.