Listen and Serve

Learn how to listen to incoming TCP communication on a port and serve HTTP requests.

Let’s learn how to build scalable back-end systems from scratch. To do so, we must understand the communication mechanism where we can listen to requests.

Press + to interact
Typical TCP listen and serve life cycle
Typical TCP listen and serve life cycle

Listening on a port

For any API server to accept incoming requests and then respond with information, our code must first be able to listen to all inbound communication on a specific port. Once we have achieved that, we must be able to understand an incoming request and serve a legitimate response.

How would we go about writing code for this? Let’s take a look.

Press + to interact
package main
import "fmt"
func main() {
fmt.Printf("Hello World")
}

This is an elementary Hello World program in Go. This syntax should be familiar.

Let us use the http package to listen on the port 8000.

Press + to interact
package main
import (
"log"
"net/http"
)
func main() {
log.Printf("Starting server on localhost:8000\n")
log.Fatal(http.ListenAndServe(":8000", nil))
}

Note: Port numbers range from 0 to 65536, but port numbers from 0 to 1024 are exclusively reserved for privileged services and designated as well-known ports.
If you are building an API service, be sure to use a port that is within the range of 1025 to 65536.

The code above will start a server that listens for incoming TCP communication on port 8000.

However, we won’t be able to interact with it because it’s just listening now. So, let’s add some code to handle an incoming request on a specific route and return a response.

Serving HTTP traffic

Press + to interact
package main
import (
"log"
"net/http"
)
// Handling the request and replying back
func handle(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello students!\n")
}
func main() {
// Registering the handler
http.HandleFunc("/", handle)
log.Printf("Starting server on localhost:8000\n")
log.Fatal(http.ListenAndServe(":8000", nil))
}

This piece of code will now not only be able to receive an incoming request, but it will also be able to reply with a message! What changed?
Let’s take a look at the handle function in line 9. It takes the following two parameters:

  • w http.ResponseWriter: This instance of the writer will allow us to write data back to the response stream of an established TCP connection.

  • r *http.Request: This is a pointer to the instance of our incoming request. This will contain all information about the incoming request, be it the path, query or URL parameters, headers, request body, etc.

In line 16, we registered the function to listen to the base entry point route: /.
This ensures that any traffic directed to localhost:8000/ will be received and handled by this handler function.

Let’s try to run this code and play around with it a little now.

package main

import (
	"io"
	"log"
	"net/http"
	"os"
)

var port = os.Getenv("PORT")

// Handling the request and replying back
func handle(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "Hello students!\n")
}

// Entry point
func main() {
	if len(port) == 0 {
		port = "8080"
	}
	// Handling a route
	http.HandleFunc("/", handle)
	log.Printf("Starting server on localhost:%s\n", port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}
Your first server

The port is being read from the PORT environment variable. If not present, it will fall back to 8080 in line 20. We will explore more ways to read such inputs in future lessons.

After running main.go, open a new terminal window using the “+” icon and please run the following command:

curl localhost:8080

The curl command-line tool allows us to make HTTP requests from the terminal. It also covers many other protocols, like FTP, though they go beyond the scope of this course.

We should see the following output once the command runs:

Hello students!

If this is working, then congratulations! You have successfully built your first server!

Note: For the remainder of the course, we will refer to the terminal that opens when we press the “Run” button as the run terminal. We will frequently open an additional terminal to run the curl commands and then switch back to our run terminal.

Play with the code and the curl command, and get comfortable with them. Change the string in line 14 in the handler and run again to see the curl response change.
This will form the base for everything we do from here on out. Right now, we have accomplished the first two basic steps essential for any back-end service—to listen and serve.