Home

Racket Web Applications Cheat Sheet

Setting Up a Racket Web Server

Basic Server Configuration

 1(require (prefix-in servlet: web-server/servlet-env))
 2
 3(servlet:serve/servlet
 4  dispatcher-function
 5  #:listen-ip "0.0.0.0"
 6  #:port 8080
 7  #:launch-browser? #f
 8  #:quit? #f
 9  #:servlet-path "/"
10  #:servlet-regexp #rx""
11  #:stateless? #t)

Pattern: Use #:stateless? #t for REST APIs that don’t need session continuations.

Main Module Entry Point

 1(module+ main
 2  (servlet:serve/servlet
 3    route-dispatch/with-middleware
 4    #:listen-ip "0.0.0.0"
 5    #:port 8080
 6    #:launch-browser? #f
 7    #:quit? #f
 8    #:servlet-path "/"
 9    #:servlet-regexp #rx""
10    #:stateless? #t))

URL Routing and Dispatching

Dispatch Rules

1(require (prefix-in dispatch: web-server/dispatch))
2
3(define-values (route-dispatch request-parser)
4  (dispatch:dispatch-rules
5    [("") homepage-handler]
6    [("api" "users") #:method "post" create-user-handler]
7    [("api" "users" (string-arg)) get-user-handler]
8    [("api" "data" (integer-arg)) #:method "delete" delete-data-handler]
9    [else not-found-handler]))

Gotcha: The dispatch-rules macro returns two values - the dispatcher and a request parser. Use define-values.

URL Parameters

 1; String argument from URL path
 2[("users" (string-arg)) user-handler]
 3; matches /users/123, /users/john, etc.
 4
 5; Integer argument from URL path
 6[("items" (integer-arg)) item-handler]
 7; matches /items/42, fails on /items/abc
 8
 9; Handler receives the argument
10(define (user-handler request user-id)
11  (response/jsexpr (hash 'user-id user-id)))

HTTP Method Routing

1(dispatch:dispatch-rules
2  [("api" "resource") #:method "get" list-handler]
3  [("api" "resource") #:method "post" create-handler]
4  [("api" "resource" (string-arg)) #:method "put" update-handler]
5  [("api" "resource" (string-arg)) #:method "delete" delete-handler])

Handling HTTP Requests: Parameters, Headers, and Body

Request Structure

1(require web-server/http)
2
3; Request fields
4(request-method request)           ; => #"GET", #"POST", etc.
5(request-uri request)              ; => url struct
6(request-headers request)          ; => list of headers
7(request-bindings request)         ; => POST/GET parameters
8(request-post-data/raw request)    ; => raw POST body bytes
9(request-host-ip request)          ; => client IP

Extracting POST Parameters

1(require web-server/http/bindings)
2
3; Extract single parameter
4(define (post-param name request)
5  (extract-binding/single name (request-bindings request)))
6
7; Usage
8(define username (post-param 'username request))
9(define email (post-param 'email request))

Gotcha: extract-binding/single raises an exception if:

  • The parameter doesn’t exist
  • Multiple parameters with the same name exist

Converting All Bindings to Hash

1(define (bindings->hash request)
2  (for/hash ([b (request-bindings/raw request)])
3    (values (binding-id b)
4            (bytes->string/utf-8 (binding:form-value b)))))
5
6; Usage
7(define params (bindings->hash request))
8(hash-ref params #"username" #f)  ; => "john" or #f

Handling Bytes vs Strings

1; Bindings may return bytes even though docs say string
2(define param-value (post-param 'field-name request))
3
4; Always handle both cases for robustness
5(define safe-value
6  (if (bytes? param-value)
7      (bytes->string/utf-8 param-value)
8      param-value))

Constructing HTTP Responses: JSON, XML, and Custom Headers

JSON Responses

1(require web-server/http/json)
2
3(define (api-handler request)
4  (response/jsexpr
5    #:code 200
6    (hash 'status "success"
7          'data (list 1 2 3)
8          'count 3)))

XML Responses (X-expressions)

 1(require web-server/http/xexpr)
 2
 3(define (xml-handler request)
 4  (response/xexpr
 5    #:code 200
 6    `(response
 7       (status ((code "200")) "OK")
 8       (data
 9         (item ((id "1")) "First")
10         (item ((id "2")) "Second")))))

Custom Responses

1(define (custom-handler request)
2  (response/full
3    200                      ; status code
4    #f                       ; message (inferred from status)
5    (current-seconds)        ; timestamp
6    #"application/json"      ; MIME type
7    '()                      ; additional headers list
8    (list #"{\"status\": \"ok\"}")))  ; body as list of byte strings

Gotcha: Response body must be a list of byte strings, not a single string.

1; WRONG
2(response/full 200 #f (current-seconds) #"text/plain" '() "body")
3
4; CORRECT
5(response/full 200 #f (current-seconds) #"text/plain" '()
6  (list (string->bytes/utf-8 "body")))

Response with Custom Headers

 1(require web-server/http/response-structs)
 2
 3(define (handler-with-headers request)
 4  (response/full
 5    200
 6    #f
 7    (current-seconds)
 8    #"text/plain"
 9    (list (header #"X-Custom-Header" #"custom-value")
10          (header #"Cache-Control" #"no-cache"))
11    (list #"Response body")))

Common Response Patterns

 1; 404 Not Found
 2(define (not-found-handler request)
 3  (response/jsexpr
 4    #:code 404
 5    (hash 'status "error"
 6          'message "Not Found")))
 7
 8; 500 Server Error
 9(define (error-handler request exn)
10  (response/jsexpr
11    #:code 500
12    (hash 'status "error"
13          'message (exn-message exn))))
14
15; 201 Created
16(define (create-handler request)
17  (define new-id (create-resource))
18  (response/jsexpr
19    #:code 201
20    (hash 'status "created"
21          'id new-id)))

Implementing Middleware for Logging and Error Handling

Error Handling Middleware

1(define (dispatch-with-error-handling request)
2  (with-handlers ([exn:fail? (lambda (exn) (error-handler request exn))])
3    (route-dispatch request)))

Pattern: Wrap the main dispatcher with with-handlers to catch all unhandled exceptions.

Full Error Handler with Stack Trace

 1(define (error-handler request exn)
 2  (define error-message (exn-message exn))
 3  (define stack-trace
 4    (if (exn:fail? exn)
 5        (continuation-mark-set->context (exn-continuation-marks exn))
 6        '()))
 7
 8  (response/xexpr
 9    #:code 500
10    `(response
11       (error
12         (message "Internal Server Error")
13         (details ,error-message)
14         (stack-trace
15           ,@(map (lambda (frame) `(frame ,(format "~a" frame)))
16                  stack-trace))))))

Request Logging Middleware

1(require (prefix-in logresp: web-server/dispatchers/dispatch-logresp))
2
3(define (dispatch-with-logging request)
4  (define response (route-dispatch request))
5  (display (logresp:apache-default-format request response))
6  response)

Pattern: Log after getting the response to include status code and timing information.

Combined Middleware

1(define (route-dispatch/with-middleware request)
2  (define response
3    (with-handlers ([exn:fail? (lambda (exn) (error-handler request exn))])
4      (route-dispatch request)))
5
6  ; Log request and response
7  (display (logresp:apache-default-format request response))
8  response)

Testing Web Applications: Unit and Integration Tests

Test Server Setup

 1(module+ test
 2  (require web-server/web-server
 3           web-server/servlet-dispatch)
 4
 5  ; Define test handler
 6  (define (test-handler req)
 7    (response/full 200 #"OK" (current-seconds) #"text/plain"
 8                   '() (list #"test response")))
 9
10  ; Start test server (non-blocking)
11  (define stop-server
12    (serve
13      #:dispatch (dispatch/servlet test-handler)
14      #:port 8089
15      #:listen-ip "127.0.0.1"))
16
17  ; Wait for server to start
18  (sleep 0.1)
19
20  ; Run tests...
21  (test-case "server responds"
22    (define response (get "http://localhost:8089/"))
23    (check-equal? (response-status-code response) 200))
24
25  ; Cleanup
26  (stop-server))

Pattern: Use serve (returns stop function) instead of serve/servlet (blocks) for tests.

Mock Request Creation

 1(require web-server/http/request-structs
 2         net/url)
 3
 4(define (make-mock-request #:method [method #"GET"]
 5                           #:url [url "http://localhost/"]
 6                           #:bindings [bindings '()])
 7  (request method
 8           (string->url url)
 9           '()                 ; headers
10           (delay bindings)    ; POST bindings (delayed)
11           #f                  ; post-data
12           "127.0.0.1"        ; host-ip
13           80                  ; host-port
14           "127.0.0.1"))       ; client-ip

Pattern: Use delayed bindings (delay) to match the request struct’s expected type.

Mock Binding Creation

 1(define (make-mock-binding name value)
 2  (make-binding:form
 3    (string->bytes/utf-8 (symbol->string name))
 4    (string->bytes/utf-8 value)))
 5
 6; Usage in tests
 7(define test-bindings
 8  (list (make-mock-binding 'username "testuser")
 9        (make-mock-binding 'email "test@example.com")))
10
11(define test-request
12  (make-mock-request
13    #:method #"POST"
14    #:bindings test-bindings))

Testing Handlers

 1(module+ test
 2  (require rackunit)
 3
 4  (test-case "handler returns correct status"
 5    (define req (make-mock-request))
 6    (define resp (homepage-handler req))
 7    (check-equal? (response-code resp) 200))
 8
 9  (test-case "handler extracts POST params"
10    (define bindings (list (make-mock-binding 'username "john")))
11    (define req (make-mock-request #:method #"POST" #:bindings bindings))
12    (define resp (create-user-handler req))
13    (check-equal? (response-code resp) 201))
14
15  (test-case "handler raises on missing param"
16    (define req (make-mock-request))
17    (check-exn exn:fail?
18               (lambda () (post-param 'missing req)))))

Integration Testing with HTTP Client

 1(module+ test
 2  (require net/http-easy)
 3
 4  ; Start test server
 5  (define stop-server (serve #:dispatch ... #:port 8089))
 6  (sleep 0.1)
 7
 8  (test-case "POST request integration"
 9    (define response
10      (post "http://localhost:8089/api/users"
11            #:data "username=john&email=john@example.com"
12            #:headers (hash 'content-type "application/x-www-form-urlencoded")))
13
14    (check-equal? (response-status-code response) 201)
15    (define body (bytes->string/utf-8 (response-body response)))
16    (check-true (string-contains? body "success")))
17
18  (stop-server))

Common Pitfalls in Racket Web Development

1. Dispatch-rules Returns Two Values

1; WRONG - only captures first value
2(define router (dispatch:dispatch-rules ...))
3
4; CORRECT - captures both dispatcher and request parser
5(define-values (route-dispatch request-parser)
6  (dispatch:dispatch-rules ...))

2. Stateless vs Stateful Servlets

1; Stateless (REST APIs) - no continuation URLs
2#:stateless? #t
3
4; Stateful (web apps with forms) - uses continuation URLs
5#:stateless? #f

Pattern: Always use #:stateless? #t for REST APIs and microservices.

3. Response Body Format

The response body must be a list of byte strings:

1; Helper to ensure correct format
2(define (make-response-body content)
3  (list (string->bytes/utf-8 content)))
4
5; Or trim and convert
6(define (xml-response-body xml-string)
7  (list (string->bytes/utf-8 (string-trim xml-string "\uFEFF"))))  ; Remove BOM

4. Request Bindings Type Ambiguity

1; Documentation says extract-binding/single returns string,
2; but it may return bytes
3(define (safe-post-param name request)
4  (define value (extract-binding/single name (request-bindings request)))
5  (if (bytes? value)
6      (bytes->string/utf-8 value)
7      value))

5. Multiple Parameters with Same Name

1; extract-binding/single fails with multiple params
2; Use extract-bindings instead
3(define (get-all-params name request)
4  (extract-bindings name (request-bindings request)))
5
6; Returns list of values
7(define tags (get-all-params 'tag request))  ; => '("racket" "web" "tutorial")

Advanced Web Development Patterns

Custom Response Helper

 1(define/contract (api-response data #:status [status 200])
 2  (->* (any/c) (#:status number?) response?)
 3  (define response-bytes (string->bytes/utf-8 (jsexpr->string data)))
 4  (response/full
 5    status
 6    #f
 7    (current-seconds)
 8    #"application/json"
 9    '()
10    (list response-bytes)))

Content Negotiation

1(define (negotiated-response request data)
2  (define accept-header
3    (header-value
4      (headers-assq #"Accept" (request-headers/raw request))))
5
6  (match accept-header
7    [#"application/json" (response/jsexpr data)]
8    [#"application/xml" (response/xexpr (hash->xexpr data))]
9    [_ (response/jsexpr data)]))  ; Default to JSON

Rate Limiting Middleware

 1(define rate-limits (make-hash))  ; IP -> (timestamp . count)
 2
 3(define (rate-limit-middleware request)
 4  (define client-ip (request-client-ip request))
 5  (define now (current-seconds))
 6  (define limit-entry (hash-ref rate-limits client-ip (cons now 0)))
 7  (define (timestamp . count) limit-entry)
 8
 9  (cond
10    [(> (- now timestamp) 60)  ; Reset after 1 minute
11     (hash-set! rate-limits client-ip (cons now 1))
12     (route-dispatch request)]
13    [(>= count 100)  ; Max 100 requests per minute
14     (response/jsexpr #:code 429
15       (hash 'error "Rate limit exceeded"))]
16    [else
17     (hash-set! rate-limits client-ip (cons timestamp (add1 count)))
18     (route-dispatch request)]))

Request Context Threading

 1; Use parameters for request-scoped values
 2(define current-user (make-parameter #f))
 3(define current-request-id (make-parameter #f))
 4
 5(define (with-context-middleware request)
 6  (parameterize ([current-request-id (generate-uuid)]
 7                 [current-user (authenticate request)])
 8    (route-dispatch request)))
 9
10; Access in handlers
11(define (protected-handler request)
12  (if (current-user)
13      (response/jsexpr (hash 'user (current-user)))
14      (response/jsexpr #:code 401 (hash 'error "Unauthorized"))))

Web Server Performance Optimization

1. Use Immutable Responses

Prefer response/jsexpr and response/xexpr which handle immutable data efficiently:

1; Efficient - uses immutable hash
2(response/jsexpr (hash 'a 1 'b 2))
3
4; Less efficient - mutable conversion
5(define h (make-hash))
6(hash-set! h 'a 1)
7(response/jsexpr h)

2. Stream Large Responses

1(define (streaming-response)
2  (response/output
3    (lambda (out)
4      (for ([i (in-range 1000000)])
5        (fprintf out "Line ~a\n" i)))))

3. Connection Pooling for External APIs

1(require net/http-easy)
2
3; Reuse HTTP connection pool
4(define client (http-easy-pool))
5
6(define (external-api-call)
7  (http-get "https://api.example.com/data" #:pool client))

Summary of Web Development Best Practices

  1. Always use #:stateless? #t for REST APIs
  2. Wrap dispatch in error handler to catch all exceptions
  3. Log after response to capture status and timing
  4. Use define-values for dispatch-rules
  5. Handle both bytes and strings from bindings
  6. Use serve not serve/servlet in tests
  7. Delay bindings in mock requests
  8. Prefer response/jsexpr and response/xexpr for common formats
  9. Test with both unit and integration tests
  10. Use parameters for request-scoped context
Tags: Racket, Web, Http, Rest-Api, Server, Routing