Skip to content

Learning Go – Miniblog #14 – Closures and Goroutines

January 24, 2013

This carries on from here, and started here.

OK, I have two remaining problems. The first is that I want my index request handler to be able to provide a custom title for the index page (and have similar functionality for the data directory in the data handler). The index handler currently looks like this:

func csvIndexHandler( rw http.ResponseWriter, req *http.Request ) {
    fmt.Fprintf( rw, "<h1>CSV index</h1>\n" )
    listIndex( rw )
}

but somehow I want it to have access to the title that was provided when the server was created.

I thought of all sorts of hacky solutions to this problem (global variables and whatnot), but then I remembered reading in A Tour of Go that the language supported functions as first-class object, and indeed it does. I’d forgotten that it also supports closures, which is exactly what I was looking for. I junked the standalone index handler and re-wrote it as a function value in the NewServer constructor function:

func NewServer( port, title, datadir string ) (*Server) { 
    s := &Server{port, title,datadir,&http.Server{Addr: ":"+port, Handler: nil } }
    mux := http.NewServeMux()
    mux.HandleFunc( CSVROOT + IDXSTR + "/", 
        func ( rw http.ResponseWriter, req *http.Request ) {
            fmt.Fprintf( rw, "<h1>%s</h1>\n", title )
            listIndex( rw )
        })
    // similar for data handler here
    s.https.Handler = mux
    return s
}

This is a bit horrible to read (later, I created the function value in a separate helper function), but you can see that I’m creating a nameless  function value which then gets passed to HandleFunc to add it as a handler to the multiplexer. A function object defined like is a closure over the variables in the enclosing function, and so gets access to the "title" variable, which it then uses to create the HTML for the index page. Similar code (not shown) creates a function value to handle data requests, and gets access to the "datadir" variable. This all worked like a charm

My last problem was to prevent the call to the Serve() function from blocking so that I could run more than one server at once. This turned out to be trivially easy – I just ran the first server as a "goroutine", which is Go parlance for a lightweight thread. Starting the thread is as simple as saying "go":

func main() {
    s1 := csvsrv.NewServer( "8080", "My Little Server", "csvdata" )
    go s1.Serve() // note go to run new thread
    s2 := csvsrv.NewServer( "8085", "Another Server", "csvdata" )
    s2.Serve()
}

So that’s it, a multi-threaded HTTP server that serves CSV data files as HTML tables, which is what I set out to achieve here. In the final article in this series, I’ll post some thoughts on how easy it was to do, and on learning Go in general.

Addendum: I’ve decided not to write the final article yet, as in the past I’ve always maintained that you need to have done a real-life project (at least 6 man-months) using a language before you can critique it, so criticising Go after only a tiny toy project like this would be hypocritical. Though I would say that the Go guys need to work a lot harder on the documentation – particularly, it needs a lot more examples!

From → golang

Leave a Comment

Leave a comment