HTTP Services in Clojure
Priyatam Mudivarti
Principal, Facjure LLC
Why?
Abstract HTTP
Write idiomatic code
Run on any web server
Bare Metal
Easy to Test
Live Coding
I've written webservices in 5 languages.
I find Clojure to be 10x simpler
HTTP services in Clojure (Ring)
I just write functions,
higher-order functions, with two data structures
No More framework abstractions
Data
Functions
Abstractions
Clojure
in 5 mins
(def a-vector [1 2 3 4 5])
(def a-nested-map
{:customer-id 1e6
:preferences {:nickname "Bob"
:avatar "http://en.gravatar.com/userimage/0/0.jpg"}
:services {:alerts {:daily true}}})
(def complex-map
{[1 2] :one-two
[3 4] :three-four})
(def a-set
#{:cat :dog :bird})
Clojure 101: data structures
;; Unlike JavaScript there is no hoisting in ClojureScript. ClojureScript has lexical scoping.
(def some-x 1)
(let [some-x 2]
some-x)
;; functions parameters
(let [fns (loop [i 0 ret []]
(if (< i 10)
(recur (inc i)
(conj ret (fn [] i)))
ret))]
(map #(%) fns))
;; destructuring
(def point {:x 5 :y 7})
(let [{:keys [x y] :as the-point} point]
(println "x:" x "y:" y "point:" the-point))
Clojure 101: variables & scope
; simple function
(defn add [a b]
(+ a b))
;; function with multiple args and default values
(defn another-function
([x] (defaults x :default))
([x y] [x y]))
;; closure
(let [a 1e3]
(defn foo []
(* a a))
(defn bar []
(+ (foo) a)))
;; higher order functions
(map inc [0 1 2 3 4 5 6 7 8 9])
(filter even? (range 10))
(reduce + (range 100))
(partition 2 [:a 1 :b 2 :c 3 :d 4 :e 5])
;; loops
(for [x (range 1 10)
y (range 1 10)
:when (and (zero? (rem x y))
(even? (quot x y)))]
[x y])
Clojure 101: functions
That's it.
can we build http services with these concepts?
;; handler
(defn my-ip [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body (:remote-addr request)})
;; middleware
(defn wrap-content-type [handler content-type]
(fn [request]
(let [response (handler request)]
(assoc-in response [:headers "Content-Type"] content-type))))
(defn wrap-content-type [handler content-type]
(fn [request]
(let [response (handler request)]
(assoc-in response [:headers "Content-Type"] content-type))))
(defn json-response [data & [status]]
{:status (or status 200)
:headers {"Content-Type" "application/edn"}
:body (pr-str data)})
(def app
(wrap-content-type my-ip "text/html"))
(defn serve [request]
(app request))
Ring - Concepts
;; Routes
(defroutes routes
(GET "/" [] (str "Server is running"))
(GET "/ws" [] handle-websocket)
(context
"/api" []
(GET "/languages" []
(let [prog (get-in db/data [:languages])]
(when prog
(edn-response prog))))
(GET "/languages/:id/:type" [id type]
(let [stats (filter #(and
(= (:id %) id) (= (:type %) type))
(get-in db/data [:stats]))]
(when stats
(response stats)))))))
Routes
(def app
(->
(handler/site router/routes)
(wrap-json-body)
(wrap-json-response)
(wrap-edn-params)
(wrap-defaults api-defaults)
(wrap-cors
:access-control-allow-origin #".+"
:access-control-allow-methods [:get :post :put :delete :head]
:access-control-allow-headers ["Content-Type" "X-Requested-With"])))
(defn -main [& _]
(let [port (Integer. (or (System/getenv "SERVER_PORT") 8000))]
(server/run-server app {:port port :join? false})
(log/info "Server started and listening at port..." port)))
Server
(defresource list-resource
:available-media-types ["application/json"]
:allowed-methods [:get :post]
:known-content-type? #(check-content-type % ["application/json"])
:malformed? #(parse-json % ::data)
:post! #(let [id (str (inc (rand-int 100000)))]
(dosync (alter entries assoc id (::data %)))
{::id id})
:post-redirect? true
:location #(build-entry-url (get % :request) (get % ::id))
:handle-ok #(map (fn [id] (str (build-entry-url (get % :request) id)))
(keys @entries)))
REST?
Demos
Thank you!
References
https://github.com/priyatam/ring-micro
Clojure HTTP Services
By Priyatam Mudivarti
Clojure HTTP Services
HTTP/RESt Services in Clojure
- 414