Skip to content

Commit

Permalink
🏗️ Configure Cache-Control headers
Browse files Browse the repository at this point in the history
Why:
- On the SPA site, the reverse proxy (nginx) handled to cache-control
  headers. With the SSR site, the settings need to be changed and there
  will not be a separate reverse proxy which serves static assets, so it
  needs to be done in Ring middleware.
  • Loading branch information
luontola committed Jul 12, 2024
1 parent 18f656d commit 036f13c
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 1 deletion.
25 changes: 24 additions & 1 deletion src/territory_bro/infra/middleware.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
;; The license text is at http://www.apache.org/licenses/LICENSE-2.0

(ns territory-bro.infra.middleware
(:require [clojure.tools.logging :as log]
(:require [clojure.string :as str]
[clojure.tools.logging :as log]
[ring-ttl-session.core :as ttl-session]
[ring.logger :as logger]
[ring.middleware.defaults :refer [site-defaults wrap-defaults]]
Expand Down Expand Up @@ -75,6 +76,27 @@
(refresh-projections!))
resp)))

(defn- static-asset? [path]
(or (str/starts-with? path "/assets/")
(= "/favicon.ico" path)))

(defn- content-hashed? [path]
(some? (re-find #"-[0-9a-f]{8,40}\.\w+$" path)))

(defn wrap-cache-control [handler]
(fn [request]
(let [response (handler request)
path (:uri request)]
(if (some? (response/get-header response "cache-control"))
response
(response/header response "Cache-Control"
(if (and (= 200 (:status response))
(static-asset? path))
(if (content-hashed? path)
"public, max-age=2592000, immutable"
"public, max-age=3600, stale-while-revalidate=86400")
"private, no-cache"))))))

(defn wrap-base [handler]
(-> handler
wrap-auto-refresh-projections
Expand All @@ -90,4 +112,5 @@
(assoc-in [:security :anti-forgery] false) ; TODO: enable CSRF, create a custom error page for it
(assoc-in [:session :store] session-store)
(assoc-in [:session :flash] false)))
wrap-cache-control
wrap-internal-error))
68 changes: 68 additions & 0 deletions test/territory_bro/infra/middleware_test.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
;; Copyright © 2015-2024 Esko Luontola
;; This software is released under the Apache License 2.0.
;; The license text is at http://www.apache.org/licenses/LICENSE-2.0

(ns territory-bro.infra.middleware-test
(:require [clojure.test :refer :all]
[ring.util.http-response :as http-response]
[ring.util.response :as response]
[territory-bro.infra.middleware :as middleware]))

(deftest wrap-cache-control-test
(testing "SSR pages are not cached"
(let [handler (-> (constantly (http-response/ok ""))
middleware/wrap-cache-control)]
(is (= {:status 200
:headers {"Cache-Control" "private, no-cache"}
:body ""}
(handler {:request-method :get
:uri "/"})
(handler {:request-method :get
:uri "/some/page"})))))

(testing "if response contains a custom cache-control header, that one is used"
(let [handler (-> (constantly (-> (http-response/ok "")
(response/header "Cache-Control" "custom value")))
middleware/wrap-cache-control)]
(is (= {:status 200
:headers {"Cache-Control" "custom value"}
:body ""}
(handler {:request-method :get
:uri "/"})
(handler {:request-method :get
:uri "/some/page"})))))

(testing "static assets are cached for one hour"
(let [handler (-> (constantly (http-response/ok ""))
middleware/wrap-cache-control)]
(is (= {:status 200
:headers {"Cache-Control" "public, max-age=3600, stale-while-revalidate=86400"}
:body ""}
(handler {:request-method :get
:uri "/favicon.ico"})
(handler {:request-method :get
:uri "/assets/style.css"})))))

(testing "static assets with a content hash are cached indefinitely"
(let [handler (-> (constantly (http-response/ok ""))
middleware/wrap-cache-control)]
(is (= {:status 200
:headers {"Cache-Control" "public, max-age=2592000, immutable"}
:body ""}
(handler {:request-method :get
:uri "/assets/style-4da573e6.css"})
(handler {:request-method :get
:uri "/assets/image-28ead48996a4ca92f07ee100313e57355dbbcbf2.svg"})))))

(testing "error responses are not cached"
(let [handler (-> (constantly (http-response/not-found ""))
middleware/wrap-cache-control)]
(is (= {:status 404
:headers {"Cache-Control" "private, no-cache"}
:body ""}
(handler {:request-method :get
:uri "/some/page"})
(handler {:request-method :get
:uri "/assets/style.css"})
(handler {:request-method :get
:uri "/assets/style-4da573e6.css"}))))))

0 comments on commit 036f13c

Please sign in to comment.