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.
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)
Manos was easy to build and install from source.
$ git clone https://jacksonh@github.com/jacksonh/manos.git
$ ./autogen.sh
$ make
$ sudo make install
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();
});
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.
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
Good article!! I think it should be better if you use the asynchronous overloads for reading the static files, right?
Thanks mate, I see you have some pretty neat posts about doing similar things with and without frameworks.
Yes, reading the file asynchronously that would be better. Here is how it might look.
Just out of interest I tried load testing each implementation with ab, the Apache HTTP server benchmarking tool. I used 10000 request and a concurrency of 300.
With the blocking file read I got 6329 request / second and with the asynchronous code I got 7800 request / second. So yeah, good call :-)
P.S. Apologies, my blog was adding and additional http:// in front of URL to you're site. The capital H in Http:// was throwing it. I've updated your URL so it links though right.