-
Notifications
You must be signed in to change notification settings - Fork 16
Description
When the status of a post changes from Draft to Published, the query for specific post (project in the example) does not have an associated key as it did before. That is, we can't invalidate the keys, it will always be HIT until the Cache-Control expires.
Even updating the data in such a post does not purge anything because it no longer has an associated key.
All other events are working fine, as far as the post was not in Draft state (or restored from trash) and then turned to Published again.
Querying a completely new created post works fine.
Testing custom Project Post type (single post is present):
Query example (testing via Insomnia):
(projects) http://localhost/wp/graphql?query={projects{edges{node{id,title}}}}
(project) http://localhost/wp/graphql?query={project(id:"our-slug",idType:SLUG){id,title}}
First run:
projects query -> MISS
c42fab2f4dc81c75f2e4b6180f1b5d0cdfd0fb22f863231bebab2f2a02bd3e10 graphql:Query list:project cG9zdDozNDU=
project query -> MISS
51404600df1e2ceed74fbd14251755e4b7958a369dc28ae551d6e5ff2a64831f graphql:Query cG9zdDozNDU=
Change status to Draft:
[14-Jun-2023 09:21:47 UTC] (graphql_purge) key: cG9zdDozNDU=, event: post_DELETE, user: 1, page: /wp/wp-admin/admin-ajax.php
[14-Jun-2023 09:21:47 UTC] (graphql_purge) key: skipped:post, event: post_DELETE, user: 1, page: /wp/wp-admin/admin-ajax.php
projects query -> MISS
c42fab2f4dc81c75f2e4b6180f1b5d0cdfd0fb22f863231bebab2f2a02bd3e10 graphql:Query list:project
project query -> MISS
51404600df1e2ceed74fbd14251755e4b7958a369dc28ae551d6e5ff2a64831f graphql:Query
Change status back to Published:
[14-Jun-2023 09:22:52 UTC] (graphql_purge) key: list:project, event: post_CREATE, user: 1, page: /wp/wp-admin/admin-ajax.php
projects query -> MISS
c42fab2f4dc81c75f2e4b6180f1b5d0cdfd0fb22f863231bebab2f2a02bd3e10 graphql:Query list:project cG9zdDozNDU=
project query -> HIT
51404600df1e2ceed74fbd14251755e4b7958a369dc28ae551d6e5ff2a64831f graphql:Query
Post is updated:
[14-Jun-2023 09:26:35 UTC] (graphql_purge) key: cG9zdDozNDU=, event: post_UPDATE, user: 1, page: /wp/wp-admin/admin-ajax.php
[14-Jun-2023 09:26:35 UTC] (graphql_purge) key: skipped:post, event: post_UPDATE, user: 1, page: /wp/wp-admin/admin-ajax.php
projects query -> MISS
c42fab2f4dc81c75f2e4b6180f1b5d0cdfd0fb22f863231bebab2f2a02bd3e10 graphql:Query list:project cG9zdDozNDU=
project query -> HIT
51404600df1e2ceed74fbd14251755e4b7958a369dc28ae551d6e5ff2a64831f graphql:Query
New post is published:
[14-Jun-2023 09:32:00 UTC] (graphql_purge) key: list:project, event: post_CREATE, user: 1, page: /wp-json/wp/v2/project/1038
projects query -> MISS
c42fab2f4dc81c75f2e4b6180f1b5d0cdfd0fb22f863231bebab2f2a02bd3e10 graphql:Query list:project cG9zdDoxMDM4 cG9zdDozNDU=
project query (new post)-> MISS
d9154add95f1fabfe6844d1716c23cd1b7ef0c4ca43d1ba867219e16859a88ed graphql:Query cG9zdDoxMDM4
purge-varnish-cache.php:
<?php
function purge_varnish_cache( $purge_keys ) {
// Determine the URL to be purged based on server host
$server_host = isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : $_SERVER['HTTP_HOST'];
// The server must be running Varnish Proxy, we must be able to target the host with a PURGE request (http only)
$purge_url = 'http://' . $server_host . '/';
// Create headers for the request with the purge keys
$headers = array(
'Xkey: ' . $purge_keys
);
// Initialize and configure a cURL request
$curl = curl_init();
curl_setopt_array(
$curl,
array(
CURLOPT_URL => $purge_url, // URL to purge
CURLOPT_CUSTOMREQUEST => 'PURGE', // HTTP method as PURGE
CURLOPT_RETURNTRANSFER => true, // Return response as string
CURLOPT_HTTPHEADER => $headers, // Set headers
)
);
// Execute the cURL request
curl_exec( $curl );
// Check for errors and log error message if any
if ( curl_errno( $curl ) ) {
$error_message = curl_error( $curl );
error_log( print_r( $error_message, true ) );
}
// Close the cURL session
curl_close( $curl );
}
// Add an action hook for purging cache using WP Graphql Smart Cache plugin
add_action( 'graphql_purge', function ( $purge_keys ) {
purge_varnish_cache( $purge_keys );
}, 10, 1 );default.vcl Varnish config:
vcl 4.1;
import xkey;
import std;
backend default {
.host = "app.internal";
.port = "8080";
}
acl purge {
"0.0.0.0"/0; // Allow all IP addresses
"::0"/0; // Allow all IPv6 addresses
}
sub vcl_recv {
# Remove empty query string parameters
# e.g.: www.example.com/index.html?
if (req.url ~ "\?$") {
set req.url = regsub(req.url, "\?$", "");
}
# Remove port number from host header
set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");
# Sorts query string parameters alphabetically for cache normalization purposes
set req.url = std.querysort(req.url);
# Remove the proxy header to mitigate the httpoxy vulnerability
# See https://httpoxy.org/
unset req.http.proxy;
# Add X-Forwarded-Proto header when using https
if (!req.http.X-Forwarded-Proto) {
if(std.port(server.ip) == 443 || std.port(server.ip) == 8443) {
set req.http.X-Forwarded-Proto = "https";
} else {
set req.http.X-Forwarded-Proto = "http";
}
}
# Set the X-Forwarded-Host header to the proxy host
set req.http.X-Forwarded-Host = "proxy-app.internal";
# Purge logic to remove objects from the cache.
if (req.method == "PURGE") {
if (client.ip !~ purge) {
return (synth(403, "Forbidden"));
}
if (req.http.xkey) {
set req.http.n-gone = xkey.softpurge(req.http.xkey);
return (synth(200, "Invalidated " + req.http.n-gone + " objects"));
}
return(synth(200, "Purged"));
}
# Only handle relevant HTTP request methods
if (
req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "PATCH" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "DELETE"
) {
return (pipe);
}
# Remove tracking query string parameters used by analytics tools
if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=") {
set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "");
set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "?");
set req.url = regsub(req.url, "\?&", "?");
set req.url = regsub(req.url, "\?$", "");
}
# Only cache GET and HEAD requests
if (req.method != "GET" && req.method != "HEAD") {
set req.http.X-Cacheable = "NO:REQUEST-METHOD";
return(pass);
}
# Mark static files with the X-Static-File header, and remove any cookies
# X-Static-File is also used in vcl_backend_response to identify static files
if (req.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|ogg|ogm|opus|otf|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {
set req.http.X-Static-File = "true";
unset req.http.Cookie;
return(hash);
}
# No caching of special URLs, logged in users and some plugins
if (
req.http.Cookie ~ "wordpress_(?!test_)[a-zA-Z0-9_]+|wp-postpass|comment_author_[a-zA-Z0-9_]+|woocommerce_cart_hash|woocommerce_items_in_cart|wp_woocommerce_session_[a-zA-Z0-9]+|wordpress_logged_in_|comment_author|PHPSESSID" ||
req.http.Authorization ||
req.url ~ "add_to_cart" ||
req.url ~ "edd_action" ||
req.url ~ "nocache" ||
req.url ~ "^/addons" ||
req.url ~ "^/bb-admin" ||
req.url ~ "^/bb-login.php" ||
req.url ~ "^/bb-reset-password.php" ||
req.url ~ "^/cart" ||
req.url ~ "^/checkout" ||
req.url ~ "^/control.php" ||
req.url ~ "^/login" ||
req.url ~ "^/logout" ||
req.url ~ "^/lost-password" ||
req.url ~ "^/my-account" ||
req.url ~ "^/product" ||
req.url ~ "^/register" ||
req.url ~ "^/register.php" ||
req.url ~ "^/server-status" ||
req.url ~ "^/signin" ||
req.url ~ "^/signup" ||
req.url ~ "^/stats" ||
req.url ~ "^/wc-api" ||
req.url ~ "^/wp-admin" ||
req.url ~ "^/wp-comments-post.php" ||
req.url ~ "^/wp-cron.php" ||
req.url ~ "^/wp-login.php" ||
req.url ~ "^/wp-activate.php" ||
req.url ~ "^/wp-mail.php" ||
req.url ~ "^/wp-login.php" ||
req.url ~ "^\?add-to-cart=" ||
req.url ~ "^\?wc-api=" ||
req.url ~ "^/preview=" ||
req.url ~ "^/\.well-known/acme-challenge/"
) {
set req.http.X-Cacheable = "NO:Logged in/Got Sessions";
if (req.http.X-Requested-With == "XMLHttpRequest") {
set req.http.X-Cacheable = "NO:Ajax";
}
return(pass);
}
# Remove any cookies left
unset req.http.Cookie;
return(hash);
}
sub vcl_hash {
if (req.http.X-Forwarded-Proto) {
# Create cache variations depending on the request protocol
hash_data(req.http.X-Forwarded-Proto);
}
}
sub vcl_backend_response {
# Inject URL & Host header into the object for asynchronous banning purposes
set beresp.http.x-url = bereq.url;
set beresp.http.x-host = bereq.http.host;
# If we dont get a Cache-Control header from the backend
# we default to 1h cache for all objects
if (!beresp.http.Cache-Control) {
set beresp.ttl = 1h;
set beresp.http.X-Cacheable = "YES:Forced";
}
# If the file is marked as static we cache it for 1 day
if (bereq.http.X-Static-File == "true") {
unset beresp.http.Set-Cookie;
set beresp.http.X-Cacheable = "YES:Forced";
set beresp.ttl = 1d;
}
if (beresp.http.Set-Cookie) {
set beresp.http.X-Cacheable = "NO:Got Cookies";
} elseif (beresp.http.Cache-Control ~ "private") {
set beresp.http.X-Cacheable = "NO:Cache-Control=private";
}
# The cached document should be "tagged" using the values returned by the x-graphql-keys header
if (beresp.http.x-graphql-keys) {
set beresp.http.xkey = beresp.http.x-graphql-keys;
}
}
sub vcl_deliver {
# Debug header
if (req.http.X-Cacheable) {
set resp.http.X-Cacheable = req.http.X-Cacheable;
} elseif (obj.uncacheable) {
if(!resp.http.X-Cacheable) {
set resp.http.X-Cacheable = "NO:UNCACHEABLE";
}
} elseif (!resp.http.X-Cacheable) {
set resp.http.X-Cacheable = "YES";
}
set resp.http.X-Cache-Hits = obj.hits;
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
# Cleanup of headers
unset resp.http.x-url;
unset resp.http.x-host;
unset resp.http.via;
unset resp.http.x-varnish;
}