From 83cc75379a8d4fb231b56b0e7fdf68dbf1e35faa Mon Sep 17 00:00:00 2001 From: Mark Kelnar <749603+markkelnar@users.noreply.github.com> Date: Mon, 21 Aug 2023 14:27:06 -0500 Subject: [PATCH] Add varnish docker image. Update docs --- composer.lock | 100 +++++++++++++++++++------ docker-compose.yml | 22 +++--- docker/varnish/default.vcl | 49 +++++++++++++ docs/development.md | 145 +++++++++++++++++++++++++++---------- docs/network-cache.md | 6 +- 5 files changed, 246 insertions(+), 76 deletions(-) create mode 100644 docker/varnish/default.vcl diff --git a/composer.lock b/composer.lock index 0ee8f26a..8f2e27e7 100644 --- a/composer.lock +++ b/composer.lock @@ -1645,16 +1645,16 @@ }, { "name": "illuminate/collections", - "version": "v10.18.0", + "version": "v10.19.0", "source": { "type": "git", "url": "https://github.com/illuminate/collections.git", - "reference": "66ff5aab0dd10659aff0efe3ff101819db192dfe" + "reference": "f494398dbaaead9e5ff16a18002d11634e8358e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/collections/zipball/66ff5aab0dd10659aff0efe3ff101819db192dfe", - "reference": "66ff5aab0dd10659aff0efe3ff101819db192dfe", + "url": "https://api.github.com/repos/illuminate/collections/zipball/f494398dbaaead9e5ff16a18002d11634e8358e6", + "reference": "f494398dbaaead9e5ff16a18002d11634e8358e6", "shasum": "" }, "require": { @@ -1696,11 +1696,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-08-02T14:57:32+00:00" + "time": "2023-08-11T14:48:51+00:00" }, { "name": "illuminate/conditionable", - "version": "v10.18.0", + "version": "v10.19.0", "source": { "type": "git", "url": "https://github.com/illuminate/conditionable.git", @@ -1746,7 +1746,7 @@ }, { "name": "illuminate/contracts", - "version": "v10.18.0", + "version": "v10.19.0", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", @@ -1794,7 +1794,7 @@ }, { "name": "illuminate/macroable", - "version": "v10.18.0", + "version": "v10.19.0", "source": { "type": "git", "url": "https://github.com/illuminate/macroable.git", @@ -1840,16 +1840,16 @@ }, { "name": "illuminate/support", - "version": "v10.18.0", + "version": "v10.19.0", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "9ce4a26975a919ec33ed21307633ac02208537a8" + "reference": "0a8526d55756955fcec6be7c2c6cd14d915c8c0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/9ce4a26975a919ec33ed21307633ac02208537a8", - "reference": "9ce4a26975a919ec33ed21307633ac02208537a8", + "url": "https://api.github.com/repos/illuminate/support/zipball/0a8526d55756955fcec6be7c2c6cd14d915c8c0f", + "reference": "0a8526d55756955fcec6be7c2c6cd14d915c8c0f", "shasum": "" }, "require": { @@ -1907,7 +1907,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-08-08T14:14:45+00:00" + "time": "2023-08-14T21:56:59+00:00" }, { "name": "ivome/graphql-relay-php", @@ -2350,25 +2350,29 @@ }, { "name": "nesbot/carbon", - "version": "2.68.1", + "version": "2.69.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "4f991ed2a403c85efbc4f23eb4030063fdbe01da" + "reference": "4308217830e4ca445583a37d1bf4aff4153fa81c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4f991ed2a403c85efbc4f23eb4030063fdbe01da", - "reference": "4f991ed2a403c85efbc4f23eb4030063fdbe01da", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4308217830e4ca445583a37d1bf4aff4153fa81c", + "reference": "4308217830e4ca445583a37d1bf4aff4153fa81c", "shasum": "" }, "require": { "ext-json": "*", "php": "^7.1.8 || ^8.0", + "psr/clock": "^1.0", "symfony/polyfill-mbstring": "^1.0", "symfony/polyfill-php80": "^1.16", "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" }, + "provide": { + "psr/clock-implementation": "1.0" + }, "require-dev": { "doctrine/dbal": "^2.0 || ^3.1.4", "doctrine/orm": "^2.7", @@ -2448,7 +2452,7 @@ "type": "tidelift" } ], - "time": "2023-06-20T18:29:04+00:00" + "time": "2023-08-03T09:00:52+00:00" }, { "name": "nikic/php-parser", @@ -3328,16 +3332,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.10", + "version": "9.6.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328" + "reference": "810500e92855eba8a7a5319ae913be2da6f957b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a6d351645c3fe5a30f5e86be6577d946af65a328", - "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/810500e92855eba8a7a5319ae913be2da6f957b0", + "reference": "810500e92855eba8a7a5319ae913be2da6f957b0", "shasum": "" }, "require": { @@ -3411,7 +3415,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.10" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.11" }, "funding": [ { @@ -3427,7 +3431,55 @@ "type": "tidelift" } ], - "time": "2023-07-10T04:04:23+00:00" + "time": "2023-08-19T07:10:56+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" }, { "name": "psr/container", diff --git a/docker-compose.yml b/docker-compose.yml index b76344fb..09a18b67 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,16 @@ version: '3.3' services: + varnish: + image: varnish:7.3 + container_name: varnish + volumes: + - "./docker/varnish/default.vcl:/etc/varnish/default.vcl" + ports: + - "8081:80" + depends_on: + - "app" + app: depends_on: - app_db @@ -16,8 +26,6 @@ services: USING_XDEBUG: ${USING_XDEBUG:-} ports: - '8091:80' - networks: - local: app_db: image: mariadb:10.11 @@ -25,8 +33,6 @@ services: - .env.dist ports: - '3306' - networks: - local: testing: depends_on: @@ -42,8 +48,6 @@ services: environment: WP_URL: http://localhost USING_XDEBUG: ${USING_XDEBUG:-} - networks: - local: testing_db: image: mariadb:10.11 @@ -51,9 +55,3 @@ services: - .env.testing ports: - '3306' - networks: - local: - - -networks: - local: diff --git a/docker/varnish/default.vcl b/docker/varnish/default.vcl new file mode 100644 index 00000000..8980ceef --- /dev/null +++ b/docker/varnish/default.vcl @@ -0,0 +1,49 @@ +vcl 4.1; + +import xkey; + +backend default { + .host = "app"; + .port = "80"; +} + +sub vcl_deliver { + if (obj.hits > 0) { + set resp.http.X-Cache = "HIT: " + obj.hits; + } else { + set resp.http.X-Cache = "MISS"; + } +} + +sub vcl_recv { + + set req.http.host = "localhost:8091"; + + if (req.method == "PURGE_GRAPHQL") { + set req.http.n-gone = 0; + + if (req.http.GraphQL-Purge-Keys && req.http.GraphQL-URL ) { + set req.http.xkeyPrefix = req.http.GraphQL-URL; + + # replace commas with spaces + set req.http.cleanPurgeKeys = regsuball(req.http.GraphQL-Purge-Keys, "(\,+),?", " "); + + # find all strings separated by space and add the host as a prefix. + set req.http.purgeKeys = regsuball(req.http.cleanPurgeKeys, "(\S+),?", req.http.xkeyPrefix + ":\1"); + + # call xkey.purge on the keys sent in the GraphQL-Purge-Keys header(s) + set req.http.n-gone = xkey.purge( req.http.purgeKeys ); + } + + # Return 200, reason showing how many queries were invalidated + return (synth(200, "Invalidated GraphQL Queries: "+req.http.purgeKeys+" "+req.http.n-gone)); + } +} + +sub vcl_backend_response { + if ( beresp.http.X-GraphQL-Keys ) { + + set beresp.http.xkeyPrefix = beresp.http.X-GraphQL-URL; + set beresp.http.xkey = regsuball(beresp.http.X-GraphQL-Keys, "(\S+),?", beresp.http.xkeyPrefix + ":\1"); + } +} diff --git a/docs/development.md b/docs/development.md index e26e17cb..1f1ccc5b 100644 --- a/docs/development.md +++ b/docs/development.md @@ -2,116 +2,185 @@ In this document you will find information about how to develop for and contribute to the WPGraphQL Smart Cache plugin. -## Local Development +- [Build](#plugin-build) +- [WordPress App](#wordpress-app) +- [WordPress Tests](#wordpess-tests) +- [Network Cache](#network-cache) + We recommend using Docker for local development. With the instructions below, you can use Docker to build the app in an isolated environment with a local running WordPress + WPGraphQL. You can also use Docker to run the test suites. -## Build +## Plugin Build Use one of the following commands to build the plugin source and it's dependencies. Do this at least once after initial checkout or after updating composer.json. - composer install --optimize-autoloader + composer install --optimize-autoloader - composer update --optimize-autoloader + composer update --optimize-autoloader One option is to use a docker image to run php/composer: - docker run -v $PWD:/app composer install --optimize-autoloader + docker run -v $PWD:/app composer install --optimize-autoloader -# Docker App Image +## WordPress App This section describes how to setup and run this plugin, WP and the wp-graphql plugin locally with docker. It requires building the images (see above) at least once, which can take a few moments the first time. -## Build +### Build Use one of the following commands to build the local images for the app and testing. -### docker-compose +#### docker-compose Build all images in the docker compose configuration. Requires having built your own wp-graphql local images. - WP_VERSION=5.9 PHP_VERSION=8.0 docker-compose build --build-arg WP_VERSION=5.9 --build-arg PHP_VERSION=8.0 + WP_VERSION=5.9 PHP_VERSION=8.0 docker-compose build --build-arg WP_VERSION=5.9 --build-arg PHP_VERSION=8.0 Build fresh docker image without cache by adding `--no-cache`. - WP_VERSION=5.9 PHP_VERSION=8.0 docker-compose build --build-arg WP_VERSION=5.9 --build-arg PHP_VERSION=8.0 --no-cache + WP_VERSION=5.9 PHP_VERSION=8.0 docker-compose build --build-arg WP_VERSION=5.9 --build-arg PHP_VERSION=8.0 --no-cache Build using wp-graphql image from docker hub registry, instead of building your own wp-graphql image. - WP_VERSION=5.9 PHP_VERSION=8.0 docker-compose build --build-arg WP_VERSION=5.9 --build-arg PHP_VERSION=8.0 --build-arg DOCKER_REGISTRY=ghcr.io/wp-graphql/ + WP_VERSION=5.9 PHP_VERSION=8.0 docker-compose build --build-arg WP_VERSION=5.9 --build-arg PHP_VERSION=8.0 --build-arg DOCKER_REGISTRY=ghcr.io/wp-graphql/ -### docker +#### docker Use this command if you want to build a specific image. If you ran the docker-compose command above, this is not necessary. - docker build -f docker/Dockerfile -t wp-graphql-smart-cache:latest-wp5.6-php8.0 --build-arg WP_VERSION=5.6 --build-arg PHP_VERSION=8.0 . + docker build -f docker/Dockerfile -t wp-graphql-smart-cache:latest-wp5.6-php8.0 --build-arg WP_VERSION=5.6 --build-arg PHP_VERSION=8.0 . -## Run +### Run Use one of the following to start the WP app with the plugin installed and running. After running, navigate to the app in a web browser at http://localhost:8091/ - docker compose up app + docker compose up app This is an example of specifying the WP and PHP version for the wp-graphql images. - WP_VERSION=5.9 PHP_VERSION=8.0 docker compose up app + WP_VERSION=5.9 PHP_VERSION=8.0 docker compose up app -## Shell +### Shell Use one of the following if you want to access the WP app with bash command shell. - docker-compose run app bash + docker-compose run app bash - WP_VERSION=5.9 PHP_VERSION=8.0 docker-compose run app bash + WP_VERSION=5.9 PHP_VERSION=8.0 docker-compose run app bash -## Stop +### Stop Use this command to stop the running app and database. - docker-compose stop + docker-compose stop -## Attach local wp-graphql plugin +### Use a local wp-graphql plugin -Add this to volumes section in docker-compose.yml if you have a copy of the wp-graphql plugin you'd like to use in the running app. +If you have a copy of the wp-graphql plugin you'd like to use in the running app, Add this to volumes section in docker-compose.yml. - - './local-wp-graphql:/var/www/html/wp-content/plugins/wp-graphql' + - './local-wp-graphql:/var/www/html/wp-content/plugins/wp-graphql' -# WP Tests +## WordPress Tests -Use this section to run the plugin codeception test suites. +Use this section to run the plugin test suites. -## Build +### Build Use one of the following commands to build the test docker image. -### docker-compose +#### docker-compose If you ran the docker-compose build command, above, this step is not necessary and you should already have the build docker image, skip to run. -### docker +#### docker - WP_VERSION=5.9 PHP_VERSION=8.0 docker build -f docker/Dockerfile.testing -t wp-graphql-smart-cache-testing:latest-wp5.7.2-php7.4 --build-arg WP_VERSION=5.9 --build-arg PHP_VERSION=8.0 --build-arg DOCKER_REGISTRY=ghcr.io/wp-graphql/ . + WP_VERSION=5.9 PHP_VERSION=8.0 docker build -f docker/Dockerfile.testing -t wp-graphql-smart-cache-testing:latest-wp5.7.2-php7.4 --build-arg WP_VERSION=5.9 --build-arg PHP_VERSION=8.0 --build-arg DOCKER_REGISTRY=ghcr.io/wp-graphql/ . - docker build -f docker/Dockerfile.testing -t wp-graphql-smart-cache-testing:latest-wp5.7.2-php7.4 --build-arg WP_VERSION=5.9 --build-arg PHP_VERSION=8.0 --build-arg DOCKER_REGISTRY=ghcr.io/wp-graphql/ . + docker build -f docker/Dockerfile.testing -t wp-graphql-smart-cache-testing:latest-wp5.7.2-php7.4 --build-arg WP_VERSION=5.9 --build-arg PHP_VERSION=8.0 --build-arg DOCKER_REGISTRY=ghcr.io/wp-graphql/ . -## Run +### Run Use one of these commands to run the test suites. - WP_VERSION=5.9 PHP_VERSION=8.0 docker-compose run testing + WP_VERSION=5.9 PHP_VERSION=8.0 docker-compose run testing - docker-compose run testing + docker-compose run testing Use the DEBUG environment variable to see the codeception debug during tests. - WP_VERSION=5.9 PHP_VERSION=8.0 docker-compose run -e DEBUG=1 testing + WP_VERSION=5.9 PHP_VERSION=8.0 docker-compose run -e DEBUG=1 testing -## Shell +### Shell Use one of the following if you want to access the WP testing app with bash command shell. - docker-compose run --entrypoint bash testing + docker-compose run --entrypoint bash testing This is an example of specifying the WP and PHP version for the wp-graphql images. - WP_VERSION=5.9 PHP_VERSION=8.0 docker-compose run --entrypoint bash testing + WP_VERSION=5.9 PHP_VERSION=8.0 docker-compose run --entrypoint bash testing + +## Network Cache + +Use these steps to run a network cache (varnish) to similate the caching behavior of graphql requests and invalidation during content updates. + + WP_VERSION=6.1 PHP_VERSION=8.1 docker compose up varnish + +The varnish app starts and listens at http://localhost:8081/ for requests. + +Test a graphql query to load post #1 "Hello World" at this 'http://localhost:8081/graphql?query={ post(id: "1", idType: DATABASE_ID) { title } }' + +### Cache Requests + +Send graphql request to http://localhost:8081/graphql end point. You will see caching headers in the response. + +Initial return will miss the cache and access the WordPress backend. + +``` +X-Cache: Miss +``` + +Subsequent requests that have cached content will return that without accessing the backend WordPress. + +``` +X-Cache: HIT: 1 +``` + +Also note the correspeonding Graphql specific headers in the response. + +``` +X-Graphql-Query-Id: +X-Graphql-Url: +``` + +### Purge Cache + +Add this code to your WordPress to handle invalidation with the running varnish server. + +``` +add_action( + 'graphql_purge', + function ( $purge_keys, $event = 'event', $url = '' ) { + $headers = + $response = wp_remote_post( + 'http://varnish', + [ + 'method' => 'PURGE_GRAPHQL', + 'headers' => [ + 'GraphQL-Purge-Keys' => $purge_keys, + 'GraphQL-URL' => graphql_get_endpoint_url(), + ], + ], + ); + error_log( "GRAPHQL_PURGE $url $purge_keys ". $response['body'] ); + }, + 10, + 3 +); +``` + +Run some graphql requests and verify seeing the cached response. Change content (mutation or Wp-Admin) and verify what was invalidated (purged) from cache. + +If using WP-Admin to change content, login at http://localhost:8091 in a different browser or incognito from where you are testing cached graphql queries. + +After data is invalidate, your previously cache request will access the WP backend for fresh data and show `X-Cache: Miss` in the response headers. diff --git a/docs/network-cache.md b/docs/network-cache.md index 32549e6b..201434ea 100644 --- a/docs/network-cache.md +++ b/docs/network-cache.md @@ -22,6 +22,8 @@ As more hosts work with us to support this feature, some nuances (for example he To follow this quick start guide, we recommend having a WordPress install on WP Engine. You can [sign up for a FREE WP Engine Atlas sandbox account](https://wpengine.com/atlas) to follow this guide. +See the [development](development.md) guide about running Varnish locally to test the behavior. + ### 👩‍💻 Execute a GraphQL Query (as an HTTP GET request) Using a browser that is not authenticated to your WordPress site (or an incognito window), execute a GraphQL query as an HTTP GET request by visiting a url for a GraphQL query in the browser, like so (replacing the domain with your domain): `https://${yourdomain.com}/graphql?query={posts{nodes{id,title,uri}}}` @@ -275,14 +277,14 @@ When these purge actions are sent out, your host should listen for the action an For example: ```php -add_action( 'graphql_purge', function ( $purge_keys ) { +add_action( 'graphql_purge', function ( $purge_keys, $event = '', $hostname = '' ) { if ( ! function_exists( 'your_network_cache_purge_function' ) ) { return; } // a function that communicates with Varnish, Fastly, etc to purge the tagged documents - your_network_cache_purge_function( $purge_keys ); + your_network_cache_purge_function( $purge_keys, $hostname ); }, 10, 1 ); ```