Search This Blog

Wednesday, March 23, 2011

Hello Web : Dynamic Compojure Web Development at the REPL


;; In an article which convinced many people to have a look at LISP

;; http://www.paulgraham.com/avg.html

;; Paul Graham wrote about how nice it was to write a web application from an
;; environment where everything was controllable from the REPL.

;; Here's how to create a web server at the REPL


;; First we'll need a function to generate a simple web page
(defn yo [] "Hello")

;; Now we attach that to /
(use 'compojure.core)
(defroutes main-routes (GET "/" [] (yo)))

;; We should wrap our routing table in a handler (note the #')
(use 'compojure.handler)
(def app (site #'main-routes))

;; And then we can run it with jetty (again note the #')
(use 'ring.adapter.jetty)
(def server (run-jetty #'app {:port 8080 :join? false}))

;; So now we're serving web pages. Go look at http://localhost:8080/

;; The web browser of the gods:
;; $ watch -d -n 1 curl -sv http://localhost:8080/ 

;; We can stop the server
(.stop server)

;; And restart it:
(.start server)

;; We can redefine those functions, and the change takes effect immediately:
(defn yo [] "<h1>Hello<h1/>")

;; (you will need to refresh the page, unless you use twbotg)

;; HTML is a bit of a pain. We already have a syntax for tree-structured data:

(use 'hiccup.page-helpers)

(defn yo []      
  (html5 [:head
          [:title "Hello World"]]
         [:body
          [:h1 "Lisp is the future"]]))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; I'm confused: see http://stackoverflow.com/questions/5398569/can-clojure-be-made-dynamic

;; The reason for the #' above is to make sure that we can redefine main-routes
;; and app at runtime. Without the indirection, the changes don't seem to get picked up.

;; And yet they do get picked up when you redefine yo.

;; I do not understand what is going on here. Any explanation would be welcome!



To start a REPL with the necessary libraries available, you can use maven.

To install maven on ubuntu:
$ sudo apt-get install maven2

Put this xml in a file named pom.xml, and then type
$ mvn clojure:repl
in the same directory. For a swank server that you can use with emacs, use
$ mvn clojure:swank

The first time you run it it will do an incredible amount of downloading to get all the necessary bits, but after that it's fairly speedy.

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.aspden</groupId>
  <artifactId>hello-compojure</artifactId>
  <version>0.1-SNAPSHOT</version>
  <name>hello-compojure</name>
  <description>maven, clojure, emacs, swank, compojure: together at last</description>
  <url>http://www.learningclojure.com</url>

  <!-- repositories -->
  <repositories>
    <repository>
      <id>clojars</id>
      <url>http://clojars.org/repo/</url>
    </repository>
    <repository>
      <id>clojure</id>
      <url>http://build.clojure.org/releases</url>
    </repository>
    <repository>
      <id>central</id>
      <url>http://repo1.maven.org/maven2</url>
    </repository>
  </repositories>

  <!-- libraries -->
  <dependencies>
    <dependency>
      <groupId>compojure</groupId>
      <artifactId>compojure</artifactId>
      <version>0.6.2</version>
    </dependency>
    <dependency>
      <groupId>hiccup</groupId>
      <artifactId>hiccup</artifactId>
      <version>0.3.4</version>
    </dependency>
    <dependency>
      <groupId>ring</groupId>
      <artifactId>ring-jetty-adapter</artifactId>
      <version>0.3.7</version>
    </dependency>
    <dependency>
      <groupId>org.clojure</groupId>
      <artifactId>clojure</artifactId>
      <version>1.2.0</version>
    </dependency>
    <dependency>
      <groupId>org.clojure</groupId>
      <artifactId>clojure-contrib</artifactId>
      <version>1.2.0</version>
    </dependency>
    <dependency>
      <groupId>jline</groupId>
      <artifactId>jline</artifactId>
      <version>0.9.94</version>
    </dependency>
    <dependency>
      <groupId>swank-clojure</groupId>
      <artifactId>swank-clojure</artifactId>
      <version>1.2.1</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <!-- talios' clojure-maven-plugin provides mvn clojure:swank etc -->
      <plugin>
        <groupId>com.theoryinpractise</groupId>
        <artifactId>clojure-maven-plugin</artifactId>
        <version>1.3.2</version>
      </plugin>
      <!-- The versions plugin allows you to find out what you can upgrade -->
      <!-- mvn versions:help -->
      <!-- mvn versions:display-dependency-updates -->
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>versions-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
  

</project>

5 comments:

  1. #'app is short for (var app). For example:

    (def foo 10)

    (type (var foo))
    (type #'foo)

    Passing a var (instead of the value) transparently re-reads the value inside the var in `run-jetty`, which leads to the dynamic magic. I blogged about it here:

    http://charsequence.blogspot.com/2010/09/interactive-web-development-with.html

    Regards,
    Shantanu

    ReplyDelete
  2. twbotg = the web browser of the gods =

    $ watch -d -n 1 curl -sv http://localhost:8080/

    ReplyDelete
  3. Very helpful. Thanks for sharing.

    ReplyDelete

Followers