Home Web Development Go Web Development Cookbook

Go Web Development Cookbook

By Arpit Aggarwal
books-svg-icon Book
eBook $39.99 $27.98
Print $48.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $39.99 $27.98
Print $48.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Creating Your First Server in Go
About this book
Go is an open source programming language that is designed to scale and support concurrency at the language level. This gives you the liberty to write large concurrent web applications with ease. From creating web application to deploying them on Amazon Cloud Services, this book will be your one-stop guide to learn web development in Go. The Go Web Development Cookbook teaches you how to create REST services, write microservices, and deploy Go Docker containers. Whether you are new to programming or a professional developer, this book will help get you up to speed with web development in Go. We will focus on writing modular code in Go; in-depth informative examples build the base, one step at a time. You will learn how to create a server, work with static files, SQL, NoSQL databases, and Beego. You will also learn how to create and secure REST services, and create and deploy Go web application and Go Docker containers on Amazon Cloud Services. By the end of the book, you will be able to apply the skills you've gained in Go to create and explore web applications in any domain.
Publication date:
April 2018
Publisher
Packt
Pages
338
ISBN
9781787286740

 

Creating Your First Server in Go

In this chapter, we will cover the following recipes:

  • Creating a simple HTTP server
  • Implementing basic authentication on a simple HTTP server
  • Optimizing HTTP server responses with GZIP compression
  • Creating a simple TCP server
  • Reading data from a TCP connection
  • Writing data to a TCP connection
  • Implementing HTTP request routing
  • Implementing HTTP request routing using Gorilla Mux
  • Logging HTTP requests
 

Introduction

Go was created to solve the problems that came with the new architecture of multi-core processors, creating high-performance networks that serve millions of requests and compute-intensive jobs. The idea behind Go was to increase productivity by enabling rapid prototyping, decreasing compile and build time, and enabling better dependency management.

Unlike most other programming languages, Go provides the net/http package, which is sufficient when creating HTTP clients and servers. This chapter will cover the creation of HTTP and TCP servers in Go.

We will start with some simple recipes to create an HTTP and TCP server and will gradually move to recipes that are more complex, where we implement basic authentication, optimize server responses, define multiple routes, and log HTTP requests. We will also cover concepts and keywords such as Go Handlers, Goroutines, and Gorilla – a web toolkit for Go.

 

Creating a simple HTTP server

As a programmer, if you have to create a simple HTTP server then you can easily write it using Go's net/http package, which we will be covering in this recipe.

How to do it...

In this recipe, we are going to create a simple HTTP server that will render Hello World! when we browse http://localhost:8080 or execute curl http://localhost:8080 from the command line. Perform the following steps:

  1. Create http-server.go and copy the following content:
package main
import
(
"fmt"
"log"
"net/http"
)
const
(
CONN_HOST = "localhost"
CONN_PORT = "8080"
)
func helloWorld(w http.ResponseWriter, r *http.Request)
{
fmt.Fprintf(w, "Hello World!")
}
func main()
{
http.HandleFunc("/", helloWorld)
err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
if err != nil
{
log.Fatal("error starting http server : ", err)
return
}
}
  1. Run the program with the following command:
$ go run http-server.go

How it works...

Once we run the program, an HTTP server will start locally listening on port 8080. Opening http://localhost:8080 in a browser will display Hello World! from the server, as shown in the following screenshot:

Hello World!

Let’s understand what each line in the program means:

  • package main: This defines the package name of the program.
  • import ( "fmt" "log" "net/http" ): This is a preprocessor command that tells the Go compiler to include all files from fmt, log, and the net/http package.
  • const ( CONN_HOST = "localhost" CONN_PORT = "8080" ): We declare constants in the Go program using the const keyword. Here we declared two constants—one is CONN_HOST with localhost as a value and another one is CONN_PORT with 8080 as a value.
  • func helloWorld(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }: This is a Go function that takes ResponseWriter and Request as an input and writes Hello World! on an HTTP response stream.

Next, we declared the main() method from where the program execution begins, as this method does a lot of things. Let’s understand it line by line:

  • http.HandleFunc("/", helloWorld): Here, we are registering the helloWorld function with the / URL pattern using HandleFunc of the net/http package, which means helloWorld gets executed, passing (http.ResponseWriter, *http.Request) as a parameter to it whenever we access the HTTP URL with pattern /.
  • err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil): Here, we are calling http.ListenAndServe to serve HTTP requests that handle each incoming connection in a separate Goroutine. ListenAndServe accepts two parameters—server address and handler. Here, we are passing the server address as localhost:8080 and handler as nil, which means we are asking the server to use DefaultServeMux as a handler.
  • if err != nil { log.Fatal("error starting http server : ", err) return}: Here, we check whether there is a problem starting the server. If there is, then log the error and exit with a status code of 1.
 

Implementing basic authentication on a simple HTTP server

Once you have created the HTTP server then you probably want to restrict resources from being accessed by a specific user, such as the administrator of an application. If so, then you can implement basic authentication on an HTTP server, which we will be covering in this recipe.

Getting ready

As we have already created an HTTP server in our previous recipe, we will just extend it to incorporate basic authentication.

How to do it...

In this recipe, we are going to update the HTTP server we created in the previous recipe by adding a BasicAuth function and modifying the HandleFunc to call it. Perform the following steps:

  1. Create http-server-basic-authentication.go and copy the following content:
package main
import
(
"crypto/subtle"
"fmt"
"log"
"net/http"
)
const
(
CONN_HOST = "localhost"
CONN_PORT = "8080"
ADMIN_USER = "admin"
ADMIN_PASSWORD = "admin"
)
func helloWorld(w http.ResponseWriter, r *http.Request)
{
fmt.Fprintf(w, "Hello World!")
}
func BasicAuth(handler http.HandlerFunc, realm string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request)
{
user, pass, ok := r.BasicAuth()
if !ok || subtle.ConstantTimeCompare([]byte(user),
[]byte(ADMIN_USER)) != 1||subtle.ConstantTimeCompare([]byte(pass),
[]byte(ADMIN_PASSWORD)) != 1
{
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
w.WriteHeader(401)
w.Write([]byte("You are Unauthorized to access the
application.\n"))
return
}
handler(w, r)
}
}
func main()
{
http.HandleFunc("/", BasicAuth(helloWorld, "Please enter your
username and password"))
err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
if err != nil
{
log.Fatal("error starting http server : ", err)
return
}
}
  1. Run the program with the following command:
$ go run http-server-basic-authentication.go

How it works...

Once we run the program, the HTTP server will start locally listening on port 8080.

Once the server starts, accessing http://localhost:8080 in a browser will prompt you to enter a username and password. Providing it as admin, admin respectively will render Hello World! on the screen, and for every other combination of username and password it will render You are Unauthorized to access the application.

To access the server from the command line we have to provide the --user flag as part of the curl command, as follows:

$ curl --user admin:admin http://localhost:8080/
Hello World!

We can also access the server using a base64 encoded token of username:password, which we can get from any website, such as https://www.base64encode.org/, and pass it as an authorization header in the curl command, as follows:

$ curl -i -H 'Authorization:Basic YWRtaW46YWRtaW4=' http://localhost:8080/

HTTP/1.1 200 OK

Date: Sat, 12 Aug 2017 12:02:51 GMT
Content-Length: 12
Content-Type: text/plain; charset=utf-8
Hello World!

Let’s understand the change we introduced as part of this recipe:

  • The import function adds an additional package, crypto/subtle, which we will use to compare the username and password from the user's entered credentials.
  • Using the const function we defined two additional constants, ADMIN_USER and ADMIN_PASSWORD, which we will use while authenticating the user.
  • Next, we declared a BasicAuth() method, which accepts two input parameters—a handler, which executes after the user is successfully authenticated, and realm, which returns HandlerFunc, as follows:
func BasicAuth(handler http.HandlerFunc, realm string) http.HandlerFunc 
{
return func(w http.ResponseWriter, r *http.Request)
{
user, pass, ok := r.BasicAuth()
if !ok || subtle.ConstantTimeCompare([]byte(user),
[]byte(ADMIN_USER)) != 1||subtle.ConstantTimeCompare
([]byte(pass),
[]byte(ADMIN_PASSWORD)) != 1
{
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
w.WriteHeader(401)
w.Write([]byte("Unauthorized.\n"))
return
}
handler(w, r)
}
}

In the preceding handler, we first get the username and password provided in the request's authorization header using r.BasicAuth() then compare it to the constants declared in the program. If credentials match, then it returns the handler, otherwise it sets WWW-Authenticate along with a status code of 401 and writes You are Unauthorized to access the application on an HTTP response stream.

Finally, we introduced a change in the main() method to call BasicAuth from HandleFunc, as follows:

http.HandleFunc("/", BasicAuth(helloWorld, "Please enter your username and password"))

We just pass a BasicAuth handler instead of nil or DefaultServeMux for handling all incoming requests with the URL pattern as /.

 

Optimizing HTTP server responses with GZIP compression

GZIP compression means sending the response to the client from the server in a .gzip format rather than sending a plain response and it’s always a good practice to send compressed responses if a client/browser supports it.

By sending a compressed response we save network bandwidth and download time eventually rendering the page faster. What happens in GZIP compression is the browser sends a request header telling the server it accepts compressed content (.gzip and .deflate) and if the server has the capability to send the response in compressed form then sends it. If the server supports compression then it sets Content-Encoding: gzip as a response header, otherwise it sends a plain response back to the client, which clearly means asking for a compressed response is only a request by the browser and not a demand. We will be using Gorilla’s handlers package to implement it in this recipe.

How to do it...

In this recipe, we are going to create an HTTP server with a single handler, which will write Hello World! on an HTTP response stream and use a Gorilla CompressHandler to send all the responses back to the client in the .gzip format. Perform the following steps:

  1. To use Gorilla handlers, first we need to install the package using the go get command or copy it manually to $GOPATH/src or $GOPATH, as follows:
$ go get github.com/gorilla/handlers
  1. Create http-server-mux.go and copy the following content:
package main
import
(
"io"
"net/http"
"github.com/gorilla/handlers"
)
const
(
CONN_HOST = "localhost"
CONN_PORT = "8080"
)
func helloWorld(w http.ResponseWriter, r *http.Request)
{
io.WriteString(w, "Hello World!")
}
func main()
{
mux := http.NewServeMux()
mux.HandleFunc("/", helloWorld)
err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT,
handlers.CompressHandler(mux))
if err != nil
{
log.Fatal("error starting http server : ", err)
return
}
}
  1. Run the program with the following command:
$ go run http-server-mux.go

How it works...

Once we run the program, the HTTP server will start locally listening on port 8080.

Opening http://localhost:8080 in a browser will display Hello World! from the server with the Content-Encoding response header value gzip, as shown in the following screenshot:

Hello World!

Let’s understand what each line in the program means:

  • package main: This defines the package name of the program.
  • import ( "io" "net/http" "github.com/gorilla/handlers" ): This is a preprocessor command that tells the Go compiler to include all files from io, net/http, and the github.com/gorilla/handlers package.
  • const ( CONN_HOST = "localhost" CONN_PORT = "8080" ): We declare constants in a Go program using the const keyword. Here, we declared two constants—one is CONN_HOST with a value of localhost and another is CONN_PORT with a value of 8080.
  • func helloWorld(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "Hello World!")}: This is a Go function that takes ResponseWriter and Request as input parameters and writes Hello World! on the HTTP response stream.

Next, we declared the main() method from where the program execution begins. As this method does a lot of things, let’s understand it line by line:

  • mux := http.NewServeMux(): This allocates and returns a new HTTP request multiplexer (ServeMux), which matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL. One of the benefits of using it is that the program has complete control over the handlers used with the server, although any handlers registered with the DefaultServeMux are ignored.
  • http.HandleFunc("/", helloWorld): Here, we are registering the helloWorld function with the / URL pattern using HandleFunc of the net/http package, which means helloWorld gets executed, passing (http.ResponseWriter, *http.Request) as a parameter to it whenever we access the HTTP URL with the / pattern.
  • err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, handlers.CompressHandler(mux)): Here, we are calling http.ListenAndServe to serve HTTP requests that handle each incoming connection in a separate Goroutine for us. ListenAndServe accepts two parameters—server address and handler. Here, we are passing the server address as localhost:8080 and handler as CompressHandler, which wraps our server with a .gzip handler to compress all responses in a .gzip format.
  • if err != nil { log.Fatal("error starting http server: ", err) return}: Here, we check whether there is any problem in starting the server. If there is, then log the error and exit with a status code of 1.
 

Creating a simple TCP server

Whenever you have to build high performance oriented systems then writing a TCP server is always the best choice over an HTTP server, as TCP sockets are less hefty than HTTP. Go supports and provides a convenient way of writing TCP servers using a net package, which we will be covering in this recipe.

How to do it...

In this recipe, we are going to create a simple TCP server that will accept a connection on localhost:8080. Perform the following steps:

  1. Create tcp-server.go and copy the following content:
package main
import
(
"log"
"net"
)
const
(
CONN_HOST = "localhost"
CONN_PORT = "8080"
CONN_TYPE = "tcp"
)
func main()
{
listener, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
if err != nil
{
log.Fatal("Error starting tcp server : ", err)
}
defer listener.Close()
log.Println("Listening on " + CONN_HOST + ":" + CONN_PORT)
for
{
conn, err := listener.Accept()
if err != nil
{
log.Fatal("Error accepting: ", err.Error())
}
log.Println(conn)
}
}
  1. Run the program with the following command:
$ go run tcp-server.go

How it works...

Once we run the program, the TCP server will start locally listening on port 8080.

Let’s understand what each line in the program means:

  • package main: This defines the package name of the program.
  • import ( "log" "net"): This is a preprocessor command that tells the Go compiler to include all files from the log and net package.
  • const ( CONN_HOST = "localhost" CONN_PORT = "8080" CONN_TYPE = "tcp" ): We declare constants in a Go program using the const keyword. Here, we declare three constants—one is CONN_HOST with a value of localhost, another one is CONN_PORT with a value as 8080, and lastly CONN_TYPE with a value as tcp.

Next, we declared the main() method from where the program execution begins. As this method does a lot of things, let’s understand it line by line:

  • listener, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT): This creates a TCP server running on localhost at port 8080.
  • if err != nil { log.Fatal("Error starting tcp server: ", err) }: Here, we check if there is any problem in starting the TCP server. If there is, then log the error and exit with a status code of 1.
  • defer listener.Close(): This defer statement closes a TCP socket listener when the application closes.

Next, we accept the incoming request to the TCP server in a constant loop, and if there are any errors in accepting the request, then we log it and exit; otherwise, we simply print the connection object on the server console, as follows:

for 
{
conn, err := listener.Accept()
if err != nil
{
log.Fatal("Error accepting: ", err.Error())
}
log.Println(conn)
}
 

Reading data from a TCP connection

One of the most common scenarios in any application is the client interacting with the server. TCP is one of the most widely used protocols for this interaction. Go provides a convenient way to read incoming connection data through bufio implementing buffered Input/Output, which we will be covering in this recipe.

Getting ready...

As we have already created a TCP server in our previous recipe, we will update it to read data from incoming connections.

How to do it...

In this recipe, we are going to update the main() method to call a handleRequest method passing the connection object to read and print data on the server console. Perform the following steps:

  1. Create tcp-server-read-data.go and copy the following content:
package main
import
(
"bufio"
"fmt"
"log"
"net"
)
const
(
CONN_HOST = "localhost"
CONN_PORT = "8080"
CONN_TYPE = "tcp"
)
func main()
{
listener, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
if err != nil
{
log.Fatal("Error starting tcp server : ", err)
}
defer listener.Close()
log.Println("Listening on " + CONN_HOST + ":" + CONN_PORT)
for
{
conn, err := listener.Accept()
if err != nil
{
log.Fatal("Error accepting: ", err.Error())
}
go handleRequest(conn)
}
}
func handleRequest(conn net.Conn)
{
message, err := bufio.NewReader(conn).ReadString('\n')
if err != nil
{
fmt.Println("Error reading:", err.Error())
}
fmt.Print("Message Received from the client: ", string(message))
conn.Close()
}
  1. Run the program with the following command:
$ go run tcp-server-read-data.go

How it works...

Once we run the program, the TCP server will start locally listening on port 8080. Executing an echo command from the command line as follows will send a message to the TCP server:

$ echo -n "Hello to TCP server\n" | nc localhost 8080

This apparently logs it to a server console, as shown in the following screenshot:

Let’s understand the change we introduced in this recipe:

  1. First, we called handleRequest from the main() method using the go keyword, which means we are invoking a function in a Goroutine, as follows:
func main() 
{
...
go handleRequest(conn)
...
}
  1. Next, we defined the handleRequest function, which reads an incoming connection into the buffer until the first occurrence of \n and prints the message on the console. If there are any errors in reading the message then it prints the error message along with the error object and finally closes the connection, as follows:
func handleRequest(conn net.Conn) 
{
message, err := bufio.NewReader(conn).ReadString('\n')
if err != nil
{
fmt.Println("Error reading:", err.Error())
}
fmt.Print("Message Received: ", string(message))
conn.Close()
}
 

Writing data to a TCP connection

Another common, as well as important, scenario in any web application is to send the data back to the client or responding to the client. Go provides a convenient way to write a message on a connection as bytes, which we will be covering in this recipe.

Getting ready...

As we have already created a TCP server that reads incoming connection data in the previous recipe, we will just update it to write the message back to the client.

How to do it...

In this recipe, we are going to update the handleRequest method in the program to write data back to the client. Perform the following steps:

  1. Create tcp-server-write-data.go and copy the following content:
package main
import
(
"bufio"
"fmt"
"log"
"net"
)
const
(
CONN_HOST = "localhost"
CONN_PORT = "8080"
CONN_TYPE = "tcp"
)
func main()
{
listener, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
if err != nil
{
log.Fatal("Error starting tcp server : ", err)
}
defer listener.Close()
log.Println("Listening on " + CONN_HOST + ":" + CONN_PORT)
for
{
conn, err := listener.Accept()
if err != nil
{
log.Fatal("Error accepting: ", err.Error())
}
go handleRequest(conn)
}
}
func handleRequest(conn net.Conn)
{
message, err := bufio.NewReader(conn).ReadString('\n')
if err != nil
{
fmt.Println("Error reading: ", err.Error())
}
fmt.Print("Message Received:", string(message))
conn.Write([]byte(message + "\n"))
conn.Close()
}
  1. Run the program with the following command:
$ go run tcp-server-write-data.go

How it works...

Once we run the program, the TCP server will start locally listening on port 8080. Execute an echo command from the command line, as follows:

$ echo -n "Hello to TCP server\n" | nc localhost 8080

This will give us the following response from the server:

Hello to TCP server

Let’s look at the changes we introduced in this recipe to write data to the client. Everything in handleRequest is exactly the same as in the previous recipe except we introduced a new line that writes data as a byte array to the connection, as follows:

func handleRequest(conn net.Conn) 
{
...
conn.Write([]byte(message + "\n"))
...
}
 

Implementing HTTP request routing

Most of the time, you have to define more than one URL route in a web application, which involves mapping the URL path to the handlers or resources. In this recipe, we will learn how we can implement it in Go.

How to do it...

In this recipe, we will define three routes, such as /, /login, and /logout along with their handlers. Perform the following steps:

  1. Create http-server-basic-routing.go and copy the following content:
package main
import
(
"fmt"
"log"
"net/http"
)
const
(
CONN_HOST = "localhost"
CONN_PORT = "8080"
)
func helloWorld(w http.ResponseWriter, r *http.Request)
{
fmt.Fprintf(w, "Hello World!")
}
func login(w http.ResponseWriter, r *http.Request)
{
fmt.Fprintf(w, "Login Page!")
}
func logout(w http.ResponseWriter, r *http.Request)
{
fmt.Fprintf(w, "Logout Page!")
}
func main()
{
http.HandleFunc("/", helloWorld)
http.HandleFunc("/login", login)
http.HandleFunc("/logout", logout)
err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
if err != nil
{
log.Fatal("error starting http server : ", err)
return
}
}
  1. Run the program with the following command:
$ go run http-server-basic-routing.go

How it works...

Once we run the program, the HTTP server will start locally listening on port 8080 and accessing http://localhost:8080/, http://localhost:8080/login, and http://localhost:8080/logout from a browser or command line will render the message defined in the corresponding handler definition. For example, execute http://localhost:8080/ from the command line, as follows:

$ curl -X GET -i http://localhost:8080/

This will give us the following response from the server:

We could also execute http://localhost:8080/login from the command line as follows:

$ curl -X GET -i http://localhost:8080/login

This will give us the following response from the server:

Let's understand the program we have written:

  1. We started with defining three handlers or web resources, such as the following:
func helloWorld(w http.ResponseWriter, r *http.Request) 
{
fmt.Fprintf(w, "Hello World!")
}
func login(w http.ResponseWriter, r *http.Request)
{
fmt.Fprintf(w, "Login Page!")
}
func logout(w http.ResponseWriter, r *http.Request)
{
fmt.Fprintf(w, "Logout Page!")
}

Here, the helloWorld handler writes Hello World! on an HTTP response stream. In a similar way, login and logout handlers write Login Page! and Logout Page! on an HTTP response stream.

  1. Next, we registered three URL paths—/, /login, and /logout with DefaultServeMux using http.HandleFunc() . If an incoming request URL pattern matches one of the registered paths, then the corresponding handler is called passing (http.ResponseWriter, *http.Request) as a parameter to it, as follows:
func main() 
{
http.HandleFunc("/", helloWorld)
http.HandleFunc("/login", login)
http.HandleFunc("/logout", logout)
err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
if err != nil
{
log.Fatal("error starting http server : ", err)
return
}
}
 

Implementing HTTP request routing using Gorilla Mux

Go’s net/http package offers a lot of functionalities for URL routing of the HTTP requests. One thing it doesn’t do very well is dynamic URL routing. Fortunately, we can achieve this with the gorilla/mux package, which we will be covering in this recipe.

How to do it...

In this recipe, we will use gorilla/mux to define a few routes, like we did in our previous recipe, along with their handlers or resources. As we have already seen in one of our previous recipes, to use external packages, first we have to install the package using the go get command or we have to copy it manually to $GOPATH/src or $GOPATH. We will do the same in the recipe as well. Perform the following steps:

  1. Install github.com/gorilla/mux using the go get command, as follows:
$ go get github.com/gorilla/mux
  1. Create http-server-gorilla-mux-routing.go and copy the following content:
package main
import
(
"net/http"
"github.com/gorilla/mux"
)
const
(
CONN_HOST = "localhost"
CONN_PORT = "8080"
)
var GetRequestHandler = http.HandlerFunc
(
func(w http.ResponseWriter, r *http.Request)
{
w.Write([]byte("Hello World!"))
}
)
var PostRequestHandler = http.HandlerFunc
(
func(w http.ResponseWriter, r *http.Request)
{
w.Write([]byte("It's a Post Request!"))
}
)
var PathVariableHandler = http.HandlerFunc
(
func(w http.ResponseWriter, r *http.Request)
{
vars := mux.Vars(r)
name := vars["name"]
w.Write([]byte("Hi " + name))
}
)
func main()
{
router := mux.NewRouter()
router.Handle("/", GetRequestHandler).Methods("GET")
router.Handle("/post", PostRequestHandler).Methods("POST")
router.Handle("/hello/{name}",
PathVariableHandler).Methods("GET", "PUT")
http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
}
  1. Run the program with the following command:
$ go run http-server-gorilla-mux-routing.go

How it works...

Once we run the program, the HTTP server will start locally listening on port 8080, and accessing http://localhost:8080/, http://localhost:8080/post, and http://localhost:8080/hello/foo from a browser or command line will produce the message defined in the corresponding handler definition. For example, execute http://localhost:8080/ from the command line, as follows:

$ curl -X GET -i http://localhost:8080/

This will give us the following response from the server:

We could also execute http://localhost:8080/hello/foo from the command line, as follows:

$ curl -X GET -i http://localhost:8080/hello/foo

This will give us the following response from the server:

Let's understand the code changes we made in this recipe:

  1. First, we defined GetRequestHandler and PostRequestHandler, which simply write a message on an HTTP response stream, as follows:
var GetRequestHandler = http.HandlerFunc
(
func(w http.ResponseWriter, r *http.Request)
{
w.Write([]byte("Hello World!"))
}
)
var PostRequestHandler = http.HandlerFunc
(
func(w http.ResponseWriter, r *http.Request)
{
w.Write([]byte("It's a Post Request!"))
}
)
  1. Next, we defined PathVariableHandler, which extracts request path variables, gets the value, and writes it to an HTTP response stream, as follows:
var PathVariableHandler = http.HandlerFunc
(
func(w http.ResponseWriter, r *http.Request)
{
vars := mux.Vars(r)
name := vars["name"]
w.Write([]byte("Hi " + name))
}
)
  1. Then, we registered all these handlers with the gorilla/mux router and instantiated it, calling the NewRouter() handler of the mux router, as follows:
func main() 
{
router := mux.NewRouter()
router.Handle("/", GetRequestHandler).Methods("GET")
router.Handle("/post", PostCallHandler).Methods("POST")
router.Handle("/hello/{name}", PathVariableHandler).
Methods("GET", "PUT")
http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
}
 

Logging HTTP requests

Logging HTTP requests is always useful when troubleshooting a web application, so it’s a good idea to log a request/response with a proper message and logging level. Go provides the log package, which can help us to implement logging in an application. However, in this recipe we will be using Gorilla logging handlers to implement it because the library offers more features such as logging in Apache Combined Log Format and Apache Common Log Format, which are not yet supported by the Go log package.

Getting Ready...

As we have already created an HTTP server and defined routes using Gorilla Mux in our previous recipe, we will update it to incorporate Gorilla logging handlers.

How to do it...

Let's implement logging using Gorilla handlers. Perform the following steps:

  1. Install the github.com/gorilla/handler and github.com/gorilla/mux packages using the go get command, as follows:
$ go get github.com/gorilla/handlers
$ go get github.com/gorilla/mux
  1. Create http-server-request-logging.go and copy the following content:
package main
import
(
"net/http"
"os"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
const
(
CONN_HOST = "localhost"
CONN_PORT = "8080"
)
var GetRequestHandler = http.HandlerFunc
(
func(w http.ResponseWriter, r *http.Request)
{
w.Write([]byte("Hello World!"))
}
)
var PostRequestHandler = http.HandlerFunc
(
func(w http.ResponseWriter, r *http.Request)
{
w.Write([]byte("It's a Post Request!"))
}
)
var PathVariableHandler = http.HandlerFunc
(
func(w http.ResponseWriter, r *http.Request)
{
vars := mux.Vars(r)
name := vars["name"]
w.Write([]byte("Hi " + name))
}
)
func main()
{
router := mux.NewRouter()
router.Handle("/", handlers.LoggingHandler(os.Stdout,
http.HandlerFunc(GetRequestHandler))).Methods("GET")
logFile, err := os.OpenFile("server.log",
os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil
{
log.Fatal("error starting http server : ", err)
return
}
router.Handle("/post", handlers.LoggingHandler(logFile,
PostRequestHandler)).Methods("POST")
router.Handle("/hello/{name}",
handlers.CombinedLoggingHandler(logFile,
PathVariableHandler)).Methods("GET")
http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
}
  1. Run the program, using the following command:
$ go run http-server-request-logging.go

How it works...

Once we run the program, the HTTP server will start locally listening on port 8080.

Execute a GET request from the command line, as follows:

$ curl -X GET -i http://localhost:8080/

This will log the request details in the server log in the Apache Common Log Format, as shown in the following screenshot:

We could also execute http://localhost:8080/hello/foo from the command line, as follows:

$ curl -X GET -i http://localhost:8080/hello/foo

This will log the request details in the server.log in the Apache Combined Log Format, as shown in the following screenshot:

Let's understand what we have done in this recipe:

  1. Firstly, we imported two additional packages, one is os, which we use to open a file. The other one is github.com/gorilla/handlers, which we use to import logging handlers for logging HTTP requests, as follows:
import ( "net/http" "os" "github.com/gorilla/handlers" "github.com/gorilla/mux" )
  1. Next, we modified the main() method. Using router.Handle("/", handlers.LoggingHandler(os.Stdout,
    http.HandlerFunc(GetRequestHandler))).Methods("GET"), we wrapped GetRequestHandler with a Gorilla logging handler, and passed a standard output stream as a writer to it, which means we are simply asking to log every request with the URL path / on the console in Apache Common Log Format.
  2. Next, we create a new file named server.log in write-only mode, or we open it, if it already exists. If there is any error, then log it and exit with a status code of 1, as follows:
logFile, err := os.OpenFile("server.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil
{
log.Fatal("error starting http server : ", err)
return
}
  1. Using router.Handle("/post", handlers.LoggingHandler(logFile, PostRequestHandler)).Methods("POST"), we wrapped GetRequestHandler with a Gorilla logging handler and passed the file as a writer to it, which means we are simply asking to log every request with the URL path /post in a file named /hello/{name} in Apache Common Log Format.
  2. Using router.Handle("/hello/{name}", handlers.CombinedLoggingHandler(logFile, PathVariableHandler)).Methods("GET"), we wrapped GetRequestHandler with a Gorilla logging handler and passed the file as a writer to it, which means we are simply asking to log every request with the URL path /hello/{name} in a file named server.log in Apache Combined Log Format.
About the Author
  • Arpit Aggarwal

    Arpit Aggarwal is a programmer with over 7 years of industry experience in software analysis, design, effort estimation, development, troubleshooting, testing, and supporting web applications. He is among the top contributors of StackOverflow with more than 9,000 reputation and more than 100 badges in multiple areas such as Java, Scala, Go, Spring, Spring-MVC, GiT, Angular, Unit Testing, Web Services, and Docker, and has written many technical articles for Java Code Geeks, System Code Geeks, Web Code Geeks, and DZone.

    Browse publications by this author
Latest Reviews (1 reviews total)
good purchase program and great content
Go Web Development Cookbook
Unlock this book and the full library FREE for 7 days
Start now