[racket] Web server templates

From: Jay McCarthy (jay.mccarthy at gmail.com)
Date: Tue Jun 5 22:43:26 EDT 2012

On Tue, Jun 5, 2012 at 7:50 PM, Sam Tobin-Hochstadt <samth at ccs.neu.edu> wrote:
> On Tue, Jun 5, 2012 at 9:38 PM, Jay McCarthy <jay.mccarthy at gmail.com> wrote:
>> There's no "cache" that could be cleared.
>>
>> The template code is included verbatim in the module that uses
>> include-template _AT COMPILE TIME_; you would need to recompile to get
>> the changes, at the very least.
>
> Jay, how convinced are you that this design is a good idea for the web
> server?  It makes the edit/compiled/debug cycle for web apps in Racket
> significantly longer than in other languages that developers will
> compare Racket with.  Certainly, when I've developed Racket web apps,
> I've wished that I didn't have to keep restarting the web server,
> which I wouldn't in most other web frameworks.
>
> There may be other considerations that I'm not thinking of here, though.

In the case of templates, I see no need for anything else, given that
Racket's composable features of moving compile-time to run-time work
smoothly:

#lang web-server/insta
(require xml
         web-server/templates)

(define i 0)
(define-namespace-anchor here)

(define (start req)
  (set! i (add1 i))
  (response/xexpr
   (make-cdata #f #f
               (eval #'(include-template (file "/tmp/template.html"))
                     (namespace-anchor->namespace here)))))

Similarly, when you use serve/servlet you normally give it a
particular closure that will be executed with the requests, but
there's no reason you couldn't give it:

(lambda (req)
 ((eval '(let () (local-require some-file) start)) req))

And then change both of these evals to something that was sensitive to
filesystem modification... that would be a simple library to make if
you wanted it that would be independent of the Web server.

Compiling templates and closures in is the more flexible choice
because going from a run-time to a compile-time would be much harder
to manage.

In general, however, it's not feasible to not restart the server. The
core problem is that the continuations that are used in the server may
have no meaning with the new code changes. For example, suppose you
have...

(define (start req)
 (define x (get-a-number))
 (define y (get-a-number))
 (+ x y))

and you accumulate a bunch of continuations at each of the two points
(the definition of x and y)

Then, you change the code so that it looks like this:

(define (start req)
 (define local-value #f)
 (define x (get-a-number))
 (set! local-value (* x 2))
 (define y (get-a-number))
 (+ x y local-value))

How are you going to convert a continuation from the old servlet at
the point after x has been defined and you're waiting for y to one for
the new servlet? Surely a magical converter could do the right thing
and figure out what to set local-value to, but we all know that's just
not feasible. Particularly given the underlying Racket infrastructure
for manipulating continuations that have already been captured.

That's not even to mention things where the new code is entirely
unrelated to the old code.

This is why when you use the stateless language, your continuations,
which are not opaque, are stamped with an MD5 of your file so that
they are revoked when you change the code.

If you are okay with allowing the old continuations to continue
existing and continue referring to the old code, then the server
already supports this with /conf/refresh-servlets (in the default
configuration). The old and new code can even communicate if you share
some modules with the servlet-namespace argument.

If you believe that you can convert from one continuation to another,
then you could inspect the continuation and include information about
it when you create URLs and have the "continuation not found or
revoked" handler inspect those and reconstruct the continuation. Web
cells and my URL-param library were designed for that purpose:

http://planet.racket-lang.org/display.ss?package=url-param.plt&owner=jaymccarthy

Overall, I don't see this as part of the "design" of the Web server. I
think it is a natural consequence of (a) the way Racket works
vis-a-vis opaque continuations, (b) caring about performance in the
default case, and (c) Racket's strengths of making features compose so
the Web server doesn't /need/ to do anything if you want more
'dynamic' behavior.

Jay

-- 
Jay McCarthy <jay at cs.byu.edu>
Assistant Professor / Brigham Young University
http://faculty.cs.byu.edu/~jay

"The glory of God is Intelligence" - D&C 93


Posted on the users mailing list.