Agent skill

clojure-ring-core-middleware

Ring core middleware for Clojure. Use when working with parameter parsing, sessions, cookies, flash messages, static files, or composing middleware stacks. Triggers on ring.middleware, wrap-params, wrap-session, wrap-cookies, or middleware composition questions.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/clojure-ring-core-middleware

SKILL.md

Ring Core Middleware

Ring middleware are higher-order functions that wrap handlers to add functionality. Middleware compose via function wrapping - outermost middleware executes first.

Quick Start

Common middleware stack:

clojure
(require '[ring.middleware.params :refer [wrap-params]]
         '[ring.middleware.keyword-params :refer [wrap-keyword-params]]
         '[ring.middleware.session :refer [wrap-session]]
         '[ring.middleware.flash :refer [wrap-flash]])

(def app
  (-> handler
      wrap-keyword-params
      wrap-params
      wrap-flash
      wrap-session))

Order matters: innermost (handler) to outermost (first to execute).

Parameter Processing

wrap-params

Parses URL-encoded parameters from query string and form body. Adds :query-params, :form-params, :params to request.

clojure
(wrap-params handler {:encoding "UTF-8"})
;; URL: /search?q=clojure -> {:params {"q" "clojure"}}

wrap-keyword-params

Converts parameter string keys to keywords. Must come after wrap-params.

clojure
(-> handler wrap-keyword-params wrap-params)
;; {"q" "clojure"} -> {:q "clojure"}

wrap-nested-params

Converts flat params to nested structures using bracket notation.

clojure
(-> handler wrap-nested-params wrap-keyword-params wrap-params)
;; {"user[name]" "Alice"} -> {:user {:name "Alice"}}

wrap-multipart-params

Handles file uploads. Adds :multipart-params with file metadata.

clojure
(wrap-multipart-params handler {:store (temp-file-store)})
;; Uploaded file structure:
;; {"file" {:filename "doc.pdf" :content-type "application/pdf"
;;          :tempfile #object[java.io.File ...] :size 51200}}

Options: :encoding, :store (temp-file-store or byte-array-store). Temp files deleted after 1 hour.

Session & State

wrap-cookies

Parses cookies from headers. Adds :cookies to request, reads from :cookies in response.

clojure
(wrap-cookies handler)
;; Request: {:cookies {"session_id" {:value "abc123"}}}
;; Response: {:cookies {"session_id" {:value "abc123" :max-age 3600
;;                                     :secure true :http-only true}}}

Attributes: :domain, :path, :secure, :http-only, :max-age, :expires, :same-site

wrap-session

Manages browser sessions via cookies. Read from :session in request, write to :session in response.

clojure
(wrap-session handler {:store (cookie-store {:key "16-byte-secret"})
                       :cookie-attrs {:max-age 3600 :secure true}})

;; Read: (get-in request [:session :username])
;; Update: (assoc response :session (assoc session :count 1))
;; Delete: (assoc response :session nil)
;; Recreate ID: (assoc response :session (vary-meta session assoc :recreate true))

Options: :store (memory-store default, cookie-store), :cookie-name, :cookie-attrs

Stores: memory-store (not multi-server), cookie-store (needs 16-byte key)

wrap-flash

Session-based flash messages persisting across one redirect. Must wrap around wrap-session.

clojure
(-> handler wrap-flash wrap-session)
;; Set: (assoc response :flash "Success!")
;; Read: (:flash request)

Messages auto-expire after being read once.

HTTP Protocol

wrap-head

Converts HEAD requests to GET and strips body. Usually innermost wrapper.

wrap-not-modified

Returns 304 responses via ETag/Last-Modified. Checks If-Modified-Since and If-None-Match headers.

wrap-content-type

Auto-adds Content-Type from file extension. Falls back to application/octet-stream.

clojure
(wrap-content-type handler {:mime-types {"txt" "text/plain"}})

wrap-content-length

Auto-calculates Content-Length. Uses SizableResponseBody protocol.

Static Content

wrap-file

Serves files from filesystem. Checks filesystem before handler.

clojure
(wrap-file handler "/var/www/public" {:index-files? true :allow-symlinks? false})

wrap-resource

Serves resources from classpath (JAR/WAR compatible). Path relative to resources/.

clojure
(wrap-resource handler "public")  ; serves resources/public/*

Static Pattern

Combine with content-type and not-modified. These must wrap outside resource/file.

clojure
(-> handler (wrap-resource "public") wrap-content-type wrap-not-modified)

Middleware Patterns

clojure
;; Basic pattern
(defn wrap-custom [handler]
  (fn [request]
    (let [response (handler request)]
      response)))

;; Add request keys
(defn wrap-user [handler]
  (fn [request]
    (if-let [user-id (-> request :session :user-id)]
      (handler (assoc request :user (get-user-by-id user-id)))
      (handler request))))

;; Conditional execution
(defn wrap-auth [handler]
  (fn [request]
    (if (authorized? request)
      (handler request)
      {:status 403 :body "Access Denied"})))

Common Stacks

Development Stack

clojure
(def app
  (-> handler
      wrap-keyword-params
      wrap-nested-params
      wrap-params
      wrap-flash
      wrap-session))

Production with Static Files

clojure
(def app
  (-> handler
      wrap-keyword-params
      wrap-nested-params
      wrap-params
      wrap-flash
      wrap-session
      (wrap-resource "public")
      wrap-content-type
      wrap-not-modified))

Key Gotchas

  1. Middleware order matters:

    • wrap-params before wrap-keyword-params before wrap-nested-params
    • wrap-session before wrap-flash
    • wrap-resource/wrap-file outermost
    • wrap-content-type and wrap-not-modified outside static middleware
  2. wrap-multipart-params doesn't include basic params - use both wrap-params and wrap-multipart-params

  3. Session stores:

    • memory-store: not suitable for multi-server deployments
    • cookie-store: requires 16-byte secret key, sessions stored in browser
  4. wrap-file-info is DEPRECATED - use wrap-content-type + wrap-not-modified instead

  5. Flash messages require wrap-session

  6. Cookie security: use :secure true for HTTPS, :http-only true to prevent JavaScript access, :same-site :strict for CSRF protection

  7. File uploads: temp-file-store auto-deletes after 1 hour - save files permanently if needed

Session Store Protocol

Custom session stores implement SessionStore:

clojure
(require '[ring.middleware.session.store :as store])

(defrecord CustomStore []
  store/SessionStore
  (read-session [_ key]
    (read-data-from-backend key))
  (write-session [_ key data]
    (let [key (or key (generate-secure-random-key))]
      (save-data-to-backend key data)
      key))
  (delete-session [_ key]
    (delete-data-from-backend key)
    nil))

CRITICAL: Generate cryptographically secure random keys for new sessions to prevent session hijacking.

References

Didn't find tool you were looking for?

Be as detailed as possible for better results