Production vs. development environments for the Racket HTTP server
The site isn't live…yetWe all know that when we're working on a web site we might write some code that's, well, not so pretty. It might contain errors. Maybe it just does the wrong thing. We work on the site to our satisfaction, then we push it live. When we're developing, we may want our site to behave mostly the same as it would if it were live, but with some small (but crucial) differences. For instance:
- If you're connecting to a database, probably you're connecting to a different database than you could connect to were you in a live environment.
- If you've got an ecommerce site, you can use your site to buy things, even while developing it…but not using real credit cards, actual money.
- Debug output is useful when you're developing…but not live. (Or: your debug output needs to go somewhere else.)
sorry an error occurredHTML page and log the error behind the scenes. When the site is not live (or, in other words, when the site is in development) and a program error occurs, we want to see a stack trace, presented in HTML. How can you do all this with the Racket web server?
Approaches to the problemBefore digging in to Racket, note that there are a couple different approaches to the production vs. development problem. One can use:
- environment variables
- presence/absence of a file on the server
- file has/does not have certain content
The purpose of this code is to define a single variable,
#lang racket/base (define production-env-variable "SITE_MODE") (define production-env-value "production") (define (production?) (let ([current (getenv production-env-variable)]) (and (string? current) (string=? current production-env-value)))) (define site-is-production? (production?)) (provide site-is-production?)
site-is-production?, whose value is #t if and only if
- the variable SITE_MODE is present in the current environment, and
- the value of this environment variable is (exactly)
ImplementationWith this basis, let's see how we can use the information about whether the site is in production mode. We will largely follow the example given in another tutorial, where the server is a toy example about rational numbers. Watch how
site-is-production?gets used. The idea here is to make a server that, when in production mode, gracefully handles all errors by giving an HTTP 500 response with a little HTML apologizing for the error. Outside of production mode, we want to use the standard Racket error handler, which pretty prints (in HTML) the exception that was raised and logs the error, too.
The main idea is that, using giving a value for
#lang racket/base (require web-server/dispatch web-server/servlet-env web-server/templates web-server/http/request-structs web-server/configuration/responders) ;; the next two files are provided in the accompanying ;; zip file -- did you download it? (require (file "response.rkt")) (require (file "site-mode.rkt")) (define html-mime "text/html;charset=utf-8") (define error-responder (make-parameter #f)) (define (get-homepage req n) (let ([r (/ 1 n)]) (response #:code 200 #:mime html-mime #:body (include-template "rational.html")))) (define (fallback url exn) (cond (site-is-production? (response #:code 500 #:mime html-mime #:body (include-template "oops.html"))) (#t (let ([handler (error-responder)]) (handler url exn))))) (define-values (dispatcher dispatch-url) (dispatch-rules [[(integer-arg)] #:method "get" get-homepage])) (module+ main (parameterize ([error-responder servlet-error-responder]) (serve/servlet dispatcher #:port 6893 #:servlet-responder fallback #:launch-browser? #f #:servlet-regexp #rx"")))
serve/servlet, we are providing our own fallback error handler. The secret sauce here (if you will) is the
parameterizebit (which goes along with the
make-parameternear the top). We are making a kind of backup:
servlet-error-responderis the standard Racket error handler, the one that generates the pretty-printed exception and logs stack traces to the console. Our goal is to use that error handler, as long as we're not in production. The idea, then, is to save that value (under the parameter
error-responder). If an error occurs, we check: are we in production mode? If so, we present a minimal HTML page as our response. If not, we can barf up the ugly stack trace. That's what you see in the implementation of
fallback, which is our fallback error handler.
Doing moreOf course, much more is possible. The main idea is to have some kind of mechanism for deciding whether we are in production mode or not. Once you've decided on a strategy for making that determination (here, we used environment variables), you're good to go. We used Racket's parameters as a way to save a value before overriding it. Parameters could also be used to store the
production-nessof a site itself; that's an option we didn't explore here and may be necessary, depending on exactly how you'd like to implement your production-or-not strategy.