;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: TBNL; Base: 10 -*- ;;; $Header: /usr/local/cvsrep/tbnl/modlisp.lisp,v 1.30 2005/03/14 21:42:30 edi Exp $ ;;; Copyright (c) 2004-2005, Dr. Edmund Weitz. All rights reserved. ;;; Redistribution and use in source and binary forms, with or without ;;; modification, are permitted provided that the following conditions ;;; are met: ;;; * Redistributions of source code must retain the above copyright ;;; notice, this list of conditions and the following disclaimer. ;;; * Redistributions in binary form must reproduce the above ;;; copyright notice, this list of conditions and the following ;;; disclaimer in the documentation and/or other materials ;;; provided with the distribution. ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (in-package #:tbnl) (defun write-header-line (key value) "Accepts a KEY and a VALUE and writes them, one line at a time, to the mod_lisp or HTTP/araneida socket stream." (cond (*modlisp-headers* (write-header-line/modlisp key value)) (t (write-header-line/http key value)))) (defun write-header-line/modlisp (key value) "Accepts a KEY and a VALUE and writes them, one line at a time, to the mod_lisp socket stream" (write-string key *apache-stream*) (write-char #\NewLine *apache-stream*) ;; remove line breaks which would confuse mod_lisp (format *apache-stream* "~A" (cl-ppcre:regex-replace-all "[\\r\\n]" value " ")) (write-char #\NewLine *apache-stream*)) (defun write-header-line/http (key value) "Accepts a KEY and a VALUE and writes them, one line at a time, to the http/araneida socket stream" (if (string= "Status" key) (format *apache-stream* "HTTP/1.0 ~A" (cl-ppcre:regex-replace-all "[\\r\\n]" value " ")) (format *apache-stream* "~A: ~A" key (cl-ppcre:regex-replace-all "[\\r\\n]" value " "))) (write-char #\Return *apache-stream*) (write-char #\NewLine *apache-stream*)) (defun send-output (content) "Sends all headers and the content body to Apache/mod_lisp. Handles the supported return codes accordingly. Called by PROCESS-APACHE-COMMAND." (let* ((return-code (return-code *reply*)) (status-line (status-line return-code)) (head-request-p (string-equal (request-method :as-keyword nil) "HEAD"))) (unless status-line (setq content (escape-for-html (format nil "Unknown http return code: ~A" return-code)) return-code +http-internal-server-error+ status-line (status-line return-code))) (unless (member return-code `(,+http-ok+ ,+http-not-modified+)) ;; call error handler, if any - should return NIL if it can't ;; handle the error. (when *http-error-handler* (setq content (funcall *http-error-handler* return-code))) ;; handle common return codes other than 200, which weren't ;; handled by the error handler. (unless content (setf (content-type *reply*) "text/html; charset=iso-8859-1" content (format nil "
~A~:[~*~;~%~%~A~]" (escape-for-html (format nil "~A" error)) *show-lisp-backtraces-p* (escape-for-html (format nil "~A" backtrace)))) (error "An error has occured") (t body)))))) (loop for path in *tmp-files* when (and (pathnamep path) (probe-file path)) do (ignore-errors (delete-file path)))))) (defun read-http-headers () (let ((headers nil)) (labels ((read-header-line () "Read a header line, considering continuations" (with-output-to-string (header-line) (loop (let* ((line (read-line *apache-stream* t nil)) (end (position #\Return line)) (next (and (> end 0) (peek-char nil *apache-stream* nil nil)))) (write-sequence line header-line :end end) (unless (or (eql next #\Space) (eql next #\Tab)) (return)))))) (split-header (line) (unless (or (not line) (zerop (length line))) (destructuring-bind (key value) (cl-ppcre:split ":" line :limit 2) (cons (nstring-capitalize key) (string-trim " " value))))) (add-header (pair) (let* ((existing-header (string-assoc (car pair) headers))) (if existing-header (rplacd (string-assoc (car pair) headers) (cdr pair)) (push pair headers))))) (loop (let ((pair (split-header (read-header-line)))) (unless pair (return)) (add-header pair))) headers))) (defun read-http-request (first-line &optional (default-port *apache-port*)) (let ((*modlisp-headers* nil)) (destructuring-bind (method url-string &optional protocol) (cl-ppcre:split " " first-line :limit 3) (declare (ignorable method protocol)) (let ((headers (and protocol (read-http-headers)))) (push (cons "method" method) headers) (push (cons "url" url-string) headers) (when protocol (push (cons "server-protocol" (string-trim '(#\Space #\NewLine #\Return) protocol)) headers)) (push (cons "content-stream" *apache-stream*) headers) ;(push (cons "server-ip-port" (format nil "~d" default-port)) headers) (multiple-value-bind (result code) (ignore-errors (process-apache-command headers) (force-output *apache-stream*)) (declare (ignorable result)) (declare (ignorable code)) (when code (format t "Error in TBNL:: CODE ~S -- RESULT ~S~%" code result)) (setf *close-apache-stream* t) nil))))) (defun get-apache-command () "Reads alternating key/value lines and posted content \(if any) from mod_lisp. Returns the results as an alist." (ignore-errors (let ((first-line (read-line *apache-stream* nil nil))) (if (find #\Space first-line) (read-http-request first-line) (let ((second-line (read-line *apache-stream* nil nil))) (let* ((headers (loop for key = (read-line *apache-stream* nil nil) while (and key (string-not-equal key "end")) for value = (read-line *apache-stream* nil nil) collect (cons key value))) (content-length (string-assoc "content-length" headers))) (push (cons first-line second-line) headers) (when content-length (push (cons "content-stream" *apache-stream*) headers)) headers)))))) (defun apache-listen (*apache-stream* command-processor &rest args) "Listens on *APACHE-STREAM* for commands from mod_lisp. Packages the command using GET-APACHE-COMMAND and passes it to the COMMAND-PROCESSOR function \(which is PROCESS-APACHE-COMMAND). ARGS are ignored. Designed to be called by a KMRCL:LISTENER object." (declare (ignore args)) (let ((*close-apache-stream* t)) (unwind-protect (loop for *apache-socket-usage-counter* from 0 do (let ((command (debug-value *command* (get-apache-command)))) (ignore-errors (when command (cond ((ignore-errors (funcall command-processor command)) (ignore-errors (force-output *apache-stream*))) (t ;; if an error occured during processing of ;; COMMAND we close this particular connection ;; to Apache (ignore-errors (setq *close-apache-stream* t))))) (when *close-apache-stream* (return))))) (ignore-errors (kmrcl:close-active-socket *apache-stream*))))) (defun start-tbnl () "Starts listening on port *APACHE-PORT* if needed. Initializes *SESSION-SECRET* if needed. Returns the newly created or already existing KMRCL:LISTENER object." (unless (boundp '*session-secret*) (reset-session-secret)) (cond ((and (boundp '*listener*) *listener*) (warn "The variable *LISTENER* is already bound to a true value") *listener*) (t (setq *listener* (make-instance 'kmrcl:listener :port *apache-port* :base-name "tbnl" :function 'apache-listen :function-args (cons 'process-apache-command nil) :format :text :wait nil :catch-errors t :timeout nil :number-fixed-workers nil :remote-host-checker nil)) (kmrcl:init/listener *listener* :start)))) (defun stop-tbnl () "Stops the KMRCL:LISTENER object bound to *LISTENER* if it exists." (cond ((and (boundp '*listener*) *listener* (typep *listener* 'kmrcl:listener)) (kmrcl:init/listener *listener* :stop) (setq *listener* nil)) (t (warn "The variable *LISTENER* is not bound to a KMRCL:LISTENER object") nil)) (values))