;;; httpd.el -- A web server in Emacs Lisp ;; Copyright (C) 2001, 2003, 2006, 2012 Free Software Foundation, Inc. ;; Author: Eric Marsden ;; John Wiegley ;; Michael Olson (slight modifications) ;; Version: 1.1 ;; Keywords: games ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License as ;; published by the Free Software Foundation; either version 3 of ;; the License, or (at your option) any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public ;; License along with this program; if not, write to the Free ;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, ;; MA 02111-1307, USA. ;; ;; The latest version of this package should be available from ;; ;; ;;; Commentary: ;; httpd.el is an HTTP server embedded in the Emacs. It can handle GET ;; and HEAD requests; adding support for POST should not be too ;; difficult. By default, httpd.el will listen on server side Emacs ;; sockets for HTTP requests. ;; ;; I have only tested this code with Emacs; it may need modifications ;; to work with XEmacs. ;; ;; This version has been modified to work with GNU Emacs 21 and 22. ;; ;;; Acknowledgements: ;; ;; httpd.el was inspired by pshttpd, an HTTP server written in ;; Postscript by Anders Karlsson . ;; ;; Thanks to John Wiegley and Cyprian Adam Laskowski. ;;; Code: (defvar httpd-document-root "/var/www") (defvar httpd-path-handlers '() "Alist of (path-regexp . handler) forms. If a GET request is made for an URL whose path component matches a PATH-REGEXP, the corresponding handler is called to generate content.") (defvar httpd-mime-types-alist '(("html" . "text/html; charset=iso-8859-1") ("txt" . "text/plain; charset=iso-8859-1") ("jpg" . "image/jpeg") ("jpeg" . "image/jpeg") ("gif" . "image/gif") ("png" . "image/png") ("tif" . "image/tiff") ("tiff" . "image/tiff") ("css" . "text/css") ("gz" . "application/octet-stream") ("ps" . "application/postscript") ("pdf" . "application/pdf") ("eps" . "application/postscript") ("tar" . "application/x-tar") ("rpm" . "application/x-rpm") ("zip" . "application/zip") ("mp3" . "audio/mpeg") ("mp2" . "audio/mpeg") ("mid" . "audio/midi") ("midi" . "audio/midi") ("wav" . "audio/x-wav") ("au" . "audio/basic") ("ram" . "audio/pn-realaudio") ("ra" . "audio/x-realaudio") ("mpg" . "video/mpeg") ("mpeg" . "video/mpeg") ("qt" . "video/quicktime") ("mov" . "video/quicktime") ("avi" . "video/x-msvideo"))) (defun httpd-mime-type (filename) (or (cdr (assoc (file-name-extension filename) httpd-mime-types-alist)) "text/plain")) (put 'httpd-exception 'error-conditions '(httpd-exception error)) (defun defhttpd-exception (name code msg) (put name 'error-conditions (list name 'httpd-exception 'error)) (put name 'httpd-code code) (put name 'httpd-msg msg)) (defhttpd-exception 'httpd-moved/perm 301 "Moved permanently") (defhttpd-exception 'httpd-moved/temp 302 "Moved temporarily") (defhttpd-exception 'httpd-bad-request 400 "Bad request") (defhttpd-exception 'httpd-forbidden 403 "Forbidden") (defhttpd-exception 'httpd-file-not-found 404 "Not found") (defhttpd-exception 'httpd-method-forbidden 405 "Method not allowed") (defhttpd-exception 'httpd-unimplemented 500 "Internal server error") (defhttpd-exception 'httpd-unimplemented 501 "Not implemented") (defhttpd-exception 'httpd-unimplemented 503 "Service unavailable") (defvar httpd-endl "\r\n") (defvar httpd-process nil) (defvar httpd-bytes-sent nil) ; only used with `httpd-process' (defvar httpd-log-accesses t) (defun httpd-add-handler (path-regexp handler) (push (cons path-regexp handler) httpd-path-handlers)) (defun httpd-try-internal-handler (path &optional cont) (catch 'result (dolist (elem httpd-path-handlers) (let ((regexp (car elem)) (handler (cdr elem))) (if (string-match regexp path) (throw 'result (funcall handler path cont))))))) (defun httpd-date-stamp () (format-time-string "[%d/%b/%Y %H:%M:%S %z]")) (defun httpd-log (&rest strings) (if httpd-log-accesses (save-excursion (goto-char (point-max)) (with-current-buffer (get-buffer-create "*httpd access_log*") (mapc 'insert strings))))) (defun httpd-send-data (&rest strings) (dolist (s strings) (send-string httpd-process s) (if httpd-bytes-sent (setq httpd-bytes-sent (+ httpd-bytes-sent (length s)))))) (defun httpd-send (code msg &rest strings) (httpd-log (number-to-string code) " ") (apply 'httpd-send-data "HTTP/1.0 " (number-to-string code) " " msg httpd-endl strings)) (defun httpd-send-eof () (httpd-log (number-to-string httpd-bytes-sent) "\n") (process-send-eof httpd-process)) (defun httpd-send-file (filename) (with-temp-buffer (insert-file-contents filename) (httpd-send-data (buffer-string)))) (defun httpd-lose (code msg) (httpd-send code msg "Content-Type: text/html" httpd-endl "Connection: close" httpd-endl httpd-endl "Error" httpd-endl "

" msg "

" httpd-endl "

" msg httpd-endl "" httpd-endl) (httpd-send-eof)) (defun httpd-handle-redirect (req where) "Redirect the client to new location WHERE." (httpd-send 301 "Moved permanently" "Location: " where httpd-endl "URI: " where httpd-endl "Connection: close" httpd-endl httpd-endl) (httpd-send-eof)) (defun httpd-handle-GET+HEAD (path &optional want-data req) (if (zerop (length path)) (setq path "index.html")) ;; could use `expand-file-name' here instead of `concat', but we ;; don't want tilde expansion, etc. (let ((filename (concat httpd-document-root "/" path)) modified-since) (cond ((httpd-try-internal-handler path) t) ((file-directory-p filename) (httpd-handle-redirect path (concat "http://" (system-name) "/" path "/"))) ((file-readable-p filename) (let ((attrs (file-attributes filename))) (if (and (string-match "^If-Modified-Since:\\s-+\\(.+\\)" req) (setq modified-since (apply 'encode-time (parse-time-string (match-string 1 req)))) (time-less-p (nth 5 attrs) modified-since)) (httpd-send 304 "Not modified" "Server: Emacs/httpd.el" httpd-endl "Connection: close" httpd-endl httpd-endl) (httpd-send 200 "OK" "Server: Emacs/httpd.el" httpd-endl "Connection: close" httpd-endl "MIME-Version: 1.0" httpd-endl "Content-Type: " (httpd-mime-type filename) httpd-endl "Content-Length: " (number-to-string (nth 7 attrs)) httpd-endl httpd-endl) (if want-data (httpd-send-file filename))) (httpd-send-eof))) (t (signal 'httpd-file-not-found path))))) (defun httpd-handle-request (req &optional cont) (httpd-log (car (process-contact httpd-process)) " - - " (httpd-date-stamp) " \"") (if (not (string-match ".+" req)) (progn (httpd-log "\"") (error "HTTP request was empty")) (let ((request (match-string 0 req))) (httpd-log request "\" ") (cond ((string-match "\\.\\." request) ;; reject requests containing ".." in the path. Should really ;; URI-decode first. (signal 'httpd-forbidden request)) ((string-match "\\`\\(GET\\|HEAD\\|POST\\)\\s-/\\(\\S-*\\)" request) (let ((kind (match-string 1 request)) (arg (match-string 2 request))) (if (string= kind "POST") (unless (httpd-try-internal-handler arg cont) (signal 'httpd-unimplemented arg)) (httpd-handle-GET+HEAD arg (string= kind "GET") req)))) (t (signal 'httpd-bad-request request)))))) (defun httpd-serve (proc string) (let ((httpd-process proc) (httpd-bytes-sent 0)) (condition-case why (httpd-handle-request string) (httpd-exception (httpd-lose (get (car why) 'httpd-code) (get (car why) 'httpd-msg))) ;; Comment out these two lines if you want to catch errors ;; inside Emacs itself. (error (httpd-lose 500 (format "Emacs Lisp error: %s" why))) ))) (defun httpd-start (&optional port) (interactive (list (read-string "Serve Web requests on port: " "8080"))) (if (null port) (setq port 8080) (if (stringp port) (setq port (string-to-number port)))) (if httpd-process (delete-process httpd-process)) (setq httpd-process (if (fboundp 'make-network-process) (make-network-process :name "httpd" :buffer (generate-new-buffer "httpd") :host 'local :service port :server t :noquery t :filter 'httpd-serve) (and (fboundp 'open-network-stream-server) (open-network-stream-server "httpd" (generate-new-buffer "httpd") port nil 'httpd-serve)))) (if (and (processp httpd-process) (eq (process-status httpd-process) 'listen)) (message "httpd.el is listening on port %d" port))) (defun httpd-stop () (interactive) (when httpd-process (message "httpd.el server on port %d has stopped" (cadr (process-contact httpd-process))) (delete-process httpd-process) (setq httpd-process nil))) (provide 'httpd) ;;; httpd.el ends here