#lang scheme/base
(require mzlib/trace
mzlib/etc
scheme/contract
scheme/list
net/url
web-server/http/request-structs
web-server/http/response-structs
web-server/dispatchers/dispatch
"request.ss"
"cookie.ss"
"response.ss"
"servlet.ss"
"proxy.ss"
)
(define (read-all in)
(define (helper acc)
(let ((term (read in)))
(if (eof-object? term)
(reverse acc)
(helper (cons term acc)))))
(helper '()))
(define (file->values path)
(call-with-input-file path read-all))
(define (thunk? p)
(and (procedure? p)
(let ((a (procedure-arity p)))
(cond ((arity-at-least? a)
(= (arity-at-least-value a) 0))
((number? a) (= a 0))
((list? a) (member 0 a))))))
(define (path->segments path)
(define (helper segments)
(filter (lambda (segment)
(not (equal? segment "")))
segments))
(helper (if (url? path)
(map path/param-path (url-path path))
(regexp-split #px"\\/" path))))
(define (normalize-path path (base (shp-handler-path ($server))))
(apply build-path base (path->segments path)))
(define (not-found-path base)
(let ((not-found (shp-handler-not-found ($server))))
(if (script? not-found)
(script-path not-found)
(build-path base not-found))))
(define (segments->partial-path segments)
(define (helper rest path default)
(cond ((null? rest)
(display (format "path not found: ~a~n" segments) (current-error-port))
(not-found-path (shp-handler-path ($server))))
((file-exists? (build-path path (car rest) default))
($pathinfo (cdr rest)) (build-path path (car rest) default))
((file-exists? (build-path path (car rest))) ($pathinfo (cdr rest)) (build-path path (car rest)))
(else
(helper (cdr rest) (build-path path (car rest)) default))))
(helper segments (shp-handler-path ($server)) (shp-handler-default ($server))))
(define (segments->path segments (partial? #t))
(let ((script (apply build-path (shp-handler-path ($server)) segments)))
(cond ((file-exists? script) script)
((and (directory-exists? script)
(file-exists? (build-path script (shp-handler-default ($server)))))
(build-path script (shp-handler-default ($server))))
(partial?
(segments->partial-path segments))
(else
(display (format "path not found: ~a~n" segments) (current-error-port))
(not-found-path script)))))
(define (resolve-path path (partial? #t))
(segments->path (path->segments path) partial?))
(define-namespace-anchor handler-namespace-anchor)
(define handler-namespace (namespace-anchor->namespace handler-namespace-anchor))
(define (require-exp? term)
(and (pair? term) (equal? (car term) 'require)))
(define (require-modules! terms)
(define (require! module)
(namespace-require module))
(define (helper listof-modules)
(parameterize ((current-namespace handler-namespace))
(for-each (lambda (modules)
(for-each require! modules))
listof-modules)))
(helper (map cdr (filter require-exp? terms))))
(define (args-exp? term)
(and (pair? term) (equal? (car term) ':args)))
(define (terms->args terms)
(define (helper args)
(cond ((null? args) '()) ((not (null? (cdr args))) (error 'filter-args "multiple args statement: ~a" args))
(else (cdr (car args)))))
(helper (filter args-exp? terms)))
(define (terms->exps terms)
(let ((exps (filter (lambda (exp)
(and (not (require-exp? exp))
(not (args-exp? exp))))
terms)))
(if (null? exps)
'("") exps)))
(define (evaluate-terms terms path)
(require-modules! terms) (eval `(lambda ,(terms->args terms)
(parameterize ((__PATH__ ,(path->string path)))
. ,(terms->exps terms)))
handler-namespace))
(define (evaluate-script path)
(evaluate-terms (file->values path) path))
(define $server (make-parameter #f))
(define-struct script (path (timestamp #:mutable)))
(define (init-script path (base (shp-handler-path ($server))))
(let ((path (normalize-path path base)))
(make-script path 0)))
(define $chrome (make-parameter #f))
(define (make-chrome-based-handler inner)
(lambda ()
(let ((result (inner)))
(if ($chrome)
(with-handlers ((exn? (lambda (e)
(error 'make-chrome-based-handler
"chrome ~a failed: ~a" ($chrome) e))))
((evaluate-script (resolve-path ($chrome) #f)) result))
result))))
(define __PATH__ (make-parameter #f))
(define (include! path
#:top? (top? #f)
#:topfilter (topfilter #f)
#:partial? (partial? #f)
#:chrome? (chrome? #f)
. args)
(define (script-helper path partial?)
(with-handlers ((exn? (lambda (e)
(error 'include! "include ~a failed: ~a" path e))))
(evaluate-script (if (script? path)
(script-path path)
(resolve-path path partial?)))))
(define (helper topfilter)
(let ((proc
(let ((proc (script-helper path partial?)))
(if (and top? (not (thunk? proc)))
(script-helper (shp-handler-not-found ($server)) #f)
proc))
))
(let ((handler
(let ((handler (lambda () (apply proc args))))
(if chrome? (make-chrome-based-handler handler) handler))))
(if topfilter
(topfilter handler)
(handler)))))
(helper (if topfilter (script-helper topfilter #f) topfilter)))
(define (eval-script-if-changed! script)
(unless (not (file-exists? (script-path script)))
(let ((timestamp (file-or-directory-modify-seconds (script-path script))))
(when (> timestamp (script-timestamp script))
(set-script-timestamp! script timestamp)
(include! script)))))
(define (handle-request server request)
(parameterize (($server server)
($request request)
($pathinfo ($pathinfo))
($status ($status))
($content-type ($content-type)))
(parameterize (($cookies (init-cookies!))
($headers (convert-headers))
($chrome (shp-handler-chrome server)))
(eval-script-if-changed! (shp-handler-required server))
(with-handlers ((response/basic?
(lambda (r) r)))
(make-response (include! (request-uri request)
#:top? #t
#:topfilter (shp-handler-topfilter server)
#:chrome? #t
#:partial? #t))))))
(define-struct shp-handler (path default not-found required topfilter chrome htdocs)
#:property prop:procedure
(lambda ($struct request)
(handle-request $struct request)))
(define ($htdocs)
(shp-handler-htdocs ($server)))
(define (*make-shp-handler path
#:default (default "index")
#:not-found (not-found #f)
#:required (required "required")
#:topfilter (topfilter #f)
#:chrome (chrome #f)
#:htdocs (htdocs #f))
(unless (directory-exists? path)
(error 'make-shp-handler "path ~a does not exist." path))
(let ((htdocs
(let ((htdocs (if (not htdocs)
(build-path path 'up "file")
htdocs)))
(if (directory-exists? htdocs)
htdocs
(error 'make-shp-handler "htdocs path ~a does not exist." htdocs)))))
(make-shp-handler path
default
(if (not not-found)
(make-script (build-path (this-expression-source-directory) "example" "shp" "notfound") 0)
not-found)
(init-script required path)
topfilter
chrome
htdocs)))
(define (punt!)
(next-dispatcher))
(provide/contract
(rename *make-shp-handler make-shp-handler
(->* (path-string?)
(#:default path-string?
#:not-found (or/c #f path-string?)
#:required path-string?
#:topfilter (or/c #f path-string?)
#:chrome (or/c #f path-string?)
#:htdocs (or/c #f path-string?))
(-> request? response/c)))
($server (parameter/c (or/c #f shp-handler?)))
($chrome (parameter/c (or/c #f path-string?)))
($htdocs (-> path-string?))
(__PATH__ (parameter/c (or/c #f path-string?)))
)