Serving simple dynamic content with Io

Filed under: breve io 

Io 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.



9 comments Leave a comment