
Started as fixing misindented definitions, then more indentations, then a bunch of similar things (square brackets, huge spaces at end-of-lines, etc).
253 lines
7.1 KiB
Racket
253 lines
7.1 KiB
Racket
#lang scribble/doc
|
|
@(require "web-server.rkt")
|
|
|
|
@(require
|
|
(for-label
|
|
web-server/stuffers/stuffer
|
|
web-server/stuffers/base64
|
|
web-server/stuffers/gzip
|
|
web-server/stuffers/hash
|
|
web-server/stuffers/serialize
|
|
web-server/stuffers/store
|
|
web-server/stuffers/hmac-sha1
|
|
(only-in web-server/lang/stuff-url
|
|
default-stuffer
|
|
make-default-stuffer
|
|
is-url-too-big?)))
|
|
|
|
@title[#:tag "stuffers"]{Stuffers}
|
|
|
|
@defmodule[web-server/stuffers]
|
|
|
|
The @racketmodname[web-server] language provides serializable continuations.
|
|
The serialization functionality is abstracted into @deftech{stuffers} that control how it operates.
|
|
You can supply your own (built with these functions) when you write a stateless servlet.
|
|
|
|
@section{Basic Combinators}
|
|
|
|
@defmodule[web-server/stuffers/stuffer]{
|
|
|
|
@defstruct[stuffer ([in (any/c . -> . any/c)]
|
|
[out (any/c . -> . any/c)])]{
|
|
|
|
A @tech{stuffer} is essentially an invertible function captured in this structure.
|
|
The following should hold:
|
|
@racketblock[
|
|
(out (in x)) = x
|
|
(in (out x)) = x
|
|
]
|
|
}
|
|
|
|
@defproc[(stuffer/c [dom any/c] [rng any/c])
|
|
contract?]{
|
|
Constructs a contract for a @tech{stuffer} where @racket[in] has
|
|
the contract @racket[(dom . -> . rng)] and @racket[out] has the contract
|
|
@racket[(rng . -> . dom)].
|
|
}
|
|
|
|
@defthing[id-stuffer (stuffer/c any/c any/c)]{
|
|
The identitiy @tech{stuffer}.
|
|
}
|
|
|
|
@defproc[(stuffer-compose [g (stuffer/c any/c any/c)]
|
|
[f (stuffer/c any/c any/c)])
|
|
(stuffer/c any/c any/c)]{
|
|
Composes @racket[f] and @racket[g], i.e., applies @racket[f] then
|
|
@racket[g] for @racket[in] and @racket[g] then @racket[f] for
|
|
@racket[out].
|
|
}
|
|
|
|
@defproc[(stuffer-sequence [f (stuffer/c any/c any/c)]
|
|
[g (stuffer/c any/c any/c)])
|
|
(stuffer/c any/c any/c)]{
|
|
@racket[stuffer-compose] with arguments swapped.
|
|
}
|
|
|
|
@defproc[(stuffer-if [c (bytes? . -> . boolean?)]
|
|
[f (stuffer/c bytes? bytes?)])
|
|
(stuffer/c bytes? bytes?)]{
|
|
Creates a @tech{stuffer} that stuffs with @racket[f] if @racket[c] is
|
|
true on the input to @racket[in]. Similarly, applies @racket[f] during
|
|
@racket[out] if it was applied during @racket[in] (which is recorded by
|
|
prepending a byte.)
|
|
}
|
|
|
|
@defproc[(stuffer-chain [x (or/c stuffer? (bytes? . -> . boolean?))]
|
|
...)
|
|
stuffer?]{
|
|
Applies @racket[stuffer-sequence] and @racket[stuffer-if] to successive tails of @racket[x].
|
|
}
|
|
|
|
}
|
|
|
|
@section{Serialization}
|
|
|
|
@(require (for-label racket/serialize
|
|
web-server/private/util))
|
|
@defmodule[web-server/stuffers/serialize]{
|
|
|
|
@defthing[serialize-stuffer (stuffer/c serializable? bytes?)]{
|
|
A @tech{stuffer} that uses @racket[serialize] and @racket[write/bytes]
|
|
and @racket[deserialize] and @racket[read/bytes].
|
|
}
|
|
|
|
}
|
|
|
|
@section{Base64 Encoding}
|
|
|
|
@(require (for-label net/base64))
|
|
@defmodule[web-server/stuffers/base64]{
|
|
|
|
@defthing[base64-stuffer (stuffer/c bytes? bytes?)]{
|
|
A @tech{stuffer} that uses @racket[base64-encode] and @racket[base64-decode].
|
|
|
|
Useful for getting URL-safe bytes.
|
|
}
|
|
|
|
}
|
|
|
|
@section{GZip Compression}
|
|
|
|
@(require (for-label file/gzip file/gunzip web-server/private/gzip))
|
|
@defmodule[web-server/stuffers/gzip]{
|
|
|
|
@defthing[gzip-stuffer (stuffer/c bytes? bytes?)]{
|
|
A @tech{stuffer} that uses @racket[gzip/bytes] and @racket[gunzip/bytes].
|
|
|
|
@warning{You should compose this with @racket[base64-stuffer] to get
|
|
URL-safe bytes.}
|
|
}
|
|
|
|
}
|
|
|
|
@section{Key/Value Storage}
|
|
|
|
The @racketmodname[web-server/stuffers/hash] @tech{stuffers} rely on a
|
|
key/value store.
|
|
|
|
@defmodule[web-server/stuffers/store]{
|
|
|
|
@defstruct[store ([write (bytes? bytes? . -> . void)]
|
|
[read (bytes? . -> . bytes?)])]{
|
|
The following should hold:
|
|
@racketblock[
|
|
(begin (write k v) (read k)) = v
|
|
]
|
|
}
|
|
|
|
@defproc[(dir-store [root path-string?])
|
|
store?]{
|
|
A store that stores key @racket[key]'s value in a file located at
|
|
@racketblock[
|
|
(build-path
|
|
root
|
|
(bytes->string/utf-8 key))
|
|
]
|
|
}
|
|
|
|
It should be easy to use this interface to create store for databases like SQLite, CouchDB, or BerkeleyDB.
|
|
}
|
|
|
|
@section{Hash-addressed Storage}
|
|
|
|
@(require (for-label file/md5))
|
|
@defmodule[web-server/stuffers/hash]{
|
|
|
|
@defthing[hash-fun/c contract?]{
|
|
Equivalent to @racket[(bytes? . -> . bytes?)].
|
|
}
|
|
|
|
@defproc[(hash-stuffer [H hash-fun/c]
|
|
[store store?])
|
|
(stuffer/c bytes? bytes?)]{
|
|
A content-addressed storage @tech{stuffer} that stores input bytes,
|
|
@racket[input], in @racket[store] with the key @racket[(H input)] and
|
|
returns the key. Similarly, on @racket[out] the original bytes are
|
|
looked up.
|
|
}
|
|
|
|
@defproc[(md5-stuffer [root path-string?])
|
|
(stuffer/c bytes? bytes?)]{
|
|
Equivalent to @racket[(hash-stuffer md5 (dir-store root))]
|
|
}
|
|
|
|
}
|
|
|
|
@section{HMAC-SHA1 Signing}
|
|
|
|
@defmodule[web-server/stuffers/hmac-sha1]{
|
|
|
|
@defproc[(HMAC-SHA1 [kb bytes?] [db bytes?])
|
|
bytes?]{
|
|
Performs a HMAC-SHA1 calculation on @racket[db] using @racket[kb] as
|
|
the key. The result is guaranteed to be 20 bytes. (You could curry
|
|
this to use it with @racket[hash-stuffer], but there is little value
|
|
in doing so over @racket[md5].)
|
|
}
|
|
|
|
@defproc[(HMAC-SHA1-stuffer [kb bytes?])
|
|
(stuffer/c bytes? bytes?)]{
|
|
A @tech{stuffer} that signs input using @racket[HMAC-SHA1] with
|
|
@racket[kb] as the key. The result of the @tech{stuffer} is the hash
|
|
prepended to the input data. When the @tech{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 @racket[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
|
|
@tech{stuffers}. This @tech{stuffer} allows the servlet to certify
|
|
that stuffed data was truly generated by the servlet. Therefore, you
|
|
@bold{should} use this if you are not using the
|
|
@racket[hash-stuffer]s.}
|
|
|
|
@warning{This @tech{stuffer} does @bold{not} encrypt the data in
|
|
anyway, so users can still observe the stuffed values.}
|
|
}
|
|
|
|
}
|
|
|
|
@section{Helpers}
|
|
|
|
@defmodule[web-server/lang/stuff-url]{
|
|
|
|
@defproc[(is-url-too-big? [v bytes?])
|
|
boolean?]{
|
|
Determines if stuffing @racket[v] into the current servlet's URL would
|
|
result in a URL that is too big for Internet Explorer.
|
|
(@link["http://www.boutell.com/newfaq/misc/urllength.html"]{IE only
|
|
supports URLs up to 2048 characters.})
|
|
}
|
|
|
|
@defproc[(make-default-stuffer [root path-string?])
|
|
(stuffer/c serializable? bytes?)]{
|
|
Constructs a @tech{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 @racket[root].
|
|
|
|
Equivalent to:
|
|
@racketblock[
|
|
(stuffer-chain
|
|
serialize-stuffer
|
|
is-url-too-big?
|
|
(stuffer-chain
|
|
gzip-stuffer
|
|
base64-stuffer)
|
|
is-url-too-big?
|
|
(md5-stuffer root))
|
|
]
|
|
}
|
|
|
|
@defthing[default-stuffer (stuffer/c serializable? bytes?)]{
|
|
Equivalent to:
|
|
@racketblock[
|
|
(make-default-stuffer
|
|
(build-path
|
|
(find-system-path 'home-dir)
|
|
".urls"))]
|
|
}
|
|
|
|
}
|