Using Racket to Configure Apache

I have a confession to make: I really hate writing boilerplate code.

I use Apache to serve lots of websites, have many lines of configuration code and have often longed for a way to capture all the patterns and reduce code duplication. I thought about using a macro language like m4 or the C preprocessor or even Ruby but neither alternative was particulary attractive. Then it hit me that I could describe the Apache configuration with plain S-expressions and use the full power of Racket to manipulate these and output normal configuration files. What makes this a compelling alternative is of course that S-expressions allow one to express tree-structured data very succinctly, like here:

(group (VirtualHost *) 
       (RedirectPermanent /

Racket—being a Lisp equipped with many powerful built-in functions—makes a full translator for these kind of trees simple to write.

Update: I got an e-mail from Eli Barzilay (of Racket fame) with some valuable feedback on how to improve my code by using functions in the scribble/text and racket/list libraries that are included with Racket. I’ve updated my code and put the old code at the bottom1 for reference. Eli also wrote about using the cool at-exp reader to mix text and code.

(require scribble/text)

(define (apache-config . complete-config)
  (define (format-apache-config config)
    (match config
      ((list 'group (list group ...) directives ...)
       (list "<"  (add-between group " ") ">" "\n" 
             "  " (add-newlines (map format-apache-config directives)) "\n" 
             "</" (first group) ">"))
      ((list (list _ ...) ...)
       (add-newlines (map format-apache-config config)))
      ((list _ ...)
       (add-between config " "))))
  (output (format-apache-config complete-config)) (newline))

The output function takes care of the indentation in a pretty clever way.

Now—equipped with apache-config—we can start building functions that work with Apache S-expressions:

(define (host-redirect from-hostname to-hostname)
  `(group (VirtualHost *)
          (ServerName ,from-hostname)
          (RedirectPermanent / ,(string-append "http://" to-hostname "/"))))

  (host-redirect "" "")
  (host-redirect "" ""))


<VirtualHost *>
  RedirectPermanent /
<VirtualHost *>
  RedirectPermanent /

Now I’m happy.

1 Click here to see the old code.