Allow Nginx cached static content to be purged (#9857) [deploy]

* Only bust_fastly_cache if fastly is enabled

* Conditionally bust nginx cache from CacheBuster#bust

* Don't _actually_ call out to openresty

* Remove redundant check for FASTLY_API_KEY in CacheBuster

* Clean up and add a spec

* Do not call .purge_ methods if fastly is not configured

* Add OPENRESTY_ ENV vars to .env_sample

* Remove extra / prepending path

* Remove ConfigurationError, clean up Purgeable concern

* Use raise instead of fail like fastly-ruby

* No longer check for Rails.env.production?

* Use let! to create article in BustMultipleCachesWorker spec
This commit is contained in:
Vaidehi Joshi 2020-08-21 09:31:23 -07:00 committed by GitHub
parent 07adb7eab9
commit 9e40e68682
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 93 additions and 17 deletions

View file

@ -2,6 +2,10 @@
export APP_DOMAIN="localhost:3000"
export APP_PROTOCOL="http://"
# Openresty domain + Protocol setting for development
export OPENRESTY_DOMAIN=""
export OPENRESTY_PROTOCOL=""
# Community Related Variables
export COMMUNITY_NAME="DEV(local)"

View file

@ -18,18 +18,49 @@ module CacheBuster
# fastly.purge(path)
#
# https://github.com/fastly/fastly-ruby#efficient-purging
return unless Rails.env.production?
return if ApplicationConfig["FASTLY_API_KEY"].blank?
HTTParty.post("https://api.fastly.com/purge/https://#{ApplicationConfig['APP_DOMAIN']}#{path}",
headers: { "Fastly-Key" => ApplicationConfig["FASTLY_API_KEY"] })
HTTParty.post("https://api.fastly.com/purge/https://#{ApplicationConfig['APP_DOMAIN']}#{path}?i=i",
headers: { "Fastly-Key" => ApplicationConfig["FASTLY_API_KEY"] })
if fastly_enabled?
bust_fastly_cache(path)
elsif nginx_enabled?
bust_nginx_cache(path)
end
rescue URI::InvalidURIError => e
Rails.logger.error("Trying to bust cache of an invalid uri: #{e}")
DatadogStatsClient.increment("cache_buster.invalid_uri", tags: ["path:#{path}"])
end
def self.fastly_enabled?
ApplicationConfig["FASTLY_API_KEY"].present? && ApplicationConfig["FASTLY_SERVICE_ID"].present?
end
def self.nginx_enabled?
ApplicationConfig["OPENRESTY_PROTOCOL"].present? && ApplicationConfig["OPENRESTY_DOMAIN"].present?
end
def self.bust_fastly_cache(path)
HTTParty.post(
"https://api.fastly.com/purge/https://#{ApplicationConfig['APP_DOMAIN']}#{path}",
headers: {
"Fastly-Key" => ApplicationConfig["FASTLY_API_KEY"]
},
)
HTTParty.post(
"https://api.fastly.com/purge/https://#{ApplicationConfig['APP_DOMAIN']}#{path}?i=i",
headers: {
"Fastly-Key" => ApplicationConfig["FASTLY_API_KEY"]
},
)
end
def self.bust_nginx_cache(path)
uri = URI.parse("#{ApplicationConfig['OPENRESTY_PROTOCOL']}#{ApplicationConfig['OPENRESTY_DOMAIN']}#{path}")
http = Net::HTTP.new(uri.host, uri.port)
response = http.request Net::HTTP::NginxPurge.new(uri.request_uri)
raise StandardError, "NginxPurge request failed: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
response.body
end
def self.bust_comment(commentable)
return unless commentable
@ -206,3 +237,12 @@ module CacheBuster
end
end
end
# Creates our own purge method for an HTTP request,
# which is used by Nginx to bust a cache.
# See Net::HTTPGenericRequest for attributes/methods.
class Net::HTTP::NginxPurge < Net::HTTPRequest # rubocop:disable Style/ClassAndModuleChildren
METHOD = "PURGE".freeze
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
end

View file

@ -645,8 +645,6 @@ class Article < ApplicationRecord
end
def bust_cache
return unless Rails.env.production?
CacheBuster.bust(path)
CacheBuster.bust("#{path}?i=i")
CacheBuster.bust("#{path}?preview=#{password}")

View file

@ -1,19 +1,20 @@
# Copied from the deprecated fastly-rails gem
# https://github.com/fastly/fastly-rails/blob/master/lib/fastly-rails/active_record/surrogate_key.rb
#
# This concern handles purge and purge_all calls to purge the edge cache (Fastly)
# This concern handles purge and purge_all calls to purge Fastly's edge cache.
# If Fastly has not been configured, these methods will short circuit and not be invoked.
module Purgeable
extend ActiveSupport::Concern
module ClassMethods
def purge_all
return if Rails.env.development?
return unless fastly
service.purge_by_key(table_key)
end
def soft_purge_all
return if Rails.env.development?
return unless fastly
service.purge_by_key(table_key, true)
end
@ -23,13 +24,14 @@ module Purgeable
end
def fastly
return if Rails.env.development?
return false if Rails.env.development?
return false if ApplicationConfig["FASTLY_API_KEY"].blank? || ApplicationConfig["FASTLY_SERVICE_ID"].blank?
Fastly.new(api_key: ApplicationConfig["FASTLY_API_KEY"])
end
def service
return if Rails.env.development?
return unless fastly
Fastly::Service.new({ id: ApplicationConfig["FASTLY_SERVICE_ID"] }, fastly)
end
@ -45,13 +47,13 @@ module Purgeable
end
def purge
return if Rails.env.development?
return unless fastly
service.purge_by_key(record_key)
end
def soft_purge
return if Rails.env.development?
return unless fastly
service.purge_by_key(record_key, true)
end

View file

@ -11,6 +11,31 @@ RSpec.describe CacheBuster, type: :labor do
let(:podcast_episode) { create(:podcast_episode, podcast_id: podcast.id) }
let(:tag) { create(:tag) }
describe "#bust_nginx_cache" do
before do
allow(cache_buster).to receive(:bust_nginx_cache).and_call_original
allow(ApplicationConfig).to receive(:[]).with("OPENRESTY_PROTOCOL").and_return("http://")
allow(ApplicationConfig).to receive(:[]).with("OPENRESTY_DOMAIN").and_return("localhost:9090")
end
it "can bust an nginx cache when configured" do
cache_buster.bust_nginx_cache("/#{user.username}")
end
end
describe "#bust_fastly_cache" do
before do
allow(cache_buster).to receive(:bust_fastly_cache).and_call_original
allow(ApplicationConfig).to receive(:[]).with("APP_DOMAIN").and_return("fake-key")
allow(ApplicationConfig).to receive(:[]).with("FASTLY_API_KEY").and_return("fake-key")
allow(ApplicationConfig).to receive(:[]).with("FASTLY_SERVICE_ID").and_return("localhost:3000")
end
it "can bust a fastly cache when configured" do
cache_buster.bust_fastly_cache("/#{user.username}")
end
end
describe "#bust_comment" do
it "busts comment" do
cache_buster.bust_comment(comment.commentable)

View file

@ -18,6 +18,8 @@ RSpec.describe Purgeable do
let(:fastly_service) { instance_double(Fastly::Service) }
before do
allow(ApplicationConfig).to receive(:[]).with("FASTLY_API_KEY").and_return("fake-key")
allow(ApplicationConfig).to receive(:[]).with("FASTLY_SERVICE_ID").and_return("fake-service-id")
allow(Fastly::Service).to receive(:new).and_return(fastly_service)
end

View file

@ -139,6 +139,9 @@ RSpec.configure do |config|
stub_request(:post, /api.fastly.com/)
.to_return(status: 200, body: "".to_json, headers: {})
stub_request(:any, /localhost:9090/)
.to_return(status: 200, body: "OK".to_json, headers: {})
stub_request(:post, /api.bufferapp.com/)
.to_return(status: 200, body: { fake_text: "so fake" }.to_json, headers: {})

View file

@ -2,7 +2,9 @@ require "rails_helper"
RSpec.describe Articles::BustMultipleCachesWorker, type: :worker do
describe "#perform" do
let(:article) { create(:article) }
# Explicitly create article before the test is invoked, since
# creating an article will invoke CacheBuster#bust in a callback.
let!(:article) { create(:article) }
let(:path) { article.path }
let(:worker) { subject }