Build a JSON over HTTP Server



A Go web server comprises one function — a net/http HandlerFunc(ResponseWriter, *Request) — for each of your API’s endpoints. Our API has two endpoints: Produce for writing to the log and Consume for reading from the log. When building a JSON/HTTP Go server, each handler consists of three steps:

  1. Unmarshal the request’s JSON body into a struct.

  2. Run that endpoint’s logic with the request to obtain a result.

  3. Marshal and write that result to the response.

If your handlers become much more complicated than this, then you should move the code out, move request and response handling into HTTP middleware, and move business logic further down the stack.

Let’s start by adding a function for users to create our HTTP server. Inside your server directory, create a file called http.go that contains the following code:

LetsGo/internal/server/http.go

​ ​package​ server
​ 
​ ​import (
​         ​"encoding/json"​
​         ​"net/http"​
​ 
​         ​"github.com/gorilla/mux"​
​ )
​ 
​ ​func​ NewHTTPServer(addr ​string​) *http.Server {
​         httpsrv := newHTTPServer()
​         r := mux.NewRouter()
​         r.HandleFunc("/", httpsrv.handleProduce).Methods("POST")
​         r.HandleFunc("/", httpsrv.handleConsume).Methods("GET")
​         ​return&http.Server{
​                 Addr:    addr,
​                 Handler: r,}}

NewHTTPServer(addr string) takes in an address for the server to run on and returns an *http.Server. We create our server and use the popular gorilla/mux library to write nice, RESTful routes that match incoming requests to their respective handlers. An HTTP POST request to / matches the produce handler and appends the record to the log, and an HTTP GET request to / matches the consume handler and reads the record from the log. We wrap our server with a *net/http.Server so the user just needs to call ListenAndServe to listen for and handle incoming requests.

Next, we’ll define our server and the request and response structs by adding this snippet below NewHTTPServer:

LetsGo/internal/server/http.go

​ ​type​ httpServer ​struct​ {
​         Log *Log
​ }
​ 
​ ​func​ newHTTPServer() *httpServer {
​         ​return&httpServer{
​                 Log: NewLog(),}}
​ 
​ ​type​ ProduceRequest ​struct​ {
​         Record Record ​`json:"record"`​
​ }
​ 
​ ​type​ ProduceResponse ​struct​ {
​         Offset ​uint64​ ​`json:"offset"`​
​ }
​ 
​ ​type​ ConsumeRequest ​struct​ {
​         Offset ​uint64​ ​`json:"offset"`​
​ }
​ 
​ ​type​ ConsumeResponse ​struct​ {
​         Record Record ​`json:"record"`​
​ }

We now have a server referencing a log for the server to defer to in its handlers. A produce request contains the record that the caller of our API wants appended to the log, and a produce response tells the caller what offset the log stored the records under. A consume request specifies which records the caller of our API wants to read and the consume response to send back those records to the caller. Not bad f