1 ;;; emacs-web-server.el --- Emacs Web Server
3 ;; Copyright (C) 2013 Eric Schulte <schulte.eric@gmail.com>
5 ;; Author: Eric Schulte <schulte.eric@gmail.com>
7 ;; License: GPLV3 (see the COPYING file in this directory)
10 (require 'emacs-web-server-status-codes)
15 (defclass ews-server ()
16 ((handler :initarg :handler :accessor handler :initform nil)
17 (process :initarg :process :accessor process :initform nil)
18 (port :initarg :port :accessor port :initform nil)
19 (clients :initarg :clients :accessor clients :initform nil)))
21 (defvar ews-servers nil
22 "List holding all ews servers.")
24 (defvar ews-time-format "%Y.%m.%d.%H.%M.%S.%N"
25 "Logging time format passed to `format-time-string'.")
27 (defun ews-start (handler port &optional log-buffer host)
28 "Start a server using HANDLER and return the server object.
30 HANDLER should be a list of cons of the form (MATCH . ACTION),
31 where MATCH is either a function (in which case it is called on
32 the request object) or a cons cell of the form (KEYWORD . STRING)
33 in which case STRING is matched against the value of the header
34 specified by KEYWORD. In either case when MATCH returns non-nil,
35 then the function ACTION is called with two arguments, the
36 process and the request object.
38 For example, the following starts a simple hello-world server on
43 (lambda (proc request)
44 (process-send-string proc
45 \"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello world\r\n\")
49 Equivalently, the following starts an identical server using a
50 function MATCH and the `ews-response-header' convenience
55 (lambda (proc request)
56 (ews-response-header proc 200 '(\"Content-type\" . \"text/plain\"))
57 (process-send-string proc \"hello world\")
62 (let ((server (make-instance 'ews-server :handler handler :port port)))
63 (setf (process server)
66 :service (port server)
72 :plist (list :server server)
74 (lexical-let ((buf log-buffer))
75 (lambda (server client message)
76 (let ((c (process-contact client)))
77 (with-current-buffer buf
78 (goto-char (point-max))
79 (insert (format "%s\t%s\t%s\t%s"
80 (format-time-string ews-time-format)
81 (first c) (second c) message)))))))))
82 (push server ews-servers)
85 (defun ews-stop (server)
87 (setq ews-servers (remove server ews-servers))
88 (mapc #'delete-process (append (mapcar #'car (clients server))
89 (list (process server)))))
91 (defun ews-parse (string)
93 ((string-match "^GET \\([^[:space:]]+\\) \\([^[:space:]]+\\)$" string)
94 (list (cons :GET (match-string 1 string))
95 (cons :TYPE (match-string 2 string))))
96 ((string-match "^\\([^[:space:]]+\\): \\(.*\\)$" string)
97 (list (cons (intern (concat ":" (upcase (match-string 1 string))))
98 (match-string 2 string))))
99 (:otherwise (message "[ews] bad header: %S" string) nil)))
101 (defun ews-filter (proc string)
102 ;; TODO: parse post DATA, see the relevent test, and use these
103 ;; - mail-header-parse-content-disposition
104 ;; - mail-header-parse-content-type
105 (with-slots (handler clients) (plist-get (process-plist proc) :server)
106 ;; register new client
107 (unless (assoc proc clients) (push (list proc "") clients))
108 (let* ((client (assoc proc clients)) ; clients are (proc pending headers)
109 (pending (concat (cadr client) string))
110 (last-index 0) index in-post)
111 (catch 'finished-parsing-headers
112 ;; parse headers and append to client
113 (while (setq index (string-match "\r\n" pending last-index))
114 ;; double \r\n outside of post data -> done w/headers, call handler
115 (when (and (not in-post) (= last-index index))
116 (throw 'finished-parsing-headers
117 (when (ews-call-handler proc (cddr client) handler)
118 (setq clients (assq-delete-all proc clients))
119 (delete-process proc))))
121 ;; build up post data, maybe set in-post to boundary
122 (error "TODO: handle POST data")
123 (let ((this (ews-parse (substring pending last-index index))))
124 (if (eql (caar this) :CONTENT-TYPE)
125 (error "TODO: handle POST data")
126 (setcdr (last client) this))))
127 (setq last-index (+ index 2)))
128 (setcar (cdr client) (substring pending last-index))))))
130 (defun ews-call-handler (proc request handler)
131 (catch 'matched-handler
132 (mapc (lambda (handler)
133 (let ((match (car handler))
134 (function (cdr handler)))
135 (when (or (and (consp match)
136 (assoc (car match) request)
137 (string-match (cdr match)
138 (cdr (assoc (car match) request))))
139 (and (functionp match) (funcall match request)))
140 (throw 'matched-handler (funcall function proc request)))))
142 (error "[ews] no handler matched request:%S" request)))
145 ;;; Convenience functions to write responses
146 (defun ews-response-header (proc code &rest header)
147 "Send the headers for an HTTP response to PROC.
148 Currently CODE should be an HTTP status code, see
149 `ews-status-codes' for a list of known codes."
152 (format "HTTP/1.1 %d %s" code (cdr (assoc code ews-status-codes)))
153 (mapcar (lambda (h) (format "%s: %s" (car h) (cdr h))) header))))
154 (setcdr (last headers) (list "" ""))
155 (process-send-string proc (mapconcat #'identity headers "\r\n"))))
157 (provide 'emacs-web-server)
158 ;;; emacs-web-server.el ends here