Yes, you can write Java Servlets in Clojure.
Create a Clojure Servlet project with the following command.
1 |
lein new luminus my-clj-servlet +war |
The important parts of the command are ‘luminus’, which is the framework used to create the Servlet, and ‘+war’, which adds uberwar and Servlet routes to the project. This creates a file called ‘handler.clj’ that replaces the traditional Java Servlet file. The configurations for the Clojure Servlet are found in ‘project.clj’ in the :uberwar section of the configurations.
By default the lein command above creates a project that will build a WAR file called my-clj-servlet.war, and when deployed will deploy in the my-clj-servlet context, meaning on a default installation of Tomcat you would find the link at http://localhost:8080/my-clj-servlet/
It is important to note that the resulting project from the above lein command actually won’t work as a Servlet, yet. But, we’ll fix that below. The project is configured in the hopes that you can run it from a Servlet container or as a standalone app, and with some massaging, you could. However, this tutorial only focuses on how to get the project compiling as a Java Servlet written in Clojure. If there is enough interest, I’ll write another tutorial on how to get the default project working as a standalone app, and a Servlet.
Any initialization code you want to write for your Clojure Servlet goes in the ‘handler.clj’ file in the ‘init’ function. The init function starts out looking like this…
1 2 3 4 5 6 7 8 |
(defn init "init will be called once when app is deployed as a servlet on an app server such as Tomcat put any initialization code here" [] (doseq [component (:started (mount/start))] (log/info component "started"))) |
The shutdown code for your Clojure Servlet goes in the ‘destroy’ function in ‘handler.clj’. The destroy function starts out like this …
1 2 3 4 5 6 7 8 |
(defn destroy "destroy will be called when your application shuts down, put any clean up code here" [] (doseq [component (:stopped (mount/stop))] (log/info component "stopped")) (shutdown-agents) (log/info "my-clj-servlet has shut down!")) |
The Java Servlet code that normally goes in the Servlet’s doGet() or doPost() methods, goes in the handler.clj file in the app function. The app function should take a request argument and return a response of some sort. For example, this one returns a simple web page.
1 2 3 4 5 6 7 8 |
(defn app [request] {:status 200 :headers {"Content-Type" "text/html"} :body "<!DOCTYPE html><html><body> <p>Hello Clojure Servlets!!</p> </body></html>" }) |
Notice the app function takes a request argument. This is an important change from the default project produced from the initial project template.
Build your WAR file with the command lein uberwar . This creates a WAR files and places it in target/my-clj-servlet.war. Deploy the resulting war file to Tomcat or any other servlet container to enjoy the fruits of your labor.
But there’s more!
Remember the request parameter our app function takes? It contains all kinds of goodies. Specifically, it has the following keys
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
( :servlet-context-path :ssl-client-cert :protocol :remote-addr :servlet-context :servlet-response :servlet :headers :server-port :servlet-request :content-length :content-type :path-info :character-encoding :context :uri :server-name :query-string :body :scheme :request-method) |
As you have probably already guessed, :servlet-request is your HttpServletRequest implementation. You can use it to grab incoming parameters and use them in your Servlet. For example, if you called your servlet with the following link,
1 |
http://localhost:8080/my-clj-servlet/?name=world |
then your app function could be rewritten as follows.
1 2 3 4 5 6 7 8 9 10 |
(defn app [request] {:status 200 :headers {"Content-Type" "text/html"} :body (str "<!DOCTYPE html><html><body> <p>Hello Clojure " (-> request :servlet-request (.getParameter "name")) "!!</p> </body></html>") }) |
Just for reference, your entire handler.clj probably looks like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
(ns my-clj-servlet.handler (:require [my-clj-servlet.middleware :as middleware] [my-clj-servlet.layout :refer [error-page]] [my-clj-servlet.routes.home :refer [home-routes]] [reitit.ring :as ring] [ring.middleware.content-type :refer [wrap-content-type]] [ring.middleware.webjars :refer [wrap-webjars]] [my-clj-servlet.env :refer [defaults]] [mount.core :as mount] [clojure.tools.logging :as log] [my-clj-servlet.config :refer [env]])) (mount/defstate init-app :start ((or (:init defaults) (fn []))) :stop ((or (:stop defaults) (fn [])))) (defn init "init will be called once when app is deployed as a servlet on an app server such as Tomcat put any initialization code here" [] (doseq [component (:started (mount/start))] (log/info component "started"))) (defn destroy "destroy will be called when your application shuts down, put any clean up code here" [] (doseq [component (:stopped (mount/stop))] (log/info component "stopped")) (shutdown-agents) (log/info "my-clj-servlet has shut down!")) (defn app [request] {:status 200 :headers {"Content-Type" "text/html"} :body (str "<!DOCTYPE html><html><body> <p>Hello Clojure " (-> request :servlet-request (.getParameter "name")) "!!</p> </body></html>") }) |
Need more details? Checkout the lein-uberwar project on github, or the Luminus framework documentation.