Claude-skill-registry clojure-ring-core-middleware
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/clojure-ring-core-middleware" ~/.claude/skills/majiayu000-claude-skill-registry-clojure-ring-core-middleware && rm -rf "$T"
skills/data/clojure-ring-core-middleware/SKILL.mdRing 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
-
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
-
wrap-multipart-params doesn't include basic params - use both wrap-params and wrap-multipart-params
-
Session stores:
- memory-store: not suitable for multi-server deployments
- cookie-store: requires 16-byte secret key, sessions stored in browser
-
wrap-file-info is DEPRECATED - use wrap-content-type + wrap-not-modified instead
-
Flash messages require wrap-session
-
Cookie security: use
for HTTPS,:secure true
to prevent JavaScript access,:http-only true
for CSRF protection:same-site :strict -
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
- Source: https://github.com/ring-clojure/ring/tree/master/ring-core/src/ring/middleware
- API Docs: https://cljdoc.org/d/ring/ring-core/
- Wiki: https://github.com/ring-clojure/ring/wiki
- Specific guides:
- Parameters: https://github.com/ring-clojure/ring/wiki/Parameters
- Sessions: https://github.com/ring-clojure/ring/wiki/Sessions
- Cookies: https://github.com/ring-clojure/ring/wiki/Cookies
- File Uploads: https://github.com/ring-clojure/ring/wiki/File-Uploads
- Static Resources: https://github.com/ring-clojure/ring/wiki/Static-Resources
- Middleware Patterns: https://github.com/ring-clojure/ring/wiki/Middleware-Patterns