Initial.
						commit
						1dfd420bac
					
				| @ -0,0 +1,10 @@ | |||||||
|  | ;;; simple.meta -*- scheme -*- | ||||||
|  | 
 | ||||||
|  | ((egg "simple.egg") | ||||||
|  |  (synopsis "") | ||||||
|  |  (license "BSD") | ||||||
|  |  (category web) | ||||||
|  |  (author "Thomas Hintz") | ||||||
|  |  (depends intarweb spiffy spiffy-request-vars uri-common | ||||||
|  |           http-session spiffy-cookies sxml-transforms) | ||||||
|  |  (files "simple.setup" "simple.meta")) | ||||||
| @ -0,0 +1,278 @@ | |||||||
|  | (module simple | ||||||
|  |     (server-response-headers sid page-exception-message enable-session | ||||||
|  |      add-request-handler-hook! remove-request-handler-hook! | ||||||
|  |      define-page redirect-to enable-session-cookie session-cookie-setter | ||||||
|  |      session-cookie-name $session $session-set! $ page-access-denied-message | ||||||
|  |      debug-file page-access-control server-start write-chunk) | ||||||
|  | 
 | ||||||
|  | (import scheme chicken data-structures utils extras ports srfi-69 files srfi-1) | ||||||
|  | (use posix srfi-13 tcp) | ||||||
|  | (use intarweb spiffy spiffy-request-vars uri-common | ||||||
|  |      http-session spiffy-cookies sxml-transforms) | ||||||
|  | 
 | ||||||
|  | (define debug-file (make-parameter #f)) | ||||||
|  | (define server-response-headers (make-parameter #f)) | ||||||
|  | (define sid (make-parameter #f)) | ||||||
|  | (define page-exception-message | ||||||
|  |   (make-parameter | ||||||
|  |    (lambda (exn) | ||||||
|  |      '(<h3> "An error has occurred while processing your request.")))) | ||||||
|  | (define enable-session (make-parameter #f)) | ||||||
|  | (define enable-session-cookie (make-parameter #t)) | ||||||
|  | (define session-cookie-name (make-parameter "awful-cookie")) | ||||||
|  | (define session-cookie-setter (make-parameter | ||||||
|  |                                (lambda (sid) | ||||||
|  |                                  (set-cookie! (session-cookie-name) sid)))) | ||||||
|  | (define page-access-denied-message (make-parameter (lambda (path) '(<h3> "Access denied.")))) | ||||||
|  | (define page-access-control (make-parameter (lambda (path) #t))) | ||||||
|  | 
 | ||||||
|  | (define http-request-variables (make-parameter #f)) | ||||||
|  | 
 | ||||||
|  | (define awful-listen (make-parameter tcp-listen)) | ||||||
|  | (define awful-accept (make-parameter tcp-accept)) | ||||||
|  | (define awful-backlog (make-parameter 100)) | ||||||
|  | (define awful-listener (make-parameter | ||||||
|  |                         (let ((listener #f)) | ||||||
|  |                           (lambda () | ||||||
|  |                             (unless listener | ||||||
|  |                               (set! listener | ||||||
|  |                                     ((awful-listen) | ||||||
|  |                                      (server-port) | ||||||
|  |                                      (awful-backlog) | ||||||
|  |                                      (server-bind-address)))) | ||||||
|  |                             listener)))) | ||||||
|  | 
 | ||||||
|  | (define %redirect (make-parameter #f)) | ||||||
|  | (define %error (make-parameter #f)) | ||||||
|  | (define %page-title (make-parameter #f)) | ||||||
|  | (define *request-handler-hooks* '()) | ||||||
|  | 
 | ||||||
|  | (define *static-resources* (make-hash-table equal?)) | ||||||
|  | (define *proc-resources* '()) | ||||||
|  | 
 | ||||||
|  | (define sxml->html | ||||||
|  |   (make-parameter | ||||||
|  |    (let ((rules `((literal *preorder* . ,(lambda (t b) b)) | ||||||
|  |                   . ,universal-conversion-rules*))) | ||||||
|  |      (lambda (sxml) | ||||||
|  |        (SRV:send-reply (pre-post-order* sxml rules)))))) | ||||||
|  | 
 | ||||||
|  | (define (concat args #!optional (sep "")) | ||||||
|  |   (string-intersperse (map ->string args) sep)) | ||||||
|  | 
 | ||||||
|  | (define (string->symbol* str) | ||||||
|  |   (if (string? str) | ||||||
|  |       (string->symbol str) | ||||||
|  |       str)) | ||||||
|  | 
 | ||||||
|  | (define (add-request-handler-hook! name proc) | ||||||
|  |   (set! *request-handler-hooks* | ||||||
|  |         (alist-update! name proc *request-handler-hooks*))) | ||||||
|  | 
 | ||||||
|  | (define (remove-request-handler-hook! name) | ||||||
|  |   (set! *request-handler-hooks* | ||||||
|  |         (alist-delete! name *request-handler-hooks*))) | ||||||
|  | 
 | ||||||
|  | (define (reset-per-request-parameters) ;; to cope with spiffy's thread reuse | ||||||
|  |   (http-request-variables #f) | ||||||
|  |   (server-response-headers #f) | ||||||
|  |   (sid #f) | ||||||
|  |   (%redirect #f) | ||||||
|  |   (%error #f) | ||||||
|  |   (%page-title #f)) | ||||||
|  | 
 | ||||||
|  | (define (debug . args) | ||||||
|  |   (when (debug-file) | ||||||
|  |     (with-output-to-file (debug-file) | ||||||
|  |       (lambda () | ||||||
|  |         (print (concat args))) | ||||||
|  |       append:))) | ||||||
|  | 
 | ||||||
|  | (define-inline (use-session? use-session) | ||||||
|  |   (or (not (enable-session)) | ||||||
|  |       use-session | ||||||
|  |       (and (enable-session) (session-valid? (sid))))) | ||||||
|  | 
 | ||||||
|  | (define-inline (render-exception exn) | ||||||
|  |   (%error exn) | ||||||
|  |   (debug (with-output-to-string | ||||||
|  |            (lambda () | ||||||
|  |              (print-call-chain) | ||||||
|  |              (print-error-message exn)))) | ||||||
|  |   ((page-exception-message) exn)) | ||||||
|  | 
 | ||||||
|  | (define-inline (maybe-create/refresh-session! use-session) | ||||||
|  |   (when use-session | ||||||
|  |     (if (session-valid? (sid)) | ||||||
|  |         (server-refresh-session!) | ||||||
|  |         (begin | ||||||
|  |           (sid (session-create)) | ||||||
|  |           ((session-cookie-setter) (sid)))))) | ||||||
|  | 
 | ||||||
|  | (define (register-dispatcher) | ||||||
|  |   (handle-not-found | ||||||
|  |    (let ((old-handler (handle-not-found))) | ||||||
|  |      (lambda (_) | ||||||
|  |        (let* ((path-list (uri-path (request-uri (current-request)))) | ||||||
|  |               (method (request-method (current-request))) | ||||||
|  |               (dir? (equal? (last path-list) "")) | ||||||
|  |               (path (if (null? (cdr path-list)) | ||||||
|  |                         (car path-list) | ||||||
|  |                         (string-append "/" (concat (cdr path-list) "/")))) | ||||||
|  |               (proc (resource-ref path method))) | ||||||
|  |          (if proc | ||||||
|  |              (run-resource proc path) | ||||||
|  |              (if dir? ;; try to find a procedure with the trailing slash removed | ||||||
|  |                  (let ((proc (resource-ref (string-chomp path "/") method))) | ||||||
|  |                    (if proc | ||||||
|  |                        (run-resource proc path) | ||||||
|  |                        (old-handler _))) | ||||||
|  |                  (old-handler _)))))))) | ||||||
|  | 
 | ||||||
|  | (define (run-resource proc path) | ||||||
|  |   (reset-per-request-parameters) | ||||||
|  |   (let ((handler | ||||||
|  |          (lambda (path proc) | ||||||
|  |            (with-headers | ||||||
|  |             (or (server-response-headers) | ||||||
|  |                 `((content-type text/html) (transfer-encoding chunked))) | ||||||
|  |             (lambda () | ||||||
|  |               (write-logged-response) | ||||||
|  |               (proc) | ||||||
|  |               (unless (eq? 'HEAD (request-method (current-request))) | ||||||
|  |                 (if (%error) | ||||||
|  |                     (send-response code: 500 | ||||||
|  |                                    reason: "Internal server error" | ||||||
|  |                                    body: ((sxml->html) ((page-exception-message) | ||||||
|  |                                                         (%error))) | ||||||
|  |                                    headers: '((content-type text/html))) | ||||||
|  |                     (if (%redirect) ;; redirection | ||||||
|  |                         (let ((new-uri (if (string? (%redirect)) | ||||||
|  |                                            (uri-reference (%redirect)) | ||||||
|  |                                            (%redirect)))) | ||||||
|  |                           (with-headers `((location ,new-uri)) | ||||||
|  |                                         (lambda () | ||||||
|  |                                           (send-status 302 "Found")))))))))))) | ||||||
|  |     (call/cc (lambda (continue) | ||||||
|  |                (for-each (lambda (hook) | ||||||
|  |                            ((cdr hook) path | ||||||
|  |                                        (lambda () | ||||||
|  |                                          (handler path proc) | ||||||
|  |                                          (continue #f)))) | ||||||
|  |                          *request-handler-hooks*) | ||||||
|  |                (handler path proc))))) | ||||||
|  | 
 | ||||||
|  | (define (resource-match/procedure path method) | ||||||
|  |   (let loop ((resources *proc-resources*)) | ||||||
|  |     (if (null? resources) | ||||||
|  |         #f | ||||||
|  |         (let* ((current-path/proc (caar resources)) | ||||||
|  |                (current-method (cadar resources)) | ||||||
|  |                (current-proc (caddar resources))) | ||||||
|  |           (if (eq? current-method method) | ||||||
|  |               ;; the arg to be given to the page handler | ||||||
|  |               (let ((result (current-path/proc path))) | ||||||
|  |                 (if (list? result) | ||||||
|  |                     (lambda () (apply current-proc (cons path result))) | ||||||
|  |                     (loop (cdr resources)))) | ||||||
|  |               (loop (cdr resources))))))) | ||||||
|  | 
 | ||||||
|  | (define (resource-ref path method) | ||||||
|  |   (if (hash-table-exists? *static-resources* (list path method)) | ||||||
|  |       (let ((proc (hash-table-ref *static-resources* (list path method)))) | ||||||
|  |         (lambda () (proc path))) | ||||||
|  |       (resource-match/procedure path method))) | ||||||
|  | 
 | ||||||
|  | (define (add-resource! path proc method) | ||||||
|  |   (let ((methods (if (list? method) method (list method)))) | ||||||
|  |     (for-each | ||||||
|  |      (lambda (method) | ||||||
|  |        (let ((upcase-method | ||||||
|  |               (string->symbol (string-upcase (symbol->string method))))) | ||||||
|  |          (if (procedure? path) | ||||||
|  |              (set! *proc-resources* (cons (list path upcase-method proc) *proc-resources*)) | ||||||
|  |              (hash-table-set! *static-resources* | ||||||
|  |                               (list path upcase-method) proc)))) | ||||||
|  |      methods))) | ||||||
|  | 
 | ||||||
|  | (define (reset-resources!) | ||||||
|  |   (set! *static-resources* (make-hash-table equal?))) | ||||||
|  | 
 | ||||||
|  | ;;; Root dir | ||||||
|  | (define (register-root-dir-handler) | ||||||
|  |   (handle-directory | ||||||
|  |    (let ((old-handler (handle-directory))) | ||||||
|  |      (lambda (path) | ||||||
|  |        (cond ((resource-ref path (request-method (current-request))) | ||||||
|  |               => (cut run-resource <> path)) | ||||||
|  |              (else (old-handler path))))))) | ||||||
|  | 
 | ||||||
|  | (define (server-refresh-session!) | ||||||
|  |   (when (and (enable-session) (session-valid? (sid))) | ||||||
|  |     (session-refresh! (sid)))) | ||||||
|  | 
 | ||||||
|  | (define (write-chunk contents) | ||||||
|  |   (printf "~X" (string-length contents)) | ||||||
|  |   (display "\n") | ||||||
|  |   (display contents) | ||||||
|  |   (display "\n")) | ||||||
|  | 
 | ||||||
|  | (define (define-page path contents #!key css title doctype headers charset | ||||||
|  |           (method 'GET) use-session) ;; for define-session-page | ||||||
|  |   (##sys#check-closure contents 'define-page) | ||||||
|  |   (add-resource! | ||||||
|  |    path | ||||||
|  |    ;; args is path + any procedure match args | ||||||
|  |    (lambda args | ||||||
|  |      (sid (get-sid use-session)) | ||||||
|  |      (server-refresh-session!) | ||||||
|  |      (if (use-session? use-session) | ||||||
|  |          (if ((page-access-control) (car args)) | ||||||
|  |              (begin | ||||||
|  |                (maybe-create/refresh-session! use-session) | ||||||
|  |                (with-output-to-port (response-port (current-response)) | ||||||
|  |                  (lambda () | ||||||
|  |                    (apply contents (cdr args)) | ||||||
|  |                    (display "0\r\n\r\n"))) | ||||||
|  |                (finish-response-body (current-response)) | ||||||
|  |                ;; (handle-exceptions exn | ||||||
|  |                ;;   (render-exception exn) | ||||||
|  |                ;;   ;; TODO render-page | ||||||
|  |                ;;   ) | ||||||
|  |                ) | ||||||
|  |              ((page-access-denied-message) path))) | ||||||
|  |      ;; TODO what is this? (redirect-to-login-page | ||||||
|  |      ;;                    (or given-path path) | ||||||
|  |      ) | ||||||
|  |    method)) | ||||||
|  | 
 | ||||||
|  | (define (server-start) | ||||||
|  |   (let ((listener ((awful-listener)))) | ||||||
|  |     (register-root-dir-handler) | ||||||
|  |     (register-dispatcher) | ||||||
|  |     (accept-loop listener (awful-accept)))) | ||||||
|  | 
 | ||||||
|  | (define (get-sid #!optional force-read-sid) | ||||||
|  |   (and (or (enable-session) force-read-sid) | ||||||
|  |        (if (enable-session-cookie) | ||||||
|  |            (or (read-cookie (session-cookie-name)) ($ 'sid)) | ||||||
|  |            ($ 'sid)))) | ||||||
|  | 
 | ||||||
|  | (define (redirect-to new-uri) | ||||||
|  |   ;; Just set the `%redirect' internal parameter, so `run-resource' is | ||||||
|  |   ;; able to know where to redirect. | ||||||
|  |   (%redirect new-uri) | ||||||
|  |   "") | ||||||
|  | 
 | ||||||
|  | (define ($ var #!optional default/converter) | ||||||
|  |   (unless (http-request-variables) | ||||||
|  |     (http-request-variables (request-vars))) | ||||||
|  |   ((http-request-variables) var default/converter)) | ||||||
|  | 
 | ||||||
|  | (define ($session var #!optional default) | ||||||
|  |   (session-ref (sid) var default)) | ||||||
|  | 
 | ||||||
|  | (define ($session-set! var val) | ||||||
|  |   (session-set! (sid) var val)) | ||||||
|  | 
 | ||||||
|  | ) | ||||||
| @ -0,0 +1,9 @@ | |||||||
|  | ;;; simple.setup -*- scheme -*- | ||||||
|  | 
 | ||||||
|  | ;; Compile the extension | ||||||
|  | (compile -s -O3 -d1 -j simple simple.scm) | ||||||
|  | (compile -s -O3 -d1 simple.import.scm) | ||||||
|  | 
 | ||||||
|  | (install-extension 'simple | ||||||
|  |                    '("simple.so" "simple.import.so") | ||||||
|  |                    `((version "0.1.0"))) | ||||||
					Loading…
					
					
				
		Reference in New Issue