2011年6月7日火曜日

CL clack & cl-markup


このエントリーをはてなブックマークに追加


Common Lispで書かれたシンプルなWebサーバとタグビルダー(?)。試行錯誤しながらちょっと使ってみた。

http://e-arrows.sakura.ne.jp/2011/05/what-ive-created-in-ariel.html


(eval-when (:compile-toplevel :load-toplevel :execute)
  (require :clack)
  (require :cl-markup)
  (require :drakma)
  )

(defpackage :sample-app
  (:use :cl
  :clack
  :clack.request
  :clack.app.route
  :cl-markup
  :drakma))

(in-package :sample-app)

(defun sample-app-1 (env)
  "
   clackupから呼び出す関数。clackupより先に定義しておく必要がある
   これはリクエストヘッダを表示するだけのシンプルなもの

   clackupから呼び出された関数は最終的に
   (responst-code (head-list) (body-list))
   という形式のリストを返す必要がある
"
  (list 200
  '(:content-type "text/plain")
  (list (format nil "~{~a: ~a~%~}~%" env))))
  
;; clack の起動はシンプル。
;; clackup の引数として関数を渡すことで、HTTPアクセス時にその関数が呼び出されるだけ。
;; 呼び出された関数の引数はHTTPリクエストヘッダを格納したリストになる。
(clackup #'sample-app-1)

これを呼び出すと。
CL-USER> (drakma:http-request "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: 51862
QUERY-STRING: 
RAW-BODY: #<FLEXI-IO-STREAM #x2102A9B54D>
CONTENT-LENGTH: NIL
CONTENT-TYPE: NIL
CLACK.UPLOADS: NIL
CLACK-HANDLER: HUNCHENTOOT
HTTP-HOST: localhost:5000
HTTP-USER-AGENT: Drakma/1.2.3 (Clozure Common Lisp Version 1.6-r14468M  (WindowsX8664); Microsoft Windows; 6.1 Build 7601 (Workstation); http://weitz.de/drakma/)

"
200
((:CONTENT-LENGTH . "518") (:DATE . "Tue, 07 Jun 2011 13:36:39 GMT") (:SERVER . "Hunchentoot 1.1.1") (:CONNECTION . "Close") (:CONTENT-TYPE . "text/plain"))
#<URI http://localhost:5000/>
#<FLEXI-STREAMS:FLEXI-IO-STREAM #x2102A5FE6D>
T
"OK"

別のuriを呼び出すと。
CL-USER> (drakma:http-request "http://localhost:5000/index&get=data1")
"REQUEST-METHOD: GET
SCRIPT-NAME: 
PATH-INFO: /index&get=data1
SERVER-NAME: localhost
SERVER-PORT: 5000
SERVER-PROTOCOL: HTTP/1.1
REQUEST-URI: /index&get=data1
URL-SCHEME: HTTP
REMOTE-ADDR: 127.0.0.1
REMOTE-PORT: 52059
QUERY-STRING: 
RAW-BODY: #<FLEXI-IO-STREAM #x2102BDD29D>
CONTENT-LENGTH: NIL
CONTENT-TYPE: NIL
CLACK.UPLOADS: NIL
CLACK-HANDLER: HUNCHENTOOT
HTTP-HOST: localhost:5000
HTTP-USER-AGENT: Drakma/1.2.3 (Clozure Common Lisp Version 1.6-r14468M  (WindowsX8664); Microsoft Windows; 6.1 Build 7601 (Workstation); http://weitz.de/drakma/)

"
200
((:CONTENT-LENGTH . "548") (:DATE . "Tue, 07 Jun 2011 13:43:34 GMT") (:SERVER . "Hunchentoot 1.1.1") (:CONNECTION . "Close") (:CONTENT-TYPE . "text/plain"))
#<URI http://localhost:5000/index&get=data1>
#<FLEXI-STREAMS:FLEXI-IO-STREAM #x2102A5428D>
T
"OK"

ClackではどんなURIを指定しても、たとえば / でも /index でも /post?xxx=yyy でも全て最初に指定した関数が呼び出される。

ページ単位の処理の記述する場合は、:request-uriを見てアクセスページごとに処理を振り分ける必要がある。手で書くこともできるが、単純な振り分けはclack.app.routeに含まれるdefroutesでシンプルに指定できる。
(defroutes sample-app-2 (env)
  (GET "/" #'index)       ;; GET で / が呼ばれたら関数 index を呼び出す
  (GET "/index" #'index)  ;; GET で /index でも 関数 index を呼び出す
  (POST "/post" #'post))  ;; POST で /post が呼び出されたら関数 post を呼び出す

;; このマクロは以下のように展開され、
;; 存在しないページはちゃんと404を返してくれる
;;
;; (DEFUN SAMPALE-APP-2 (#:REQ0)
;;   (LET ((#:REQUEST-METHOD1 (GETF #:REQ0 :REQUEST-METHOD))
;;         (#:REQUEST-PATH2 (GETF #:REQ0 :PATH-INFO)))
;;     (DECLARE (IGNORABLE #:REQUEST-METHOD1 #:REQUEST-PATH2))
;;     (OR (WHEN (STRING= #:REQUEST-METHOD1 'ENV)
;;           (MULTIPLE-VALUE-BIND (#:MATCHED3 #:REGS4)
;;               (CL-PPCRE:SCAN-TO-STRINGS "^$" #:REQUEST-PATH2)
;;             (DECLARE (IGNORABLE #:REGS4))
;;             (IF #:MATCHED3 (CALL NIL #:REQ0))))
;;         (WHEN (STRING= #:REQUEST-METHOD1 'GET)
;;           (MULTIPLE-VALUE-BIND (#:MATCHED3 #:REGS4)
;;               (CL-PPCRE:SCAN-TO-STRINGS "^/$" #:REQUEST-PATH2)
;;             (DECLARE (IGNORABLE #:REGS4))
;;             (IF #:MATCHED3 (CALL #'INDEX #:REQ0))))
;;         (WHEN (STRING= #:REQUEST-METHOD1 'GET)
;;           (MULTIPLE-VALUE-BIND (#:MATCHED3 #:REGS4)
;;               (CL-PPCRE:SCAN-TO-STRINGS "^/index$" #:REQUEST-PATH2)
;;             (DECLARE (IGNORABLE #:REGS4))
;;             (IF #:MATCHED3 (CALL #'INDEX #:REQ0))))
;;         (WHEN (STRING= #:REQUEST-METHOD1 'POST)
;;           (MULTIPLE-VALUE-BIND (#:MATCHED3 #:REGS4)
;;               (CL-PPCRE:SCAN-TO-STRINGS "^/post$" #:REQUEST-PATH2)
;;             (DECLARE (IGNORABLE #:REGS4))
;;             (IF #:MATCHED3 (CALL #'POST #:REQ0))))
;;         (LIST 404 NIL NIL))))

(defun index (env)
  "#\Linefeedは出力をわかりやすくするためにつけているだけなので実際は不要"
  `(200
 (:content-type "text/html")
 ,(list
   (markup
    (html
  (:head
   (:meta :content "text/html" :charset "UTF-8") #\Linefeed
   (:title "Clackへようこそ!") #\Linefeed)
  (:body :bgcolor "efefef"
      (:p "Clackへようこそ!") (:br) #\Linefeed
      "cl-markup では &<>\"が自動エスケープされます"))))))

(defun post (env)
  "
   postデータを受け取る
   いったんmake-requestでリクエストヘッダをクラスインスタンスへ格納し、
   そこからbody-parameterで取り出す事ができる。
"
  (let ((req (make-request env)))
 `(200  
   (:content-type "text/plain")  
   ("post data: " ,(body-parameter req "name")))))

(clackup #'sample-app-2)


まずは普通に呼び出してみる
CL-USER> (drakma:http-request "http://localhost:5000")
"<html><head><meta content=\"text/html\" charset=\"UTF-8\" />
<title>Clackへようこそ!</title>
</head><body bgcolor=\"efefef\"><p>Clackへようこそ!</p><br />
cl-markup では &<>"が自動エスケープされます</body></html>"
200
((:CONTENT-LENGTH . "249") (:DATE . "Tue, 07 Jun 2011 14:19:59 GMT") (:SERVER . "Hunchentoot 1.1.1") (:CONNECTION . "Close") (:CONTENT-TYPE . "text/html"))
#<URI http://localhost:5000/>
#<FLEXI-STREAMS:FLEXI-IO-STREAM #x2104109E0D>
T
"OK"

別の呼び出し方
CL-USER> (drakma:http-request "http://localhost:5000/index")
"<html><head><meta content=\"text/html\" charset=\"UTF-8\" />
<title>Clackへようこそ!</title>
</head><body bgcolor=\"efefef\"><p>Clackへようこそ!</p><br />
cl-markup では &<>"が自動エスケープされます</body></html>"
200
((:CONTENT-LENGTH . "249") (:DATE . "Tue, 07 Jun 2011 14:20:06 GMT") (:SERVER . "Hunchentoot 1.1.1") (:CONNECTION . "Close") (:CONTENT-TYPE . "text/html"))
#<URI http://localhost:5000/index>
#<FLEXI-STREAMS:FLEXI-IO-STREAM #x21041BDF4D>
T
"OK"


存在しないページを呼び出す
CL-USER> (drakma:http-request "http://localhost:5000/404")
NIL
404
((:CONTENT-LENGTH . "0") (:DATE . "Tue, 07 Jun 2011 14:20:10 GMT") (:SERVER . "Hunchentoot 1.1.1") (:CONNECTION . "Close") (:CONTENT-TYPE . "text/html; charset=utf-8"))
#<URI http://localhost:5000/404>
#<FLEXI-STREAMS:FLEXI-IO-STREAM #x2104002DDD>
T
"Not Found"


POSTデータを送信する。
CL-USER> (let ((temp "name=data1"))
     (drakma:http-request "http://localhost:5000/post"
         :method :post
         :content temp
         :content-length (length temp)))
"post data: 
data1"
200
((:CONTENT-LENGTH . "17") (:DATE . "Tue, 07 Jun 2011 14:20:19 GMT") (:SERVER . "Hunchentoot 1.1.1") (:CONNECTION . "Close") (:CONTENT-TYPE . "text/plain"))
#<URI http://localhost:5000/post>
#<FLEXI-STREAMS:FLEXI-IO-STREAM #x2104055CED>
T
"OK"

0 件のコメント:

コメントを投稿