Ring は、プログラミング言語 Clojure で Web アプリケーションを構築するための低レベルのライブラリです。Ruby の Rack や Python の WSGI、あるいは Java の Servlet の仕様に似ています。
Web アプリケーションの基盤として Ring を使うと、以下のメリットがあります。
Clojure で Web アプリケーションを書く場合、Ring は業界の標準として認められている基盤です。 Compojure や lib-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 のマップで表現されます。常に存在するいくつかの標準的なキーがありますが、リクエストにはミドルウェアによって追加されたカスタムキーが含まれることがあります(しばしばそうなります)。
標準的なキーは次の通りです。
:server-port
リクエストを処理するためのポートです。:server-name
解決されたサーバ名、またはサーバの IP アドレスです。:remote-addr
リクエストを送信したクライアントまたは最後のプロキシの IP アドレスです。:uri
リクエストの URI (ドメイン名の後のフルパス)です。:query-string
クエリ文字列があれば、それを入力します。:scheme
トランスポートプロトコルです。:http
または :https
のいずれかになります。:request-method
HTTP リクエストメソッドで、:get
, :head
, :options
, :put
, :post
, :delete
のいずれかになります。:headers
(ヘッダー) 小文字のヘッダ名文字列から対応するヘッダ値文字列への Clojure マップです。:body
もしあれば、リクエストボディの入力ストリームです。以前のバージョンの Ring では以下のキーがありましたが、これらは現在 DEPRECATED されています。
:content-type
リクエストボディの MIME タイプ(分かっている場合)。:Content-Length
もしわかっていれば、リクエストボディのバイト数です。:character-encoding
もしわかっていれば、リクエストボディで使用されている文字エンコーディングの名前です。レスポンスマップはハンドラによって作成され、3 つのキーが含まれています。
:status
200、302、404 などの HTTP ステータスコードです。:headers
HTTP ヘッダ名からヘッダ値への Clojure マップです。これらの値は文字列で、その場合は HTTP レスポンスで 1 つの名前/値のヘッダが送信されます。また、文字列の集合体で、その場合は各値に対して名前/値のヘッダが送信されます。:body
レスポンスのステータスコードに対してレスポンスボディが適切な場合には、レスポンスボディの表現です。ボディは4つのタイプのうちの1つです。・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-request
と foo-response
はリクエストとレスポンスを操作するヘルパー関数です。
このミドルウェアが書き込まれると、それをハンドラに適用することができます。
(def app (wrap-content-type handler "text/html"))
これは、ハンドラ handler
に wrap-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-file
や wrap-resource
を他のミドルウェアと組み合わせたいと思うでしょう。通常は wrap-content-type
と wrap-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-resource
や wrap-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 つだけです。
:encoding
パラメータの文字エンコーディングを指定します。デフォルトでは、リクエストの 文字エンコーディングが設定されていない場合は、UTF-8 がデフォルトです。 セットされます。パラメータミドルウェアは、ハンドラに適用されると、リクエストマップに 3 つの新しいキーを追加します。
:query-params
クエリ文字列のパラメータのマップ:form-params
送信されたフォームデータに含まれるパラメータのマップ:params
すべてのパラメータをマージしたマップ例えば、以下のようなリクエストがあったとします。
{: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 の値を設定するだけでなく、追加の属性を設定することもできます。
:domain
Cookie を特定のドメインに制限します。:path
Cookie を特定のパスに制限します。:secure
true の場合、Cookie を HTTPS URL に制限します。:http-only
trueの場合、Cookie を HTTP に制限します(例えば JavaScript でアクセスできない)。:max-age
Cookie の有効期限が切れるまでの秒数:expires
Cookie の有効期限が切れる特定の日時:same-site
Cookie をクロスサイト要求で送信するかどうかを決定するために、:strict
、:lax
、または :none
を指定します。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}}))
セッションデータは、セッションストアに保存されます。Ring には2つのストアがあります。
ring.middleware.session.memory/memory-store
メモリ内にセッションを保存します。ring.middleware.session.cookie/cookie-store
クッキーで暗号化されたセッションを保存します。デフォルトでは、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)
このミドルウェア機能のオプションは
:encoding
パラメータの文字エンコーディングを指定します。wrap-params
の同じオプションと同じ働きをします。 オプションと同じ働きをします。:store
アップロードされたファイルを保存するために使用する関数です。Ring には、2 つのストア Ring に含まれています。以下は完全な例です。
(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/copy
と io/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 つのコマンドにまとめたり、ショートカットに付けたりすることもできます。
コンポーネントには以下のようなものが含まれます。
この方法をサポートしているライブラリの一覧です。
Clojure Web Server Benchmarks を参照してください。
以下の事例を参照してください。
Ring 自体に搭載されているミドルウェアをご紹介します。
ring/ring-core
ring/ring-devel
様々なオプションにより、開発ワークフローを最適化できます。
1 つ目のアプローチは、高速で簡単にセットアップができます。ring-devel は、この目的のためにミドルウェアを提供しています。
しかし、名前空間のリロードはアプリケーションの状態をリロードしないという欠点があり、サーバの再起動が必要になる場合があります。
lein-ring プラグインが一番わかりやすい方法です。2 つのファイルを作成します。
src/sample.clj
project.clj
src/sample.clj
は 'Hello World'
を返すシンプルなサービスです。
(ns sample) (defn handler [request] {:status 200 :headers {"Content-Type" "text/plain"} :body "Hello world."})
プロジェクトファイル project.clj
に lein-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 は推移依存性グラフの展開とクラスパスの作成のためのコマンドラインツールを提供します。詳細は 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 は Clojure のためのビルドツールです。 https://boot-clj.github.io/
2つのファイルを作成します。
build.boot
src/sample.clj
最小限の 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.boot
の dependencies
に ring/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 アーカイブは、既存の Tomcat や Jetty のサーブレットエンジンにアプリケーションをコピーするための標準的なパッケージであり、clojure のコードとすべてのライブラリを含む特定の構造を持つ ZIP ファイルです。
以下のコマンドでアーカイブを作成します。
lein ring uberwar
作成したアーカイブをサーブレットエンジンにデプロイします。
profile.clj
に lein-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 をご覧ください。
すでにコンパイルされた Clojure コードとすべてのライブラリを含む JAR ファイルを作成します。
ステップ
src/sample.clj
エントリポイントハンドラで 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
を使うことが重要です。
次のステップは、ビルドツールに依存します。
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}})
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
モジュール・ライフサイクル・ライブラリ のページを参照してください。