Two Guys Arguing

Start Tomcat from the REPL

Posted in Uncategorized by youngnh on 11.10.10

One of the first things that I wrote in Clojure was a few files that launched a tomcat server from a repl. My code was separated into 3 files:

  • tomcat.clj created a Tomcat server through the Embedded class, which you could then start and stop from the repl
  • tomcat_loader.clj which handled separating classloaders for tomcat itself and the webapps deployed to it
  • classloading.clj which held generic utility functions for creating class loaders and searching file paths

Creating an embedded tomcat engine is an excercise in Clojure-Java iterop:

(defn create-embedded-tomcat [#^String hostname app-base contexts]
  (let [embedded (Embedded.)
        engine (.createEngine embedded)
        host (.createHost embedded hostname app-base)
        http-connector (.createConnector embedded hostname 8080 "http")
        ssl-connector (.createConnector embedded hostname 8443 true)]
    (.setParentClassLoader engine tomcat-shared-loader)
    (doall (map #(% embedded host) contexts))
    (.addChild engine host)
    (.addEngine embedded engine)
    (.addConnector embedded http-connector)
    (.addConnector embedded ssl-connector)

app-base is a file path from which Tomcat will serve webapps out of, it’s webapps/ in the standalone distribution, but creating your own engine, you can now set it to anywhere you’d like. contexts is a list of Context objects, applications that you want Tomcat to serve that might not be in the app-base folder. I wrote a helper fn to create then:

(defn context [path war-file]
  (fn [embedded host]
    (let [context (.createContext embedded path war-file)]
      (.addChild host context))))

(context "/myapp" "/home/awesomesauce/dinosaur-app.war")

The Embedded object that create-embedded-tomcat returns has .start and .stop methods on it, so no need for special Clojure fns for them, just invoke the methods directly on the object.

The classloading magic happens in tomcat.clj‘s ns header. It :uses tomcat_loader.clj which creates 3 classloaders:

(def thread-context-loader (.. (Thread/currentThread) getContextClassLoader))

(def tomcat-common-loader (create-class-loader (expand-paths tomcat-common-path) thread-context-loader))
(def tomcat-catalina-loader (create-class-loader (expand-paths tomcat-catalina-path) tomcat-common-loader))
(def tomcat-shared-loader (create-class-loader (expand-paths tomcat-shared-path) tomcat-common-loader))

Each of these class-loaders are created via create-class-loader which takes a “loader-map” and a parent classloader to create a URLClassLoader. The 3 “loader-maps” are specified like so:

(def *catalina-home* (or (System/getProperty "catalina.home") (System/getProperty "user.home")))

(def tomcat-common-path 
      {"common/classes" :dir
       "common/i18n/" :glob
       "common/endorsed/" :glob
       "common/lib/" :glob}})

(def tomcat-catalina-path 
      {"server/classes" :dir
       "server/lib/" :glob}})

(def tomcat-shared-path 
      {"shared/classes" :dir
       "shared/lib/" :glob}})

So they’re simple maps that specify where to find jars and class files on the system. With this hierarchy of classloaders set up, Clojure then has the means to find the classes it needs when we ask it to instantiate a Tomcat server.

I haven’t run this code in a while and it’s getting late, but when I get a chance, I’ll scrub it and run it through it’s paces and post it somewhere you can get your hands on it.

3 Responses

Subscribe to comments with RSS.

  1. pmonks said, on 11.10.10 at 1:57 am

    I wonder how different this would look with Jetty?

    • youngnh said, on 11.10.10 at 9:56 am

      Pretty significantly, I’d suspect, as Jetty was specifically written to be embeddable. I put this together to deploy a legacy web app which was written for Tomcat 5.5 specifically and which I strongly suspect won’t run under Jetty, though I haven’t confirmed that yet.

      More than anything, this excercise was an eye-opening one, since when I wrote it, I was managing my classpath by hand, literally launching clojure with a tremendously long -classpath flag that I was keeping in a script and editing by hand. I’ve since migrated to lein to manage my dependencies and classpath. It was interesting to find that there is another way.

      • pmonks said, on 11.10.10 at 11:13 am

        Cool! Yeah this is great stuff, and my question re Jetty was along the lines of “if it’s this easy with Tomcat (which isn’t really designed for embeddability), I wonder how easy it’d be with something that is?”.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s