when i want to transfer a file in a network, I normal use the following command to host a simple http server in python that host the current directory
python3 -m http.server 8888
Here we are using the http.server module and telling it to run in 8888 port
One of my junior developer uses chatgpt at work to solve some problems
I was impressed very much with the relevant answer, I logged in yesterday and tried bunch of other things and i thought > HMM why not ask the ChatGPT to create a simple HTTP server in clojure I started asking ton of question and the answer is just pouring out, It's like raining but you cann't harvest all the water. Time to harvest some answers.
I started connecting the pieces together and voila, HTTP server that can serve a directory in clojure. It makes few mistakes though not biggy
Let's discuss the code piece by piece
We need HTTP server lib first so i choosed http-kit
.
deps.edn
{:deps {http-kit/http-kit {:mvn/version "2.7.0-alpha1"}}}
Full code: https://github.com/ThangaAyyanar/Learning-Adventure/blob/master/Clojure/simple-http-server.clj
Let's get straight into the coding
(ns simple-http-server (:require [org.httpkit.server :as http] [clojure.java.io :as io] [clojure.string :as string]))
here we required - java.io
to reading files and folder - string
split and join the string - Next, Setting up the http sever
(defonce server (atom nil)) (defn stop-server [] (when-not (nil? @server) ;; graceful shutdown: wait 100ms for existing requests to be finished ;; :timeout is optional, when no timeout, stop immediately (@server :timeout 100) (reset! server nil))) (defn -main [& args] (reset! server (http/run-server #'handler {:port 8080})))
- Starting and stoping the http server code
- Below we have the handler function that does the routing and downloading file logics
(defn get-path [uri] (if (= uri "/") dir-path (str dir-path uri))) (def error-response {:status 404 :body "404 Not Found"}) (defn file-download-response [file-path] {:status 200 :headers {"Content-Type" "application/octet-stream" "Content-Disposition" (str "attachment; filename="(.getName file-path))} :body (io/input-stream file-path)}) (defn folder-html-response [uri path] {:status 200 :headers {"Content-Type" "text/html"} :body (contents uri path)}) (defn handler [request] (let [uri (or (:uri request) "/") path (get-path uri) file-path (io/file path)] (if file-path (cond (.isFile file-path) (file-download-response file-path) (.isDirectory file-path) (folder-html-response uri path) :else error-response ) error-response)))
- one of the core function of this program is getting all the files and directory
(defn list-folders ([path] (list-folders "/" path)) ([uri path] (->> (io/file path) (.listFiles) (map #(dirs-to-string uri %)) (string/join) )))
- Here we used multi arity function.
- First we will convert the path Eg:
/User/Ayan/Temp
into file object. - Get all the files and folders using
.listFiles
- Convert it to strings using dirs-to-string function
defn folder-html [uri name] (str "<li><a href='" (str uri name) "'>" (str name "/") "</a> </li>")) (defn file-html [uri name] (str "<li><a href='" (str uri name) "' download>" (str name) "</a> </li>")) (defn dirs-to-string [uri file] (let [name (.getName file) new-uri (if (= uri "/") uri (str uri "/"))] (cond (.isDirectory file) (folder-html new-uri name) (.isFile file) (file-html new-uri name) :else "")))
- We use
.getName
function to get the name of the file or folder. - Then construct a html string out of it.
(defn prepend-up-dir [uri] (if (= uri "/") nil (let [prev-folder (string/join "/" (pop (string/split uri #"/")))] (str "<li><a href='" (if (empty? prev-folder) "/" prev-folder) "'>..</a></li>"))))
- Also added a link to traverse back to previous folder if we move into other folder other than root.
- Finally generating overall html
(defn generate-html [title uri items] (let [title-html (str "<h1>" title "</h1>") uri-html (str "<b>URI:</b>" uri) prev-dir (or (prepend-up-dir uri) "") contents (str "<hr/><ul>" prev-dir items "</ul><hr/>")] (str title-html uri-html contents))) (defn contents [uri path] (let [folders (list-folders uri path)] (generate-html "Simple HTTP Server" uri folders)))
generate-html
function generate page for the browser- I know, I have not included the boiler plate html code like html, head and body tags.
- That is a exercise for you :p
Would you like to go further, Try few things below
- Run the above script in babashka
- How to add simple authentication token ?
- Simple Token in header
- Authentication using jwt token
- Restrict maximum concurrent downloads
- My future self crazy idea's
Resources
- ChatGPT
- Clojure Examples
- Amazing clojure docs website
- Last but not least, Stackoverflow