clojure-ring-core-middleware

Ramblurr's avatarfrom Ramblurr

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.

⭐0stars🔀1forks📁View on GitHub🕐Updated Jan 4, 2026

When & Why to Use This Skill

This Claude skill provides comprehensive support for Clojure Ring core middleware, enabling developers to efficiently manage HTTP request processing and web server configurations. It covers essential web development tasks including parameter parsing (URL, form, and multipart), session and cookie management, flash messages for redirects, and serving static resources. By providing guidance on middleware composition and best practices, it simplifies the creation of robust, secure, and high-performance Clojure web applications and APIs.

Use Cases

  • Case 1: Implementing secure user authentication and state management using wrap-session and wrap-cookies with encrypted cookie stores.
  • Case 2: Processing complex web forms and handling multi-part file uploads by correctly configuring wrap-params and wrap-multipart-params middleware.
  • Case 3: Optimizing web application performance by serving static assets through wrap-resource combined with wrap-content-type and wrap-not-modified for browser caching.
  • Case 4: Building a standardized middleware stack for development or production environments to ensure consistent request handling and security headers.
  • Case 5: Managing temporary user notifications and feedback across HTTP redirects using the wrap-flash middleware.
nameclojure-ring-core-middleware
description|

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:

(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.

(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.

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

wrap-nested-params

Converts flat params to nested structures using bracket notation.

(-> 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.

(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.

(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.

(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.

(-> 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.

(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.

(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/.

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

Static Pattern

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

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

Middleware Patterns

;; 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

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

Production with Static Files

(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:

(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