Search icon
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletters
Free Learning
Arrow right icon
Go Web Development Cookbook
Go Web Development Cookbook

Go Web Development Cookbook: Build full-stack web applications with Go

By Arpit Aggarwal
£28.99 £19.99
Book Apr 2018 338 pages 1st Edition
eBook
£28.99 £19.99
Print
£37.99
Subscription
£13.99 Monthly
eBook
£28.99 £19.99
Print
£37.99
Subscription
£13.99 Monthly

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Buy Now

Product Details


Publication date : Apr 23, 2018
Length 338 pages
Edition : 1st Edition
Language : English
ISBN-13 : 9781787286740
Vendor :
Google
Category :
Table of content icon View table of contents Preview book icon Preview Book

Go Web Development Cookbook

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.
Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • •Become proficient in RESTful web services
  • •Build scalable, high-performant web applications in Go
  • •Get acquainted with Go frameworks for web development

Description

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.

What you will learn

[*] Create a simple HTTP and TCP web server and understand how it works [*] Explore record in a MySQL and MongoDB database [*] Write and consume RESTful web service in Go [*] Invent microservices in Go using Micro – a microservice toolkit [*] Create and Deploy the Beego application with Nginx [*] Deploy Go web application and Docker containers on an AWS EC2 instance

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Buy Now

Product Details


Publication date : Apr 23, 2018
Length 338 pages
Edition : 1st Edition
Language : English
ISBN-13 : 9781787286740
Vendor :
Google
Category :

Table of Contents

13 Chapters
Preface Chevron down icon Chevron up icon
Creating Your First Server in Go Chevron down icon Chevron up icon
Working with Templates, Static Files, and HTML Forms Chevron down icon Chevron up icon
Working with Sessions, Error Handling, and Caching in Go Chevron down icon Chevron up icon
Writing and Consuming RESTful Web Services in Go Chevron down icon Chevron up icon
Working with SQL and NoSQL Databases Chevron down icon Chevron up icon
Writing Microservices in Go Using Micro – a Microservice Toolkit Chevron down icon Chevron up icon
Working with WebSocket in Go Chevron down icon Chevron up icon
Working with the Go Web Application Framework – Beego Chevron down icon Chevron up icon
Working with Go and Docker Chevron down icon Chevron up icon
Securing a Go Web Application Chevron down icon Chevron up icon
Deploying a Go Web App and Docker Containers to AWS Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Filter icon Filter
Top Reviews
Rating distribution
Empty star icon Empty star icon Empty star icon Empty star icon Empty star icon 0
(0 Ratings)
5 star 0%
4 star 0%
3 star 0%
2 star 0%
1 star 0%

Filter reviews by


No reviews found
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.