Published
The simplest way to get a website up and running is to create a fully static site, that is, a site composed exclusively of unchanging content. The server delivers the same files to each user.
Go makes this easy with the net/http
package, because it has a static file server.
In this post, we'll create two static file severs: one for CSS files and images, and another for raw HTML templates.
Start by changing directories to your $GOPATH:
cd ~/go/src/github.com/<your GitGub username> # Change directories to your GOPATH.
mkdir blog # Make a new directory named "blog".
cd blog # Change directories to the new blog directory.
touch README.md main.go # Create the README.md and main.go files.
In main.go
, type:
package main
import (
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
})
log.Println("Server started on http://localhost:3000")
http.ListenAndServe(":3000", nil)
}
Start your server by running go run main.go
. If you visit http://localhost:3000, you should see this.
Next, we'll add the ability to serve static files. We'll add a static file server, and register the route with our router.
package main import ( "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hello world")) })
fs := http.FileServer(http.Dir("static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
log.Println("Server started on http://localhost:3000") http.ListenAndServe(":3000", nil) }
After that, create the static
directory that we're serving:
mkdir static
Then, create an index.html
file there:
touch static/index.html
In that file, type:
<!DOCTYPE html>
<html>
<head>
<title>Blog</title>
</head>
<body>
<h1>Blog</h1>
</body>
</html>
Start your server again by running
go run main.go
and visit http://localhost:3000/static. You'll see that the index.html
file is being served:
However, this isn't ideal for two reasons:
/static
path.To change this, we can change our "/"
handler to serve HTML templates.
package main import ( "html/template" "log" "net/http" ) func main() { templates, err := template.ParseGlob("templates/*") if err != nil { log.Fatalf("failed to parse templates directory: %v", err) } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
path := r.URL.Path
if err := templates.ExecuteTemplate(w, path, nil); err != nil {
log.Printf("error executing template %s: %v", path, err)
}}) fs := http.FileServer(http.Dir("static")) http.Handle("/static/", http.StripPrefix("/static/", fs)) log.Println("Server started on http://localhost:3000") http.ListenAndServe(":3000", nil) }
We need to create this templates directory, and move our index.html
under it:
mkdir templates # Create the templates directory.
mv static/index.html templates/index.html # Move the index.html file from the static directory to the templates directory.
Now we can run our server again:
go run main.go
And visit http://localhost:3000.
But nothing shows up. If you check the output in your terminal, you'll see output that looks like,
2018/08/07 02:59:55 error executing template /index: html/template: "/index" is undefined
To fix this, we need to add a check that serve the index.html
when the value of path
is "/"
.
package main import ( "html/template" "log" "net/http" ) func main() { templates, err := template.ParseGlob("templates/*") if err != nil { log.Fatalf("failed to parse templates directory: %v", err) } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path
if path == "/" {
path = "index"
}
if err := templates.ExecuteTemplate(w, path, nil); err != nil { log.Printf("error executing template %s: %v", path, err) } }) fs := http.FileServer(http.Dir("static")) http.Handle("/static/", http.StripPrefix("/static/", fs)) log.Println("Server started on http://localhost:3000") http.ListenAndServe(":3000", nil) }
However, this code will still cause an error that looks like:
2018/08/07 03:04:42 error executing template index: html/template: "index" is undefined
At this point, the solution is pretty obvious: we need to add the .html
file extension so that we can an exact match on the template:
package main import ( "html/template" "log" "net/http" ) func main() { templates, err := template.ParseGlob("templates/*") if err != nil { log.Fatalf("failed to parse templates directory: %v", err) } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path if path == "/" { path = "index" }
if err := templates.ExecuteTemplate(w, path+".html"
, nil); err != nil {log.Printf("error executing template %s: %v", path, err) } }) fs := http.FileServer(http.Dir("static")) http.Handle("/static/", http.StripPrefix("/static/", fs)) log.Println("Server started on http://localhost:3000") http.ListenAndServe(":3000", nil) }
We're almost there. There's still one issue left with our code. Let's add another HTML file in our templates folder, which will make the last issue obvious:
touch templates/about.html
In that file, we'll add some HTML:
<!DOCTYPE html>
<html>
<head>
<title>About</title>
</head>
<body>
<h1>About</h1>
</body>
</html>
Run our server with:
go run main.go
If you open your browser to http://localhost:3000/about, you'll still see nothing. If you check the terminal output, you'll see:
2018/08/07 03:11:23 error executing template /about: html/template: "/about.html" is undefined
The issue is the leading "/"
character. The template lookup function look for a file named "/about.html" in the the templates directory, but we've only got a file named "about.html". To fix this, we need to strip that leading "/"
:
package main import ( "html/template" "log" "net/http" "strings" ) func main() { templates, err := template.ParseGlob("templates/*") if err != nil { log.Fatalf("failed to parse templates directory: %v", err) } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path if path == "/" { path = "index" }
path = strings.TrimPrefix(path, "/")
if err := templates.ExecuteTemplate(w, path+".html", nil); err != nil { log.Printf("error executing template %s: %v", path, err) } }) fs := http.FileServer(http.Dir("static")) http.Handle("/static/", http.StripPrefix("/static/", fs)) log.Println("Server started on http://localhost:3000") http.ListenAndServe(":3000", nil) }
Now, if we run our code:
go run main.go
and open our browser to http://localhost:3000/about, we'll see:
Any HTML file we place under /posts
will now be served at /posts/<filename>>
. While this is very basic, it gives us a fully functioning website.
In the next post, we'll see how to use the html/template
package to render HTML content dynamically.
ben at bentranter dot io
.