Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compojure development without web server restarts

This is quite an old question, and there have been some recent changes that make this much easier.

There are two main things that you want:

  1. Control should return to the REPL so you can keep interacting with your server. This is accomplished by adding {:join? false} to options when starting the Jetty server.
  2. You'd like to automatically pick up changes in certain namespaces when the files change. This can be done with Ring's "wrap-reload" middleware.

A toy application would look like this:

(ns demo.core
  (:use webui.nav
    [clojure.java.io]
    [compojure core response]
    [ring.adapter.jetty :only [run-jetty]]
    [ring.util.response]
    [ring.middleware file file-info stacktrace reload])
  (:require [compojure.route :as route] view)
  (:gen-class))

; Some stuff using Fleet omitted.    

(defroutes main-routes
  (GET "/" [] (view/layout {:body (index-page)})
  (route/not-found (file "public/404.html"))
)

(defn app
  []
  (-> main-routes
      (wrap-reload '(demo.core view))
      (wrap-file "public")
      (wrap-file-info)
      (wrap-stacktrace)))

(defn start-server
  []
  (run-jetty (app) {:port 8080 :join? false}))

(defn -main [& args]
  (start-server))

The wrap-reload function decorates your app routes with a function that detects changes in the listed namespaces. When processing a request, if those namespaces have changed on disk, they are reloaded before further request processing. (My "view" namespace is dynamically created by Fleet, so this auto-reloads my templates whenever they change, too.)

I added a few other pieces of middleware that I've found consistently useful. wrap-file handles static assets. wrap-file-info sets the MIME type on those static assets. wrap-stacktrace helps in debugging.

From the REPL, you could start this app by using the namespace and calling start-server directly. The :gen-class keyword and -main function mean that the app can also be packaged as an uberjar for startup from outside the REPL, too. (There's a world outside the REPL? Well, some people have asked for it anyway...)


Here's an answer I got from James Reeves in the Compojure Google Group (the answer's here with his permission):

You can reload a namespace in Clojure using the :reload key on the use or require commands. For example, let's say you have a file "demo.clj" that contains your routes:

(ns demo 
  (:use compojure))

(defroutes demo-routes 
  (GET "/" 
    "Hello World") 
  (ANY "*" 
    [404 "Page not found"])) 

At the REPL, you can use this file and start a server:

user=> (use 'demo) 
nil 
user=> (use 'compojure) 
nil 
user=> (run-server {:port 8080} "/*" (servlet demo-routes)) 
... 

You could also put the run-server command in another clojure file. However, you don't want to put it in the same file as the stuff you want to reload.

Now make some changes to demo.clj. At the REPL type:

user=> (use 'demo :reload) 
nil 

And your changes should now show up on http://localhost:8080


I wanted to add an answer, since things have changed a bit since the newest answer and I had spent a bit of time looking for this myself.

  1. Install leiningen (just follow the instructions there)

  2. Create project

    lein new compojure compojure-test 
    
  3. Edit the ring section of project.clj

    :ring {:handler compojure-test.handler/app 
           :auto-reload? true
           :auto-refresh? true}
    
  4. Start the server on whatever port you want

    lein ring server-headless 8080
    
  5. Check that the server is running in your browser, the default base route should just say "Hello world". Next, go modify your handler (it's in src/project_name). Change the hello world text, save the file and reload the page in your browser. It should reflect the new text.


Following up on Timothy's link to Jim Downing's setup, I recently posted on a critical addition to that baseline that I found was necessary to enable automatic redeployment of compojure apps during development.


I have a shell script that looks like this:

#!/bin/sh                                                                                                                                   
CLASSPATH=/home/me/install/compojure/compojure.jar
CLASSPATH=$CLASSPATH:/home/me/clojure/clojure.jar
CLASSPATH=$CLASSPATH:/home/me/clojure-contrib/clojure-contrib.jar
CLASSPATH=$CLASSPATH:/home/me/elisp/clojure/swank-clojure

for f in /home/me/install/compojure/deps/*.jar; do
    CLASSPATH=$CLASSPATH:$f
done

java -server -cp $CLASSPATH clojure.lang.Repl /home/me/code/web/web.clj

web.clj looks like this

(use '[swank.swank])                                                                                                                        
(swank.swank/ignore-protocol-version "2009-03-09")                                                                                          
(start-server ".slime-socket" :port 4005 :encoding "utf-8")

Whenever I want to update the server I create an ssh tunnel from my local machine to the remote machine.

Enclojure and Emacs (running SLIME+swank-clojure) can connect to the remote REPL.