This is a tutorial that will hopefully show you how to install and get started making web services with Elnode.
Elnode is a node.js like webserver tool for Emacs. It let's you make and run web servers and services from inside Emacs.
You should install Elnode from the package available on Marmalade.
For dealing with package repositories check out the Emacs Wiki but the short version is to add the following to your .emacs or your .emacs.d/init.el:
(add-to-list 'package-archives '("marmalade" . "http://marmalade-repo.org/packages/"))
And then do:
M-x list-packages
find Elnode in the list and press i or RET to install it.
If you don't want to use packages you can just install elnode.el on your load-path somewhere and:
(require 'elnode)
Now we've installed Elnode you'll want to start making web services with it. Let's start with a Hello World example.
open a new Emacs file
C-x C-f my-elnode-hello-world.el
enter the Lisp code for the handler, for that old time feel you could type this in, or if you're under 35, maybe just cut and paste
(defun my-elnode-hello-world-handler (httpcon) (elnode-http-start httpcon 200 '("Content-Type" . "text/html")) (elnode-http-return httpcon "<html><body><h1>Hello World</h1></body></html>")) (elnode-start 'my-elnode-hello-world-handler :port 8028 :host "localhost")
make the Lisp code live
M-x eval-buffer
now open http://localhost:8028 in your browser - you should see Hello World!
Elnode provides a builtin webserver that can serve files from a directory on your computer. The Elnode webserver is turned on by default (it's all configurable though).
By default the webserver delivers files from:
~/public_html
so if you have a public_html directory in your home directory then just browse to http://localhost:8000 and you should see an index of that directory.
If you don't have a ~/public_html directory then just make one and drop a file or two in it.
Alternately, try configuring the webserver root directory:
M-x customize-variable RET elnode-webserver-docroot RET
to another directory. Then try hitting http://localhost:8000 again.
Now let's make a new webserver service.
Make a new docroot:
mkdir ~/myspecialdocroot
Put an html file in there:
cat <<EOF > ~/myspecialdocroot/saybum.html <html> <h1>BUM!</h1> </html>
Now we have something to serve we can use Elnode to make the web service.
Open a new Emacs file:
C-x C-f my-elnode-webserver.el
Add this Lisp:
(defconst my-elnode-webserver-handler (elnode-webserver-handler-maker "~/myspecialdocroot")) (elnode-start my-elnode-webserver-handler :port 8001 :host "localhost")
Now evaluate that with: M-x eval-buffer
Now open http://localhost:8001/saybum.html
Now open http://localhost:8001 - you should see an automated index of ~/myspecialdocroot.
We've started a couple of servers now. Let's stop the two servers that we've started:
M-x elnode-stop RET 8028 RET M-x elnode-stop RET 8001 RET
Those servers are now stopped and you won't be able to hit them.
Instead of starting new servers all the time we can add bindings to the standard Elnode server. Why would we do this? I think using a separate server for developing something initially is a good idea, but then you either have something you want to package up as it's own server (a wiki engine you've developed and want to give to other people, for example) or you have something you want to make available in your own default server. Of course, it's always a judgement, the way URLs work mean that you can pretty much always make any service available on it's own server or under a URL on another one.
Let's make our Hello World example available again by binding it to the default server (which is still listening on port 8000 if you haven't changed anything).
Go back to hello world:
C-x b my-elnode-hello-world.el
Remove the elnode-start line and add this:
(add-to-list 'elnode-hostpath-default-table '("/helloworld/" . my-elnode-hello-world-handler))
So now it should look like this:
(defun my-elnode-hello-world-handler (httpcon) (elnode-http-start httpcon 200 '("Content-Type" . "text/html")) (elnode-http-return httpcon "<html><body><h1>Hello World</h1></body></html>")) (add-to-list 'elnode-hostpath-default-table '("/helloworld/" . my-elnode-hello-world-handler))
Now eval the buffer with M-x eval-buffer
Now open http://localhost:8000/helloworld/ in your browser.
Just to prove the webserver is still there, open http://localhost:8000/. This should still show your ~/public_html directory (or whatever you configured elnode-webserver-docroot to).
Check the variable elnode-hostpath-default-table with C-h v elnode-hostpath-default-table
The value should be something like:
(("/helloworld/" . my-elnode-hello-world-handler) ("[^/]+/.*" . elnode-webserver))
elnode-hostpath-default-table can also be customized to add more services. But any handler mapped in there will have to be loaded in at Emacs startup so you either need to package and load your Elnode code or put it in your load-path and require it from Emacs init.
So far, all the examples have been quite trivial. Though I hope you think it's interesting that you can do all these things quite easily from inside Emacs.
But now let's try something harder - let's make an web based editor.
This is an exercise that will grow with the tutorial. I hope you'll be interested in the first draft, even though it's going to be relatively simple.
Make a new file C-x C-f my-elnode-editor.el.
Add the following Lisp code:
(defvar my-elnode-editor-buffer (get-buffer-create "*my-elnode-editor-buffer*")) (defun my-elnode-editor-handler (httpcon) (elnode-http-start httpcon 200 '("Content-Type" . "text/plain")) (elnode-http-return httpcon (with-current-buffer my-elnode-editor-buffer (buffer-substring-no-properties (point-min) (point-max)))))
Eval that with M-x eval-buffer.
Now go and type some text in *my-elnode-editor-buffer*. This will be served by the editor service.
Now let's start the service:
M-x elnode-start my-elnode-editor-handler 8002 localhost
Now try and hit http://localhost:8002 - you should see whatever you typed in the *my-elnode-editor-buffer*.
Try updating the text in the buffer and refreshing the browser. We're displaying that buffer whatever it has in it.
Ok. So we've published a buffer. But what about someone else updating it?
Let's make another handler to handle updates, add this to your my-elnode-editor.el:
(defun my-elnode-editor-update-handler (httpcon) (let ((change-text (elnode-http-param httpcon "change"))) (with-current-buffer my-elnode-editor-buffer (goto-char (point-max)) (if (stringp change-text) (insert change-text)))) (elnode-http-start httpcon 302 '("Location" . "/")) (elnode-http-return httpcon))
Now we have two handlers we'll have to map them together somehow. Let's map one to the root (/) and one to /update/. Add the following code to my-elnode-editor.el:
(defconst my-elnode-editor-urls `(("^/$" . my-elnode-editor-handler) ("^/update/.*$" . my-elnode-editor-update-handler)))
And now we need to add a handler to do the dispatching for these URLs, add this to my-elnode-editor.el as well:
(defun my-elnode-editor-dispatcher-handler (httpcon) (elnode-dispatcher httpcon my-elnode-editor-urls))
What is a dispatcher? - a dispatcher is a handler that take a list of URL pattern mappings and works out, by reading the data from the HTTP connection, what handler should be invoked for what request.
Now we have our new dispatcher based code we need to stop the old server:
M-x elnode-stop 8002
And now start the new server with the dispatcher handler:
M-x elnode-start my-elnode-editor-dispatcher-handler 8002 localhost
Now visit http://localhost:8002 and see the buffer as it stands and then visit http://localhost:8002/update/?change=%0dlah+dee+dah%0d and see the updated buffer.
Let's take our editor on another step. Let's add some static files and have the Elnode handlers be called by client side Javascript.
If we're going to add some static files, we'll need a webserver. We already know how to do that. Once we've got some javascript though, we'll probably not want to retrieve the text by HTTP GETing the root url, so let's alter that binding to /text/ as well:
(defconst my-elnode-editor-webserver-handler (elnode-webserver-handler-maker "~/my-directory") "The webserver handler.") (defconst my-elnode-editor-urls '(("^/text/$" . my-elnode-editor-handler) ("^/update/.*$" . my-elnode-editor-update-handler) ("^/[^/]+/.*$" . my-elnode-editor-webserver-handler)))
Obviously ~/my-directory needs to be the place where you are going to save your HTML and Javascript files.
Now we need those HTML and Javascript files. Let's make the HTML first:
<html> <head> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" language="Javascript"> </script> <script src="my-elnode-editor.js" language="Javascript"> </script> </head> <body> <textarea id="text" cols="60" rows="10"> </textarea> </body> </html>
We're going to pull jQuery from Google's Content Delivery Network. We've put in a placeholder for our own Javascript file and other than that the HTML is really just a textarea element. We'll use that for putting the buffer text in.
Now, what should the Javascript do?
- when the page loads
- make an AJAX call to Elnode for the buffer text
- stick the received text into the textarea
Ok. So here is my-elnode-editor.js:
var my_elnode_editor = (function () { var self = { /** Get the text from Emacs. */ get_text: function () { $.ajax("/text/", { dataType: "text", success: function (data, textStatus, jqXHR) { $("#text").text(data); } }); } }; return self; })(); $(document).ready( function () { my_elnode_editor.get_text(); } );
Save this as my-elnode-editor.js (in whatever directory the webserver is serving) and save the HTML in the same directory, call it my-elnode-editor.html, say?
You don't even have to restart the Elnode handler, because it already is pointing to the dispatcher handler. If you just:
M-x eval-buffer
this will re-evaluate the URL mappings. Now if you visit http://localhost:8002/my-elnode-editor.html you should see the webpage with the textarea and the text of your buffer.
This is as far as Nic has got writing the tutorial. More will come soon I hope:
- defer with an example based around the editor service
- debugging a running Elnode service