Clojure Guide — Ring: The HTTP Server Library in Depth
In this tutorial, you will learn about Clojure Guide. We cover key concepts, practical examples, and best practices to help you master this topic.
Ring is the foundational HTTP library in Clojure that defines a simple, complete abstraction for web servers: every request is a Clojure map, every handler returns a response map, and middleware transforms request-processing pipelines.
What You'll Learn
- Advanced Ring concepts: async responses, file uploads, streaming
- Adapter layer: Jetty, HTTP-Kit, Aleph
- Middleware patterns and composition
- Testing Ring applications
Why It Matters
Ring is the foundation of every Clojure web framework. Understanding Ring deeply lets you build custom middleware, optimize performance, and debug issues in any Clojure web application. Durga Antivirus Pro uses Ring for its REST API.
Real-World Use
Ring powers CircleCI's API, Puppet's tools, and Walmart's Clojure services. Its clean abstraction makes it a stable foundation for production web services.
flowchart LR
A["Ring Deep Dive"] --> B["Adapters"]
B --> C["Middleware"]
C --> D["Async"]
D --> E["Testing"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Adapters
Ring adapters connect handlers to HTTP servers:
;; Jetty adapter (production)
(require '[ring.adapter.jetty :as jetty])
(jetty/run-jetty handler {:port 8080 :join? false})
;; HTTP-Kit (high performance)
(require '[org.httpkit.server :as http-kit])
(http-kit/run-server handler {:port 8080})
;; Aleph (async TCP)
(require '[aleph.http :as aleph])
(aleph/start-server handler {:port 8080})
Advanced Middleware
;; CORS middleware
(defn wrap-cors [handler origins]
(fn [request]
(let [response (handler request)]
(if (= :options (:request-method request))
{:status 200
:headers {"Access-Control-Allow-Origin" origins
"Access-Control-Allow-Methods" "GET,POST,PUT,DELETE"}}
(update-in response [:headers]
assoc "Access-Control-Allow-Origin" origins)))))
;; Rate limiting middleware
(defn wrap-rate-limit [handler max-reqs window-ms]
(let [counters (atom {})]
(fn [request]
(let [ip (:remote-addr request)
now (System/currentTimeMillis)
clean (fn [m] (into {} (filter #(> (- now (val %)) window-ms)) m))]
(swap! counters clean)
(let [count (get @counters ip 0)]
(if (>= count max-reqs)
{:status 429 :body "Rate limit exceeded"}
(do (swap! counters update ip (fnil inc 0))
(handler request))))))))
Async Responses
;; Using core.async for async response
(require '[clojure.core.async :as async])
(require '[ring.util.response :as resp])
(defn async-handler [request]
(let [ch (async/chan)]
(async/go
(async/<! (async/timeout 100))
(async/>! ch (resp/response "Async response")))
{:status 200
:body ch}))
File Uploads
(require '[ring.middleware.multipart-params :refer [wrap-multipart-params]])
(require '[ring.middleware.params :refer [wrap-params]])
(defn upload-handler [request]
(let [file (get-in request [:params :file])
temp-file (:tempfile file)]
(clojure.java.io/copy temp-file
(clojure.java.io/file "uploads/" (:filename file)))
{:status 200 :body "Uploaded!"}))
(def app (-> upload-handler wrap-multipart-params wrap-params))
Testing Ring
(require '[ring.mock.request :as mock])
(require '[clojure.test :refer :all])
(deftest test-hello-handler
(let [request (mock/request :get "/hello" {:name "Alice"})
response (handler request)]
(is (= 200 (:status response)))
(is (clojure.string/includes? (:body response) "Hello"))))
;; Test with middleware
(deftest test-auth-middleware
(let [request (mock/request :get "/protected")
response (auth-handler request)]
(is (= 401 (:status response)))))
Streaming Responses
(defn streaming-handler [request]
{:status 200
:headers {"Content-Type" "text/event-stream"}
:body (fn [write]
(future
(doseq [i (range 10)]
(write (str "data: " i "\n\n"))
(Thread/sleep 1000))
(.close ^java.io.Writer write)))})
Common Mistakes
1. Long-running requests blocking the thread pool
Use async responses or run-task for CPU-intensive work. Jetty's thread pool handles limited connections.
2. Not using ring.middleware.keyword-params
Request params are strings by default. Use keyword-params middleware to convert keys to keywords.
3. Stack traces in production responses
Always catch exceptions and return generic error responses. Never expose internal state.
4. Memory leaks with streaming bodies
Always close InputStreams in request bodies. Use with-open to ensure cleanup.
5. Assuming all servers are Jetty
Middleware should never depend on adapter-specific features. Test with mock requests for portability.
Practice Questions
1. What does an adapter do in Ring? An adapter connects a Ring handler to an actual HTTP server implementation (Jetty, HTTP-Kit, etc.), handling the translation between HTTP protocol and Ring maps.
2. How do you create async responses? Return a response map where the body is a core.async channel, or provide a callback function for streaming.
3. What is ring.middleware.multipart-params for? It parses multipart form data (file uploads), providing access to uploaded files via the request params map.
Challenge: Build a Ring middleware stack that includes CORS, logging, Rate Limiting, and session handling.
FAQ
{{< faq question="Which Ring adapter is fastest?" >}} HTTP-Kit is generally the fastest for high-concurrency scenarios. Aleph is best for streaming and Websocket-heavy applications. Jetty is the most stable and feature-complete. {{< /faq >}}
{{< faq question="Can Ring serve static files?" >}}
Yes, use the wrap-file or wrap-resource middleware to serve static files from a directory or classpath.
{{< /faq >}}
{{< faq question="How do I handle cookies with Ring?" >}}
Use ring.middleware.cookies/wrap-cookies to parse and set cookies automatically in the request/response maps.
{{< /faq >}}
{{< faq question="Is Ring production-ready?" >}} Yes. Ring with Jetty is used in production by major companies. The abstraction is stable and well-tested. {{< /faq >}}
{{< faq question="Can I use Ring without a framework?" >}} Yes, many services use Ring directly with custom routing. This is common for small APIs and Microservices. {{< /faq >}}
Mini Project
Build a file server with Ring that streams large files efficiently:
(defn file-server [request]
(let [file (java.io.File. (str "public" (:uri request)))]
(if (.exists file)
{:status 200
:headers {"Content-Type" (clojure.java.io/resource "mime-types.properties")
"Content-Length" (str (.length file))}
:body (clojure.java.io/input-stream file)}
{:status 404 :body "Not Found"})))
(def app
(-> file-server wrap-logging))
What's Next
Now that you understand Ring deeply, explore Compojure for a routing DSL and Hiccup for HTML templating.
| Topic | Description | Link |
|---|---|---|
| Clojure Compojure | Routing library | {{< ref "22-compojure" >}} |
| Clojure Hiccup | HTML templating | {{< ref "23-hiccup" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro