t-cool

はじめてのClack - Common LispでWeb開発


本記事は、原著者の許諾のもと、翻訳・掲載しています。 Getting started with clack / Jason Miller

チュートリアルを始める前に、roswellをインストールしてください。

rowellをインストール後、TerminalでREPLを起動すると、準備完了です。

$ ros run
* 

では、チュートリアルに進みましょう。


はじめてのClack

Clackは、様々なLisp Webサーバを統一して利用するためのシンプルなフレームワークです。Clackに関する文献が少ないので、このページでは、Clackの使い方について書きます。

依存環境を読み込む

* (ql:quickload '(clack alexandria optima))
* (use-package :optima)

サーバを起動する

clackupに必要な引数はapplicationだけです。applicationは、(http-response-code http-headers-alist &optional `body)をリストで返す必要があります。

bodyには、(unsigned-byte 8)のベクタ、パス名、文字列のリストを書くことができます。

(defparameter *clack-server* (clack:clackup (lambda (env)
                                         '(200 nil ("Hello, World!")))))
Hunchentoot server is started.
Listening on localhost:5000.

curlでから始めましょう:

curl -s http://localhost:5000
Hello, World!

サーバを停止する

(clack:stop *clack-server*)

ハンドラを再定義する

サーバを毎回再起動するのは苦痛なので、再定義ができるハンドラを定義しましょう:

(defun handler (env) '(200 nil ("Hello World, redefinable!")))

そしてサーバを起動します。再定義が可能になるように、関数を名前で呼び出します:

(defparameter *clack-server*
  (clack:clackup (lambda (env) (funcall 'handler env))))
Hunchentoot server is started.
Listening on localhost:5000.

正常に動作するか確認しましょう:

curl -s http://localhost:5000
Hello World, redefinable!

では、再定義して、環境内でどうになっているかを見てみましょう:

(defun handler (env)
  `(200 nil (,(prin1-to-string env))))

結果を確認しましょう...

curl -s http://localhost:5000
(:REQUEST-METHOD :GET 
 :SCRIPT-NAME ""
 :PATH-INFO "/" 
 :SERVER-NAME "localhost"
 :SERVER-PORT 5000
 :SERVER-PROTOCOL :HTTP/1.1
 :REQUEST-URI "/"
 :URL-SCHEME "http"
 :REMOTE-ADDR "127.0.0.1"
 :REMOTE-PORT 53824
 :QUERY-STRING NIL
 :RAW-BODY #<FLEXI-STREAMS:FLEXI-IO-STREAM {1021B536E3}>
 :CONTENT-LENGTH NIL
 :CONTENT-TYPE NIL
 :CLACK.STREAMING T
 :CLACK.IO #<CLACK.HANDLER.HUNCHENTOOT::CLIENT {1021B537F3}>
 :HEADERS #<HASH-TABLE :TEST EQUAL :COUNT 3 {1021B53C13}>)

これがclackの中心部であり、環境を表すplistです。

環境についてのドキュメントは、lack documentにあります。

plistであることにより、捕捉される値は、destructuring-bind で処理できます。

(defun handler (env)
  (destructuring-bind (&key request-method path-info request-uri
                            query-string headers &allow-other-keys)
       env
     `(200
       nil
       (,(format nil "Method: ~S Path: ~S URI: ~A Query: ~S~%Headers: ~S"
                 request-method path-info request-uri query-string
                 (alexandria:hash-table-alist headers))))))
curl -s http://localhost:5000
Method: :GET Path: "/" URI: / Query: NIL
Headers: (("accept" . "*/*") ("user-agent" . "curl/7.53.0")
          ("host" . "localhost:5000"))

Optimaを使うと便利です:

(defun handler (env)
  (optima:match env
    ((guard (property :path-info path)
            (alexandria:starts-with-subseq "/foo/" path))
     `(200 nil (,(format nil "The path '~A' is in /foo/~%" path))))
    ((guard (property :path-info path)
            (alexandria:starts-with-subseq "/bar/" path))
     `(200 nil (,(format nil "The path '~A' is in /bar/~%" path))))
    ((property :path-info path)
     `(404 nil (,(format nil "Path ~A not found~%" path))))))
curl -s http://localhost:5000/foo/quux
curl -s http://localhost:5000/bar/quux
curl -s http://localhost:5000/baz/quux
The path '/foo/quux' is in /foo/
The path '/bar/quux' is in /bar/
Path /baz/quux not found

Public API

CLACK:CLACKUP

シンタックス:

    *clackup* app &key server port debug silent
    use-thread use-default-middlewares &allow-other-keys
    => ハンドラ

引数と値:

  • app - 1つの引数の関数を示します。lack.component:lack-componentのサブクラスです。パス名か文字列です。

  • server - シンボル。初期値は、:hunchentoot

  • port - 整数。初期値は、5000

  • debug - 論理値。初期値は、t

  • silent - 論理値。初期値は、nil

  • use-thread - 論理値。初期値は、スレッドサポートのシステムではt、そうでない場合はnil

  • use-default-middlewares - 論理値。初期値は、t

  • handler - clack.handler::handler

説明:

clackupは、指定されたサーバとportをバックエンドとして使いながら、サーバを起動します。

appは、次のように、サーバがハンドラの連鎖を構成するために使われます:

  • もしappが関数の場合は、appは直接つかわれます。そして、リクエスト環境を唯一の引数として、それぞれのリクエストに対して、呼び出されます。

  • applack.component:lack-componentのサブクラスの場合、(lack.component:call app environment)がリクエスト毎に呼び出されます。

  • appがパス名の場合、lispファイルとして扱われて実行されます。ファイルにある最後の式の結果が、上のように使われます。

  • appが文字列の場合、パス名に変換されて、上のように使われます。

  • use-default-middlewarestrueの場合、appはデフォルトのミドルウェアに内包(wrap)されます。

serverは、利用するバックエンドを指定します。バックエンドが見つからない場合、clackupは、quicklispasdfを用いて、バックエンドを読み込もうとします。

portは、サービスを受けつけるポートを特定します。

debugは、デバッグモードを指定します。ここでの結果は、バックエンドによって異なりますが、appの本体で生じる全てのエラーは、falseの場合、500のシスポンスを返すことで処理します。

silentは、ステータスのメッセージが出ないように抑えます。

use-threadtrueの場合, 別のスレッドでバックエンドが起動します。

(記事 終)


(参考) lack document より

環境 (Environment)

applicationは、環境(Environment)を受け取ります。中身はplistであり、以下のキーを含みます:

:request-method (必須, キーワード)
    HTTP リクエストメソッドとして、:GET :HEAD :OPTIONS :PUT :POST :DELETEを指定します。

:script-name (必須, 文字列)
    リクエストURIパスの最初の部分であり、Clack applicationに対応します。
    このキーの値は、クライアントがサーバーのルートでアクセスする場合、空の文字列になります。
    そうでない場合は、スラッシュ/から始めて指定します。

:path-info (必須, 文字列)
    リクエストURIパスの残りです。トレイリングスラッシュがない場合、空の文字列になります。

:query-string (オプション, 文字列)
   もし URIに?クエリがある場合、対応します。

:url-scheme (必須, 文字列)
    リクエストされるURIにより、"http"か"https"になります。

:server-name (必須, 文字列)
    名前解決されるサーバ名か、サーバのIPアドレスです。

:server-port (必須, 整数)
    リクエストが処理されているポート番号です。

:server-protocol (必須, キーワード)
    クライアントがリクエストを送るプロトコルのバージョンです。
    :HTTP/1.0か :HTTP/1.1 のことが多いです。

:request-uri (必須, 文字列)
    リクエストのURIです. 常に"/"で始まります。

:raw-body (オプション, ストリーム)
    リクエストの新しいbody部です。

:remote-addr (必須, 文字列)
    リモート用のアドレスです。

:remote-port (必須, 整数)
    リモート用のポートです。

:content-type (必須, 文字列)
    Content-Typeのヘッダー情報です。

:content-length (オプション, 整数)
    Content-Lengthのヘッダーの値です。

:headers (必須, ハッシュテーブル)
    ヘッダーのハッシュテーブルです。