Ring は、プログラミング言語 Clojure で Web アプリケーションを構築するための低レベルのライブラリです。Ruby の Rack や Python の WSGI、あるいは Java の Servlet の仕様に似ています。

Web アプリケーションの基盤として Ring を使うと、以下のメリットがあります。

Clojure で Web アプリケーションを書く場合、Ring は業界の標準として認められている基盤です。 Compojurelib-noir のような高レベルのフレームワークもありますが、 lib-noir などの高レベルのフレームワークは Ring を共通の基盤として使用しています。

Ring は低レベルのインターフェースしか提供していませんが、高レベルのインターフェースを使用する場合でも Ring の動作を理解しておくと便利です。 Ring の基本的な理解がなければミドルウェアを書くこともできませんし、アプリケーションのデバッグも困難になるでしょう。

Ring 用に開発された Web アプリケーションは、4 つのコンポーネントで構成されています。

ハンドラ

(defn what-is-my-ip [request]
  {:status 200
   :headers {"Content-Type" "text/plain"}
   :body (:remote-addr request)})

この関数は、Ring が HTTP レスポンスに変換できるマップを返します。このレスポンスは、Web アプリケーションへのアクセスに使用された IP アドレスを含むプレーンテキストファイルを返します。

ハンドラは 非同期 の場合もあります。このタイプのハンドラは 3 つの引数を取ります。 リクエストのマップ、レスポンスのコールバック、例外のコールバックです。

(defn what-is-my-ip [request respond raise]
  (respond {:status 200
            :headers {"Content-Type" "text/plain"}
            :body (:remote-addr request)}))

この 2 つのタイプのハンドラはそれぞれ異なるアリティを持っているので、これらを組み合わせて、同期または非同期に使用できるハンドラを作ることができます。

(defn what-is-my-ip
  ([request]
    {:status 200
     :headers {"Content-Type" "text/plain"}
     :body (:remote-addr request)})
  ([request respond raise]
    (respond (what-is-my-ip request))))

公式の Ring ミドルウェアは、両方のタイプのハンドラをサポートしていますが、ほとんどの目的では同期ハンドラで十分です。

ハンドラ関数は、次のセクションで説明するさまざまな方法でウェブアプリケーションに変換することができます。

リクエスト

HTTP リクエストは Clojure のマップで表現されます。常に存在するいくつかの標準的なキーがありますが、リクエストにはミドルウェアによって追加されたカスタムキーが含まれることがあります(しばしばそうなります)。

標準的なキーは次の通りです。

以前のバージョンの Ring では以下のキーがありましたが、これらは現在 DEPRECATED されています。

レスポンス

レスポンスマップはハンドラによって作成され、3 つのキーが含まれています。

・String ボディはクライアントに直接送信されます。

・ISeq seqの各要素は、文字列としてクライアントに送信されます。

・File 参照されるファイルの内容がクライアントに送信されます。

・InputStream ストリームの内容がクライアントに送信されます。ストリームを使い切ると、ストリームは閉じられます。

ミドルウェア

ミドルウェアとは、ハンドラに追加機能を与える上位の関数です。ミドルウェア関数の第 1 引数にはハンドラを指定し、その戻り値には元のハンドラを呼び出す新しいハンドラ関数を指定します。簡単な例を見てみましょう。

(defn wrap-content-type [handler content-type]
  (fn [request]
    (let [response (handler request)]
      (assoc-in response [:headers "Content-Type"] content-type))))

このミドルウェア機能は、ハンドラが生成するすべてのレスポンスに Content-Type ヘッダを追加します。この関数は同期型ハンドラでのみ動作しますが、同期型と非同期型の両方のハンドラをサポートするように拡張することができます。

(defn content-type-response [response content-type]
  (assoc-in response [:headers "Content-Type"] content-type))

(defn wrap-content-type [handler content-type]
  (fn
    ([request]
      (-> (handler request) (content-type-response content-type)))
    ([request respond raise]
      (handler request #(respond (content-type-response % content-type)) raise))))

レスポンスを変更する共通のコードを独自の関数に分解したことに注目してください。慣習的に、 wrap-foo がミドルウェアの関数だとすると、foo-requestfoo-response はリクエストとレスポンスを操作するヘルパー関数です。

このミドルウェアが書き込まれると、それをハンドラに適用することができます。

(def app
  (wrap-content-type handler "text/html"))

これは、ハンドラ handlerwrap-content-type ミドルウェアを適用したもので、新しいハンドラ app を定義しています。

スレッディングマクロ( )を使うと、ミドルウェアを連鎖させることができます。

(def app
  (-> handler
      (wrap-content-type "text/html")
      (wrap-keyword-params)
      (wrap-params)))

ミドルウェアは Ring で頻繁に使用され、生の HTTP リクエストを処理する以外の機能の多くを提供するために使用されています。パラメータ、セッション、ファイルのアップロードなどは、すべて Ring 標準ライブラリのミドルウェアによって処理されます。

Ring のレスポンス・マップは手動で作成することもできますが、ring.util.response 名前空間には、この作業を容易にする便利な関数が多数含まれています。

response 関数は、基本的な "200 OK" レスポンスを作成します。

(response "Hello World")

; => {:status 200
    :headers {}
    :body "Hello World"}

そして、content-type のような関数を使って、ベースとなるレスポンスを変更し、追加のヘッダーや他のコンポーネントを追加することができます。

(content-type (response "Hello World") "text/plain")
 
; => {:status 200
    :headers {"Content-Type" "text/plain"}
    :body "Hello World"}

また、リダイレクトを行うための特別な機能も存在します。

(redirect "http://example.com")
 
; => {:status 302
    :headers {"Location" "http://example.com"}
    :body ""}

また、静的なファイルやリソースを返すためには、以下のようにします。

(file-response "readme.html" {:root "public"})
; => {:status 200
    :headers {}
    :body (io/file "public/readme.html")}

(resource-response "readme.html" {:root "public"})
; => {:status 200
    :headers {}
    :body (io/input-stream (io/resource "public/readme.html"))}

これらの関数やその他の詳細については、 ring.util.response API documentation を参照してください。

Web アプリケーションでは、画像やスタイルシートなどの静的コンテンツを提供する必要があります。Ring はこれを実現するために 2 つのミドルウェア関数を提供しています。

1 つは wrap-file です。これは、ローカルファイルシステム上のディレクトリから静的コンテンツを提供します。

(use 'ring.middleware.file)
(def app
  (wrap-file your-handler "/var/www/public"))

これは、与えられたルートパスのディレクトリに、リクエストに応答するための静的ファイルがあるかどうかをチェックし、そのようなファイルが存在しない場合は、ラップされたハンドラにリクエストをプロキシするようにハンドラをラップします。

もう 1 つは wrap-resource です。これは、JVM のクラスパスから静的コンテンツを提供します。

(use 'ring.middleware.resource)
(def app
  (wrap-resource your-handler "public"))

Leiningen のような Clojure ビルドツールを使用している場合、プロジェクトのソースファイル以外のリソースは、resources ディレクトリに保存されます。このディレクトリにあるファイルは、自動的に jar ファイルや war ファイルにインクルードされます。

つまり、上記の例では、resources/public ディレクトリに置かれたファイルは、静的ファイルとして提供されます。

多くの場合、wrap-filewrap-resource を他のミドルウェアと組み合わせたいと思うでしょう。通常は wrap-content-typewrap-not-modified です。

(use 'ring.middleware.resource
     'ring.middleware.content-type
     'ring.middleware.not-modified)
 
(def app
  (-> your-handler
      (wrap-resource "public")
      (wrap-content-type)
      (wrap-not-modified))

wrap-content-type ミドルウェアは、ファイルの拡張子に基づいてコンテントタイプを選択します。例えば、hello.txt というファイルのコンテントタイプは text/plain となります。

wrap-not-modified ミドルウェアは、レスポンスの Last-Modified ヘッダと、リクエストの If-Modified-Since ヘッダを照合します。これにより、クライアントが既にキャッシュされたリソースをダウンロードする必要がなくなり、帯域幅を節約することができます。

この追加のミドルウェアは、wrap-resourcewrap-file 関数をラップして(つまり、後から)来る必要があることに注意してください。

wrap-content-type ミドルウェアを使って、URI のファイル拡張子に基づいてContent-Type ヘッダを追加することができます。

(use 'ring.middleware.content-type)
 
(def app
  (wrap-content-type your-handler))

つまり、ユーザーが以下のスタイルシートにアクセスした場合、

http://example.com/style/screen.css

content-type ミドルウェアは、次のようなヘッダーを追加します。

Content-Type: text/css

デフォルトのコンテンツタイプのマッピングは、 ring-core/src/ring/util/mime_types.clj で見ることができます。

また、:mime-types オプションを使って、カスタムの mime-types を追加することもできます。

(use 'ring.middleware.content-type)
 
(def app
  (wrap-content-type
   your-handler
   {:mime-types {"foo" "text/x-foo"}}))

URL エンコードされたパラメータは、ブラウザがウェブアプリケーションに値を渡す主な手段です。ユーザーがフォームを送信する際に送信され、通常はページネーションなどに使用されます。

Ring は低レベルのインターフェースであるため、適切なミドルウェアを適用しない限り、パラメータには対応していません。

(use 'ring.middleware.params)
(def app
  (wrap-params your-handler))

wrap-params ミドルウェアは、URL エンコードされたパラメータを、クエリ文字列または HTTP リクエストボディからサポートします。

ファイルのアップロードはサポートしていません。これはミドルウェアの wrap-multipart-params で処理されます。マルチパートフォームの詳細については、ファイルアップロードのセクションを参照してください。

wrap-params 関数はオプションのマップを受け取ります.現在、認識できるキーは 1 つだけです。

パラメータミドルウェアは、ハンドラに適用されると、リクエストマップに 3 つの新しいキーを追加します。

例えば、以下のようなリクエストがあったとします。

{:request-method :get
 :uri "/search"
 :query-string "q=clojure"}

すると、 wrap-params ミドルウェアは、リクエストを次のように修正します。

{:request-method :get
 :uri "/search"
 :query-string "q=clojure"
 :query-params {"q" "clojure"}
 :form-params {}
 :params {"q" "clojure"}}

通常は、 :params キーのみを使用しますが、他のキーは、クエリ文字列で渡されるパラメータと、POST された HTML フォームで渡されるパラメータを区別する必要がある場合に備えてあります。

パラメータのキーは文字列で、値は、パラメータ名に関連する値が1つしかない場合は文字列、同じ名前の名前と値のペアが複数ある場合はベクターとなります。

例えば、次のような URL があったとします。

http://example.com/demo?x=hello

そうすると、パラメータマップは次のようになります。

{"x" "hello"}

しかし、同じ名前のパラメータが複数あると

http://example.com/demo?x=hello&x=world

そうすると、パラメータマップは次のようになります。

{"x" ["hello", "world"]}

Ring ハンドラにクッキーサポートを追加するには、ミドルウェアの wrap-cookies でラップする必要があります。

(use 'ring.middleware.cookies)
(def app
  (wrap-cookies your-handler))

これにより、リクエストマップに :cookies キーが追加され、次のようなクッキーのマップが含まれるようになります。

{"session_id" {:value "session-id-hash"}}

Cookie を設定するには、:cookie キーをレスポンス・マップに追加します。

{:status 200
 :headers {}
 :cookies {"session_id" {:value "session-id-hash"}}
 :body "Setting a cookie."}

Cookie の値を設定するだけでなく、追加の属性を設定することもできます。

1 時間で期限切れになる安全なクッキーが欲しい場合、次のようにします。

{"secret" {:value "foobar", :secure true, :max-age 3600}}

Ring のセッションは、可能な限り機能的であることを目指しているため、想像していたものとは少し異なります。

セッションデータは、リクエストマップの :session キーで渡されます。次の例では、セッションから現在のユーザー名を出力しています。

(use 'ring.middleware.session
     'ring.util.response)
 
(defn handler [{session :session}]
  (response (str "Hello " (:username session))))
 
(def app
  (wrap-session handler))

セッションデータを変更するには、更新されたセッションデータを含む :session キーをレスポンスに追加します。次の例では、現在のセッションがページにアクセスした回数をカウントします。

(defn handler [{session :session}]
  (let [count   (:count session 0)
        session (assoc session :count (inc count))]
    (-> (response (str "You accessed this page " count " times."))
        (assoc :session session))))

セッションを完全に削除するには、レスポンスの :session キーを nil に設定します。

(defn handler [request]
  (-> (response "Session deleted.")
      (assoc :session nil)))

例えば、特権の昇格などにより、単にセッションを再作成したい場合は、セッションのメタデータに :recreate キーを追加します。これにより、ブラウザに送信されるセッション識別子が変更されます。

(defn handler [request]
  (-> (response "Session identifier recreated")
      (assoc :session (vary-meta (:session request) assoc :recreate true))))

セッションクッキーがユーザーのブラウザ上に存在する時間をコントロールしたい場合がよくあります。セッションクッキーの属性を変更するには、:cookie-attrs オプションを使います。

(def app
  (wrap-session handler {:cookie-attrs {:max-age 3600}}))

この場合、クッキーの最大寿命は 3600 秒(1 時間)に設定されています。

また、HTTPS で保護されたサイトのセッション・クッキーが HTTP で漏洩しないようにするためにも使用できます。

(def app
  (wrap-session handler {:cookie-attrs {:secure true}}))

Session Stores

セッションデータは、セッションストアに保存されます。Ring には2つのストアがあります。

デフォルトでは、Ring はセッションデータをメモリ上に保存しますが、:store オプションで上書きすることができます。

(use 'ring.middleware.session.cookie)
 
(def app
  (wrap-session handler {:store (cookie-store {:key "a 16-byte secret"})})

ring.middleware.session.store/SessionStore プロトコルを実装することで、独自のセッションストアを書くことができます。

(use 'ring.middleware.session.store)
 
(deftype CustomStore []
  SessionStore
  (read-session [_ key]
    (read-data key))
  (write-session [_ key data]
    (let [key (or key (generate-new-random-key))]
      (save-data key data)
      key))
  (delete-session [_ key]
    (delete-data key)
    nil))

セッションの書き込み時に、新しいセッションの場合はキーが nil になることに注意してください。セッションストアはこれを想定して、新しいランダムなキーを生成する必要があります。そうしないと、悪意のあるユーザーが他の人のセッションデータにアクセスできてしまうからです。

Web サイトにファイルをアップロードするには、マルチパートのフォームを処理する必要がありますが、Ring は wrap-multipart-params というミドルウェアでこれを提供しています。

(wrap-multipart-params handler)
(wrap-multipart-params handler options)

このミドルウェア機能のオプションは

以下は完全な例です。

(require '[ring.middleware.params :refer [wrap-params]]
         '[ring.middleware.multipart-params :refer [wrap-multipart-params]])
 
(def app
  (-> your-handler
      wrap-params
      wrap-multipart-params))

アップロードされたファイルは、レスポンスの :multipart-params キーで確認できます。

初期設定ではアップロードは一時ファイルに保存され、アップロードされてから 1 時間後に削除されます。これは ring.middleware.multipart-params.temp-file/temp-file-store 関数で処理されます。

curl -XPOST  "http://localhost:3000" -F file=@words.txt

これにより、リクエストの :params マップに file キーが追加されます。:tempfile はアップロードされたデータを格納する java.io.File オブジェクトです。これを使って、さらに必要な処理を行うことができます。

{...
 :params
  {"file" {:filename     "words.txt"
           :content-type "text/plain"
           :tempfile     #object[java.io.File ...]
           :size         51}}
 ...}

上記の例に基づいて、Ring サーバが作成した一時ファイルをサーバ上の別の場所や恒久的な場所に保存したい場合は、bean を使って tempfile のパス情報を取得し、io/copyio/file を組み合わせてファイルを読み、新しい場所に保存することができます。

(def demo-ring-req
  {:params {"file"
            {:filename     "words.txt"}
            :content-type "text/plain"
            :tempfile     #object[java.io.File ...]
            :size         51}})
 
(def save-file [req]
  (let [tmpfilepath (:path (bean (get-in req [:params "file" :tempfile])))
        custom-path "/your/custom/path/file.txt"]
    (do
      (io/copy (io/file tmpfilepath) (io/file custom-path))
      {:status 200
       :headers  {"Content-Type" "text/html"}
       :body (str "File now available for download at: http://localhost:3000/" custom-path)})))
 
(save-file demo-ring-req)

ソースファイルを編集したときに名前空間を再読み込みすることは、開発に役立つことがあります。 ring-devel ライブラリは、この目的のためのミドルウェアを提供します。

(ns myapp
  (:require
   [compojure.core :refer [GET defroutes]]
   [ring.middleware.reload :refer [wrap-reload]]))
 
(defroutes app
  (GET "/" [] "hello world"))
 
(def reloadable-app
  (wrap-reload #'app))

コンポーネントのライフサイクル管理

リロードされたワークフローは、最初に reloaded workflow で説明しました。

リロードされたワークフローは、起動や停止が可能なコンポーネントを使用し、システムとしてまとめられています。開発中、システムは実行中の REPL で開始されます。ソースファイルが変更された後、システムは停止され、ソースファイルがリロードされ、再びシステムが開始されます。 これを 1 つのコマンドにまとめたり、ショートカットに付けたりすることもできます。

コンポーネントには以下のようなものが含まれます。

この方法をサポートしているライブラリの一覧です。

アダプタ

フレームワーク

ミドルウェア

セッション管理

Leiningen のプラグイン

テスト

ユーティリティ

Clojure Web Server Benchmarks を参照してください。

以下の事例を参照してください。

Ring 自体に搭載されているミドルウェアをご紹介します。

ring/ring-core

ring/ring-devel

様々なオプションにより、開発ワークフローを最適化できます。

1 つ目のアプローチは、高速で簡単にセットアップができます。ring-devel は、この目的のためにミドルウェアを提供しています。

しかし、名前空間のリロードはアプリケーションの状態をリロードしないという欠点があり、サーバの再起動が必要になる場合があります。

lein-ring プラグインによるセットアップ

lein-ring プラグインが一番わかりやすい方法です。2 つのファイルを作成します。

src/sample.clj'Hello World' を返すシンプルなサービスです。

(ns sample)
 
(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/plain"}
   :body "Hello world."})

プロジェクトファイル project.cljlein-ring を追加し、Ring のハンドラを設定します。

:ring {:handler sample/handler}

次は、最小限の project.clj です。

(defproject lein-demo "0.1.0-SNAPSHOT"
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [ring/ring-core "1.6.3"]
                 [ring/ring-jetty-adapter "1.6.3"]
                 [ring/ring-devel "1.6.3"]]
  :ring {:handler sample/handler}
  :plugins [[lein-ring "0.12.5"]])

開発用サーバを起動します。

​​lein ring server

http://localhost:3000 のサーバをご覧ください。

サーバは、ソースディレクトリ内の変更されたファイルを自動的に再読み込みします。

Clojure deps と CLI を使ったセットアップ

Clojure は推移依存性グラフの展開とクラスパスの作成のためのコマンドラインツールを提供します。詳細は https://clojure.org/guides/deps_and_cli をご覧ください。

次のようなプロジェクトのファイル構成を作成します。

.
├── deps.edn
├── dev
│   └── hotreload.clj
└── src
    └── sample
        └── server.clj

依存関係とクラスパスを記述します。

deps.edn

{:paths ["src"]
 :deps
 {org.clojure/clojure {:mvn/version "1.9.0"}
  ring/ring-core {:mvn/version "1.6.3"}
  ring/ring-jetty-adapter {:mvn/version "1.6.3"}}
 :aliases
 {:dev
  {:extra-paths ["dev"]
   :extra-deps {ring/ring-devel {:mvn/version "1.6.3"}}
   :main-opts ["-m" "hotreload"]}}}

本番サーバを作成します。

src/sample/server.clj

(ns sample.server
  (:require [ring.adapter.jetty :refer [run-jetty]])
  (:gen-class))
(defn handler [request]
  {:status  200
   :headers {"Content-Type" "text/plain; charset=UTF-8"}
   :body    "hello world!\n"})
(defn -main [& args]
  (run-jetty handler {:port 3000}))

clojure -M -m sample.server でサーバを起動します。http://localhost:3000 にアクセスし、文字列 "hello world!" が表示されることを確認します。

開発用サーバを作成します。

dev/hotreload.clj

(ns hotreload
  (:require [ring.adapter.jetty :refer [run-jetty]]
            [ring.middleware.reload :refer [wrap-reload]]
            [sample.server :refer [handler]])
  (:gen-class))
(def dev-handler
  (wrap-reload #'handler))
(defn -main [& args]
  (run-jetty dev-handler {:port 13000}))

clojure -A:dev -M:dev でサーバを起動します。http://localhost:13000 にアクセスして、文字列 "hello world!" が表示されることを確認してください。 新しい Clojure CLI のバージョンでは、余分な -A:dev の部分は必要ありません。

サーバは、ソースディレクトリ内の変更されたファイルを自動的に再読み込みします。

boot によるセットアップ

Boot は Clojure のためのビルドツールです。 https://boot-clj.github.io/

2つのファイルを作成します。

最小限の build.boot の例です。

(set-env!
  :resource-paths #{"src"}
  :dependencies '[[org.clojure/clojure "1.8.0"]
                  [ring/ring-core "1.6.3"]
                  [ring/ring-jetty-adapter "1.6.3"]
                  [ring/ring-devel "1.6.3"]])
 
(deftask dev
  "Run server hot reloading Clojure namespaces"
  [p port PORT int "Server port (default 3000)"]
  (require '[sample :as app])
  (apply (resolve 'app/run-dev-server) [(or port 3000)]))

リロードせずに 'Hello World' を返すシンプルなサービスです

src/sample.clj

(ns sample
  (:require
   [ring.adapter.jetty :refer [run-jetty]]))
 
(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/plain"}
   :body "Hello world"})
 
(defn run-dev-server
  [port]
  (run-jetty handler {:port port}))

boot のタスクを確認して見ましょう。

boot dev -h

サーバを起動するには次のようにします。

boot dev

http://localhost:3000 をご覧ください。

次に、ホットリロードを追加します。build.bootdependenciesring/ring-devel が含まれていることを確認します。ハンドラを wrap-reload ハンドラに指定します。

(ns sample
  (:require
   [ring.adapter.jetty :refer [run-jetty]]
   [ring.middleware.reload :refer [wrap-reload]]))
 
(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/plain"}
   :body "Hello world"})
 
(def dev-handler
  (wrap-reload #'handler))
 
(defn run-dev-server
  [port]
  (run-jetty dev-handler {:port port}))

サーバを再起動し、 http://localhost:3000 を開きます。サーバはソースディレクトリ内の変更されたファイルを自動的に再読み込みします。

lein ring プロジェクトを本番用にセットアップするには、多くのオプションがあります。

WAR アーカイブ

WAR アーカイブは、既存の Tomcat や Jetty のサーブレットエンジンにアプリケーションをコピーするための標準的なパッケージであり、clojure のコードとすべてのライブラリを含む特定の構造を持つ ZIP ファイルです。

以下のコマンドでアーカイブを作成します。

lein ring uberwar

作成したアーカイブをサーブレットエンジンにデプロイします。

lein-ring plugin

profile.cljlein-ring plugin を追加する必要があります。

:plugins [[lein-ring "0.12.1"]]
:ring {:handler sample/handler}

環境オプションを使って、ホットリロードとランダムなポート選択を無効にします。

LEIN_NO_DEV

サーバを起動します。

LEIN_NO_DEV=true lein ring server-headless

詳しい情報は https://github.com/weavejester/ring-server をご覧ください。

組み込まれた Jetty server

すでにコンパイルされた Clojure コードとすべてのライブラリを含む JAR ファイルを作成します。

ステップ

ハンドラで Jetty を起動するには、ring.adapter.jettyを使用します。

(ns sample
  (:require [ring.adapter.jetty :refer [run-jetty]])
  (:gen-class))
 
(defn handler [request]
  {:status  200
   :headers {"Content-Type" "text/text"}
   :body    "Hello world"})
 
(defn -main [& args]
  (run-jetty handler {:port (Integer/valueOf (or (System/getenv "port") "3000"))}))

AOT のコンパイルでは、:gen-class を使うことが重要です。

次のステップは、ビルドツールに依存します。

Leiningenを使ったビルド

project.clj:aot:main を追加する必要があります。

(defproject lein-ring "0.1.0-SNAPSHOT"
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [ring/ring-core "1.6.3"]
                 [ring/ring-jetty-adapter "1.6.3"]
                 [ring/ring-devel "1.6.3"]]
  :profiles{
    :uberjar{
      :aot :all
      :main sample}})

Boot を使ったビルド

uberjar を作成するタスクを含むシンプルな build.boot です。

(set-env!
  :resource-paths #{"src"}
  :dependencies '[[org.clojure/clojure "1.8.0"]
                  [ring/ring-core "1.6.3"]
                  [ring/ring-jetty-adapter "1.6.3"]
                  [ring/ring-devel "1.6.3"]])
 
(deftask build
  "Builds an uberjar of this project that can be run with java -jar"
  []
  (comp
   (aot :namespace #{'sample})
   (uber)
   (jar :file "project.jar" :main 'sample)
   (sift :include #{#"project.jar"})
   (target)))

サーバの実行

JAR ファイルを任意のポートで実行します。

port=2000 java -jar target/project.jar

モジュールシステムの一部としての Jetty サーバの組み込み

モジュール・ライフサイクル・ライブラリ のページを参照してください。