simple-httpd and impatient-mode
After settling in with MELPA I wanted to see about into turning my Emacs web server into an installable package. Someone had already uploaded my code to Marmalade after taking credit for all the work and slapping the GPL on it (my version is public domain). So, due to that and because the name
httpd.el is already overloaded as it is, I renamed it to
simple-httpd. That’s the name of the package in MELPA.
I did more than rename the package; it got an overhaul. I rewrote a few functions, tossed a whole bunch of functions, created a test suite, and finally added directory listings – a feature that had long been on the TODO list. To keep with the name “simple”, I ripped out the clunky servlet system (sorry Chunye). This new version was leaner, cleaner, and more useful.
I’ve definitely improved my software development skill over the last three years since I originally wrote it. In my refactor I made it buffer oriented. When a request comes in, the server fills a buffer with the response and sends it back. This means I could send a
Content-Length header and use keep-alive to serve multiple requests over one connection. It also suggested a new servlet paradigm – the servlet prepares a buffer and the server sends it to the client.
So I ended up adding servlet support again, from scratch. This time it’s really easy to use. Here’s a “Hello, World” servlet,
(defservlet hello-world text/plain () (insert "Hello, World"))
The “function name” part is the path to the servlet. This one would be found at
/hello-world. The second is the MIME type as a symbol. We’re just sending plain text in this example. The third is the argument list. A servlet takes up to three arguments: the path, the query alist, and the full request object (which includes the first two). Unless a more specific servlet is defined, this servlet handles everything under its root. In this case
/hello-world/foo/bar.txt. This is why the path argument is relevant.
This servlet uses the path to get a name,
(defservlet hello text/plain (path) (insert "hello, " (file-name-nondirectory path)))
If you visit
/hello/Chris it will send you “Hello, Chris”. Servlets are trivial to write!
This one serves the contents of the
(defservlet scratch text/plain () (insert-buffer-substring (get-buffer "*scratch*")))
In the background I continue to use Chunye’s symbol dispatch technique, so all servlets are actually functions that begin with
httpd/hello). For a more advanced servlet, the function can be written directly. There’s another macro,
with-httpd-buffer to help keep this simple. The server will always pass four arguments (the three servlet arguments plus one more), so when creating the function directly it needs to accept at least four arguments.
(defun httpd/hello (proc path &rest args) (with-httpd-buffer proc "text/plain" (insert "hello, " (file-name-nondirectory path))))
proc object here is the network connection process, providing more exclusive access to the client. This allows the servlet to do more interesting things like respond in the future (long polls). The
with-httpd-buffer macro creates a temporary buffer and, when the body completes, sends an HTTP header and the buffer as the content, similar to
With access to the process, the servlet can do more specialized things like send custom headers with
httpd-send-header, send files with
httpd-send-file, send an error page with
httpd-error, or do redirects with
httpd-redirect. The file server part of the server is actually just another a servlet as well:
httpd/. This could be redefined to redirect the browser to our example servlet (HTTP 301).
(defun httpd/ (proc &rest args) (httpd-redirect proc "/hello-world"))
I showed this to Brian, like I do everything, and he found my servlet concept to be compelling, especially the buffer-serving servlet. I believe his exact words were, “That’s so simple.” He found it interesting enough that he wrote a mode based on it called
It serves a buffer’s content live to the web browser, including syntax highlighting (via htmlize). Updates to the buffer are communicated by a long-poll. The browser initiates a request in the background for an update. Emacs adds the request to a list. A hook in
after-change-functions updates all the browsers waiting for an update.
impatient-mode, a minor mode, publishes the buffer. If the server’s running, the list of published buffers can be found under
/imp – i.e. http://localhost:8080/imp. The buffer can be accessed directly at
/imp/live/<buffer-name>, which is where
/imp will link.
Perhaps the coolest thing is serving an HTML buffer without htmlize. That is, send the raw buffer as
text/html. Brian has a demo of this in the linked post. You can tweak CSS and HTML and watch it update live in the browser as you edit. It’s a really neat way to edit CSS, since it’s often unintuitive (at least for me).
blog comments powered by Disqus
impatient-mode can also be installed through MELPA.