HTTP API in Go – Part I

Level: Beginner  
Pretty much all the services we design everyday, require some sort of interface to communicate with the outside World. From a pool of available options, HTTP is a widespread medium which is used by many services to provide an input/output data flow channel. Whether you design an internet facing API for your customers or simply need a health check endpoint to register with your load balancer, the simplicity of HTTP, makes it one of the best available options on the table.
Go services are not exempted from the above. What makes Go different to many other languages though, is that HTTP support is build into the standard library. That means, you won’t need any third party package to make your service HTTP enabled. That said, building an HTTP server in Go is famous in the community, for being a very cumbersome and sophisticated task (Don’t panic! Bare with me) . But don’t worry. We are going to build an HTTP server together in baby steps.

Let’s code

Ready to get our hands dirty with some code? Alright! Let’s start simple and setup the skeleton of our API first. Obviously, the main component of every HTTP API is the HTTP server itself. Let’s see what it takes to get a very basic HTTP server up and running in Go:
package main

import (
  "fmt"
  "net/http"
)

func main() {
  http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
    fmt.Fprint(writer, "This is your first HTTP API endpoint in Go")
  })

  http.ListenAndServe("", nil)
}
Yup! That’s it! I am not messing with you. This is all it takes to spin up a new HTTP server in Go. ONLY 15 lines of code!! Now, you can see it yourself how “cumbersome and sophisticated” it was hay? Shall we prove it? Run your server  (go run main ), open a new browser and go to http://localhost. Notice that we haven’t used any third party package to build our server. Everything we used was from the standard library.

Code Analysis

The first line of the main method is the registration of our handler function with the server. This is where we tell the server what code to execute whenever it receives an HTTP GET request on the default route / . http.HandleFunc is one of the possible alternatives for registering HTTP handlers in Go. We will get to more details on how to build our custom request multiplexer (aka ServeMux ) in the future posts, but for now, let’s keep it simple and see how we can use http.Handle function instead:  
package main

import (
  "fmt"
  "net/http"
)

func main() {
  http.Handle("/", &homePageHandler{})
  http.ListenAndServe("", nil)
}

type homePageHandler struct{}

func (*homePageHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  fmt.Fprint(w, "This is your first HTTP API endpoint in Go")
}
More modular, compared to the inline functions, right? To register a handler to serve a different route, all we need to do is to tell the multiplexer which route to choose upon receiving a request. For example, http.Handle("/home", &homePageHandler{}) instructs the multiplexer to serve the request on /home instead of the / route. On the second line we start our HTTP server by calling http.ListenAndServe method. ListenAndServe(...) is a blocking call which according to the documentations “listens on the specified TCP network address and then calls the Serve method with handler to handle requests on incoming connections”. That means, as long as your server is not interrupted by the operating system, it will keep serving incoming requests. We can ask our server to serve on a different port (instead of default port 80), by setting the first parameter of http.ListenAndServe.   
func main() {
  http.Handle("/", &homePageHandler{})
  http.ListenAndServe(":8080", nil)
}
 

Setting HTTP Headers

We can set standard HTTP headers of the response object or add custom headers if required. As an example, let’s see how we can change our handler function to return Json instead of plain text. 
func (*homePageHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  fmt.Fprint(w, `{"message":"This is your first HTTP API endpoint in Go"}`)
}
Now let’s curl with -i flag to print the response flags and see if header has been set as expected:
curl -i http://localhost:8080
HTTP/1.1 200 OK
Content-Type: application/json
Date: Mon, 01 Oct 2018 03:52:43 GMT
Content-Length: 56

{"message":"This is your first HTTP API endpoint in Go"}
We can use the Set(...) method to set a custom header as well:
func (*homePageHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  w.Header().Set("X-Language", "go")
  fmt.Fprint(w, `{"message":"This is your first HTTP API endpoint in Go"}`)
}
curl -i http://localhost:8080
HTTP/1.1 200 OK
Content-Type: application/json
X-Language: go
Date: Mon, 01 Oct 2018 03:58:24 GMT
Content-Length: 56
But is this really how people populate Json response in Go? What if my response has more properties than a simple text message? Well… I am glad you asked. Let’s see how lovely Gophers usually do this.
// Response the HTTP response returned by the API
type Response struct {
  // Message response message
  Message string `json:"message"`
}

func (*homePageHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  response := Response{
    Message: "This is your first HTTP API endpoint in Go",
  }
  rb, err := json.Marshal(response)
  if err != nil {
    http.Error(w, "Internal Server Error", http.StatusInternalServerError)
    return
  }
  fmt.Fprint(w, string(rb))
}
Or even better,
func (*homePageHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  w.Header().Set("Content-Type", "application/json")
  response := Response{
    Message: "This is your first HTTP API endpoint in Go",
  }

  enc := json.NewEncoder(w)
  err := enc.Encode(response)
  if err != nil {
    http.Error(w, "Internal Server Error", http.StatusInternalServerError)
  }
}
  The main benefit of the later method is that the encoder directly writes the bytes into the response stream and no type casting is required at the end. Notice that we used http.Error method to report the failure if things go south. http.Error is called an HTTP helper handler. Helper handlers are shortcuts to perform common HTTP tasks. http.Redirect and http.NotFound are another examples of helper methods.  

 Set HTTP status code

Looking at the curl output above shows that if everything goes smoothly, the http package sets the response status code to 200 by default (HTTP/1.1 200 OK). But what if we want to set the status to a different code? It’s super simple. All we need to do is to call the response stream’s WriteHeader method. As an example, let’s set the status to 202 (HTTP/1.1 202 Accepted) before writing the response:
w.WriteHeader(http.StatusAccepted)
err := enc.Encode(response)
if err != nil {
  http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
 
curl -i http://localhost:8080
HTTP/1.1 202 Accepted
Content-Type: application/json
X-Language: go
Date: Mon, 01 Oct 2018 06:04:47 GMT
Content-Length: 57
  Perfect! Everything works as expected. But what happens if we set the status code after writing the response. Let’s give it a go:
err := enc.Encode(response)
if err != nil {
  http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
w.WriteHeader(http.StatusAccepted)
curl -i http://localhost:8080
HTTP/1.1 200 OK
As you can see, calling WriteHeader method had no effect and the client is getting 200 back instead of 202. Looking at our server’s logs reveals that we have done something that we were not actually supposed to do. Here is what the server spits out to the logs:
http: multiple response.WriteHeader calls
What’s going on here? I only called WriteHeader once, who else is calling that method. Let’s have a look at the documentation of Write and `WriteHeader` methods:
// Write writes the data to the connection as part of an HTTP reply.
//
// If WriteHeader has not yet been called, Write calls
// WriteHeader(http.StatusOK) before writing the data.
Write([]byte) (int, error)

// WriteHeader sends an HTTP response header with the provided
// status code.
//
// If WriteHeader is not called explicitly, the first call to Write
// will trigger an implicit WriteHeader(http.StatusOK).
// Thus explicit calls to WriteHeader are mainly used to
// send error codes.
WriteHeader(statusCode int)
Alright! That explains it. According to the docs, if WriteHeader has not yet been called explicitly, Write calls WriteHeader(http.StatusOK) before writing the data. The takeaway here is to make sure that we always set the status code explicitly before writing the response back to the client, otherwise setting the status code will have zero effect.

Summary

In this part we learned the basics of how to write a simple HTTP server in Go. Now, we know how to set custom HTTP headers and how to write the status codes. We also learned two different methods of encoding JSON into our response stream and how to use http helper functions to return errors to our clients. In the next part we will learn together how to serve different HTTP verbs and how to feed our server with client data. Stay tuned and Go everyday… Codehttps://github.com/xitonix/xhttp/part1

Leave a Reply

Your email address will not be published. Required fields are marked *