3 Stateless Servlets
#lang web-server/base | package: web-server-lib |
#lang web-server |
A stateless servlet should provide the following exports:
value
interface-version : (one-of/c 'stateless)
value
If it is not provided, it defaults to default-stuffer.
If it is not provided, it defaults to (create-none-manager #f).
#lang web-server (require web-server/http) (provide interface-version stuffer start) (define interface-version 'stateless) (define stuffer (stuffer-chain serialize-stuffer (md5-stuffer (build-path (find-system-path 'home-dir) ".urls")))) (define (start req) (response/xexpr `(html (body (h2 "Look ma, no state!")))))
The web-server/base language exports all of the functions and syntax from racket/base and nothing else.
The web-server language exports all of the functions and syntax from the following libraries: racket, net/url, web-server/http, web-server/http/bindings, web-server/lang/abort-resume, web-server/lang/web, web-server/lang/native, web-server/lang/web-param, web-server/lang/web-cells, web-server/lang/file-box, web-server/lang/soft, web-server/dispatch, and web-server/stuffers. Some of these are documented in the subsections that follow.
3.2 Usage Considerations
All uses of letrec are removed and replaced with equivalent uses of let and imperative features.
The program is converted into ANF (Administrative Normal Form), making all continuations explicit.
All continuations and continuations marks are recorded in the continuation marks of the expression they are the continuation of.
All calls to external modules are identified and marked.
All uses of call/cc are removed and replaced with equivalent gathering of the continuations through the continuation marks installed earlier.
The program is defunctionalized with a serializable data-structure for each lambda.
This process allows the continuations captured by your servlet to be serialized. This means they may be stored on the client’s browser or the server’s disk.
This means your servlet has no cost to the server other than execution. This is very attractive if you’ve used Racket servlets and had memory problems.
This means your server can restart in the middle of a long running Web interaction without the URLs that have been shared with the client expiring. This is very attractive if you’ve used Racket servlets and had session timeout problems.
This process is defined on all of Racket and occurs after macro-expansion, so you are free to use all interesting features of Racket. However, there are some considerations you must make.
First, this process drastically changes the structure of your program. It will create an immense number of lambdas and structures your program did not normally contain. The performance implication of this has not been studied with Racket.
Second, the defunctionalization process is sensitive to the syntactic structure of your program. Therefore, if you change your program in a trivial way, for example, changing a constant, then all serialized continuations will be obsolete and will error when deserialization is attempted. This is a feature, not an error! It is a small price to pay for protection from the sorts of errors that would occur if your program were changed in a meaningful way. If you use the default-stuffer or web-server/stuffers/hash, then whenever you change your servlet’s code, you can safely delete all saved continuations, because they won’t be used any longer.
Third, the values in the lexical scope of your continuations must be serializable for the continuations itself to be serializable. This means that you must use define-serializable-struct rather than define-struct, and take care to use modules that do the same. Similarly, you may not use parameterize, because parameterizations are not serializable.
Fourth, and related, this process only runs on your code, not on the
code you require. Thus, your continuations—
(define requests (map (lambda (rg) (send/suspend/url rg)) response-generators))
(define requests (serial->native (map (lambda (rg) (native->serial (send/suspend/url rg))) response-generators)))
Fifth, the store is not serialized. If you rely on the store you will be taking huge risks. You will be assuming that the serialized continuation is invoked on the same server before the server is restarted or the memory is garbage collected.
This process is derived from the papers Continuations from Generalized Stack Inspection by Pettyjohn et al. in 2005, Automatically RESTful Web Applications, Or Marking Modular Serializable Continuations by Jay McCarthy in 2009, and The Two-State Solution: Native and Serializable Continuations Accord by Jay McCarthy in 2010, We thank Greg Pettyjohn for his initial implementation of this algorithm.
3.3 Serializable Continuations
(require web-server/lang/abort-resume) | |
package: web-server-lib |
procedure
(call-with-serializable-current-continuation response-generator)
→ any response-generator : (continuation? . -> . any)
syntax
(serial->native expr)
syntax
(native->serial expr)
(build-list 3 (lambda (i) (call-with-serializable-current-continuation (lambda (k) (serialize k)))))
(serial->native (build-list 3 (lambda (i) (native->serial (call-with-serializable-current-continuation (lambda (k) (serialize k)))))))
3.4 Native Interfaces
(require web-server/lang/native) | |
package: web-server-lib |
syntax
(define-native (native arg-spec ...) original)
arg-spec : ho
arg-spec : _
(define-native (build-list/native _ ho) build-list)
(define (build-list/native fst snd) (serial->native (build-list fst (lambda args (native->serial (apply snd args))))))
3.5 Stateless Web Interaction
(require web-server/lang/web) | package: web-server-lib |
procedure
(send/suspend/url response-generator) → request?
response-generator : (url? . -> . response?)
procedure
(send/suspend response-generator) → request?
response-generator : (string? . -> . response?)
procedure
(send/suspend/hidden response-generator) → request?
response-generator : (url? xexpr/c . -> . response?)
procedure
(send/suspend/url/dispatch make-response) → any
make-response : (((request? . -> . any) . -> . url?) . -> . response?)
procedure
(send/suspend/dispatch make-response) → any
make-response : (((request? . -> . any) . -> . string?) . -> . response?)
procedure
(redirect/get) → request?
3.6 Stateless Web Cells
(require web-server/lang/web-cells) | |
package: web-server-lib |
procedure
v : any/c
syntax
(make-web-cell default-expr)
procedure
(web-cell-ref wc) → any/c
wc : web-cell?
procedure
(web-cell-shadow wc v) → void
wc : web-cell? v : any/c
3.7 File Boxes
(require web-server/lang/file-box) | |
package: web-server-lib |
procedure
p : path-string? v : serializable?
procedure
(file-unbox fb) → serializable?
fb : file-box?
procedure
(file-box-set? fb) → boolean?
fb : file-box?
procedure
(file-box-set! fb v) → void
fb : file-box? v : serializable?
3.8 Stateless Web Parameters
(require web-server/lang/web-param) | |
package: web-server-lib |
syntax
(make-web-parameter default)
procedure
(web-parameter? v) → boolean?
v : any/c
syntax
(web-parameterize ([web-parameter-expr value-expr] ...) expr ...)
3.9 Soft State
(require web-server/lang/soft) | package: web-server-lib |
procedure
(soft-state? v) → boolean?
v : any/c
procedure
(make-soft-state thnk) → soft-state?
thnk : (-> any/c)
procedure
(soft-state-ref ss) → any/c
ss : soft-state?
syntax
(soft-state expr ...)
#lang web-server (provide interface-version start) (define interface-version 'stateless) (define softie (soft-state (printf "Doing a long computation...\n") (sleep 1))) (define (start req) (soft-state-ref softie) (printf "Done\n") (start (send/suspend (lambda (k-url) (response/xexpr `(html (body (a ([href ,k-url]) "Done"))))))))
$ plt-web-server -p 8080 |
Doing a long computation... |
Done |
Done |
Done |
Done |
^Cuser break |
$ plt-web-server -p 8080 |
Doing a long computation... |
Done |
3.10 Stuffers
(require web-server/stuffers) | package: web-server-lib |
The web-server language provides serializable continuations. The serialization functionality is abstracted into stuffers that control how it operates. You can supply your own (built with these functions) when you write a stateless servlet.
3.10.1 Basic Combinators
(require web-server/stuffers/stuffer) | |
package: web-server-lib |
struct
(struct stuffer (in out) #:extra-constructor-name make-stuffer) in : (any/c . -> . any/c) out : (any/c . -> . any/c)
value
procedure
(stuffer-compose g f) → (stuffer/c any/c any/c)
g : (stuffer/c any/c any/c) f : (stuffer/c any/c any/c)
procedure
(stuffer-sequence f g) → (stuffer/c any/c any/c)
f : (stuffer/c any/c any/c) g : (stuffer/c any/c any/c)
procedure
(stuffer-if c f) → (stuffer/c bytes? bytes?)
c : (bytes? . -> . boolean?) f : (stuffer/c bytes? bytes?)
3.10.2 Serialization
(require web-server/stuffers/serialize) | |
package: web-server-lib |
value
3.10.3 Base64 Encoding
(require web-server/stuffers/base64) | |
package: web-server-lib |
value
3.10.4 GZip Compression
(require web-server/stuffers/gzip) | |
package: web-server-lib |
value
3.10.5 Key/Value Storage
The web-server/stuffers/hash stuffers rely on a key/value store.
(require web-server/stuffers/store) | |
package: web-server-lib |
struct
(struct store (write read) #:extra-constructor-name make-store) write : (bytes? bytes? . -> . void) read : (bytes? . -> . bytes?)
procedure
root : path-string?
(build-path root (bytes->string/utf-8 key))
3.10.6 Hash-addressed Storage
(require web-server/stuffers/hash) | |
package: web-server-lib |
value
procedure
(hash-stuffer H store) → (stuffer/c bytes? bytes?)
H : hash-fun/c store : store?
procedure
(md5-stuffer root) → (stuffer/c bytes? bytes?)
root : path-string?
3.10.7 HMAC-SHA1 Signing
(require web-server/stuffers/hmac-sha1) | |
package: web-server-lib |
procedure
(HMAC-SHA1-stuffer kb) → (stuffer/c bytes? bytes?)
kb : bytes?
3.10.8 Helpers
(require web-server/lang/stuff-url) | |
package: web-server-lib |
procedure
(is-url-too-big? v) → boolean?
v : bytes?
procedure
(make-default-stuffer root) → (stuffer/c serializable? bytes?)
root : path-string?