Menu Close

Pass arguments to HTTP handlers in Go

The net/http package in Go defines the http.Handler Interface and the http.HandlerFunc type. These types are used as an entry point for HTTP Requests in many Go applications.

An http.HandlerFunc is defined as:

type HandlerFunc func(ResponseWriter, *Request)

As you can see an http.HandlerFunc doesn’t take any arguments except an http.ResponseWriter and a pointer to an http.Request. In this article, we’re going to cover ways to pass data, configuration, and dependencies to these functions anyways.

An http.Handler is just an Interface defining an http.HandlerFunc called ServeHTTP. We’ll focus on http.HanlderFunc in this article, but the same concepts can also be applied to an http.Handler.

HandleFunc parameter using Global Variable

A straightforward way to pass data to an HTTP handler is through global variables. We just declare a global variable and use it in the handler definition:

package main

import "net/http"

const name = "UserService"

func main() {
	http.HandleFunc("/", testHandler)
	http.ListenAndServe(":8080", nil)
}

func testHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hi, from Service: " + name))
}

But using global variables is seldom the best approach, as they can easily be changed and used throughout the whole package or program (if you export them).

Handlefunc parameter using a Wrapped Handler

A commonly used pattern is to define a wrapper function that takes some arguments and returns an http.HandlerFunc. The real handler function can then be declared as a closure and can access the arguments passed to the wrapping function:

package main

import "net/http"

func main() {
	http.HandleFunc("/", testHandler("UserService"))
	http.ListenAndServe(":8080", nil)
}

func testHandler(name string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hi, from Service: " + name))
	}
}

The wrapper function approach is good if there are just a few arguments you want to pass to the handler, and you don’t want to hold an internal state between requests.

Handlefunc parameter using Struct with Handler

The object-oriented approach to pass data is by internal object state. To do this in Go you can define a struct that has fields for data or dependencies and implements http.HandlerFunc methods, which access those fields:

import "net/http"

func main() {
	handlers := wrapperStruct{name: "UserService"}
	http.HandleFunc("/", handlers.testHandler)
	http.ListenAndServe(":8080", nil)
}

type wrapperStruct struct {
	name string
}

func (ws wrapperStruct) testHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hi, from Service: " + ws.name))
}

Make sure access to the fields is thread-safe as each incoming HTTP Request will execute the handler in a new goroutine.

Conclusion

This article shows three different approaches to pass arguments to HTTP handler functions in Go. The global state approach is seldom a good choice for production code, but it is good enough for fast prototyping. The other two approaches are commonly used, and you should choose which one you use depending on your specific use-case.

1 Comment

Comments are closed.