Serving simple dynamic content with Io
Filed under: breve ioIo happens to include a nice little HTTP server. The HTTP protocol parsing is handled by Zed Shaw's Ragel-generated C parser (same as Mongrel uses), but the rest is in Io. On top of that, I've used an improved version of the Breve-like DSL I outlined a couple of days ago for generating dynamic content (which I am dubbing Iota for the time being).
Io is not a particularly fast language, but it excels at concurrency, as I'll demonstrate here. First, the complete code:
Iota := Object clone do (
stack := nil
output := nil
init := method (
stack = List clone
output = List clone
)
squareBrackets := method (
for ( arg, 0, call argCount - 1,
output append ( call evalArgAt ( arg ) )
)
output append ( stack pop )
""
)
forward := method (
name := call message name
attrs := call message arguments map ( arguments ) map ( pair,
k := pair at ( 0 ) asString slice ( 1, -1 )
v := pair at ( 1 )
" " .. k .. "=" .. v
) join
if ( call message next ?name == "squareBrackets",
output append ( "<" .. name .. attrs .. ">" )
stack push ( "</" .. name .. ">" )
self
, // else
output append ( "<" .. name .. attrs .. " />" )
""
)
)
)
server := HTTPServer do (
setPort ( 5000 )
setHost ( "localhost" )
streamResponse := method ( socket, request,
HTTPResponse withSocket ( socket ) setBody (
createBody
) send
)
createBody := method (
Iota clone do (
html [
head [
title [ "the title" ]
]
body [
div ( class="foo", id="bar" ) [
"hello, world" , br
"this is a test"
]
]
]
) output join
)
)
server start
First I'll make clear I'm benchmarking on my laptop (1.5GHz Core2 Duo, 2.5GB of RAM, Fedora 9, 32 bit) which isn't the fastest thing around. Also ab is running on the same system, so we have a fast pipe but some of the CPU is going to ab. Not an ideal setup for benchmarking, but adequate for my purposes.
Now, the above code isn't particularly fast (the HTTPServer on its own gets around 339req/s serving a the same amount of static HTML). However, the requests per second barely dip as the concurrency increases (ab output is trimmed):
$ ab -c 10 -n 10000 http://localhost:5000/ Document Length: 149 bytes Concurrency Level: 10 Time taken for tests: 49.915 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Non-2xx responses: 10000 Total transferred: 1870000 bytes HTML transferred: 1490000 bytes Requests per second: 200.34 [#/sec] (mean) Time per request: 49.915 [ms] (mean) Time per request: 4.992 [ms] (mean, across all concurrent requests) Transfer rate: 36.59 [Kbytes/sec] received
So we're seeing around 200req/s at a concurrency level of 10.
Now let's push it:
$ ab -c 1000 -n 10000 http://localhost:5000/ Document Length: 149 bytes Concurrency Level: 1000 Time taken for tests: 63.247 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Non-2xx responses: 10000 Total transferred: 1870000 bytes HTML transferred: 1490000 bytes Requests per second: 158.11 [#/sec] (mean) Time per request: 6324.657 [ms] (mean) Time per request: 6.325 [ms] (mean, across all concurrent requests) Transfer rate: 28.87 [Kbytes/sec] received
At 1000 concurrent connections, we drop to 158req/s. That's a 100-fold increase in concurrency with only a 21% drop in performance. Not too shabby.
Next up I'll be visiting Io's PostgreSQL library so we can verify that we have the tools for a full web framework stack.







I'm the author of the HttpServer you're using. Glad someone is looking into using it! I think we could really get the performance up if the networking code were written in C.
I wrote the HttpServer so that Io could be embedded in long running server processes that cache objects between requests (see seaside). Long running server processes can be a pain to maintain (see Mongrel's restart troubles). If you want to use Io for a web application where the server is stateless (other than db / memcache), we might want to take a look at doing a mod_io for apache. Let me know if you are interested and I would be glad to help.