{"size": 7253, "ext": "scrbl", "max_stars_repo_path": "web-server-doc/web-server/scribblings/stuffers.scrbl", "max_stars_repo_name": "jessealama/web-server", "max_stars_repo_stars_event_min_datetime": "2015-01-21T08:11:58.000Z", "max_stars_repo_stars_event_max_datetime": "2022-03-20T08:28:39.000Z", "max_issues_repo_path": "web-server-doc/web-server/scribblings/stuffers.scrbl", "max_issues_repo_name": "jessealama/web-server", "max_issues_repo_issues_event_min_datetime": "2015-01-14T08:06:37.000Z", "max_issues_repo_issues_event_max_datetime": "2022-02-12T14:38:40.000Z", "max_forks_repo_path": "web-server-doc/web-server/scribblings/stuffers.scrbl", "max_forks_repo_name": "jessealama/web-server", "max_forks_repo_forks_event_min_datetime": "2015-03-05T15:07:36.000Z", "max_forks_repo_forks_event_max_datetime": "2021-07-30T18:59:25.000Z"}
{"max_stars_count": 82, "max_issues_count": 105, "max_forks_count": 37, "avg_line_length": 28.6679841897, "max_line_length": 107, "alphanum_fraction": 0.6707569282}
#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"))]
}

}
