ClojureでWebアプリケーションを作り始める

CompojureとLeiningenを使って、Webアプリを作るためのメモ。

Compojure

Clojure用のWebフレームワーク。バージョン0.4.0からサーバ-Compojure間のインターフェイス/ミドルウェアの部分はRingというライブラリに分けられています。

Compojure

Leiningen

使用するライブラリの依存関係を管理したり、プロジェクトのビルドをしてくれるツール。
Leiningen

プロジェクトを作る

Leiningenでプロジェクトを作ります。

% lein new helloweb
Created new project in: helloweb
% cd helloweb 
% tree
.
|-- README
|-- project.clj
|-- src
|   `-- helloweb
|       `-- core.clj
`-- test
    `-- helloweb
        `-- core_test.clj

newタスクでプロジェクトに最低限必要なファイルとディレクトリが生成されます。


project.cljというファイルがhellowebアプリケーションの設定ファイルになります。デフォルトだとこんな感じの内容。

(defproject helloweb "1.0.0-SNAPSHOT"
  :description "FIXME: write"
  :dependencies [[org.clojure/clojure "1.1.0"]
                 [org.clojure/clojure-contrib "1.1.0"]])

各オプションについては、サンプルが用意されてました。


ここではCompojureを使うので、":dependencies"にCompojureを追加します。

(defproject helloweb "1.0.0-SNAPSHOT"
  :description "FIXME: write"
  :dependencies [[org.clojure/clojure "1.1.0"]
                 [org.clojure/clojure-contrib "1.1.0"]
                 [compojure "0.4.1"]])


ここでdepsタスクを実行すると、project.cljで指定したライブラリを全部取ってきてくれます。本来はプロジェクトをビルドするなど、必要な時にLeiningenがやってくれますが、depsタスクは強制的に実行する感じです。ライブラリ類はlibディレクトリに配置されます。

% tree lib
lib
|-- clojure-1.1.0.jar
|-- clojure-contrib-1.1.0.jar
|-- clout-0.2.0.jar
|-- commons-codec-1.4.jar
|-- commons-fileupload-1.2.1.jar
|-- commons-io-1.4.jar
|-- compojure-0.4.1.jar
|-- ring-core-0.2.5.jar
`-- servlet-api-2.5.jar

ただし、Compojureだけでもring-coreを取得してくれるのですが、他のライブラリを取ってきてくれない(jetty-adapter,servlet...)のでringも:dependenciesに追加しておきます。

(defproject helloweb "1.0.0-SNAPSHOT"
  :description "FIXME: write"
  :dependencies [[org.clojure/clojure "1.1.0"]
                 [org.clojure/clojure-contrib "1.1.0"]
                 [compojure "0.4.1"]
                 [ring/ring "0.2.5"])


Clojureのライブラリはhttp://clojars.org/というリポジトリに集まってるみたいです。


これで、アプリケーションを作成する準備が整ったので、実際にコードを書いていきます。ソースコードはデフォルトでは、PROJECT_HOME/src/PROJECT/以下に用意します。


Leiningenがcore.cljというファイルを用意してくれているので、とりあえずこいつにコードを書いていきます。

(ns helloweb.core
  (:gen-class)
  (:use compojure.core, ring.adapter.jetty)
  (:require [compojure.route :as route]))

(defroutes serve
  (GET "/" [] "<h1>Hello WWW</h1>"))

(defn -main [& args]
  (run-jetty serve {:port 8080}))

(:gen-class)でコンパイル時にJavaのクラスファイルを作らせるように指定しておきます。

次にdefroutesで2つのルートを定義します。ルートは、リクエストが与えられた式にマッチしない場合にはnilを返して、マッチすれば定義したレスポンスを返すハンドラです。ハンドラを素で書くとこんな感じになります。

(defn serve
  [request]
  (if (and (= (:request-method request) :get)
           (= (:uri request) "/"))
  ;; response
  {:status 200
   :headers {"ContentType" "text/html"}
   :body "Hello WWW"})

さすがにこれを逐一書いていくのは面倒なので、Compojureで用意されているdefrouteマクロを使います。

この場合には、localhost:8080/にGET遷移したら”Hello WWW"を返すハンドラです。


最後に

(defn -main [& args]
  (run-jetty serve {:port 8080}))

の部分で、main関数となる関数を定義します。上で定義したルートをjetty起動時に渡します。


メイン関数を作った場合には、project.cljにそれが属している名前空間を指定する必要があります。

(defproject helloweb "1.0.0-SNAPSHOT"
  :description "FIXME: write"
  :dependencies [[org.clojure/clojure "1.1.0"]
                 [org.clojure/clojure-contrib "1.1.0"]
                 [compojure "0.4.1"]
                 [ring/ring "0.2.5"]]
  :main helloweb.core)


これで、サンプルのプログラムができたので、.jarファイルを生成して実行してやります。

% lein deps
% lein compile
% lein uberjar
.
.
.
All :namespaces already compiled.
Created ~/clojure/helloweb/helloweb.jar
.
.
.
% java -jar helloweb-standalone.jar
2010-07-17 12:34:46.776::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog
2010-07-17 12:34:46.778::INFO:  jetty-6.1.x
2010-07-17 12:34:46.802::INFO:  Started SocketConnector@0.0.0.0:8080

これでhttp://localhost:8080/にアクセスすればおk。

なお、ルートは複数書けるので

(defroutes serve
  (GET "/" [] "<h1>Welcome</h1>")
  (GET "/article" [] "<h1>article</h1>")
  (GET "/about" [] "<h1>about</h1>" ))

の様に書けば、それぞれ

への遷移時に返すレスポンスを分ける事ができます。

【追記】
/src/compojure/route.cljを眺めてたら"not-found"という関数がありました。これで定義してないルートにアクセスされた場合をハンドルするみたいです。

という事で、ルートに追加

(defroutes serve
  (GET "/" [] "<h1>Welcome</h1>")
  (GET "/article" [] "<h1>article</h1>")
  (GET "/about" [] "<h1>about</h1>" )
  (route/not-found "Page not found"))

http://localhost:8080/nowhereのようなURLにアクセスするとステータス404と一緒に"Page not found"が返ってきます。

次は、URLパラメータとかDBへの接続。