連載
» 2009年02月20日 00時00分 公開

Gaucheでメタプログラミング(5):継続を使ったコントローラを作る (2/3)

[吉田裕美,有限会社イーワイオフィス]

継続ベースのWebアプリケーションを作る

 ここでは、独自の継続ベースのコントローラと簡単なWebサーバを作ります。前回、説明したORマッパーとテンプレートエンジンを組み合わせ、テーブルの一覧表示や編集ができる簡単なWebアプリケーションを作ってみます。

モジュール

 継続を使ったWebアプリケーションのプログラムの説明をする前に、Gaucheのモジュールについて少し説明します。ほとんどの言語は、関数名などの衝突を回避したり、コードの再利用性を高めたりするためのモジュール機能を持っています。Javaではパッケージと呼ばれるものがそれに当たります。

 Gaucheのモジュールは、下のテンプレートエンジンモジュールのようにdefine-moduleでモジュールを定義します。define-moduleの中には、そのモジュールで利用するモジュール名を指定するuse、そのモジュールで公開する関数などを指定するexportなどを記述します。

 その下のselect-moduleは、以降の関数定義などの所属するモジュールを指定しています。最後のprovideは、このモジュールをuseするモジュールが複数あっても再読み込みしないようにするための設定です。

 通常はモジュール単位でファイルを分けます。

(define-module template
  (use file.util)
  (export render rendering-text rendering-file))
(select-module template)
 
(define (template->s-exp templ)
  (define (quote-display m)
    (format "(display ~s *p*)" (m 1)))
  (if (not (#/<%/ templ))
      templ
      (let* ((s (regexp-replace-all* templ
                                     #/<%=(.*?)%>/ "<% (display \\1 *p*) %>"
                                     #/%>(.*?)<%/ quote-display))
             (str-s-exp (regexp-replace* s
                                         #/^(.*?)<%/ quote-display
                                         #/%>(.*?)$/ quote-display)))
        (read-from-string (string-append "(call-with-output-string
(lambda(*p*)"
                                         str-s-exp
                                         "))")))))
 
(define (rendering-text templ vars args)
  (apply (eval `(lambda ,vars ,(template->s-exp templ))
               (interaction-environment)) args))
 
(define (rendering-file templ-file vars args)
   (rendering-text (file->string templ-file) vars args))
 
(define-macro (render tmpl-file . args)
  `(rendering-file ,tmpl-file (quote ,args) (list ,@args)))
 
(provide "template")
template.scm

 これから各モジュールを簡単に解説します。関数などの詳細な説明しませんので適宜ユーザーリファレンスを調べてください。

templateモジュール

 templateモジュールは、前回作ったテンプレートエンジンを少し変更しました。変更点は次のとおりです。

  • 生成されるS式を(call-with-output-string (lambda(*p*) .... )とし、テンプレート全体を文字列として戻すようにしました
  • 上に伴い、内部で使っているdisplay関数を文字列ポートに出力するようにしました
  • rendering-text関数は、テンプレート評価の際にテンプレート内の変数に値を渡せるようlambda式にしました
  • ファイルからテンプレートを読み込むrendering-file関数を追加しました
  • テンプレートに渡す変数のリストを指定するだけでrendering-fileを利用できるようにするrenderマクロを追加しました

 Gaucheで大量の文字列を生成する際に、string-appendのような文字列操作関数を多用するとメモリを大量に消費し効率的ではありません。このような用途には、文字列ポートを使います。

serverプログラム

 このファイルは、モジュールではなくメインプログラムです。8080番のポートにソケットを作成し、Webブラウザからのリクエストを待ち、GETリクエストがあれば処理部分であるdispatch関数を呼び出し、その結果をWebブラウザに送れる形にします。

 このサーバはGETリクエストにしか対応していません。また、出力はHTMLのみです。何かエラーが起きた場合は、guardマクロで例外処理を行います。ここではエラー情報を表示し、終了します。

 ちなみに、report-error関数はドキュメント化されてない関数ですが、便利なので使っています。ただし、ドキュメント化されてない関数は将来、仕様が変更されるかもしれないので注意してください。

(add-load-path "./")
(use gauche.net)
(use controller)
 
(define (web-server)
  (let1 server-socket (make-server-socket 'inet 8080 :reuse-addr? #t)
    (guard (e (else (socket-close server-socket) (report-error e)))
     (while #t
       (let* ((client-socket (socket-accept server-socket))
              (request (get-request (socket-input-port client-socket))))
        (put-response
         (dispatch request)
         (socket-output-port client-socket))
        (socket-close client-socket))))))
 
 (define (get-request in-port)
   (cond ((rxmatch #/^GET\s+(\S+)\s/ (read-line in-port)) => (lambda (m) (m 1)))
         (else "")))
 
 (define (put-response content out-port)
   (display "HTTP/1.1 200 OK\r\n" out-port)
   (display "Content-Type: text/html; charset=utf-8\r\n" out-port)
   (display #`"Content-Length: ,(string-size content)\r\n" out-port)
   (display "\r\n" out-port)
   (display content out-port))
 
(web-server)
server.scm

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。