From 74ba198201fd273d9b3cc195f803267d75b7b40b Mon Sep 17 00:00:00 2001 From: Joey Bloom <15joeybloom@users.noreply.github.com> Date: Fri, 26 Mar 2021 17:25:47 -0500 Subject: [PATCH] Update example notes app to use org.httpkit.server (#762) [skip ci] --- examples/notes.clj | 159 +++++++++++++++++---------------------------- 1 file changed, 61 insertions(+), 98 deletions(-) diff --git a/examples/notes.clj b/examples/notes.clj index 1d005597..c108e2b0 100755 --- a/examples/notes.clj +++ b/examples/notes.clj @@ -1,13 +1,9 @@ #!/usr/bin/env bb -;; Note that babashka comes with org.httpkit.server now, so you don't need to -;; build an http server from scratch anymore like we do here. We should update -;; this script to the built-in webserver. Whoever reads this is welcome to -;; submit a PR. - -(import (java.net ServerSocket)) (require '[clojure.java.io :as io] - '[clojure.string :as str]) + '[clojure.pprint :refer [pprint]] + '[clojure.string :as str] + '[org.httpkit.server :as server]) (def debug? true) (def user "admin") @@ -40,43 +36,23 @@ (str/join " " (map html v)) :else (str v))) -(defn write-response [out session-id status headers content] - (let [cookie-header (str "Set-Cookie: notes-id=" session-id) - headers (str/join "\r\n" (conj headers cookie-header)) - response (str "HTTP/1.1 " status "\r\n" - (str headers "\r\n") - "Content-Length: " (if content (count content) - 0) - "\r\n\r\n" - (when content - (str content)))] - (when debug? (println response)) - (binding [*out* out] - (print response) - (flush)))) - ;; the home page -(defn home-response [out session-id] - (let [body (str - "\n" - (html - [:html - [:head - [:title "Notes"]] - [:body - [:h1 "Notes"] - [:pre (when (.exists notes-file) - (slurp notes-file))] - [:form {:action "/" :method "post"} - [:input {:type "text" :name "note"}] - [:input {:type "submit" :value "Submit"}]]]]))] - (write-response out session-id "200 OK" nil body))) - -(defn basic-auth-response [out session-id] - (write-response out session-id - "401 Unauthorized" - ["WWW-Authenticate: Basic realm=\"notes\""] - nil)) +(defn home-response [session-id] + {:status 200 + :headers {"Set-Cookie" (str "notes-id=" session-id)} + :body (str + "\n" + (html + [:html + [:head + [:title "Notes"]] + [:body + [:h1 "Notes"] + [:pre (when (.exists notes-file) + (slurp notes-file))] + [:form {:action "/" :method "post"} + [:input {:type "text" :name "note"}] + [:input {:type "submit" :value "Submit"}]]]]))}) (def known-sessions (atom #{})) @@ -86,67 +62,54 @@ (swap! known-sessions conj uuid) uuid)) -(defn get-session-id [headers] - (if-let [cookie-header (first (filter #(str/starts-with? % "Cookie: ") headers))] - (let [parts (str/split (str/replace cookie-header "Cookie: " "") #"; ")] - (if-let [notes-id (first (filter #(str/starts-with? % "notes-id") parts))] - (str/replace notes-id "notes-id=" "") - (new-session!))) - (new-session!))) - -(defn basic-auth-header [headers] - (some #(str/starts-with? % "Basic-Auth: ") headers)) - (def authenticated-sessions (atom #{})) (defn authenticate! [session-id headers] (or (contains? @authenticated-sessions session-id) - (when (some #(= % (str "Authorization: Basic " base64)) headers) + (when (= (headers "authorization") (str "Basic " base64)) (swap! authenticated-sessions conj session-id) true))) +(defn parse-session-id [cookie] + (when cookie + (when-let [notes-id (first (filter #(str/starts-with? % "notes-id") + (str/split cookie #"; ")))] + (str/replace notes-id "notes-id=" "")))) + +(defn basic-auth-response [session-id] + {:status 401 + :headers {"WWW-Authenticate" "Basic realm=\"notes\"" + "Set-Cookie" (str "notes-id=" session-id)}}) + ;; run the server -(with-open [server-socket (let [s (new ServerSocket 8080)] - (println "Server started on port 8080.") - s)] - (loop [] - (let [client-socket (.accept server-socket)] - (future - (with-open [conn client-socket] - (try - (let [out (io/writer (.getOutputStream conn)) - is (.getInputStream conn) - in (io/reader is) - [_req & headers :as response] - (loop [headers []] - (let [line (.readLine in)] - (if (str/blank? line) - headers - (recur (conj headers line))))) - session-id (get-session-id headers) - form-data (let [sb (StringBuilder.)] - (loop [] - (when (.ready in) - (.append sb (char (.read in))) - (recur))) - (-> (str sb) - (java.net.URLDecoder/decode))) - _ (when debug? (println (str/join "\n" response))) - _ (when-not (str/blank? form-data) - (when debug? (println form-data)) - (let [note (str/replace form-data "note=" "")] - (write-note! note))) - _ (when debug? (println))] - (cond - ;; if we didn't see this session before, we want the user to re-authenticate - (not (contains? @known-sessions session-id)) - (let [uuid (new-session!)] - (basic-auth-response out uuid)) - (not (authenticate! session-id headers)) - (basic-auth-response out session-id) - :else (home-response out session-id))) - (catch Throwable t - (binding [*err* *out*] - (println t))))))) - (recur))) +(defn handler [req] + (when debug? + (println "Request:") + (pprint req)) + (let [body (some-> req :body slurp java.net.URLDecoder/decode) + session-id (parse-session-id (get-in req [:headers "cookie"])) + _ (when (and debug? body) + (println "Request body:" body)) + response (cond + ;; if we didn't see this session before, we want the user to + ;; re-authenticate + (not (contains? @known-sessions session-id)) + (basic-auth-response (new-session!)) + + (not (authenticate! session-id (:headers req))) + (basic-auth-response session-id) + + :else (do (when-not (str/blank? body) + (let [note (str/replace body "note=" "")] + (write-note! note))) + (home-response session-id)))] + (when debug? + (println "Response:") + (pprint (dissoc response :body)) + (println)) + response)) + +(server/run-server handler {:port 8080}) +(println "Server started on port 8080.") +@(promise) ;; wait until SIGINT