How to generate a Location header
dispatch-rules, though, the solution is right under your nose. Here's a tutorial where we generate random greetings in various languages, and return a Location header where we indicate how to get a non-random greeting.
Generating URLs with dispatch-rulesDownload the code for this demonstration here (for location.rkt) and here (for respond.rkt).
dispatch-rulestakes as input a description of a family of URLs—a kind of mini-DSL—and returns two values. The first one is the one you're probably thinking of: a servlet that works as a kind of
cond, matching an incoming URL and sending the request to the first matching
subservlet. The second return value is a URL generator. It's this value that does he job we're looking to have done when we're asking for Location URLs. The second return value can be thought of as a kind of inverse of the first value. The first value takes URLs as input and decides which servlet should be used. The second value takes a servlet and its arguments and generates the URL.
/hello, a random language will be selected and we'll receive a plain text response whose body is
Helloin that language. The response will also contain a Location header that shows you how to get this greeting in a non-random way. Thus, if, by chance, we get the Arabic greeting, the response will contain a Location header that says where one can always get the Arabic greeting. When we visit that URL, the greeting is not selected randomly. It's always Arabic.
ImplementationWe'll start from the end—the dispatcher—and explain as we go along the various bits that make it up. Here's where we're headed:
(define-values (dispatcher url-generator) (dispatch-rules [("hello") #:method "get" hello] [("hello" (string-arg)) #:method "get" hello+lang] [("hello") #:method (regexp ".*") method-not-allowed] [else not-found]))
method-not-allowedservlets are not especially interesting; they are fallbacks to deal with weird incoming requests (specifically, unknown URLs and HTTP methods other than GET). Let's get started with the most basic data here. The list of languages is an immutable hash table:
We will later need to have all the languages in a list, and their number:
(define greetings/hash (hash "en" "Hello!" "de" "Hallo!" "es" "¡Hola!" "pt" "Ola!" "jp" "こんにちは" "ar" "مرحب"))
To generate a random greeting, we first select a random language and then use it as a key into the
;; all available languages (define languages (hash-keys greetings/hash)) ;; the number of available language (define num-languages (hash-count languages))
If someone requests, say,
;; -> string? (define (random-language) (list-ref languages (random num-languages)))
/hello/ar, we should generate the Arabic greeting. There's no randomness here, and no Location header. The servlet responsible for this,
hello+lang, looks like this:
(Notice that we check whether the given language code makes sense. If someone requests
;; request? -> response? (define (hello+lang req lang) (define greeting (hash-ref greetings/hash lang #f)) (cond ((string? greeting) (respond #:code 200 #:mime "text/plain;charset=UTF-8" #:body greeting)) (else (not-found))))
/hello/12345, they're going to get a Not Found response.) What about random greetings? That's the interesting bit. In this case, it's not difficult to imagine how this will work: we pick a random language, and emit the greeting in that language. That's fine, but how to we generate the Location header for the response? Here, at long last, we'll use
url-generator, the second return value coming from
Notice the call to
(define (hello req) (define lang (random-language)) (define greeting (hash-ref greetings/hash lang)) (define loc (url-generator hello+lang lang)) (respond #:code 200 #:mime "text/plain;charset=UTF-8" #:headers (list (cons 'Location loc)) #:body greeting))
url-generator. Its arguments are the servlet and the argument—here, the randomly generated language—for that servlet. How does this work? Isn't it weird that a servlet is being used as the argument of the URL generator? Think of it this way: the big dispatch table with which
dispatch-ruleswas initially called is consulted, where we look up which servlet
hello+langcorresponds to; then, we know what the URL should look like, given a language (it's one of the
placeholderarguments). That's how you make the Location URLs for our languages.