(module servlet mzscheme (require (lib "contract.ss") (lib "class.ss") (lib "etc.ss") (lib "xml.ss" "xml")) (require "response.ss" "private/servlet.ss" "private/url.ss" "servlet-helpers.ss" "timer.ss" "web-cells.ss") ;; CONTRACT HELPERS (define servlet-response? any/c) (define (xexpr/callback? x) (correct-xexpr? x (lambda () #t) (lambda (exn) (if (procedure? (exn:invalid-xexpr-code exn)) #t (begin ((error-display-handler) (exn-message exn) exn) #f))))) (define response-generator? (string? . -> . servlet-response?)) (define url-transform? (string? . -> . string?)) (define expiration-handler? (request? . -> . void?)) (define (parameter/c c) parameter?) (define embed/url? (((request? . -> . any/c)) (expiration-handler?) . opt-> . string?)) ;; ************************************************************ ;; HELPERS ;; replace-procedures : (proc -> url) xexpr/callbacks? -> xexpr? ;; Change procedures to the send/suspend of a k-url (define (xexpr/callback->xexpr p->a p-exp) (cond [(list? p-exp) (map (lambda (p-e) (xexpr/callback->xexpr p->a p-e)) p-exp)] [(procedure? p-exp) (p->a p-exp)] [else p-exp])) ;; Weak contracts: the input is checked in output-response, and a message is ;; sent directly to the client (Web browser) instead of the terminal/log. (provide/contract [xexpr/callback? (any/c . -> . boolean?)] [xexpr/callback->xexpr (embed/url? xexpr/callback? . -> . xexpr?)] [current-url-transform (parameter/c url-transform?)] [current-servlet-continuation-expiration-handler (parameter/c expiration-handler?)] [redirect/get (-> request?)] [redirect/get/forget (-> request?)] [adjust-timeout! (number? . -> . void?)] [clear-continuation-table! (-> void?)] [send/back (any/c . -> . void?)] [send/finish (any/c . -> . void?)] [send/suspend ((response-generator?) (expiration-handler?) . opt-> . request?)] [send/forward ((response-generator?) (expiration-handler?) . opt-> . request?)] [send/suspend/dispatch ((embed/url? . -> . servlet-response?) . -> . any/c)] [send/suspend/callback (xexpr/callback? . -> . any/c)]) (require "url.ss") (provide (all-from "web-cells.ss") (all-from "servlet-helpers.ss") (all-from "url.ss")) ;; ************************************************************ ;; EXPORTS ;; current-url-transform : string? -> string? (define current-url-transform (make-parameter identity)) ;; current-servlet-continuation-expiration-handler : request -> response (define current-servlet-continuation-expiration-handler (make-parameter #f)) ;; adjust-timeout! : sec -> void ;; adjust the timeout on the servlet (define (adjust-timeout! secs) (send (current-servlet-manager) adjust-timeout! (get-current-servlet-instance-id) secs)) ;; ext:clear-continuations! -> void (define (clear-continuation-table!) (send (current-servlet-manager) clear-continuations! (get-current-servlet-instance-id))) ;; send/back: response -> void ;; send a response and don't clear the continuation table (define (send/back resp) (define ctxt (servlet-instance-data-context (current-servlet-instance-data))) (output-response (execution-context-connection ctxt) resp) ((execution-context-suspend ctxt))) ;; send/finish: response -> void ;; send a response and clear the continuation table (define (send/finish resp) (clear-continuation-table!) ; If we readjust the timeout to something small, the session will expire shortly ; we cannot wait for send/back to return, because it doesn't ; Also, we cannot get the initial-connection-timeout variable from here ; In the future, we should use the servlet's specific default-timeout (adjust-timeout! 10) (send/back resp)) ;; send/suspend: (url -> response) [(request -> response)] -> request ;; send a response and apply the continuation to the next request (define send/suspend (opt-lambda (response-generator [expiration-handler (current-servlet-continuation-expiration-handler)]) (with-frame-after (let/cc k (define instance-id (get-current-servlet-instance-id)) (define ctxt (servlet-instance-data-context (current-servlet-instance-data))) (define k-embedding (send (current-servlet-manager) continuation-store! instance-id k expiration-handler)) (define k-url ((current-url-transform) (embed-ids (list* instance-id k-embedding) (request-uri (execution-context-request ctxt))))) (define response (response-generator k-url)) (output-response (execution-context-connection ctxt) response) ((execution-context-suspend ctxt)))))) ;; send/forward: (url -> response) [(request -> response)] -> request ;; clear the continuation table, then behave like send/suspend (define send/forward (opt-lambda (response-generator [expiration-handler (current-servlet-continuation-expiration-handler)]) (clear-continuation-table!) (send/suspend response-generator expiration-handler))) ;; send/suspend/dispatch : ((proc -> url) -> response) [(request -> response)] -> request ;; send/back a response generated from a procedure that may convert ;; procedures to continuation urls (define (send/suspend/dispatch response-generator) ; This restores the tail position. ; Note: Herman's syntactic strategy would fail without the new-request capture. ; (Moving this to the tail-position is not possible anyway, by the way.) (let ([thunk (let/cc k0 (send/back (response-generator (opt-lambda (proc [expiration-handler (current-servlet-continuation-expiration-handler)]) (let/ec k1 (let ([new-request (send/suspend k1 expiration-handler)]) (k0 (lambda () (proc new-request)))))))))]) (thunk))) ;; send/suspend/callback : xexpr/callback? -> void ;; send/back a response with callbacks in it; send/suspend those callbacks. (define (send/suspend/callback p-exp) (send/suspend/dispatch (lambda (embed/url) (xexpr/callback->xexpr embed/url p-exp)))) ;; ************************************************************ ;; HIGHER-LEVEL EXPORTS (define ((make-redirect/get send/suspend)) (send/suspend (lambda (k-url) (redirect-to k-url temporarily)))) ; redirect/get : -> request (define redirect/get (make-redirect/get send/suspend)) (define redirect/get/forget (make-redirect/get send/forward)))