#lang scheme/base
(require web-server/http/request-structs
web-server/http/response-structs
web-server/dispatchers/dispatch
"base.ss"
"depend.ss"
"request.ss"
"response.ss"
"script.ss"
"cookie.ss"
"default.ss"
"convert.ss"
"query.ss"
"xmlrpc.ss"
"json.ss"
)
(define-namespace-anchor handler-namespace-anchor)
(define handler-namespace (namespace-anchor->namespace handler-namespace-anchor))
(provide/contract
(handler-namespace namespace?))
(define (punt!)
(next-dispatcher))
(define (evaluate path namespace)
(define (helper exp)
(eval exp namespace))
(for-each helper (file->values path)))
(define (load-required! shp)
(define (required?)
(with-handlers ((exn? (lambda (e) #f))
(http-result? (lambda (e) #f)))
(build-script-path shp (shp-config-ref shp 'required) #f)))
(if-it (required?)
(let ((path (shp-config-ref shp 'required-path)))
(when (or (not path) (> (*path-mtime it) (*path-mtime path)))
(shp-config-set! shp 'required-path it)
(evaluate (*path-full it) (shp-namespace shp))
(registry-clear! (shp-handlers shp))))
(void)))
(define (chrome-handler shp inner)
(lambda ()
(let ((result (inner)))
(if ($chrome)
((ensure-script-handler! shp ($chrome)) result)
result))))
(define (parse-xmlrpc inner)
(define (specs-helper script)
(if (webcall? script)
(args-converter-specs (webcall-args script))
(specs-helper (script-inner script))))
(define (match-composite-param-args args)
(define (helper rest-spec rest-args hash)
(cond ((null? rest-spec)
hash)
((null? rest-args)
hash)
(else
(let ((spec (car rest-spec)))
(helper (cdr rest-spec)
(cdr rest-args)
(hash-set hash
(symbol->string (convert-spec-key spec))
(car rest-args)))))))
(helper (specs-helper inner) args (make-immutable-hash '())))
(match-composite-param-args
(xmlrpc-params (xexpr->sxml
(read-xexpr (open-input-bytes (normalize-xml-bytes ($post/raw))))))))
(define (parse-request-for-webcall inner)
(let((content-type (if-it ($header "Content-Type")
(read-content-type it)
#f)))
(case ($method)
((post)
(cond ((and content-type
(string-ci=? (content-type/full content-type) "text/xml"))
(parse-xmlrpc inner))
((and content-type
(string-ci=? (content-type/full content-type) "text/json"))
(read-json ($post/raw)))
(else
(group-kvs ($queries)))))
(else
(group-kvs ($queries))))))
(define (handle-with-webcall shp inner topfilter status)
(define (response-helper v status)
(define (result-helper)
(let ((content-type (if-it ($header "Content-Type")
(read-content-type it)
#f)))
(case ($method)
((post)
(cond ((and content-type
(string-ci=? (content-type/full content-type) "text/json"))
handle-json-result)
(else handle-xmlrpc-result)))
(else handle-xmlrpc-result))))
(make-response ((result-helper) (unconvert v)) status))
(with-handlers ((exn?
(lambda (e)
(response-helper e 'internal-server-error))))
(parameterize ((use-webcall? #t))
(response-helper (if topfilter
(topfilter (lambda ()
(inner (parse-request-for-webcall inner))))
(inner (parse-request-for-webcall inner)))
status))))
(define (handle-with-topfilter shp inner (status ($status)))
(define (is-webcall? script)
(if (script? script)
(webcall? (script-inner script))
(webcall? script)))
(define (get-topfilter)
(if-it (shp-topfilter shp)
(ensure-script-handler! shp it)
#f))
(let ((topfilter (get-topfilter)))
(if (is-webcall? inner)
(handle-with-webcall shp inner topfilter status)
(let ((inner (chrome-handler shp inner)))
(make-response (if topfilter
(topfilter inner)
(inner))
status)))))
(define (handle-with-default-handler shp result)
(let* ((status (http-result-status result))
(inner (with-handlers ((exn?
(lambda (e)
(default-handler-ref ($status->code status)))))
(ensure-script-handler!
shp
(string-append "/" (symbol->string status))))))
(handle-with-topfilter shp
(lambda () (apply inner (http-result-args result))))))
(define (handle-via-webcall-first-then-default shp result)
(define (query-has-part-of-dispatcher)
(define (helper kvs)
(if (null? kvs)
#f
(let ((result (car kvs)))
(substring (car result)
2
(string-length (car result))))))
(helper (filter (lambda (kv)
(and (>= (string-length (car kv)) 2)
(equal? (substring (car kv) 0 2) "**")))
($queries))))
(define (this-is-an-xmlrpc-post-call)
(let ((content-type (if-it ($header "Content-Type")
(read-content-type it)
#f)))
(case ($method)
((post)
(if (string-ci=? (content-type/full content-type) "text/xml")
(xmlrpc-method (xexpr->sxml (read-xexpr (open-input-bytes (normalize-xml-bytes ($post/raw))))))
#f))
(else #f))))
(if (eq? (http-result-status result) 'not-found)
(cond-it ((query-has-part-of-dispatcher)
(with-handlers ((http-result?
(lambda (e)
(display (format "error!: ~a~n" e)
(current-error-port))
(handle-with-default-handler shp result))))
(handle-top-handler shp (build-path (url-path/string ($uri))
it))))
((this-is-an-xmlrpc-post-call)
(with-handlers ((http-result?
(lambda (e)
(display (format "error!: ~a~n" e)
(current-error-port))
(handle-with-default-handler shp result))))
(handle-top-handler shp (build-path (url-path/string ($uri))
it))))
(else
(handle-with-default-handler shp result)))
(handle-with-default-handler shp result)))
(define (handle-top-handler shp path)
(handle-with-topfilter shp (ensure-script-handler! shp path)))
(define (handle-request shp request)
(parameterize (($server shp)
($request request)
($pathinfo ($pathinfo))
($chrome (shp-config-ref shp 'chrome))
($status ($status))
($content-type ($content-type)))
(parameterize (($cookies (init-cookies!))
($headers (convert-headers))
($queries (convert-queries))
($response-headers ($response-headers)))
(with-handlers ((response/basic? identity)
(exn?
(lambda (e)
(display (format "error: ~a~n" e)(current-error-port))
(handle-with-default-handler
shp
(make-http-result 'internal-server-error (list e)))))
(http-result?
(lambda (e)
(handle-via-webcall-first-then-default shp e))))
(load-required! shp)
(handle-top-handler shp ($uri))))))
(define (make-shp-handler base
#:default (default "index")
#:required (required "/required")
#:topfilter (topfilter #f)
#:chrome (chrome #f)
#:htdocs (htdocs #f))
(define (make-htdocs base htdocs)
(define (helper)
(if htdocs
(build-path base htdocs)
(build-path base 'up "file")))
(let ((htdocs (helper)))
(if (directory-exists? htdocs)
htdocs
(error 'missing-htdocs-directory))))
(make-shp base
(make-immutable-hash-registry `((default . ,default)
(required . ,required)
(topfilter . ,topfilter)
(chrome . ,chrome)
(htdocs . ,(make-htdocs base htdocs))))
handle-request
(make-immutable-hash-registry)
handler-namespace))
(provide/contract
(make-shp-handler (->* (path-string?)
(#:default (or/c false/c path-string?)
#:required (or/c false/c path-string?)
#:topfilter (or/c false/c path-string?)
#:chrome (or/c false/c #t path-string?)
#:htdocs (or/c false/c path-string?))
shp?))
)