A simple main function to serve our API

A web service is nothing more than a simple Go program that binds to a specific HTTP address and port and serves requests, so we get to use all our command-line tool-writing knowledge and techniques.

Tip

We also want to ensure that our main function is as simple and modest as possible, which is always a goal of coding, especially in Go.

Before we write our main function, let's look at a few design goals of our API program:

  • We should be able to specify the HTTP address and port to which our API listens and the address of the MongoDB instances without having to recompile the program (through command-line flags)
  • We want the program to gracefully shut down when we terminate it, allowing the in-flight requests (requests that are still being processed when the termination signal is sent to our program) to complete
  • We want the program to log out status updates and report errors properly

Atop the main.go file, replace the main function placeholder with the following code:

func main() {
  var (
    addr  = flag.String("addr", ":8080", "endpoint address")
    mongo = flag.String("mongo", "localhost", "mongodb address")
  )
  flag.Parse()
  log.Println("Dialing mongo", *mongo)
  db, err := mgo.Dial(*mongo)
  if err != nil {
    log.Fatalln("failed to connect to mongo:", err)
  }
  defer db.Close()
  mux := http.NewServeMux()
  mux.HandleFunc("/polls/", withCORS(withVars(withData(db, withAPIKey(handlePolls)))))
  log.Println("Starting web server on", *addr)
  graceful.Run(*addr, 1*time.Second, mux)
  log.Println("Stopping...")
}

This function is the entirety of our API main function, and even as our API grows, there is just a little bloat we would need to add to this.

The first thing we do is to specify two command-line flags, addr and mongo, with some sensible defaults, and to ask the flag package to parse them. We then attempt to dial the MongoDB database at the specified address. If we are unsuccessful, we abort with a call to log.Fatalln. Assuming the database is running and we are able to connect, we store the reference in the db variable before deferring the closing of the connection. This ensures our program properly disconnects and tidies up after itself when it ends.

We then create a new http.ServeMux object, which is a request multiplexer provided by the Go standard library, and register a single handler for all requests that begin with the path /polls/.

Finally, we make use of Tyler Bunnell's excellent Graceful package, which can be found at https://github.com/stretchr/graceful to start the server. This package allows us to specify time.Duration when running any http.Handler (such as our ServeMux handler), which will allow any in-flight requests some time to complete before the function exits. The Run function will block until the program is terminated (for example, when someone presses Ctrl + C).

Using handler function wrappers

It is when we call HandleFunc on the ServeMux handler that we are making use of our handler function wrappers, with the line:

withCORS(withVars(withData(db, withAPIKey(handlePolls)))))

Since each function takes an http.HandlerFunc type as an argument and also returns one, we are able to chain the execution just by nesting the function calls as we have done previously. So when a request comes in with a path prefix of /polls/, the program will take the following execution path:

  1. withCORS is called, which sets the appropriate header.
  2. withVars is called, which calls OpenVars and defers CloseVars for the request.
  3. withData is then called, which copies the database session provided as the first argument and defers the closing of that session.
  4. withAPIKey is called next, which checks the request for an API key and aborts if it's invalid, or else calls the next handler function.
  5. handlePolls is then called, which has access to variables and a database session, and which may use the helper functions in respond.go to write a response to the client.
  6. Execution goes back to withAPIKey that just exits.
  7. Execution goes back to withData that exits, therefore calling the deferred session Close function and clearing up the database session.
  8. Execution goes back to withVars that exits, therefore calling CloseVars and tidying that up too.
  9. Execution finally goes back to withCORS that just exits.

Note

The order that we nest the wrapper functions in is important, because withData puts the database session for each request in that request's variables map using SetVar. So withVars must be outside withData. If this isn't respected, the code will likely panic and you may want to add a check so that the panic is more meaningful to other developers.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.99.71