Split site into static and dynamic parts, for easy cacheability etc.

This commit is contained in:
Tony Garnock-Jones 2014-11-10 23:55:40 -05:00
parent d2ef8edd6a
commit 84c7068f75
4 changed files with 347 additions and 268 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ compiled/
*.[0-9]* *.[0-9]*
server-cert.pem server-cert.pem
private-key.pem private-key.pem
static/cached/

View File

@ -3,6 +3,7 @@
(module+ main (module+ main
(require "entrypoint.rkt") (require "entrypoint.rkt")
(void (make-entry-point 'refresh-packages! "packages.rkt")) (void (make-entry-point 'refresh-packages! "packages.rkt"))
(void (make-entry-point 'rerender-all! "site.rkt"))
(start-service #:reloadable? (getenv "SITE_RELOADABLE") (start-service #:reloadable? (getenv "SITE_RELOADABLE")
(make-entry-point 'request-handler "site.rkt") (make-entry-point 'request-handler "site.rkt")
(make-entry-point 'on-continuation-expiry "site.rkt"))) (make-entry-point 'on-continuation-expiry "site.rkt")))

View File

@ -32,5 +32,8 @@
(poll-signal "../signals/.fetchindex" (poll-signal "../signals/.fetchindex"
"Index refresh signal received" "Index refresh signal received"
(lambda () ((entry-point-value (lookup-entry-point 'refresh-packages!))))) (lambda () ((entry-point-value (lookup-entry-point 'refresh-packages!)))))
(poll-signal "../signals/.rerender"
"Static rerender request received"
(lambda () ((entry-point-value (lookup-entry-point 'rerender-all!)))))
(sleep 0.5) (sleep 0.5)
(loop))))) (loop)))))

View File

@ -1,7 +1,8 @@
#lang racket/base #lang racket/base
(provide request-handler (provide request-handler
on-continuation-expiry) on-continuation-expiry
rerender-all!)
(require racket/set) (require racket/set)
(require racket/match) (require racket/match)
@ -20,6 +21,9 @@
(require "reload.rkt") (require "reload.rkt")
(require "daemon.rkt") (require "daemon.rkt")
(define static-cached-directory "../static/cached")
(define static-cached-urlprefix "/cached")
(define nav-index "Package Index") (define nav-index "Package Index")
(define nav-search "Search") (define nav-search "Search")
@ -48,11 +52,14 @@
(define-values (request-handler named-url) (define-values (request-handler named-url)
(dispatch-rules (dispatch-rules
[("index") main-page]
[("") main-page] [("") main-page]
[("search") search-page] [("search") search-page]
[("package" (string-arg)) package-page] [("package" (string-arg)) package-page]
[("package" (string-arg) "edit") edit-package-page] [("package" (string-arg) "edit") edit-package-page]
[("create") edit-package-page] [("create") edit-package-page]
[("login") login-page]
[("register-or-reset") register-or-reset-page]
[("logout") logout-page] [("logout") logout-page]
[("json" "search-completions") json-search-completions] [("json" "search-completions") json-search-completions]
[("json" "tag-search-completions") json-tag-search-completions] [("json" "tag-search-completions") json-tag-search-completions]
@ -65,6 +72,8 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define static-render (make-parameter #f))
(define-syntax-rule (authentication-wrap #:request request body ...) (define-syntax-rule (authentication-wrap #:request request body ...)
(authentication-wrap* #f request (lambda () body ...))) (authentication-wrap* #f request (lambda () body ...)))
@ -82,47 +91,41 @@
#:path "/" #:path "/"
#:expires "Thu, 01 Jan 1970 00:00:00 GMT")) #:expires "Thu, 01 Jan 1970 00:00:00 GMT"))
(define (authentication-wrap* require-login? request body) (define-syntax-rule (with-session-cookie cookie-value body ...)
(define original-session-cookies (let ((v cookie-value))
(parameterize ((bootstrap-cookies
(if v
(list (make-cookie COOKIE v #:path "/" #:secure? #t))
(list clear-session-cookie))))
body ...)))
(define (request->session request)
(define session-cookies
(filter (lambda (c) (equal? (client-cookie-name c) COOKIE)) (filter (lambda (c) (equal? (client-cookie-name c) COOKIE))
(request-cookies request))) (request-cookies request)))
(define original-session-keys (define session-keys (map client-cookie-value session-cookies))
(map client-cookie-value original-session-cookies)) ;; (log-info "Session keys from cookie: ~a" session-keys)
;; (log-info "Session keys from cookie: ~a" original-session-keys) (for/or ((k session-keys)) (lookup-session/touch! k)))
(let redo ((session-keys original-session-keys))
(define session (for/or ((k session-keys)) (lookup-session/touch! k))) (define (authentication-wrap* require-login? request body)
(define session (request->session request))
;; (log-info "session: ~a" session) ;; (log-info "session: ~a" session)
(define requested-url (url->string (request-uri request)))
;; If needed in future, we can change this to preserve *all* of
;; the original request by simply calling redo with the new
;; session key, (redo (list new-session-key)).
;;
;; For now, we need to redirect to a clean URL in every case, so
;; just do that.
(define (after-login new-session-key)
(parameterize ((bootstrap-cookies
(if new-session-key
(list (make-cookie COOKIE new-session-key #:path "/" #:secure? #t))
(list clear-session-cookie))))
(with-site-config
(bootstrap-redirect (url->string (request-uri request))))))
(send/suspend/dispatch
(lambda (embed-url)
(if (and require-login? (not session)) (if (and require-login? (not session))
(after-login (login-page)) (login-or-register-flow* requested-url login-form)
(parameterize ((bootstrap-navbar-extension (parameterize ((bootstrap-navbar-extension
(cond (cond
[(not session) [(not session)
`((a ((id "register-button") `((a ((id "register-button")
(class "btn btn-default navbar-btn navbar-right") (class "btn btn-default navbar-btn navbar-right")
(href ,(embed-url (href ,(login-or-register-url requested-url
(lambda (req) (after-login (register-page)))))) (named-url register-or-reset-page))))
"Register") "Register")
(a ((id "sign-in-button") (a ((id "sign-in-button")
(class "btn btn-success navbar-btn navbar-right") (class "btn btn-success navbar-btn navbar-right")
(href ,(embed-url (href ,(login-or-register-url requested-url
(lambda (req) (after-login (login-page)))))) (named-url login-page))))
"Sign in"))] "Sign in"))]
[else [else
`((ul ((class "nav navbar-nav navbar-right")) `((ul ((class "nav navbar-nav navbar-right"))
@ -144,8 +147,10 @@
(session-email session)))))) (session-email session))))))
,(glyphicon 'user) " My packages")) ,(glyphicon 'user) " My packages"))
(li ((class "divider")) (li ((class "divider"))
(li (a ((href ,(embed-url (li (a ((href
(lambda (req) (after-login #f))))) ,(login-or-register-url
requested-url
(named-url logout-page))))
,(glyphicon 'log-out) " Log out")))))))])) ,(glyphicon 'log-out) " Log out")))))))]))
(current-session session) (current-session session)
(bootstrap-cookies (bootstrap-cookies
@ -155,7 +160,7 @@
#:path "/" #:path "/"
#:secure? #t)) #:secure? #t))
(list)))) (list))))
(with-site-config (body)))))))) (with-site-config (body)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -208,7 +213,34 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (login-page [error-message #f]) (define (login-or-register-url k baseurl)
(format "~a?~a"
baseurl
(alist->form-urlencoded (list (cons 'k k)))))
(define (login-or-register-flow request thunk)
(define-form-bindings request ([k (named-url main-page)]))
(login-or-register-flow* k thunk))
(define (login-or-register-flow* k thunk)
(with-session-cookie (thunk)
(with-site-config
(bootstrap-redirect k))))
(define (login-page request)
(login-or-register-flow request login-form))
(define (register-or-reset-page request)
(login-or-register-flow request register-form))
(define (logout-page request)
(define session (request->session request))
(when session (destroy-session! (session-key session)))
(login-or-register-flow request (lambda () #f)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (login-form [error-message #f])
(with-site-config (with-site-config
(send/suspend/dispatch (send/suspend/dispatch
(lambda (embed-url) (lambda (embed-url)
@ -222,10 +254,10 @@
,(form-group 2 2 (label "password" "Password:") ,(form-group 2 2 (label "password" "Password:")
0 5 (password-input "password")) 0 5 (password-input "password"))
,(form-group 4 5 ,(form-group 4 5
`(a ((href ,(embed-url (lambda (req) (register-page))))) `(a ((href ,(embed-url (lambda (req) (register-form)))))
"Need to reset your password?")) "Need to reset your password?"))
,(form-group 4 5 ,(form-group 4 5
`(a ((href ,(embed-url (lambda (req) (register-page))))) `(a ((href ,(embed-url (lambda (req) (register-form)))))
"Register an account")) "Register an account"))
,@(maybe-splice ,@(maybe-splice
error-message error-message
@ -246,16 +278,16 @@
(define-form-bindings request (email password)) (define-form-bindings request (email password))
(if (or (equal? (string-trim email) "") (if (or (equal? (string-trim email) "")
(equal? (string-trim password) "")) (equal? (string-trim password) ""))
(login-page "Please enter your email address and password.") (login-form "Please enter your email address and password.")
(match (authenticate-with-server! email password "") (match (authenticate-with-server! email password "")
["wrong-code" ["wrong-code"
(login-page "Something went awry; please try again.")] (login-form "Something went awry; please try again.")]
[(or "emailed" #f) [(or "emailed" #f)
(summarise-code-emailing "Incorrect password, or nonexistent user." email)] (summarise-code-emailing "Incorrect password, or nonexistent user." email)]
[else [else
(create-session! email password)]))) (create-session! email password)])))
(define (register-page #:email [email ""] (define (register-form #:email [email ""]
#:code [code ""] #:code [code ""]
#:error-message [error-message #f]) #:error-message [error-message #f])
(with-site-config (with-site-config
@ -308,7 +340,7 @@
(define (apply-account-code request) (define (apply-account-code request)
(define-form-bindings request (email code password confirm_password)) (define-form-bindings request (email code password confirm_password))
(define (retry msg) (define (retry msg)
(register-page #:email email (register-form #:email email
#:code code #:code code
#:error-message msg)) #:error-message msg))
(cond (cond
@ -346,14 +378,24 @@
(code ,email) ". Please check your email and then click " (code ,email) ". Please check your email and then click "
"the button to continue:") "the button to continue:")
`(a ((class "btn btn-primary") `(a ((class "btn btn-primary")
(href ,(embed-url (lambda (req) (register-page))))) (href ,(embed-url (lambda (req) (register-form)))))
"Enter your code")))))) "Enter your code"))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (package-link package-name) (define (main-page-url)
(if (current-session)
(named-url main-page)
(format "~a~a" static-cached-urlprefix (named-url main-page))))
(define (view-package-url package-name)
(define package-name-str (~a package-name)) (define package-name-str (~a package-name))
`(a ((href ,(named-url package-page package-name-str))) ,package-name-str)) (if (current-session)
(named-url package-page package-name-str)
(format "~a~a" static-cached-urlprefix (named-url package-page package-name-str))))
(define (package-link package-name)
`(a ((href ,(view-package-url package-name))) ,(~a package-name)))
(define (doc-destruct doc) (define (doc-destruct doc)
(match doc (match doc
@ -469,6 +511,11 @@
(define package-name-list (package-search "" '((main-distribution #f)))) (define package-name-list (package-search "" '((main-distribution #f))))
(authentication-wrap (authentication-wrap
#:request request #:request request
(cond
[(and (not (current-session)) (not (static-render)))
;; Redirect to static version
(bootstrap-redirect (main-page-url))]
[else
(bootstrap-response "Racket Package Index" (bootstrap-response "Racket Package Index"
#:title-element "" #:title-element ""
#:body-class "main-page" #:body-class "main-page"
@ -494,13 +541,7 @@
`(div `(div
(p ((class "package-count")) (p ((class "package-count"))
,(format "~a packages" (length package-name-list))) ,(format "~a packages" (length package-name-list)))
,(package-summary-table package-name-list)))))) ,(package-summary-table package-name-list)))]))))
(define (logout-page request)
(with-site-config
(parameterize ((bootstrap-cookies (list clear-session-cookie)))
(when (current-session) (destroy-session! (session-key (current-session))))
(bootstrap-redirect (named-url main-page)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -517,11 +558,16 @@
(define package-name (string->symbol package-name-str)) (define package-name (string->symbol package-name-str))
(define pkg (package-detail package-name)) (define pkg (package-detail package-name))
(define default-version (hash-ref (or (@ pkg versions) (hash)) 'default (lambda () #f))) (define default-version (hash-ref (or (@ pkg versions) (hash)) 'default (lambda () #f)))
(if (not pkg) (cond
[(and (not (current-session)) (not (static-render)))
;; Redirect to static version
(bootstrap-redirect (view-package-url package-name))]
[(not pkg)
(bootstrap-response #:code 404 (bootstrap-response #:code 404
#:message #"No such package" #:message #"No such package"
"Package not found" "Package not found"
`(div "The package " (code ,package-name-str) " does not exist.")) `(div "The package " (code ,package-name-str) " does not exist."))]
[else
(bootstrap-response (~a package-name) (bootstrap-response (~a package-name)
#:title-element "" #:title-element ""
`(div ((class "jumbotron")) `(div ((class "jumbotron"))
@ -666,7 +712,7 @@
,@(for/list ((mod (or (@ pkg modules) '()))) ,@(for/list ((mod (or (@ pkg modules) '())))
(match-define (list kind path) mod) (match-define (list kind path) mod)
`(li ((class ,kind)) ,path))))) `(li ((class ,kind)) ,path)))))
))))) ))])))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -678,7 +724,7 @@
(cond (cond
[(and pkg (not (member (current-email) (or (@ pkg authors) '())))) [(and pkg (not (member (current-email) (or (@ pkg authors) '()))))
;; Not ours. Show it instead. ;; Not ours. Show it instead.
(bootstrap-redirect (named-url package-page package-name-str))] (bootstrap-redirect (view-package-url package-name))]
[(not pkg) [(not pkg)
;; Doesn't exist. ;; Doesn't exist.
(package-form #f (draft-package "" (package-form #f (draft-package ""
@ -847,7 +893,7 @@
has-old-name? has-old-name?
" " " "
`(a ((class "btn btn-default") `(a ((class "btn btn-default")
(href ,(named-url package-page old-name))) (href ,(view-package-url old-name)))
"Cancel changes and return to package page")))))) "Cancel changes and return to package page"))))))
)))))) ))))))
@ -866,7 +912,7 @@
(define completion-ch (make-channel)) (define completion-ch (make-channel))
(delete-package! completion-ch (string->symbol package-name-str)) (delete-package! completion-ch (string->symbol package-name-str))
(channel-get completion-ch) (channel-get completion-ch)
(bootstrap-redirect (named-url main-page)))) (bootstrap-redirect (main-page-url))))
(define ((update-draft draft0) request) (define ((update-draft draft0) request)
(define draft (read-draft-form draft0 (request-bindings request))) (define draft (read-draft-form draft0 (request-bindings request)))
@ -875,7 +921,7 @@
["save_changes" ["save_changes"
(if (save-draft! draft) (if (save-draft! draft)
(with-site-config (with-site-config
(bootstrap-redirect (named-url package-page (~a (draft-package-name draft))))) (bootstrap-redirect (view-package-url (draft-package-name draft))))
(package-form "Save failed." (package-form "Save failed."
;; ^ TODO: This is the worst error message. ;; ^ TODO: This is the worst error message.
;; Right up there with "parse error". ;; Right up there with "parse error".
@ -1126,18 +1172,46 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (rerender-indexes!) (define (static-render! handler . named-url-args)
(log-info "Rerendering indexes")) (local-require racket/promise)
(local-require racket/file)
(local-require web-server/private/servlet)
(local-require web-server/http/request-structs)
(define request-url (apply named-url handler named-url-args))
(log-info "Rendering static version of ~a" request-url)
(define response
(parameterize ((static-render #t))
(call-with-continuation-barrier
(lambda ()
(call-with-continuation-prompt
(lambda ()
(apply handler
(request #"GET"
(string->url request-url)
'()
(delay '())
#f
"127.0.0.1"
0
"127.0.0.1")
named-url-args))
servlet-prompt)))))
(define filename (format "~a~a" static-cached-directory request-url))
(make-parent-directory* filename)
(call-with-output-file filename
(response-output response)
#:exists 'replace))
(define (rerender-package! package-name) (define (rerender-all!)
(log-info "Rerendering package ~a" package-name)) (for ((p (all-package-names))) (static-render! package-page (symbol->string p)))
(static-render! main-page))
(define (package-change-handler) (define (package-change-handler)
(let loop ((index-rerender-needed? #f) (let loop ((index-rerender-needed? #f)
(pending-completions '())) (pending-completions '()))
(sync/timeout (and index-rerender-needed? (sync/timeout (and index-rerender-needed?
(lambda () (lambda ()
(rerender-indexes!) (static-render! main-page)
(for ((completion-ch pending-completions)) (for ((completion-ch pending-completions))
(channel-put completion-ch (void))) (channel-put completion-ch (void)))
(loop #f '()))) (loop #f '())))
@ -1145,7 +1219,7 @@
(lambda (_) (lambda (_)
(match (thread-receive) (match (thread-receive)
[(list completion-ch package-name) [(list completion-ch package-name)
(rerender-package! package-name) (static-render! package-page (symbol->string package-name))
(loop #t (if completion-ch (loop #t (if completion-ch
(cons completion-ch pending-completions) (cons completion-ch pending-completions)
pending-completions))])))))) pending-completions))]))))))