{"size": 9854, "ext": "scrbl", "max_stars_repo_path": "web-server-doc/web-server/scribblings/dispatch.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/dispatch.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/dispatch.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": 38.4921875, "max_line_length": 225, "alphanum_fraction": 0.6980921453}
#lang scribble/doc
@(require "web-server.rkt"
          racket/sandbox)
@(require (for-label web-server/servlet
                     web-server/dispatchers/dispatch
                     web-server/servlet-env
                     web-server/dispatch/extend
                     (except-in syntax/parse attribute)
                     racket/match
                     racket/list
                     net/url
                     xml))

@(define dispatch-eval
   (let ([the-eval (make-base-eval)])
     (the-eval '(require web-server/http
                         net/url
                         racket/list 
                         racket/promise
                         web-server/dispatch
                         web-server/dispatch/extend))
     the-eval))

@title[#:tag "dispatch"]{URL-Based Dispatch}

@defmodule[web-server/dispatch]

The library allows the creation of two-way mappings between permanent URLs and request-handling procedures.

@margin-note{This library was inspired by the @racketmodname[(planet untyped/dispatch)] package.}

@section{Using @racketmodname[web-server/dispatch]}

Suppose you are writing a blog application and want pretty URLs for different views of the site.
You would define some URL dispatching rules as follows:

@interaction[#:eval dispatch-eval
 (define-values (blog-dispatch blog-url)
   (dispatch-rules
    [("") list-posts]
    [("posts" (string-arg)) review-post]
    [("archive" (integer-arg) (integer-arg)) review-archive]
    [else list-posts]))
]

And define your request handlers as follows:
@interaction[#:eval dispatch-eval
(define (list-posts req) `(list-posts))
(define (review-post req p) `(review-post ,p))
(define (review-archive req y m) `(review-archive ,y ,m))
]

Now when a request is sent to your application, it will be directed to the appropriate handler:
@interaction[#:eval dispatch-eval
(define (url->request u)
  (make-request #"GET" (string->url u) empty
                (delay empty) #f "1.2.3.4" 80 "4.3.2.1"))
(blog-dispatch 
 (url->request "http://www.chrlsnchrg.com"))
(blog-dispatch 
 (url->request "http://www.chrlsnchrg.com/"))
(blog-dispatch
 (url->request 
  "http://www.chrlsnchrg.com/posts/Extracurricular-Activity"))
(blog-dispatch
 (url->request "http://www.chrlsnchrg.com/archive/1984/10"))
(blog-dispatch
 (url->request "http://www.chrlsnchrg.com/contact"))
]       

You can also generate these pretty URLs from procedure calls:
@interaction[#:eval dispatch-eval
(blog-url list-posts)
(blog-url review-post "Another-Saturday-Night")
(blog-url review-archive 1984 11)
]

After mastering the world of blogging software, you decide to put the ubiquitous Add-Two-Numbers.com out of business with Sum.com:
@interaction[#:eval dispatch-eval
 (define-values (sum-dispatch sum-url)
   (dispatch-rules
    [((integer-arg) ...) sum]
    [else (lambda (req) (sum req empty))]))
 
 (define (sum req is)
   (apply + is))
 
 (sum-dispatch (url->request "http://www.sum.com/"))
 (sum-dispatch (url->request "http://www.sum.com/2"))
 (sum-dispatch (url->request "http://www.sum.com/2/3/4"))
 (sum-dispatch (url->request "http://www.sum.com/5/10/15/20"))
 
 (sum-url sum empty)
 (sum-url sum (list 1))
 (sum-url sum (list 2 3 5 7))
]

When you use @racketmodname[web-server/dispatch] with
@racket[serve/servlet], you almost always want to use the
@racket[#:servlet-regexp] argument with the value @racket[""] to
capture all top-level requests. However, make sure you don't include
an @racket[else] in your rules if you are also serving static files,
or else the filesystem server will never see the requests.

@section{API Reference}

@defform/subs[#:literals (else)
 (dispatch-rules
  dispatch-clause ...
  maybe-else-clause)
([dispatch-clause
  [dispatch-pattern maybe-method dispatch-fun]]
 [dispatch-pattern
  ()
  (string . dispatch-pattern)
  (bidi-match-expander ... . dispatch-pattern)
  (bidi-match-expander . dispatch-pattern)]
 [maybe-method
  code:blank
  (code:line #:method method)]
 [method pat]
 [maybe-else-clause
  code:blank
  [else else-fun]])
#:contracts
([else-fun (request? . -> . any)]
 [dispatch-fun (request? any/c ... . -> . any)])]{

 Returns two values: the first is a dispatching function with the
contract @racket[(request? . -> . any)] that calls the appropriate
@racket[dispatch-fun] based on the first @racket[dispatch-pattern]
that matches the request's URL (and method), the second is a URL-generating
function with the contract @racket[(procedure? any/c ... . ->
. string?)] that generates a URL using @racket[dispatch-pattern] for
the @racket[dispatch-fun] given as its first argument.

 If @racket[else-fun] is left out, one is provided that calls
@racket[(next-dispatcher)] to signal to the Web Server that this
dispatcher does not apply.

 The @racket[_method] syntax is used in a @racket[match] expression to
match the @racket[request-method] part of the incoming request
object. However, since HTTP allows methods to use any case, the byte
string from @racket[request-method] is normalized to a lower-case
string. Thus, valid patterns are things like: @racket["get"],
@racket["post"], @racket["head"], @racket[(or "get" "post")], etc.

 If @racket[_method] is left out, it assumed to apply to requests
without methods and GET methods.
}

@defform[
 (dispatch-rules+applies
  dispatch-clause ...
  maybe-else-clause)]{
 Like @racket[dispatch-rules], except returns a third value with the contract @racket[(request? . -> . boolean?)] that returns
      @racket[#t] if the dispatching rules apply to the request and @racket[#f] otherwise.
      }

@defform[
 (dispatch-case
  dispatch-clause ...
  maybe-else-clause)]{
 Returns a dispatching function as described by @racket[dispatch-rules].
}

@defform[#:literals (else)
         (dispatch-url
          [dispatch-pattern dispatch-fun]
          ...)
         #:contracts
         ([dispatch-fun (request? any/c ... . -> . any)])]{
 Returns a URL-generating function as described by @racket[dispatch-rules].
}

@defproc[(serve/dispatch [dispatch (request? . -> . can-be-response?)])
         void]{
 Calls @racket[serve/servlet] with a @racket[#:servlet-regexp] argument (@racket[#rx""]) so that every request is handled by @racket[dispatch].
}
              
@section{Imperative Dispatch Containers}

@racket[dispatch-rules] is purely functional. This presents a more declarative interface, but inhibits some programming and modularity patterns. @deftech{Containers} provide an imperative overlay atop @racket[dispatch-rules].

@defproc[(container? [x any/c]) boolean?]{ Identifies @tech{containers}. }

@defform[(define-container container-id (dispatch-id url-id))]{
 Defines @racket[container-id] as a container as well as @racket[dispatch-id] as its dispatching function and @racket[url-id] as its URL lookup function.}

@defform[(dispatch-rules! container-expr [dispatch-pattern dispatch-fun] ...)]{
 Like @racket[dispatch-rules], but imperatively adds the patterns to the container specified by @racket[container-expr]. The new rules are consulted @emph{before} any rules already in the container. }
              
@section{Built-in URL patterns}

@racketmodname[web-server/dispatch] builds in a few useful URL component patterns.

@defform[(number-arg)]{
 A @tech{bi-directional match expander} that parses a @racket[number?] from the URL and generates a URL with a number's encoding as a string.
}

@defform[(integer-arg)]{
 A @tech{bi-directional match expander} that parses a @racket[integer?] from the URL and generates a URL with a integer's encoding as a string.
}

@defform[(real-arg)]{
 A @tech{bi-directional match expander} that parses a @racket[real?] from the URL and generates a URL with a real's encoding as a string.
}

@defform[(string-arg)]{
 A @tech{bi-directional match expander} that parses a @racket[string?] from the URL and generates a URL containing the string.
}

@defform[(symbol-arg)]{
 A @tech{bi-directional match expander} that parses a @racket[symbol?] from the URL and generates a URL with a symbol's encoding as a string.
}

@section{Extending @racketmodname[web-server/dispatch]}

@defmodule[web-server/dispatch/extend]

You can create new URL component patterns by defining @tech{bi-directional match expanders}.

@defform[(define-bidi-match-expander id in-xform out-xform)]{
 Binds @racket[id] to a @deftech{bi-directional match expander}
 where @racket[in-xform] is a match expander (defined by @racket[define-match-expander]) that is used when parsing URLs
 and @racket[out-xform] is one used when generating URLs.
 
 Both @racket[in-xform] and @racket[out-xform] should use the syntax @racket[(_xform arg ... _id)] where the @racket[arg]s are 
 specific to @racket[id] and compatible with both @racket[in-xform] and @racket[out-xform]. @racket[_id] will typically be provided
 automatically by @racket[dispatch-rules].
}

@defidform[bidi-match-going-in?]{
 A @tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{syntax parameter} used by @tech{bi-directional match expanders} to determine if a URL is being parsed or generated.
}

When defining new patterns, you may find it useful to use these helper functions:

@defform[(define-coercion-match-expander id test? coerce)]{
 Binds @racket[id] to a match expander that expands @racket[(id _x)] to
 @racket[(? test? (app coerce _x))] (i.e., uses @racket[test?] to determine if the pattern matches and @racket[coerce] to transform the binding.)
}
 
@defproc[(make-coerce-safe? [coerce (any/c . -> . any/c)])
         (any/c . -> . boolean?)]{
 Returns a function that returns @racket[#t] if @racket[coerce] would not throw an exception or return @racket[#f] on its input.
         
 @examples[#:eval dispatch-eval
  (define string->number? (make-coerce-safe? string->number))
  (string->number? "1")
  (string->number? "1.2")
  (string->number? "+inf.0")
  (string->number? "one")
 ]
}
