The net/http package provides all the features we need to write HTTP clients and servers. It gives us the capability to send requests to other servers communicating using the HTTP protocol as well as the ability to run a HTTP server that can route requests to separate Go funcs, serve static files, and much more.
To begin we should ask the question, what technical book would be complete without a simple hello world example? I say none and this is exactly where we will begin.
In this example, we are going to create an HTTP server with a single endpoint that returns static text represented by the JSON standard, this will introduce the basic functions of the HTTP server and handlers. We will then modify this endpoint to accept a request that is encoded in JSON and using the encoding/json package return a response to the client. We will also examine how the routing works by adding a second endpoint that returns a simple image.
By the end of this chapter, you will have a fundamental grasp of the basic packages and how you can use them to quickly and efficiently build a simple microservice.
Building a web server in Go is incredibly easy thanks to the HTTP package, which is distributed as part of the standard library.
It has everything you need to manage routing, dealing with Transport Layer Security (TLS), which we will cover in Chapter 8, Security, support for HTTP/2 out of the box, and the capability to run an incredibly efficient server that can deal with a huge number of requests.
The source code for this chapter can be found on GitHub at http://github.com/building-microservices-with-go/chapter1.git, all the examples in this and subsequent chapters will reference the source extensively so if you have not already done so, go and clone this repo before continuing.
Let's look at the syntax for creating a basic server then we can walk through the packages in more depth:
Example 1.0 basic_http_example/basic_http_example.go
09 func main() {
10 port := 8080
11
12 http.HandleFunc("/helloworld", helloWorldHandler)
13
14 log.Printf("Server starting on port %v\n", 8080)
15 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", port), nil))
16 }
17
18 func helloWorldHandler(w http.ResponseWriter, r *http.Request) {
19 fmt.Fprint(w, "Hello World\n")
20 }
The first thing we are doing is calling the HandleFunc method on the http package. The HandleFunc method creates a Handler type on the DefaultServeMux handler, mapping the path passed in the first parameter to the function in the second parameter:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
In line 15 we start the HTTP server, ListenAndServe takes two parameters, the TCP network address to bind the server to and the handler that will be used to route requests:
func ListenAndServe(addr string, handler Handler) error
In our example, we are passing the network address :8080" this means we would like to bind the server to all available IP addresses on port 8080.
The second parameter we are passing is nil, this is because we are using the DefaultServeMux handler, which we are setting up with our call to http.HandleFunc. In Chapter 3, Introducing Docker, you will see the use of this second parameter when we introduce more sophisticated routers, but for now we can ignore it.
If the ListenAndServe function fails to start a server it will return an error, the most common reason for this is that you may be trying to bind to a port that is already in use on the server. In our example, we are passing the output of ListenAndServe straight to log.Fatal(error), which is a convenience function equivalent to calling fmt.Print(a ...interface{}) followed by a call to os.Exit(1). Since ListenAndServe blocks if the server starts correctly we will never exit on a successful start.
Let's quickly run and test our new server:
$ go run ./basic_http_example.go
You should now see the application output:
2016/07/30 01:08:21 Server starting on port 8080
What if you do not see the preceding output and instead see something like the following?
2016/07/19 03:51:11 listen tcp :8080: bind: address already in use exit status 1
Take another look at the signature of ListenAndServe and the way we are calling it. Remember what we were saying about why we were using log.Fatal?
If you do get this error message it means that you are already running an application on your computer that is using port 8080, this could be another instance of your program or it could be another application. You can check that you do not have another instance running by checking the running processes:
$ ps -aux | grep 'go run'
If you do see another go run ./basic_http_example.go then you can simply kill it and retry. If you do not have another instance running, then you probably have some other software that is bound to this port. Try changing the port on line 10 and restart your program.
To test the server, open a new browser and type in the URI http://127.0.0.1:8080/helloworld and if things are working correctly you should see the following response from the server:
Hello World
Congratulations, that's the first step into microservice mastery. Now that we have our first program running, let's take a closer look at how we can return and accept JSON.