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>
6 ;; Keywords: elnode html sprunge paste
7 ;; License: GPLV3 (see the COPYING file in this directory)
13 (defclass ews-server ()
14 ((handler :initarg :handler :accessor handler :initform nil)
15 (process :initarg :process :accessor process :initform nil)
16 (port :initarg :port :accessor port :initform nil)
17 (clients :initarg :clients :accessor clients :initform nil)))
19 (defvar ews-servers nil
20 "List holding all ews servers.")
22 (defvar ews-time-format "%Y.%m.%d.%H.%M.%S.%N"
23 "Logging time format passed to `format-time-string'.")
25 (defun ews-start (handler port &optional log-buffer host)
26 "Start a server using HANDLER and return the server object.
28 HANDLER should be a list of cons of the form (MATCH . ACTION),
29 where MATCH is either a function (in which case it is called on
30 the request object) or a cons cell of the form (KEYWORD . STRING)
31 in which case STRING is matched against the value of the header
32 specified by KEYWORD. In either case when MATCH returns non-nil,
33 then the function ACTION is called with two arguments, the
34 process and the request object.
36 For example, the following starts a simple hello-world server on
41 (lambda (proc request)
42 (process-send-string proc
43 \"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello world\r\n\")
48 (let ((server (make-instance 'ews-server :handler handler :port port)))
49 (setf (process server)
52 :service (port server)
58 :plist (list :server server)
60 (lexical-let ((buf log-buffer))
61 (lambda (server client message)
62 (let ((c (process-contact client)))
63 (with-current-buffer buf
64 (goto-char (point-max))
65 (insert (format "%s\t%s\t%s\t%s"
66 (format-time-string ews-time-format)
67 (first c) (second c) message)))))))))
68 (push server ews-servers)
71 (defun ews-stop (server)
73 (setq ews-servers (remove server ews-servers))
74 (mapc #'delete-process (append (mapcar #'car (clients server))
75 (list (process server)))))
77 (defun ews-parse (string)
79 ((string-match "^GET \\([^[:space:]]+\\) \\([^[:space:]]+\\)$" string)
80 (list (cons :GET (match-string 1 string))
81 (cons :TYPE (match-string 2 string))))
82 ((string-match "^\\([^[:space:]]+\\): \\(.*\\)$" string)
83 (list (cons (intern (concat ":" (upcase (match-string 1 string))))
84 (match-string 2 string))))
85 (:otherwise (message "[ews] bad header: %S" string) nil)))
87 (defun ews-filter (proc string)
88 (with-slots (handler clients) (plist-get (process-plist proc) :server)
89 ;; register new client
90 (unless (assoc proc clients) (push (list proc "") clients))
91 (let* ((client (assoc proc clients)) ; clients are (proc pending headers)
92 (pending (concat (cadr client) string))
94 (catch 'finished-parsing-headers
95 ;; parse headers and append to client
96 (while (setq index (string-match "\r\n" pending last-index))
97 (when (= last-index index) ; double \r\n, done headers, call handler
98 (throw 'finished-parsing-headers
99 (when (ews-call-handler proc (cddr client) handler)
100 (setq clients (assq-delete-all proc clients))
101 (delete-process proc))))
102 (setcdr (last client)
103 (ews-parse (substring pending last-index index)))
104 (setq last-index (+ index 2)))
105 (setcar (cdr client) (substring pending last-index))))))
107 (defun ews-call-handler (proc request handler)
108 (catch 'matched-handler
109 (mapc (lambda (handler)
110 (let ((match (car handler))
111 (function (cdr handler)))
112 (when (or (and (consp match)
113 (assoc (car match) request)
114 (string-match (cdr match)
115 (cdr (assoc (car match) request))))
116 (and (functionp match) (funcall match request)))
117 (throw 'matched-handler (funcall function proc request)))))
119 (error "[ews] no handler matched request:%S" request)))
121 (provide 'emacs-web-server)
122 ;;; emacs-web-server.el ends here