gemmaによる継続サーバの実装です。Apache+cgi(gosh)で動きます。
cgiはリクエスト毎に終了してしまい、これでは継続を保存できないので、各セッションにひとつプロセスをforkしてそこに保存する形をとっています。ゾンビプロセスだらけになるのが欠点です。
(define-module gaup (use text.tree) (use text.html-lite) (use gauche.net) (use gauche.selector) (use www.cgi) (use srfi-13) (use gauche.parseopt) (export gaup-pid gaup-cont show gaup-init)) (select-module gaup) (define gaup-debug #t) (define gaup-pid (sys-getpid)) (define gaup-cont 0) (define selector (make <selector>)) ;;debug時は、print-pageは標準出力へのwrite-treeに、read-inputは標準入力からのread-lineになる。 (define (print-page page) (if gaup-debug (write-tree page) (call-with-client-socket (make-client-socket 'unix (format #f "/tmp/gaup~a" gaup-pid)) (lambda (in out) (write-tree page out))))) (define (read-input) (if gaup-debug (let1 query (begin (display "Enter parameters (name=value). ^D to stop.\n") (flush) (let loop ((line (read-line)) (params '())) (if (eof-object? line) (string-join (reverse params) "&") (loop (read-line) (if (string-null? line) params (cons line params)))))) (cgi-parse-parameters :query-string query)) (call/cc (lambda (return) (let* ((path (format #f "/tmp/gaup~a-~a" gaup-pid gaup-cont)) (server (make-server-socket 'unix path))) (sys-chmod path #o777) ;;継続サーバの肝である"継続"は、このselector-add!のクロージャに保存しておく。 (selector-add! selector (socket-fd server) (lambda (fd flag) (let* ((client (socket-accept server)) (input (socket-input-port client)) (params (read input))) (socket-close client) (return params))) '(r))) (inc! gaup-cont) (do () (#f) (selector-select selector)))))) (define (show page) (print-page page) (read-input)) (define (gaup-init args) ;;debug時には、gosh sum.scm と実行してもらい、継続サーバからの呼出し時には、gosh sum.scm -x と実行することにしてある。 (let-args (cdr args) ((#f "x" => (lambda () (set! gaup-debug #f))))) (set-signal-handler! SIGINT (lambda (n) (print `"Received SIGINT") (exit 0))) (unless gaup-debug ;;標準ポートを閉じる。ブラウザが、いつページ読み込みが完了するのかと混乱するのを、防ぐため。 (close-input-port (standard-input-port)) (close-output-port (standard-output-port)) (close-output-port (standard-error-port)))) (provide "gaup")
#!/usr/local/bin/gosh (use gauche.net) (use gauche.process) (use www.cgi) (use text.html-lite) (define (run-cont gaup-pid gaup-cont params) ;;path に unixドメインソケットをはり、sum.scmなどからの書き込みを待つ。 ;;☆出力がきたらそのままブラウザに返してこのCGIは終了。 (let* ((path (format #f "/tmp/gaup~a" gaup-pid)) (server (make-server-socket 'unix path))) ;;sum.scmなどが書き込めるようにパーミッションをゆるくする。 (sys-chmod path #o777) (cgi-add-temporary-file path) (unless (eq? gaup-cont #f) ;;継続サーバとしての動作を行う。このとき、sum.scmなどは、"/tmp/gaup-3192-1"ソケットにサーバをはって、このCGIからの書き込みを待っている。 (call-with-client-socket (make-client-socket 'unix (format #f "/tmp/gaup~a-~a" gaup-pid gaup-cont)) (lambda (in out) ;;CGIにあたえられたパラメータをそっくりそのままwriteする。 (write params out)))) ;;☆の部分。出力がきたらそのままブラウザに返してこのCGIは終了。 (let* ((client (socket-accept server)) (input (socket-input-port client))) (begin0 (port->string input) (socket-close client))))) (define (main args) ;;インデックス、プログラムファイル名、プログラムの説明、を対にしたリスト。 (define sample-program-list '(("0" "counter.scm" "counter") ("1" "sum.scm" "?+?=?") ("2" "guess.scm" "guess number game"))) ;;どのプログラムを起動するかを選択するページ。 (define (default-page) (html:html (html:body (map (lambda (li) (list (html:h1 (html:a :href (string-append "./cont.cgi?boot=" (car li)) (caddr li))) (html:br))) sample-program-list)))) (cgi-main (lambda (params) (list (cgi-header) (html-doctype) (let ((boot (cgi-get-parameter "boot" params :default #f :convert x->number)) (gaup-pid (cgi-get-parameter "gaup-pid" params :default #f)) (gaup-cont (cgi-get-parameter "gaup-cont" params :default #f))) (cond ;;cont.cgi?boot=1 なら gosh sum.scm -x をforkするといった具合。 ((boolean boot) (run-cont (process-pid (run-process "gosh" (cadr (list-ref sample-program-list boot)) "-x")) #f '())) ;;cont.cgi?gaup-pid=3192&gaup-cont=1 なら継続サーバとしての動作を行う。 ((and (boolean gaup-pid) (boolean gaup-cont)) (run-cont gaup-pid gaup-cont params)) ;;cont.cgiなどなら、デフォルトのページをだす。 (else (default-page)))))))) ;; Local variables: ;; mode: gauche ;; end:
(use text.html-lite) (use www.cgi) (use gaup) (define (main args) (gaup-init args) (let* ((a (cgi-get-parameter "value" (show (html:html (html:body (html:p "? + ? = ?") (html:p "input number") (html:form :method "GET" :action "./cont.cgi" (html:input :type "hidden" :name "gaup-pid" :value gaup-pid) (html:input :type "hidden" :name "gaup-cont" :value gaup-cont) (html:input :type "text" :name "value") (html:input :type "submit"))))) :convert x->number)) (b (cgi-get-parameter "value" (show (html:html (html:body (html:p (format #f "~a + ? = ?" a)) (html:p "input number") (html:form :method "GET" :action "./cont.cgi" (html:input :type "hidden" :name "gaup-pid" :value gaup-pid) (html:input :type "hidden" :name "gaup-cont" :value gaup-cont) (html:input :type "text" :name "value") (html:input :type "submit"))))) :convert x->number))) (show (html:html (html:body (html:p (format #f "~a + ~a = ~a" a b (+ a b))))))))
(use text.html-lite) (use www.cgi) (use gaup) (define (main args) (gaup-init args) (let loop ((counter 0)) (let1 action (cgi-get-parameter "action" (show (html:html (html:body (html:h1 counter) (html:br) (html:a :href (format #f "./cont.cgi?gaup-pid=~a&gaup-cont=~a&action=inc" gaup-pid gaup-cont) "++") " " (html:a :href (format #f "./cont.cgi?gaup-pid=~a&gaup-cont=~a&action=dec" gaup-pid gaup-cont) "--"))))) (cond ((equal? action "inc") (loop (+ counter 1))) ((equal? action "dec") (loop (- counter 1))) (else (loop counter))))))
(use text.html-lite) (use www.cgi) (use gaup) (use math.mt-random) (define answer (let1 m (make <mersenne-twister> :seed (sys-time)) (mt-random-integer m 100))) (define (main args) (gaup-init args) (let loop ((count 1) (guess 0)) (if (= guess answer) (show (html:html (html:head (html:meta :http-equiv "Content-Type" :content "text/html; charset=utf-8")) (html:body (html:p (format #f "~a 回目で正解です! 答えは ~a" (- count 1) answer))))) (let1 req (show (html:html (html:head (html:meta :http-equiv "Content-Type" :content "text/html; charset=utf-8")) (html:body (html:p (format #f "~a 回目の挑戦です。" count)) (html:p (cond ((= count 1) (format #f "数当てゲームです。答えは 0 から 99 の間の数です。さて、いくつ?")) ((> guess answer) (format #f "いいえ。 ~a より小さな数です。さて、いくつ?" guess)) ((< guess answer) (format #f "いいえ。 ~a より大きな数です。さて、いくつ?" guess)))) (html:form :method "GET" :action "./cont.cgi" (html:input :type "hidden" :name "gaup-pid" :value gaup-pid) (html:input :type "hidden" :name "gaup-cont" :value gaup-cont) (html:input :type "text" :name "value") (html:input :type "submit"))))) (loop (+ count 1) (cgi-get-parameter "value" req :convert x->integer))))))rio orange