Get the original request, no matter where you are

Dude, where’s my request?

You know the feeling: you’re deep inside some code in your web application, far away from the place where you received the initial request, but you realize: Wait, what was the original request that brought me here? You left the original request behind a long time ago. How do you get it back?

You could be really disciplined and pass along the request to every function that has anything to do, potentially, with your web application. That way, any function that potentially needs the request has it, directly. That’s not wrong; indeed, one might say that that’s really correct. But such an approach also has the downside of polluting your code with arguments that rarely get used. There might be lots of functions that take a request as input and simply pass it along.

Here’s one approach to the problem. The idea is to use a a wrapper handler, a function that receives the initial request, sets up some initial state, and passes it along to the real handler(s).

Along with a wrapper handler, the other ingredient in the solution under discussion is to use Racket parameters. Parameters allow you to access values that were set up outside of your lexical scope. That is to say, parameters are available to you even though they’re not actually arguments to your functions. (We’ve discussed parameters before, in the production-vs-development problem.)

Here’s how this can play out. Imagine that we have a wrapper handler, start. The function start takes the initial request. That’s the natural place to give the parameter its value. That’s done using parameterize. But before all that, we need to define the parameter in the first place.

Here’s how you put those pieces together:

;; define the parameter for the original request
(define original-request (make-parameter #f))

;; define your dispatcher here, either directly or with utilities
;; such as dispatch-rules

(define (dispatcher req) ;; ...
)

;; here's the parameterize magic:
(define (start req) (parameterize ([original-request req]) (dispatcher req)))

If you are anywhere in your web code, access the original request using original-request, like so:

(define (my-cool-function x y)
  (let ([req (original-request)])
    ;; do something with the original request,
    ;; even though it wasn't given as an argument
    ;; to this function!
  ))

How to organize your parameters

From a code organization point of view, consider putting your parameters in a separate file, say, parameters.rkt:

(define original-request (make-parameter #f))

(provide original-request)

And then, in your web code:

(require (file "parameters.rkt"))

;; define your web stuff here, possibly using
;; (original-request)

If you’re dealing with code that might be called outside the context of handling a request, it’s a good idea to check that the parameter really does contain an HTTP request. Use request? for that:

(require (only-in web-server/http/request-structs request?))
(require (file "parameters.rkt"))

(define (my-cool-function a b c) ;; check that we really do have a request: (if (request? (original-request)) ;; deal with the original request here ;; and in the other branch of the conditional ;; deal with the case where you're not dealing ;; with a request ))