I fucking love the Manos do Mono manifesto. It has very grand ideals and a philosophy of simplicity which I really dig, it is also excellently illustrated with pictures of cats. I've wanted try it since listening to Jackson Harper talking about it on a Herding Code podcast last year.

Installing Mono

I'm not sure if I needed to upgrade from version 2.6.7 to use Manos, which is in included with the Linux Mint distribution I use, but I knew I would need a newer version to for some other stuff I want to do. So I built and installed the latest Mono (2.11) from source, which was pretty straight forward.

$ git clone git://github.com/mono/mono.git
$ cd mono
$ ./autogen.sh --prefix=/usr/local
**Error**: You must have 'libtool' installed to compile Mono.

Ok, install libtool.

$ sudo apt-get install libtool
$ ./autogen.sh --prefix=/usr/local
..
configure: error: msgfmt not found. You need to install the 'gettext' package, or pass --enable-nls=no to configure.

I think I have gettext, oh well.

$ ./autogen.sh --prefix=/usr/local --enable-nls=no
$ make
$ make install

And make sure the new Mono works, awesome.

$ mono --version
Mono JIT compiler version 2.11 (master/fbff787 Fri Jul 15 17:54:49 EST 2011)
Copyright (C) 2002-2011 Novell, Inc, Xamarin, Inc and Contributors. www.mono-project.com
        TLS:           __thread
        SIGSEGV:       altstack
        Notifications: epoll
        Architecture:  amd64
        Disabled:      none
        Misc:          softdebug
        LLVM:          supported, not enabled.
        GC:            Included Boehm (with typed GC and Parallel Mark)

Installing Manos

Manos was easy to build and install from source.

$ git clone https://jacksonh@github.com/jacksonh/manos.git
$ ./autogen.sh
$ make
$ sudo make install

Getting started

Manos has a command line tool like rails to create, build and serve websites.

$ manos
manos usage is: manos [command] [options]

  -h, -?, --help
      --init, -i
      --server, -s
      --docs, -d
      --build, -b
      --show-environment, --se

      --run, -r=VALUE

The --init option creates a new application.

$ manos --init ManosChat
initing: ManosChat
$ cd ManosChat/ && ls
ManosChat.cs  StaticContentModule.cs

Which creates a new folder with two files. The initial app is setup to serve static content from a content folder.

We'll replace those files with a ridiculously simple long polling "chat" application which will consist of three files; a C# file, a HTML page and a JavaScript file.

The Manos application has four routes, one for the HTML page, the JavaScript file and routes to post messages and wait for updates. The routing metadata hopefully looks pretty familiar.

That is it!

using Manos;
using System;
using System.IO;
using System.Threading;
using System.Collections.Generic;

namespace ManosChat {

    public class ManosChat : ManosApp {

        public List<IManosContext> _waiting;

        public ManosChat ()
        {
            _waiting = new List<IManosContext>();
        }

        [Post ("/send")]
        public void Send(IManosContext context)
        {
            var message = context.Request.PostData["message"];

            foreach(var listener in _waiting) {
                try {
                    listener.Response.End(message);
                } catch(Exception) {
                }
            }
            _waiting.Clear();

            context.Response.End();
        }

        [Post ("/wait")]
        public void Wait(IManosContext context)
        {
            _waiting.Add(context);
        }

        [Get("/")]
        public void Home(IManosContext context)
        {
            var content = File.ReadAllText("index.html");
            context.Response.End(content);
        }

        [Get("/app.js")]
        public void Script(IManosContext context)
        {
            var content = File.ReadAllText("app.js");
            context.Response.End(content);
        }

    }
}

My HTML looks something like this.

<!DOCTYPE HTML>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Manos Chat</title>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
    <script type="text/javascript" src="/app.js"></script>
</head>
<body>
    <h1>Chat</h1>
    <input type="text" id="message" name="message" />
    <input type="submit" id="send-message" name="Send" />
    <ul id="messages">
    </ul>
</body>
</html>

And my JavaScript something like this.

$(function() {

    var showMessage = function(message) {
        $('#messages').prepend($('<li/>').append(message));
        };

    var getMessages = function() {
        $.ajax({
              type: 'POST',
              url: '/wait',
              data: 'data=none',
              success: function(data) {
                showMessage(data);
                getMessages(); }
              });
        };

    var sendMessage = function(message) {
         $.ajax({
              type: 'POST',
              url: '/send',
              data: 'message=' +  message,
              success: function(data) {
                }
              });
    }

    $('#send-message').click(function(e) {
        e.preventDefault();
            var m = $('#message').val();
            sendMessage(m);
        });

    getMessages();

});

Building and Running

The web application can be built and served using the Manos command-line tool

$ manos --build
$ manos --server
Running ManosChat.ManosChat on port 8080.

Now we point some browser windows at the server and start chatting.

chat in the browser

Some Notes

The chat browser windows are "Waiting for localhost" even though the page has fully loaded. This is because we are not responding to the request until we have a message, this is known as long polling. A common way to avoid this is to make the request from inside an iframe. And yes, it is possible for clients to miss messages between requests to wait in this chat application. Also requests timeouts are not handled correctly.

The reason we can access the list of clients without thread synchronization mechanisms is because all our requests a called on the same thread. This is great for this example, but it also means that if you block the handler thread (by making database query or something) then all other requests will have to wait. This just means you need to do any time consuming work or IO on another thread. It may be worth noting that request are queued in another thread, so the server isn't blocking while we are handling a request.

I hope to take this a little further and add Riak storage and maybe fix some of the obvious short failings of this example. Finally, props to Jackson Harper and everyone who has contributed to Manos and Mono, you are all awesome.

Comments