A simple web server
The first thing our chat application needs is a web server that has two main responsibilities:
Serving the HTML and JavaScript chat clients that run in the user's browser
Accepting web socket connections to allow the clients to communicate
Note
The GOPATH
environment variable is covered in detail in Appendix, Good Practices for a Stable Go environment. Be sure to read that first if you need help getting set up.
Create a main.go
file inside a new folder called chat
in your GOPATH
and add the following code:
package main import ( "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(` <html> <head> <title>Chat</title> </head> <body> Let's chat! </body> </html> )) }) // start the web server if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal("ListenAndServe:", err) } }
This is a complete, albeit simple, Go program that will:
Listen to the root path using the
net/http
packageWrite out the hardcoded HTML when a request is made
Start a web server on port
:8080
using theListenAndServe
method
The http.HandleFunc
function maps the path pattern /
to the function we pass as the second argument, so when the user hits http://localhost:8080/
, the function will be executed. The function signature of func(w http.ResponseWriter, r *http.Request)
is a common way of handling HTTP requests throughout the Go standard library.
Tip
We are using package main
because we want to build and run our program from the command line. However, if we were building a reusable chatting package, we might choose to use something different, such as package chat
.
In a terminal, run the program by navigating to the main.go
file you just created and execute the following command:
go run main.go
Tip
The go run
command is a helpful shortcut for running simple Go programs. It builds and executes a binary in one go. In the real world, you usually use go build
yourself to create and distribute binaries. We will explore this later.
Open the browser and type http://localhost:8080
to see the Let's chat! message.
Having the HTML code embedded within our Go code like this works, but it is pretty ugly and will only get worse as our projects grow. Next, we will see how templates can help us clean this up.
Separating views from logic using templates
Templates allow us to blend generic text with specific text, for instance, injecting a user's name into a welcome message. For example, consider the following template:
Hello {{name}}, how are you?
We are able to replace the {{name}}
text in the preceding template with the real name of a person. So if Bruce signs in, he might see:
Hello Bruce, how are you?
The Go standard library has two main template packages: one called text/template
for text and one called html/template
for HTML. The html/template
package does the same as the text version except that it understands the context in which data will be injected into the template. This is useful because it avoids script injection attacks and resolves common issues such as having to encode special characters for URLs.
Initially, we just want to move the HTML code from inside our Go code to its own file, but won't blend any text just yet. The template packages make loading external files very easy, so it's a good choice for us.
Create a new folder under our chat
folder called templates
and create a chat.html
file inside it. We will move the HTML from main.go
to this file, but we will make a minor change to ensure our changes have taken effect:
<html> <head> <title>Chat</title> </head> <body> Let's chat (from template) </body> </html>
Now, we have our external HTML file ready to go, but we need a way to compile the template and serve it to the user's browser.
Tip
Compiling a template is a process by which the source template is interpreted and prepared for blending with various data, which must happen before a template can be used but only needs to happen once.
We are going to write our own struct
type that is responsible for loading, compiling, and delivering our template. We will define a new type that will take a filename
string, compile the template once (using the sync.Once
type), keep the reference to the compiled template, and then respond to HTTP requests. You will need to import the text/template
, path/filepath
, and sync
packages in order to build your code.
In main.go
, insert the following code above the func main()
line:
// templ represents a single template type templateHandler struct { once sync.Once filename string templ *template.Template } // ServeHTTP handles the HTTP request. func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { t.once.Do(func() { t.templ = template.Must(template.ParseFiles(filepath.Join("templates", t.filename))) }) t.templ.Execute(w, nil) }
Tip
Did you know that you could automate the adding and removing of imported packages? See Appendix, Good Practices for a Stable Go Environment, on how to do this.
The templateHandler
type has a single method called ServeHTTP
whose signature looks suspiciously like the method we passed to http.HandleFunc
earlier. This method will load the source file, compile the template and execute it, and write the output to the specified http.ResponseWriter
method. Because the ServeHTTP
method satisfies the http.Handler
interface, we can actually pass it directly to http.Handle
.
Tip
A quick look at the Go standard library source code, which is located at
http://golang.org/pkg/net/http/#Handler, will reveal that the interface definition for http.Handler
specifies that only the ServeHTTP
method need be present in order for a type to be used to serve HTTP requests by the net/http
package.
Doing things once
We only need to compile the template once, and there are a few different ways to approach this in Go. The most obvious is to have a NewTemplateHandler
function that creates the type and calls some initialization code to compile the template. If we were sure the function would be called by only one goroutine (probably the main one during the setup in the main
function), this would be a perfectly acceptable approach. An alternative, which we have employed in the preceding section, is to compile the template once inside the ServeHTTP
method. The sync.Once
type guarantees that the function we pass as an argument will only be executed once, regardless of how many goroutines are calling ServeHTTP
. This is helpful because web servers in Go are automatically concurrent and once our chat application takes the world by storm, we could very well expect to have many concurrent calls to the ServeHTTP
method.
Compiling the template inside the ServeHTTP
method also ensures that our code does not waste time doing work before it is definitely needed. This lazy initialization approach doesn't save us much in our present case, but in cases where the setup tasks are time- and resource-intensive and where the functionality is used less frequently, it's easy to see how this approach would come in handy.
Using your own handlers
To implement our templateHandler
type, we need to update the main
body function so that it looks like this:
func main() { // root http.Handle("/", &templateHandler{filename: "chat.html"}) // start the web server if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal("ListenAndServe:", err) } }
The templateHandler
structure is a valid http.Handler
type so we can pass it directly to the http.Handle
function and ask it to handle requests that match the specified pattern. In the preceding code, we created a new object of the type templateHandler
, specifying the filename as chat.html
that we then take the address of (using the &
address of the operator) and pass it to the http.Handle
function. We do not store a reference to our newly created templateHandler
type, but that's OK because we don't need to refer to it again.
In your terminal, exit the program by pressing Ctrl + C and re-run it, then refresh your browser and notice the addition of the (from template) text. Now our code is much simpler than an HTML code and free from its ugly blocks.
Properly building and executing Go programs
Running Go programs using a go run
command is great when our code is made up of a single main.go
file. However, often we might quickly need to add other files. This requires us to properly build the whole package into an executable binary before running it. This is simple enough, and from now on, this is how you will build and run your programs in a terminal:
go build -o {name} ./{name}
The go build
command creates the output binary using all the .go
files in the specified folder, and the -o
flag indicates the name of the generated binary. You can then just run the program directly by calling it by name.
For example, in the case of our chat application, we could run:
go build -o chat ./chat
Since we are compiling templates the first time the page is served, we will need to restart your web server program every time anything changes in order to see the changes take effect.