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 件のコメント:
コメントを投稿