Skip to content

Instantly share code, notes, and snippets.

@tsenart
Last active March 24, 2024 08:59
Show Gist options
  • Save tsenart/5fc18c659814c078378d to your computer and use it in GitHub Desktop.
Save tsenart/5fc18c659814c078378d to your computer and use it in GitHub Desktop.
HTTP Closures
package main
import (
"net/http"
"database/sql"
"fmt"
"log"
"os"
)
func helloHandler(db *sql.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var name string
// Execute the query.
row := db.QueryRow("SELECT myname FROM mytable")
if err := row.Scan(&name); err != nil {
http.Error(w, err.Error(), 500)
return
}
// Write it back to the client.
fmt.Fprintf(w, "hi %s!\n", name)
})
}
func withMetrics(l *log.Logger, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
began := time.Now()
next.ServeHTTP(w, r)
l.Printf("%s %s took %s", r.Method, r.URL, time.Since(began))
})
}
func main() {
// Open our database connection.
db, err := sql.Open("postgres", "…")
if err != nil {
log.Fatal(err)
}
// Create our logger
logger := log.New(os.Stdout, "", 0)
// Register our handler.
http.Handle("/hello", helloHandler(db))
// Register our handler with metrics logging
http.Handle("/hello_again", withMetrics(logger, helloHandler(db)))
http.ListenAndServe(":8080", nil)
}
@mikeschinkel
Copy link

This was super helpful to me as I have been learning GoLang. Thanks for posting!

@ldelossa
Copy link

+1 this is good.

@smurariu
Copy link

smurariu commented May 8, 2018

@alexedwards I don't think there's an advantage or disadvantage but it does somewhat limit the explosion of types/interfaces. And I'm guessing that in some scenarios you'll want to use your approach in others this one so it's really useful to be aware of both.

@montera82
Copy link

and how do you write a test against the handler hellohandler using this approach?

@admercs
Copy link

admercs commented May 23, 2018

The main issue with the @tsenart method is if both your handlers and handles live in a separate package from your main database instance. This still requires creating a struct or global variable in order to pass the database to these functions from main. Hence, I don't consider it a very realistic solution for large projects. In terms of package layout and compartmentalization, the method used here looks very nice: https://hackernoon.com/make-yourself-a-go-web-server-with-mongodb-go-on-go-on-go-on-48f394f24e

@Carlislegroup
Copy link

Diving into best practices to separate DB from code for scalability/flexibility, and came here from ben johnson's post (https://medium.com/@benbjohnson/structuring-applications-in-go-3b04be4ff091). Thank you for putting this together. Is there any blog posts on how to write tests against this approach? Thanks!

@ace3
Copy link

ace3 commented Jul 1, 2018

+1 very helpful

@blinderjay
Copy link

smarter way to organize golang code

@Gimongi
Copy link

Gimongi commented Oct 14, 2020

and how do you write a test against the handler hellohandler using this approach?

Execute helloHandler and put the returned HandlerFunc into a var

@zhouzilong2020
Copy link

zhouzilong2020 commented Mar 10, 2022

and how do you write a test against the handler hellohandler using this approach?

Execute helloHandler and put the returned HandlerFunc into a var

I guess writing unit test this way is again unrealistic, just imagine if there are thousands of handlers to be tested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment