diff --git a/README.md b/README.md index 8766daa..cb42fb6 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,9 @@ parameters: cors_allow_origin: - 'http://localhost:3000' - 'http://localhost:9080' + varnish_enabled: false + varnish_urls: + - 'http://varnish' ``` Build the image: diff --git a/app/AppKernel.php b/app/AppKernel.php index 6c3b419..02f201f 100644 --- a/app/AppKernel.php +++ b/app/AppKernel.php @@ -27,6 +27,7 @@ public function registerBundles() $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle(); $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); + $bundles[] = new Symfony\Bundle\WebServerBundle\WebServerBundle(); $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(); $bundles[] = new Hautelook\AliceBundle\HautelookAliceBundle(); diff --git a/app/config/config.yml b/app/config/config.yml index 80d3576..6880e4e 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -83,3 +83,13 @@ fos_oauth_server: auth_code_class: AppBundle\Entity\OAuth\AuthCode service: user_provider: app.security.account_provider + +api_platform: + http_cache: + invalidation: + enabled: "%varnish_enabled%" + varnish_urls: "%varnish_urls%" + max_age: 0 + shared_max_age: 3600 + vary: ['Content-Type', 'Authorization'] + public: true diff --git a/app/config/parameters.yml.dist b/app/config/parameters.yml.dist index 295b7ec..a295557 100644 --- a/app/config/parameters.yml.dist +++ b/app/config/parameters.yml.dist @@ -13,3 +13,6 @@ parameters: secret: 4b0792ed56174458edbee5173fada1f20bfb94a3 cors_allow_origin: ["http://localhost:3000", "http://localhost:9080"] + + varnish_enabled: false + varnish_urls: ["http://varnish"] diff --git a/composer.json b/composer.json index 63600fa..c4f02b9 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,8 @@ }, "require": { "php": ">=7.1", - "symfony/symfony": "3.2.*", - "api-platform/core": "^2.0", + "symfony/symfony": "^3.3", + "api-platform/core": "^2.1@beta", "doctrine/orm": "^2.5", "doctrine/doctrine-bundle": "^1.6", "doctrine/doctrine-cache-bundle": "^1.2", @@ -37,38 +37,35 @@ "nelmio/cors-bundle": "^1.4", "phpdocumentor/reflection-docblock": "^3.0", "doctrine/doctrine-migrations-bundle": "^1.2", - "friendsofsymfony/oauth-server-bundle": "^1.5" + "friendsofsymfony/oauth-server-bundle": "^1.5", + "guzzlehttp/guzzle": "^6.0" }, "require-dev": { - "api-platform/schema-generator": "^1.2", "sensio/generator-bundle": "^3.0", "symfony/phpunit-bridge": "^3.0", "behat/behat": "^3.1", "behat/symfony2-extension": "^2.1", "behat/mink": "^1.7", "behat/mink-extension": "^2.2", - "behat/mink-browserkit-driver": "^1.3.1", + "behat/mink-browserkit-driver": "^1.3", "behatch/contexts": "^2.5", "doctrine/doctrine-fixtures-bundle": "^2.3", "hautelook/alice-bundle": "^1.4", "phpunit/phpunit": "^6.2" }, "scripts": { - "post-install-cmd": [ + "symfony-scripts": [ "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile", "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::prepareDeploymentTarget" ], + "post-install-cmd": [ + "@symfony-scripts" + ], "post-update-cmd": [ - "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::prepareDeploymentTarget" + "@symfony-scripts" ] }, "extra": { @@ -82,7 +79,7 @@ "file": "app/config/parameters.yml" }, "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } } } diff --git a/docker-compose.yml b/docker-compose.yml index 5438d45..f4215fc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,7 @@ services: image: postgres:9-alpine volumes: - db-data:/var/lib/postgresql + nginx: image: nginx:1.11-alpine depends_on: @@ -17,6 +18,20 @@ services: volumes: - ./docker/nginx/conf.d:/etc/nginx/conf.d:ro - ./web:/srv/api-platform/web + + + varnish: + build: + context: ./docker/varnish + dockerfile: ./Dockerfile + depends_on: + - php + - nginx + volumes: + - ./docker/varnish/conf:/etc/varnish:ro + ports: + - "80:80" + php: build: . depends_on: diff --git a/docker/varnish/Dockerfile b/docker/varnish/Dockerfile new file mode 100644 index 0000000..354820f --- /dev/null +++ b/docker/varnish/Dockerfile @@ -0,0 +1,9 @@ +FROM alpine:3.5 + +RUN apk add --no-cache varnish + +COPY start.sh /usr/local/bin/docker-app-start + +RUN chmod +x /usr/local/bin/docker-app-start + +CMD ["docker-app-start"] \ No newline at end of file diff --git a/docker/varnish/conf/default.vcl b/docker/varnish/conf/default.vcl new file mode 100644 index 0000000..4273e84 --- /dev/null +++ b/docker/varnish/conf/default.vcl @@ -0,0 +1,86 @@ +vcl 4.0; + +import std; + +backend default { + .host = "nginx"; + .port = "80"; + # Health check + .probe = { + .url = "/"; + .timeout = 5s; + .interval = 10s; + .window = 5; + .threshold = 3; + } +} + +# Hosts allowed to send BAN requests +acl ban { + "localhost"; + "app"; +} + +sub vcl_backend_response { + # Ban lurker friendly header + set beresp.http.url = bereq.url; + + # Add a grace in case the backend is down + set beresp.grace = 1h; +} + +sub vcl_deliver { + # Don't send cache tags related headers to the client + unset resp.http.url; + # Uncomment the following line to NOT send the "Cache-Tags" header to the client (prevent using CloudFlare cache tags) + #unset resp.http.Cache-Tags; +} + +sub vcl_recv { + # Remove the "Forwarded" HTTP header if exists (security) + unset req.http.forwarded; + + # To allow API Platform to ban by cache tags + if (req.method == "BAN") { + if (client.ip !~ ban) { + return(synth(405, "Not allowed")); + } + + if (req.http.ApiPlatform-Ban-Regex) { + ban("obj.http.Cache-Tags ~ " + req.http.ApiPlatform-Ban-Regex); + + return(synth(200, "Ban added")); + } + + return(synth(400, "ApiPlatform-Ban-Regex HTTP header must be set.")); + } + + # Don't cache in dev mode + if (req.url ~ "^/app_dev.php") { + return(pass); + } +} + +# From https://github.com/varnish/Varnish-Book/blob/master/vcl/grace.vcl +sub vcl_hit { + if (obj.ttl >= 0s) { + # Normal hit + return (deliver); + } elsif (std.healthy(req.backend_hint)) { + # The backend is healthy + # Fetch the object from the backend + return (fetch); + } else { + # No fresh object and the backend is not healthy + if (obj.ttl + obj.grace > 0s) { + # Deliver graced object + # Automatically triggers a background fetch + return (deliver); + } else { + # No valid object to deliver + # No healthy backend to handle request + # Return error + return (synth(503, "API is down")); + } + } +} \ No newline at end of file diff --git a/docker/varnish/start.sh b/docker/varnish/start.sh new file mode 100644 index 0000000..ecbfe72 --- /dev/null +++ b/docker/varnish/start.sh @@ -0,0 +1,5 @@ +#!/bin/sh +set -xe + +varnishd -a :80 -f /etc/varnish/default.vcl -s malloc,256m +varnishlog \ No newline at end of file diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php index f6ad030..ad9bef0 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/FeatureContext.php @@ -1,7 +1,6 @@