2014年2月19日水曜日

Clack & Swank によるインタラクティブなWebシステム開発

以前にこんな記事 「実行中 Common Lisp アプリのホットディプロイ」 を書きましたがその続きです。

Swankを使って起動しているCommon Lisp処理系に接続して動的に実行環境を操作しましたが、それをもう少し実践的に使ってみます。



前提と簡単な説明

Clozure CL
処理系はなんでもOKだと思います。

Quicklisp
CLでパッケージ管理システムです。

Clack
Common Lisp上で稼働するWebアプリケーションサーバです。作者曰く

Clackを一言で説明するならば、「それぞれのWebサーバが持つ差異を吸収し、統一的なAPIを提供するためのWebアプリケーション環境」です。PythonのWSGI、RubyのRackからインスパイアされて実装しました。特に実装はPerlのPlackを参考にしています。

シンプルで扱いやすく拡張も容易です。

SLIME
Emacs上で稼働するLispの開発環境です。SwankサーバとS式を使って通信します。

■Swank
SLIMEの一部で、S式を使って通信するサーバ機能を提供しています。


概要

Web Server                           
   +---------------------------------------------+
   |                                             |
   |   +----------------+                        |
   |   |   CL Process   |                        |
   |   | +------------+ |                        |
   |   | | clack 5001 | |   ssh                  |
   |   | +------------+ |   port                 |
   |   | +------------+ |   forward    +------+  |
   |   | | swank 5555 +----------------+ sshd |  |
   |   | +------------+ |              +---+--+  |
   |   +----------------+                  |     |
   |                                       |     |
   +---------------------------------------|-----+
                                           |
                                           |
                                           |
                                           |
                                           |
   Clinet                                  |
   +---------------------------------------|-----+
   |                                       |     |
   |    +-----------+         +------------+--+  |
   |    |   Emacs   |         |      ssh      |  |
   |    | +-------+ |         |   +-------+   |  |
   |    | | Slime +---------------+ 5555  |   |  |
   |    | +-------+ |         |   +-------+   |  |
   |    +-----------+         +---------------+  |
   |                                             |
   +---------------------------------------------+

Swankには認証機能が無いため、sshを経由してセキュリティを確保しています。


サーバ側コード

sample.lisp
(ql:quickload :clack)
(ql:quickload :swank)

(defpackage cl-simple-web
  (:use :cl
        :swank
        :clack))

(in-package :cl-simple-web)

(defvar *clack* nil)

(defun start-app (env)
  (index env))

(defun index (req)
  `(200 (:content-type "text/plain") ("Hello Clack!")))

(setf *clack* (clackup #'start-app
                       :port 5001
                       :debug t))

(create-server :port 5555
               :style :spawn
               :dont-close t)


サーバ側起動方法

サーバを起動します
server# wx86cl64 -e '(load "sample.lisp")'

動作確認
server# curl http://127.0.0.1:5001
Hello Clack!


クライアント側の接続

EmacsからSLIMEを使ってリモートのSwankへ接続しますが、その前に ssh でトンネリングしておきます。
client# ssh -L 5555:127.0.0.1:5555 root@webserver.hostname.or.ip

次にEmacsからSwankへ接続します。接続するには slime-connect を使います。
M-x slime-connect
Host: 127.0.0.1
Port: 5555

接続されたら名前空間を変更します。
; SLIME 2013-03-12
CL-USER> (in-package :cl-simple-web)
#<Package "CL-SIMPLE-WEB">

サーバ側で定義した変数の中身が参照できるようになります。
CL-SIMPLE-WEB> *clack*
#<CLACK.HANDLER:<HANDLER> #x2101E2645D>


動作の書き換え


つまらない例ですが、出力される「Hello Clack!」を変更してみます。
# curl http://127.0.0.1:5001
Hello Clack!

CL-SIMPLE-WEB> (defun index (req)
                 `(200 (:content-type "text/plain") ("Hello Clack!!")))
;Compiler warnings :
;   In INDEX: Unused lexical variable REQ
INDEX

# curl http://127.0.0.1:5001
Hello Clack!!


もう少し変更してみます。
CL-SIMPLE-WEB> (defun index (req)
                 (let ((method (getf req :request-method)))
                   (cond
                     ((eql method :get) '(200 (:content-type "text/plain") ("GET")))
                     ((eql method :put) '(200 (:content-type "text/plain") ("PUT")))
                     (t '(404 (:content-type "text/plain") ("404"))))))
INDEX

# curl -X GET http://127.0.0.1:5001
GET

# curl -X PUT http://127.0.0.1:5001
PUT

# curl -X DELETE http://127.0.0.1:5001
404


さらに変更してみます。
CL-SIMPLE-WEB> (defun 404notfound (req)
                 `(404 (:content-type "text/plain") ,req))
404NOTFOUND

CL-SIMPLE-WEB> (defun users-op (req)
                 (let ((method (getf req :request-method)))
                   (cond
                     ((eql method :get) '(200 (:content-type "text/plain") ("users GET")))
                     ((eql method :put) '(200 (:content-type "text/plain") ("users PUT")))
                     (t (404notfound req)))))
USERS-OP

CL-SIMPLE-WEB> (defun members-op (req)
                 (let ((method (getf req :request-method)))
                   (cond
                     ((eql method :get) '(200 (:content-type "text/plain") ("members GET")))
                     ((eql method :put) '(200 (:content-type "text/plain") ("members PUT")))
                     (t (404notfound req)))))
MEMBERS-OP

CL-SIMPLE-WEB> (defun index (req)
                 (let ((path (getf req :path-info)))
                   (cond
                     ((string= path "/users")   (users-op req))
                     ((string= path "/members") (members-op req))
                     (t (404notfound req)))))
INDEX

# curl -X GET http://127.0.0.1:5001/users
users GET

# curl -X PUT http://127.0.0.1:5001/users
users PUT

# curl -X PUT http://127.0.0.1:5001/members
members PUT

# curl -X GET http://127.0.0.1:5001/members
members GET

# curl -X GET http://127.0.0.1:5001/
REQUEST-METHOD
GET
SCRIPT-NAME

PATH-INFO
/
SERVER-NAME
127.0.0.1
SERVER-PORT
5001
SERVER-PROTOCOL
HTTP/1.1
REQUEST-URI
/
URL-SCHEME
HTTP
REMOTE-ADDR
127.0.0.1
REMOTE-PORT
54079
QUERY-STRING

RAW-BODY
#<FLEXI-IO-STREAM #x210203DD3D>
CONTENT-LENGTH
NIL
CONTENT-TYPE
NIL
HTTP-USER-AGENT
curl/7.29.0
HTTP-HOST
127.0.0.1:5001


Swankとの切断

M-x slime-disconnect

で切断できます。

補足

今回のプログラムでは呼び出されるパスとメソッドによって動作を変えていますが、Clackでは付属の defroutes というマクロを使う事でシンプルに定義できます。defroutes は defun に展開されます。
(ql:quickload :clack)
(ql:quickload :clack-app-route)
(ql:quickload :swank)

(defpackage cl-simple-web
  (:use :cl
        :swank
        :clack
        :clack.app.route))

(in-package :cl-simple-web)

(defvar *clack* nil)

(defun start-wrapper (env)
  (index env))

(defroutes index (req)
  (GET "/" #'top-page)
  (PUT "/users" #'users-op))

(defun clack-start ()
  (setf *clack* (clackup #'start-wrapper
                       :port 5001
                       :debug t)))

(defun clack-stop ()
  (stop *clack*))

(defun clack-restart ()
  (clack-stop)
  (clack-start))

(create-server :port 5555
               :style :spawn
               :dont-close t)

(clack-start)

0 件のコメント:

コメントを投稿