Skip to main content
1 of 2
GaryL
  • 101
  • 4

I'm answering my own question. Perhaps it will be useful for someone else.

Short answer: You can short-circuit the page generation process via wp_die() or wp_send_json().

But since I don't want to send a 500 status it eliminates wp_die(), and since I want to send html not json, it eliminates wp_send_json(). So instead, I copied & modified the wp_send_json() source to produce the following:

function wp_send_html( $response = null, $status_code = null ) {
    @header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );
    if ( null !== $status_code ) {
        status_header( $status_code );
    }
    if ($response) {
        echo $response; 
    }
    die;
};

In my code, after I determine the request is unchanged from the prior request, I call:

wp_send_html(null, 304);

... which sends a 304 response including any html response headers that have setup, and an empty body.

In the single post places that I'm using this, I've decreased response times from about 4-5 secs, down to 1.0-1.3 seconds.

Longer answer:

From @Mark's post, the primary things I need to consider are the post last change, and the last change for any theme or other global element. I am only handling only single page/post pages, and I've added a $MIN_UPD_DATE global at the top of my functions.php with I'll update when I do global/style changes (yeah, I know that's a little kludgey).

My more complete code in my functions.php is as follows:

// Update this on sitewide changes
$MIN_UPD_DATE = DateTime::createFromFormat('M d Y H:i:s', 'Sep 01 2018 01:01:01');

// For web pages and single post pages - note the last changed date
    // Thanks to: https://wordpress.stackexchange.com/questions/172966/if-modified-since-http-header
    // This will support clients sending HTTP header: If-Modified-Since 
function handle_modified_since_header() {
    global $MIN_UPD_DATE;
    //Check if we are in a single post of any type (archive pages have no modified date)

    if( is_singular() ) { // excludes multi-post pages
        $post_id = get_queried_object_id();
        if( $post_id ) {
            header("Cache-Control: public");  
            // inherited/default was: Cache-Control: no-cache, must-revalidate, max-age=0
            
            $postModTime = new DateTime(get_the_modified_time('D, d M Y H:i:s', $post_id));
            if ($MIN_UPD_DATE > $postModTime) {
                $postModTime = $MIN_UPD_DATE;
            };
            header("Last-Modified: " . $postModTime->format("D, d M Y H:i:s") . " GMT" );
            return true;    // can use modified date to expire page
        }
    }
    return false;   // can NOT use modified date to expire page
};

// Checks single post & page entries for a Post Modified Date 
//  after the http request header: IF_MODIFIED_SINCE
//     or after the MIN_UPD_DATE
//  else, on any missing elements, assumes that request is expired.
function is_request_expired() {
    global $MIN_UPD_DATE;
    $MOD_SINCE   = 'HTTP_IF_MODIFIED_SINCE';
    $DATE_FMT    = 'D, d M Y H:i:s O';
    $postModTime = null;
    $isExpired   = true;
    $httpLastUpdate    = null;

    if (!isset( $_SERVER[$MOD_SINCE] )) {  // Quit on no http last mod date
        return $isExpired;
    };
    
    $httpLastUpdate = DateTime::createFromFormat($DATE_FMT, $_SERVER[$MOD_SINCE]);
    if (!$httpLastUpdate) {       // Quit on can't decode last mod date
        return $isExpired;
    }

    $post_id = get_queried_object_id(); // Get Post last modified date
    if( $post_id ) {
        $postModTime = new DateTime(get_the_modified_time('D, d M Y H:i:s', $post_id));
    };

    if ($postModTime) {
        // http last-mod-date is before post-list-mod-date or before min-upd-date
        $isExpired = ($httpLastUpdate < $postModTime) || ($httpLastUpdate < $MIN_UPD_DATE);
    };
    return $isExpired;
}

function send_on_not_expired_single( $wp_query ) {      
    if ($_SERVER['REQUEST_METHOD']!=='GET') {  // ignore for non-GET requests
        return $wp_query;
    }

    $use_modified_since_rule = handle_modified_since_header(); // conditionally set Last-Modified http header
    if ($use_modified_since_rule) {
        $is_expired = is_request_expired();
        if (!$is_expired) {
            wp_send_html(null, 304);
        };
    };
    return $wp_query;
}
add_filter( 'parse_query', 'send_on_not_expired_single', 200);

/**
 * Send an HTML response back to an html request.
 *              patterned on the v4.9.8 WP codex wp_save_json
 *              from: https://core.trac.wordpress.org/browser/tags/4.9.8/src/wp-includes/functions.php#L3179
 *
 * @param mixed $response    Variable (usually an array or object),
 *                           then print and die.
 * @param int   $status_code The HTTP status code to output.
 */
function wp_send_html( $response = null, $status_code = null ) {
    @header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );
    if ( null !== $status_code ) {
        status_header( $status_code );
    }
    if ($response) {
        echo wp_json_encode( $response );   
    }
    die;
};

As a final note, per some comments/suggestions, I did review some caching plug-in's. Although some of them do have very good 'last-modifed-date' checking/updating logic (which I don't), the ones that I reviewed either specifically exclude logged in users from caching, or have significant limitations for logged in users which eliminate their usefulness for my focus.

GaryL
  • 101
  • 4