├── LICENSE.txt ├── Makefile ├── README.md ├── log └── run ├── run ├── signals └── README.md ├── src ├── config.rkt ├── main.rkt ├── signals.rkt └── site.rkt └── static └── style.css /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2014 Tony Garnock-Jones 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | SITE_RELOADABLE=yes ./run 3 | 4 | reload: 5 | touch signals/.reload 6 | 7 | # This is intended for when the service is running under daemontools 8 | restart: 9 | touch signals/.restart 10 | 11 | clean: 12 | find . -depth -type d -iname compiled -exec rm -rf {} \; 13 | 14 | .PHONY: run 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example Reloadable Racket Website 2 | 3 | ## Prerequisites 4 | 5 | You will need to install the following Racket packages: 6 | 7 | raco pkg install reloadable 8 | 9 | The homepage of the reloadable package is 10 | . 11 | 12 | ## Local testing 13 | 14 | Running `src/main.rkt` starts a local server. For your convenience, 15 | 16 | make run 17 | 18 | starts the server with code reloading enabled. 19 | 20 | ### Automatic code reloading 21 | 22 | If you would like to enable the automatic code-reloading feature, set 23 | the environment variable `SITE_RELOADABLE` to a non-empty string. (A 24 | good place to do that is in a `run-prelude` script; see below.) 25 | 26 | You must also delete any compiled code `.zo` files. Otherwise, the 27 | system will not be able to correctly replace modules while running. 28 | You can use `make clean` to delete any stray `.zo` files. 29 | 30 | ## Deployment 31 | 32 | ### Supervision 33 | 34 | Startable using djb's [daemontools](http://cr.yp.to/daemontools.html); 35 | symlink this directory into your services directory and start it as 36 | usual. The `run` script starts the program, and `log/run` sets up 37 | logging of stdout/stderr. 38 | 39 | If the file `run-prelude` exists in the same directory as `run`, it 40 | will be dotted in before racket is invoked. I use this to update my 41 | `PATH` to include my locally-built racket `bin` directory, necessary 42 | because I don't have a system-wide racket. It's also a good place to 43 | put a `SITE_RELOADABLE` definition, if you would like that enabled for 44 | a production deployment. 45 | 46 | On Debian, daemontools can be installed with `apt-get install 47 | daemontools daemontools-run`, and the services directory is 48 | `/etc/service/`. 49 | 50 | ### Control signals 51 | 52 | You can send signals to the running service by creating files in the 53 | `signals/` directory. For example: 54 | 55 | - creating `.pull` causes the server to shell out to `git pull` and 56 | then exit. Daemontools will restart it. 57 | 58 | - creating `.restart` causes it to exit, to be restarted by 59 | daemontools. 60 | 61 | - creating `.reload` forces a reload, for when you don't have 62 | `SITE_RELOADABLE` defined and you want to do a one-off code reload. 63 | 64 | In particular, a git `post-receive` hook can be used to create the 65 | `.pull` signal in order to update the service on git push. 66 | 67 | ## Copyright and License 68 | 69 | Copyright © 2014 Tony Garnock-Jones 70 | 71 | Permission is hereby granted, free of charge, to any person obtaining a copy 72 | of this software and associated documentation files (the "Software"), to deal 73 | in the Software without restriction, including without limitation the rights 74 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 75 | copies of the Software, and to permit persons to whom the Software is 76 | furnished to do so, subject to the following conditions: 77 | 78 | The above copyright notice and this permission notice shall be included in 79 | all copies or substantial portions of the Software. 80 | 81 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 82 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 83 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 84 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 85 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 86 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 87 | THE SOFTWARE. 88 | -------------------------------------------------------------------------------- /log/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec multilog t ./main 3 | -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -f ./run-prelude ]; then . ./run-prelude; fi 3 | cd src 4 | PLTSTDERR=info 5 | export PLTSTDERR 6 | echo '=============================================' 7 | exec racket main.rkt 2>&1 8 | -------------------------------------------------------------------------------- /signals/README.md: -------------------------------------------------------------------------------- 1 | Touch files `.pull`, `.restart`, or `.reload` in this directory to 2 | update the running server. Useful for use in git post-receive hooks. 3 | -------------------------------------------------------------------------------- /src/config.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | ;; 3 | ;; (Reloadable) Configuration for the site. 4 | ;; 5 | 6 | (provide configurable-text) 7 | 8 | (define configurable-text "This is configurable text.") 9 | -------------------------------------------------------------------------------- /src/main.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (require web-server/servlet-env) 4 | (require reloadable) 5 | (require "signals.rkt") 6 | 7 | (define (main) 8 | (define request-handler 9 | (reloadable-entry-point->procedure 10 | (make-reloadable-entry-point 'request-handler "site.rkt"))) 11 | 12 | (when (not (getenv "SITE_RELOADABLE")) 13 | (set-reload-poll-interval! #f)) 14 | 15 | (reload!) 16 | 17 | (start-restart-signal-watcher) 18 | (serve/servlet request-handler 19 | #:launch-browser? #f 20 | #:quit? #f 21 | #:listen-ip #f 22 | #:port 8765 23 | #:extra-files-paths (list (build-path (current-directory) "../static")) 24 | #:servlet-regexp #rx"")) 25 | 26 | (main) 27 | -------------------------------------------------------------------------------- /src/signals.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | ;; Watching for control signals from the outside (Unix) environment 3 | 4 | (provide poll-signal 5 | start-restart-signal-watcher) 6 | 7 | (require reloadable) 8 | 9 | (define (poll-signal signal-file-name message handler) 10 | (when (file-exists? signal-file-name) 11 | (log-info message) 12 | (delete-file signal-file-name) 13 | (handler))) 14 | 15 | (define (start-restart-signal-watcher) 16 | (thread 17 | (lambda () 18 | (let loop () 19 | (flush-output) ;; Somewhat gratuitous; help ensure timely stdout logging 20 | (poll-signal "../signals/.pull" 21 | "Pull signal received" 22 | (lambda () 23 | (local-require racket/system) 24 | (system "git pull") 25 | (exit 0))) 26 | (poll-signal "../signals/.restart" 27 | "Restart signal received - attempting to restart" 28 | (lambda () (exit 0))) 29 | (poll-signal "../signals/.reload" 30 | "Reload signal received - attempting to reload code" 31 | reload!) 32 | (sleep 0.5) 33 | (loop))))) 34 | -------------------------------------------------------------------------------- /src/site.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | (provide request-handler) 4 | 5 | (require racket/match) 6 | (require racket/date) 7 | (require web-server/servlet) 8 | (require reloadable) 9 | 10 | (require "config.rkt") 11 | 12 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 13 | ;; Persistent state: 14 | 15 | (define counter (make-persistent-state 'counter (lambda () 0))) 16 | 17 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 18 | ;; Transient state: 19 | 20 | (define reload-date (current-date)) 21 | 22 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 23 | 24 | (define-values (request-handler named-url) 25 | (dispatch-rules 26 | [("index") main-page] 27 | [("") main-page] 28 | [("changecounter") #:method "post" change-counter-page] 29 | )) 30 | 31 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 32 | 33 | (define (respond xexpr) 34 | (response/xexpr 35 | #:preamble #"\n" 36 | `(html 37 | (head (meta ((charset "utf-8"))) 38 | (title "Example Reloadable Racket Website") 39 | (link ((rel "stylesheet") (href "/style.css") (type "text/css")))) 40 | (body ,xexpr)))) 41 | 42 | (define (main-page req) 43 | (respond `(div 44 | (h1 "Example Reloadable Racket Website") 45 | (p ,configurable-text) 46 | (p "The counter's current value is " 47 | ,(number->string (counter)) 48 | ".") 49 | (p "The last reload happened at " 50 | ,(date->string reload-date #t) 51 | ".") 52 | (hr) 53 | (form ((id "counter-form") 54 | (method "post") 55 | (action ,(named-url change-counter-page))) 56 | (button ((type "submit") 57 | (name "action") 58 | (value "increment")) 59 | "Increment counter") 60 | (button ((type "submit") 61 | (name "action") 62 | (value "decrement")) 63 | "Decrement counter"))))) 64 | 65 | (define (change-counter-page req) 66 | (match (extract-binding/single 'action (request-bindings req)) 67 | ["increment" 68 | (counter (+ (counter) 1))] 69 | ["decrement" 70 | (counter (- (counter) 1))] 71 | [_ 72 | (void)]) 73 | (redirect-to (named-url main-page))) 74 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:500,400,300,600,700); 2 | 3 | body { 4 | font-family: "Open Sans"; 5 | font-weight: 300; 6 | } 7 | 8 | h1, h2, h3, h4, h5, h6 { 9 | font-family: "Open Sans"; 10 | font-weight: 700; 11 | } 12 | --------------------------------------------------------------------------------