3 Stateless Servlets
A stateless servlet should provide the following exports:
This indicates that the servlet is a stateless servlet.
This is the stuffer that will be used for the servlet.
If it is not provided, it defaults to default-stuffer.
This is the manager that will be used for the servlet.
If it is not provided, it defaults to (create-none-manager #f).
This function is called when an instance of this servlet is started.
The argument is the HTTP request that initiated the instance.
An example 'stateless servlet module:
These servlets have an extensive API available to them: 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 Serializable Continuations
The main purpose of the stateless language is to provide serializable continuations to your servlet.
Captures the current continuation in a serializable way and calls response-generator with it, returning the result.
native->serial informs the serializing runtime that
expr marks first expression after returning from an untransformed context.
This captures the
untransformed context such that
call-with-serializable-current-continuation can store it on the server and reference it from serializable
continuations.
For example,
will fail at runtime because
build-list is not transformed. However,
will succeed and
k will reference a cell in the current servlet’s
manager that stores the part of the continuation in
build-list.
3.3 Native Interfaces
It is sometimes inconvenient to use
serial->native and
native->serial throughout your program.
This module provides a macro for creating wrappers.
Builds an interface around
original named
native such that calls to
native are wrapped in
serial->native
and all arguments marked with
ho in
arg-spec are assumed to procedures and are wrapped in
native->serial.
3.4 Stateless Web Interaction
Captures the current continuation. Serializes it and stuffs it into
a URL. Calls response-generator with this URL and delivers
the response to the client. If the URL is invoked
the request is returned to this continuation.
Captures the current continuation. Serializes it and stuffs it into a hidden INPUT
form element.
Calls response-generator with this URL and form field and delivers
the response to the client. If the URL is invoked with form data containing
the hidden form,
the request is returned to this continuation.
Calls
make-response with a function that, when called with a procedure from
request? to
any/c will generate a URL, that when invoked will call
the function with the
request? object and return the result to the caller of
send/suspend/dispatch.
3.5 Stateless Web Cells
3.6 File Boxes
As mentioned earlier, it is dangerous to rely on the store in
Web Language servlets, due to the deployment scenarios available
to them. This module provides a simple API to replace
boxes in a safe way.
Checks if v is a file-box.
Creates a file-box that is stored at p, with the default
contents of v.
Returns the value inside fb
Returns #t if fb contains a value.
Saves v in the file represented by fb.
Warning: If you plan on using a load-balancer, make sure your file-boxes
are on a shared medium.
3.7 Stateless Web Parameters
It is not easy to use
parameterize in the
Web Language. This module provides (roughly) the same
functionality in a way that is serializable. Like other serializable
things in the Web Language, they are sensitive to source code modification.
Expands to the definition of a web-parameter with
default as the default value. A web-parameter is
a procedure that, when called with zero arguments, returns
default
or the last value
web-parameterized in the dynamic context
of the call.
Checks if v appears to be a web-parameter.
Runs
(begin expr ...) such that the web-parameters that
the
web-parameter-exprs evaluate to are bound to the
value-exprs.
From the perspective of the
value-exprs, this is like
let.
3.8 Soft State
Sometimes you want to reference a large data-structure from a stateless program without the data-structure being serialized
and increasing the size of the serialization. This module provides support for this scenario.
Determines if v is a soft state record.
Creates a piece of soft state that is computed by thnk. This value is serializable.
Extracts the value associated with ss. If the value is not available (perhaps because of garbage collection, deserialization in an uninitialized process, etc), then the thunk associated with ss is invoked and the value is cached.
Here’s an example servlet that uses soft state:
When this is run and the link is clicked a few times, the output is:
$ plt-web-server -p 8080 |
Doing a long computation... |
Done |
Done |
Done |
Done |
If the server is restarted or the hostname in the URL is changed to a different host with the same code, and the URL is clicked:
^Cuser break |
$ plt-web-server -p 8080 |
Doing a long computation... |
Done |
3.9 Stuffers
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.9.1 Basic Combinators
A
stuffer is essentially an invertible function captured in this structure.
The following should hold:
(out (in x)) = x |
(in (out x)) = x |
Constructs a contract for a
stuffer where
in has
the contract
(-> dom rng) and
out has the contract
(-> rng dom).
Composes f and g, i.e., applies f then g for in
and g then f for out.
Creates a
stuffer that stuffs with
f if
c is true on the input
to
in. Similarly, applies
f during
out if it was applied during
in (which is recorded by prepending a byte.)
3.9.2 Serialization
3.9.3 Base64 Encoding
Useful for getting URL-safe bytes.
3.9.4 GZip Compression
Warning: You should compose this with
base64-stuffer to get URL-safe bytes.
3.9.5 Key/Value Storage
The web-server/stuffers/hash stuffers rely on a key/value store.
The following should hold:
A store that stores key key’s value in a file located at
It should be easy to use this interface to create store for databases like SQLite, CouchDB, or BerkeleyDB.
3.9.6 Hash-addressed Storage
A content-addressed storage
stuffer that stores input bytes,
input, in
store with the key
(H input)
and returns the key. Similarly, on
out the original bytes are looked up.
3.9.7 HMAC-SHA1 Signing
Performs a HMAC-SHA1 calculation on
db using
kb as the key. The result is guaranteed to be 20 bytes.
(You could curry this to use it with
hash-stuffer, but there is little value in doing so over
md5.)
A
stuffer that signs input using
HMAC-SHA1 with
kb as the key. The result of the
stuffer is
the hash prepended to the input data. When the
stuffer is run in reverse, it checks if the first 20 bytes are the correct
has for the rest of the data.
Warning: You should compose this with
base64-stuffer to get URL-safe bytes.
Warning: Without explicit provision, it is possible for users to modify the continuations they are sent through the other
stuffers.
This
stuffer allows the servlet to certify that stuffed data was truly generated by the servlet. Therefore, you
should use this
if you are not using the
hash-stuffers.
Warning: This
stuffer does
not encrypt the data in anyway, so users can still observe the stuffed values.
3.9.8 Helpers
Constructs a
stuffer that serializes, then if the URL is too big, compresses (and base64-encodes), if the URL is still too big
then it stores it in an MD5-indexed database rooted at
root.
3.10 Usage Considerations
A stateless servlet has the following process performed on it automatically:
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.
Thus, 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 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 a bug! 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.
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 – to be serializable – must not
be in the context of another module. For example, the following will fail with an "unsafe context"
exception:
because
map is not transformed by the process. However, if you defined
your own
map function, there would be no problem. Another solution is to
store the
map part of the continuation on the server with
serial->native
and
native->serial:
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 ICFP papers
Continuations from Generalized Stack Inspection by Pettyjohn et al. in 2005 and
Automatically RESTful Web Applications, Or Marking Modular Serializable Continuations by Jay McCarthy in 2009.
We thank Greg Pettyjohn for his initial implementation of this algorithm.