.htaccessif-statementapache2.4errordocument

Custom 503 messages based on conditions in .htaccess


Okay - the logic of my brain is far from the logic of how code in a .htaccess file gets processed... to bad as it get frustrating sometimes when you try to achieve something "logical".

I would like to set a custom ErrorDocument when variable MAINTENANCE_MODE is set but it will simply not load the correct file. Is this because the If-directive is looked at earlier than the RewriteRules?

SetEnv MAINTENANCE_MODE 1

ErrorDocument 503 /msg/503_default.html

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /

    RewriteCond %{ENV:MAINTENANCE_MODE} 1
    RewriteRule ^ - [E=MAINTENANCE_503:1]
</IfModule>

<If "%{ENV:MAINTENANCE_503} == '1'">
    ErrorDocument 503 /msg/503_maintenance.php
    
    RewriteRule !^503 - [R=503]
</If>

When it's totally not possible, which alternative route would be producing the best result?

Would that be to create a 503_default.php which can receive a value so it knows which custom message to show? If so, how do I set/pass that value onto the default 503 page?


Btw - this is just a simplified base version of what it will have to become. I also have other scenario's in which I want to serve a custom 503 message. But without a base there is not point in writing extended code :)


Solution

  • Okay - with help of the input from MrWhite to this and my previous question I managed to land on a working solution with a custom ErrorDocument 503. Thanks @MrWhite!


    The requirement

    A custom 503 message for 3 specific situations:

    1. maintenance/upgrades (not invoked by WordPress updates)
    2. block the shop for parts of the world where we don't want to ship to as that is just too much hassle
    3. 'scheduled downtime' meaning we temporarily shut down parts of the website from T-15m up to T+45m of the release of a new product in the shop

    The third reason might need some explanation. We (a community of whisky enthousiasts) buy casks and anyone in the community can buy a share of it through the shop, as long as it's in stock of course. We always release products at 1900 CE(S)T after announcing it first in the newsletter. So especially with popular/hard to get brands it can get busy real fast on the server as typically we have between 30 to 100 caskshares in a cask and the community has over 1.000 members these days. So this measure helps us to crash only at a later load and has helped us to reduce the occurrences of crashing the site from roughly 3 times a year to 0 (so far, in the last 3 years). As it's a profitless hobby initiative we just keep the webhosting costs under control as we can't justify to enlarge the server capacity for just that handful of times in a year.

    The solution

    1. Setup of a custom ErrorDocument in .htaccess to handle these requirements.
    2. Include the code in .htaccess to capture the situations and to relay a code to the custom 503 handler.

    The .htaccess file

    Let's start with the .htaccess file. The (simplified) code I landed on is:

    SetEnvIf ^ ^ GEO_BLOCK_MODE=1
    SetEnvIf ^ ^ MAINTENANCE_MODE=0
    SetEnvIf ^ ^ PRODUCT_RELEASE_MODE=1
    
    ErrorDocument 503 /msg/503_handler.php
    
    RewriteEngine On
    
    # [0] off [1] on
    RewriteCond %{ENV:GEO_BLOCK_MODE}               =1
    RewriteCond %{ENV:REDIRECT_STATUS}              !=503
    RewriteCond %{ENV:GEOIP_CONTINENT_CODE}         ^(AF|AS|SA)$ [OR]
    RewriteCond %{ENV:GEOIP_CONTINENT_CODE_V6}      ^(AF|AS|SA)$ [OR]
    RewriteCond %{ENV:GEOIP_COUNTRY_CODE}           ^(AL|AM|AZ|BY|BG|GE|KZ|MD|MK|RO|RU|TR)$ [OR]
    RewriteCond %{ENV:GEOIP_COUNTRY_CODE_v6}        ^(AL|AM|AZ|BY|BG|GE|KZ|MD|MK|RO|RU|TR)$
    RewriteCond %{REQUEST_URI}                      ^/shop/?.* [NC]
    RewriteRule ^ - [R=503,E=503_MODE:GEO_BLOCK]
    
    # [0] off [1] this morning | afternoon | evening/night [2] entire day [3] this weekend
    RewriteCond %{ENV:MAINTENANCE_MODE}             !=0
    RewriteCond %{ENV:REDIRECT_STATUS}              !=503
    RewriteRule ^ - [R=503,E=503_MODE:MAINTENANCE]
    
    # [0] off [1] on
    RewriteCond %{ENV:PRODUCT_RELEASE_MODE}         =1
    RewriteCond %{TIME_HOUR}%{TIME_MIN}             >1844
    RewriteCond %{TIME_HOUR}%{TIME_MIN}             <1946
    RewriteCond %{DOCUMENT_ROOT}/.productrelease    -f
    RewriteCond %{REQUEST_URI}                      ^/(forum|etcetera)(/.*)?$ [NC]
    RewriteRule ^ - [R=503,E=503_MODE:PRODUCT_RELEASE]
    

    With GEO_BLOCK_MODE set to 1 the section is enabled.

    Then we check if the page is not a redirected page with code 503 before we continue with the other conditions we want to be applicable to the situation.

    At the end we redirect to 503 and set a 503_MODE $_SERVER variable we can retrieve in our 503_handler.php via REDIRECT_503_MODE.

    The other 2 sections are setup in the same fashion and with regards to the PRODUCT_RELEASE_MODE section... there are 2 cronjobs running on the server:

    1. prior to 1845 to check if a new product is scheduled for that day, if so it will create the .productrelease file
    2. after 1945 to clear the .productrelease file, when it exists

    The 503_handler.php file

    The code in our .htaccess leads us to a custom 503 handler file. Which I have setup as follow to suit my needs:

    <?php
    
    $minutes        = 15;
    $retry_after    = date("D, d M Y H:i:s T", strtotime('+' . $minutes . ' minutes'));
    $refresh        = $minutes * 60;
    
    header('HTTP/1.1 503 Service Unavailable', true, 503);
    header('Retry-After: '. $retry_after );
    header('Refresh: '. $refresh .'; url=' . $_SERVER["REQUEST_URI"] );
    
    switch( $_SERVER['REDIRECT_503_MODE'] ) {
        case "GEO_BLOCK":
            $class  = "bg-yellow";
            $title  = "Service Unavailable";
            $text   = "...";
            break;
            
        case "MAINTENANCE":
            $class  = "bg-blue";
            $title  = "Maintenance Window";
            switch( $_SERVER['REDIRECT_MAINTENANCE_MODE'] ) {
                case 2: # today, entire day
                    $text   = "...";
                    break;
                case 3: # this weekend
                    $text   = "...";
                    break;
                # case 1: # dynamic: morning | afternoon | evening/night
                default:
                    $hour = date('G');
                        
                    if( $hour >= 6 && $hour < 12 ) {
                        $text   = '...morning';
                    } elseif( $hour >= 12 && $hour < 18 ) {
                        $text   = '...afternoon';
                    } else {
                        $text   = '...evening/night';
                    }
                    break;              
            }
            break;
        
        case "PRODUCT_RELEASE":
            $class  = "bg-blue";
            $title  = "Scheduled Downtime";
            $text   = "...";
            break;
                    
        default:
            $class  = "bg-purple";
            $title  = "Service Unavailable";
            $text   = "...";
            break;  
    }
    
    ?><!DOCTYPE html>
    <html>
    ...
    

    Okay - this is just the bare bones of my custom 503_handler.php file but it gives you a clear idea on how to set it up and how to capture the values we've setup / generated within our .htaccess code.

    That's it

    And that is it.

    I now have a working solution to show custom 503 messages for the 3 defined satiations and best of all:

    Sweet!