From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Tobias Gerdin Newsgroups: gmane.lisp.guile.user Subject: The Web, Continuations, and All That Date: Sun, 22 Jan 2012 19:46:15 +0100 Message-ID: NNTP-Posting-Host: lo.gmane.org Mime-Version: 1.0 Content-Type: text/plain; charset=ISO-8859-1 X-Trace: dough.gmane.org 1327264388 31881 80.91.229.12 (22 Jan 2012 20:33:08 GMT) X-Complaints-To: usenet@dough.gmane.org NNTP-Posting-Date: Sun, 22 Jan 2012 20:33:08 +0000 (UTC) To: Guile Users Original-X-From: guile-user-bounces+guile-user=m.gmane.org@gnu.org Sun Jan 22 21:33:04 2012 Return-path: Envelope-to: guile-user@m.gmane.org Original-Received: from lists.gnu.org ([140.186.70.17]) by lo.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1Rp463-0008Ol-W7 for guile-user@m.gmane.org; Sun, 22 Jan 2012 21:33:04 +0100 Original-Received: from localhost ([::1]:39238 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Rp463-0004s3-4U for guile-user@m.gmane.org; Sun, 22 Jan 2012 15:33:03 -0500 Original-Received: from eggs.gnu.org ([140.186.70.92]:52348) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Rp2Qk-0001wD-Gd for guile-user@gnu.org; Sun, 22 Jan 2012 13:46:19 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Rp2Qj-0005JQ-7j for guile-user@gnu.org; Sun, 22 Jan 2012 13:46:18 -0500 Original-Received: from mail-pz0-f41.google.com ([209.85.210.41]:60068) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Rp2Qi-0005JL-Tw for guile-user@gnu.org; Sun, 22 Jan 2012 13:46:17 -0500 Original-Received: by dang27 with SMTP id g27so1481747dan.0 for ; Sun, 22 Jan 2012 10:46:15 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=mime-version:date:message-id:subject:from:to:content-type; bh=YxhK8xv2ha5/uw3Suq6KwTV3CoMFLa/gcZSAP/r6Pvs=; b=OZLmzAe499QUXFwjV5Bh01TGzXr2hTbanzBSJfqvo67eqsAwDmYciF5o2J3KDXSiWH KLXezp5TwGJWG9138msPp2umLoNi6Mav8N5IFwW04epXdccRmDynHq+g1sIXKc68KEUu kXDf1T3Jdj2lsTFyEKDxYVA4Hpq8oDl1qQdsQ= Original-Received: by 10.68.196.232 with SMTP id ip8mr15415372pbc.19.1327257975343; Sun, 22 Jan 2012 10:46:15 -0800 (PST) Original-Received: by 10.142.242.17 with HTTP; Sun, 22 Jan 2012 10:46:15 -0800 (PST) X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 209.85.210.41 X-Mailman-Approved-At: Sun, 22 Jan 2012 15:32:59 -0500 X-BeenThere: guile-user@gnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: General Guile related discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: guile-user-bounces+guile-user=m.gmane.org@gnu.org Original-Sender: guile-user-bounces+guile-user=m.gmane.org@gnu.org Xref: news.gmane.org gmane.lisp.guile.user:9173 Archived-At: Hello, To get better acquainted with continuations I have been playing with them in the context of web programming a bit. Much has been written on the topic (in particular I find the Racket tutorials very instructive), and here I would just like to share the small experiments I did using delimited continuations and the Guile (web server). Kudos to Andy for providing a very nice foundation. The ingredients: (use-modules (web server) (web request) (web response) (web uri) (sxml simple) (ice-9 control)) To begin with, a simple request handler making use of a table to map between request URI paths and other request handlers (also functions as a continuation table): (define dispatch-table (make-hash-table)) (define (handler request body) (let* ((path (split-and-decode-uri-path (uri-path (request-uri request)))) (h (hash-ref dispatch-table (string->symbol (car path))))) (if h (% (h request body)) (values '((content-type . (text/plain))) (string-append "Unknown page: " (car path)))))) So a prompt is installed just before the matching request handler is dispatched to. Next there is Christian Queinnec's `show' operator (see Christian Queinnec's seminal "The Influence of Browsers on Evaluators or, Continuations to Program Web Servers"), although here I've call it `send-suspend', which is what it is called in Racket (actually, they call it `send/suspend'): (define (send-suspend make-response) (abort (lambda (k) (let ((k-uri-id (gensym "k"))) ; Use something more sophisticated here! (hash-set! dispatch-table k-uri-id k) (make-response (format #f "/~a" k-uri-id)))))) So it's a something that when called will save the state of the computation (the continuation `k') in the table and associate it with a generated uri, and then apply that uri to a procedure that returns a response object. The comment is there to draw attention to the fact that security hinges on the generated uri being difficult to predict, or else the user's session is open to hijacking. Additional security measures could be put in place as well. The point of the above is to able to write request handlers spanning several requests in this style: (define (hello-world r b) (send-suspend (lambda (k-url) (values '((content-type . (text/html))) (string-append "hello")))) (values '((content-type . (text/html))) "world!")) To try it the handler needs to be mounted and the web server fired up, pointed at the dispatching handler: (hash-set! dispatch-table 'hello hello-world) (run-server handler) Visiting http://localhost:8080/hello should give a "hello" link yielding a second page saying "world!". Since constructing the response objects in the above way is tedious and verbose I made use of the `respond' helper from the "Web Examples" section in the Guile reference manual: (define (hello-world2 r b) (send-suspend (lambda (k-uri) (respond `((a (@ (href ,k-uri)) "hello"))))) (respond '("world!"))) (hash-set! dispatch-table 'hello2 hello-world) Lexical scope is preserved over requests: (define (count req body) (let loop ((n 0)) (send-suspend (lambda (k-uri) (respond `((a (@ (href ,k-uri)) ,(number->string n)))))) (loop (1+ n)))) (hash-set! dispatch-table 'count count) An common use-case is to return values from pages (such as data provided by the user through forms): ;; Dirty hack to extract a single param from encoded form data (define (get-name form-data) (uri-decode (cadr (string-split form-data #\=)))) (define (get-greet req body) (let ((req (send-suspend (lambda (k-uri) (respond `((form (@ (action ,k-uri) (method "GET)) "Your name: " (input (@ (type text) (name name))) (input (@ (type submit)))))))))) (respond `("Hi " ,(get-name (uri-query (request-uri req))))))) (hash-set! dispatch-table 'greet get-greet) Since the stored continuation is invoked with both the request and the body you access the latter by making use of some multiple-values binding form: (use-modules (srfi srfi-11)) ; let-values (use-modules (rnrs bytevectors)) ; utf8->string (define (post-greet req body) (let-values (((req body) (send-suspend (lambda (k-uri) (respond `((form (@ (action ,k-uri) (method "POST")) "Your name: " (input (@ (type text) (name name))) (input (@ (type submit)))))))))) (respond `("Hi " ,(get-name (utf8->string body)))))) (hash-set! dispatch-table 'greet2 post-greet) Happy hacking. -Tobias