Table of Contents
Introductions to web programming often mention the statelessness of the HTTP protocol, to then describe how cookies and sessions solve the problem. This is not a complete solution however: being "sessionful" is not the same as being "stateful."
Cookies and sessions are mechanisms laid on top of the HTTP protocol respectively to: a) allow an application to recognize two requests coming from the same browser as such, and b) to store data common to such requests.
Neither cookies nor sessions completely address the issue of statelesness because, while programs have the means to associate two requests to a given user (as they contain the same cookie), they have no means by which to associate two requests as being part of the same navigation flow. It is in fact possible to manually place requests out of sync with program state, and often trigger incorrect behavior. Traditional web applications are thus less like a program with a GUI interface and more like an API open to the world. Forced browsing, or even using the back button, can cause an application to misbehave in ways that are not easily foreseeable as application complexity increases.
Aside from state, the Session object is common to all requests from the same browser, so simply using a web application from two windows can produce concurrency problems such as loss of consistency or race conditions. Moreover, since the session is a global scope for the application, in fact the only scope that traditionally persists from request to request, developers are unable to take advantage of the finer scoping mechanisms available in modern programming languages.
One last major issue with traditional web applications is that they are made of short-lived code snippets which, after parsing the request in the context of the session, generate a response and exit. Because their state is lost, it is impossible to use structured programming techniques in code spanning multiple requests. Even the simplest for-loop must be expressed in terms of a (global) session variable increment and a GOTO (HREF).
Continuations afford a truly stateful approach. When a program generates a response, it suspends its execution instead of exiting. This suspended state is a continuation, and it includes stack and variables in scope. When the execution is resumed by the subsequent request, stack and variables are as they were left -- no manual storage in the session is needed.
SISCweb, like other similar frameworks, is able to determine which requests are associated with which continuations by branding each request and its corresponding continuation with a unique, cryptographically strong hash.
Branding reduces the issue of the back button because any request originating from a page in the browser history is associated with a unique continuation. Re-submitting the same request results in the same code being run, with the same local variables and stack it had at the time it was suspended. A program written in functional style (i.e. with no side effects) will be completely immune to the back button.
Saving the local state means that a for-loop can be written at the language level. (The Polyglot Hello World example included with SISCweb demonstrates this feature.) Saving the stack also means that an entire navigation flow can be represented as a subroutine returning a value, rather than as a sequence of code snippets connected by GOTOs (HREFs) finally depositing a value in the global scope.
Most complex applications will require a finer amount of control over state. For instance, a shopping cart should reflect the most recent operations even if the user were to backtrack by a few pages and resume shopping from a point in the navigation history. SRFI-39 or plain session variables can be used in such cases.
In other cases state is more closely dependent to the specific page, such as in the case of sorting a table in the browser. The sorting order should behave consistently in the presence of backtracking, cloning, and concurrent requests.
A possible solution for these cases is state-passing style, where a structure is passed from continuation to continuation and updated non-destructively. This can be somewhat inconvenient though, so instead SISCweb implements Web Cells, a dynamic scoping mechanism that follows the page flow and branches with backtracking and cloning.
Requires: (import srfi-39)
A complete treatment of parameters is available in SRFI-39. This section is concerned with the use of the dynamic environment to maintain state shared by all requests for the same user in the same execution flow.
SRFI-39 parameters are essentially dynamically-scoped
variables. They can be defined in the global or the module scope
and then bound to the dynamic scope -- the scope of the
execution flow -- in the application entry point through the
parameterize
form.
When using send-*/[suspend|forward]
, the
parameters, being part of the dynamic environment, are captured
in the suspended state -- the suspension of the program
execution is transparent to parameter bindings.
However procedures stored with
@href-p
-style attributes or the
forward/store!
procedure will run in the
base dynamic environment, and thus will see fresh values of the
parameters. In those cases on can use
forward/dynenv/store!
, which instead
preserves the dynamic environment.
The Counter with SRFI-39 Parameters example demonstrates this technique.
Requires: (import siscweb/webcells)
Web Cells are a way to track program state according to the navigation flow. They are described in full in the paper Interaction-Safe State for the Web.
In essence, cells establish a dynamic scope over the navigation path of a user. Successive pages can overshadow values of bindings set in previous pages, but do not destroy them.
If a user backtracks the browser window, previous values are again visible. If the user clones the window and proceeds through two different navigation branches, each branch sees the values it overshadows.
The Counter with Webcells example demonstrates this technique.
procedure:
(webcell/make [name] default-value) => <cell>
Creates a cell with the given
default-value
with the specified name. Please note thatwebcell/make
is a macro, soname
is an identifier, not a symbol.If
name
is omitted, a random name is generated. Omitting a specific name is useful to avoid name collisions (since cells are dynamically, not lexically, scoped). However if it is expected that sessions be serialized across context or server restarts, it may be important to guarantee that cells defined in the toplevel or int the module scope carry the same name.
procedure:
(webcell/make-parameter [name] default-value) => cell-parameter-proc
Creates a parameter procedure in the sense of SRFI-39. As in the case of
webcell/make
, thename
parameter is optional, but recommended if maintaining the same name across context or server restarts is important.
procedure:
(webcell/set! cell value) => undefined
Sets the content of the given
cell
to the specifiedvalue
.Of course the specified value is only accessible to the current and successive request.
procedure:
(webcell/ref cell) => value
Returns the value of the specified
cell
in the context of the current request.If the current request did not set a value already, the value will be looked up from the previous request (leading to the current), and then the previous again.
If no value can be found through the history of requests, then the cell's default value is returned.
Requires:
(import siscweb/session)
Located in:
siscweb.jar
In some situations a global, per-user scope may be desirable. This approach is more prone to concurrency problems, but its semantics is very straightforward.
The Session object can be accessed via a syntax similar to that
of SRFI-39
parameters, or via Scheme wrappers around the
Session.get/setAttribute()
calls. While the
former is appropriate for storing Scheme values, the latter is
provided as a means to store and retrieve Java objects (or
java-wrap
ped Scheme values.)
procedure:
(session/make-parameter name) => proc
Returns a procedure
proc
that, when invoked, sets or retrieves a Scheme value to/from the a Session attribute namedname
, depending on whether a value is passed or not to. Ifproc
is passed the #f value, the attribute is removed from the Session object altogether.(module security (define auth-token (session/make-parameter "auth-key")) ;; sets an auth token in the session object (define (login usr pwd) (auth-token (make-token usr pwd))))
procedure:
(session/get-java-attribute name) => jobject
procedure:(session/get-java-attribute-names) => list
procedure:(session/set-java-attribute! name jobject) => #!void
procedure:(session/set-java-attribute! name jobject) => #!void
These functions access Java attributes as session objects. Complete descriptions are given in the section called “Session Procedures”.