Larry Price

And The Endless Cup Of Coffee

Finishing the Google Go Writing Web Applications Tutorial

| Comments

A golang web app tutorial

I did some work with Google Go recently and had the chance to follow their great tutorial Writing Web Applications. The tutorial is pretty simple: use the Go http library to create a very simple wiki-style site. I like this tutorial a lot because there’s not too much hand-holding, but they do eventually hand you the final code listing. Then the good people at Google give you the tall task of completing the following ‘Other tasks’ without solutions:

This is what I’d like to go over. I scoured the web and didn’t have much luck finding solutions to these issues. That would be okay if they were all trivial, but the final step is not straightforward. I’m going to assume you’ve already gone over the tutorial. You can see my repository on Github, and I have included links to the appropriate commits in the code sections of this blog post.

Store templates in tmpl/ and page data in data/

The tutorial originally has the developer store all pages in the project directory. Every time a user made a new wiki page, a new file would creep into the project directory. All HTML templates were also stored in the project directory.

Moving templates is quite trivial. In the global scope:

wiki.golink
1
2
-var templates = template.Must(template.ParseFiles("edit.html", "view.html"))
+var templates = template.Must(template.ParseFiles("tmpl/edit.html", "tmpl/view.html"))

I found moving the page data to data/ was a little trickier, especially if the directory didn’t already exist. You may not have the same issue, but I remedied this by creating the directory if it doesn’t exist. My save function differences:

wiki.golink
1
2
3
4
5
6
7
func (p *Page) save() error {
-    filename := p.Title + ".txt"
-    return ioutil.WriteFile(filename, p.Body, 0600)
+  os.Mkdir("data", 0777)
+  filename := "data/" + p.Title + ".txt"
+  return ioutil.WriteFile(filename, p.Body, 0600)
}

Add a handler to make the web root redirect to /view/FrontPage

All we’re going to do is create a simple handler called rootHandler that redirects to a new page called FrontPage. We then add it in the main function. The tutorial had us wrap out handlers in a function call to take some special actions, but that wrapper would mess up our handler in its current form. So I just Redirect to the view handler, which will then decide whether to view or create the FrontPage.

wiki.golink
1
2
3
4
5
6
7
8
9
10
11
+ func rootHandler(w http.ResponseWriter, r *http.Request) {
+   http.Redirect(w, r, "/view/FrontPage", http.StatusFound)
+ }

...

func main() {
+  http.HandleFunc("/", rootHandler)
  http.HandleFunc("/view/", makeHandler(viewHandler))
  http.HandleFunc("/edit/", makeHandler(editHandler))
  http.HandleFunc("/save/", makeHandler(saveHandler))

Spruce up the page templates by making them valid HTML and adding some CSS rules.

I took my old .html files and put them through a validator. Making them valid only involved adding DOCTYPE, html, and head tags. The head tag needed meta, and title tags and we were valid. I’ve shown view.html below.

view.htmllink
1
2
3
4
5
6
7
8
9
10
11
12
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title>Wiki made using Golang</title>
+</head>
 <h1></h1>

 <p>[<a href="/edit/">edit</a>]</p>

 <div></div>
+</html>

Implement inter-page linking by converting instances of [PageName]

Converting [PageName] to a hyperlink was a bit more complicated than expected. I originally just tried to run the string through ReplaceAllFunc and replace all instance of [PageName] with an equivalent hyperlink. This does not work because we use Go’s ExecuteTemplate method to render our template. ExecuteTemplate escapes any HTML that we give it to prevent us from displaying unwanted HTML. Getting around this was the fun part, because I want the benefit of escaped HTML while still having the ability to substitute my own HTML.

As it turns out, ExecuteTemplate will not escape variables of the type template.HTML. So I added another variable onto the Page struct called DisplayBody.

wiki.golink
1
2
3
4
5
type Page struct {
     Title string
     Body  []byte
+    DisplayBody template.HTML
}

Next, I create a regular expression to find instances of [PageName] and I put the defintion above the main method.

wiki.golink
1
+var linkRegexp = regexp.MustCompile("\\[([a-zA-Z0-9]+)\\]")

In my viewHandler, I escape Body and then set DisplayBody to that escaped string with the links substituted.

wiki.golink
1
2
3
4
5
6
7
8
9
+  escapedBody := []byte(template.HTMLEscapeString(string(p.Body)))
+
+  p.DisplayBody = template.HTML(linkRegexp.ReplaceAllFunc(escapedBody, func(str []byte) []byte {
+      matched := linkRegexp.FindStringSubmatch(string(str))
+      out := []byte("<a href=\"/view/"+matched[1]+"\">"+matched[1]+"</a>")
+      return out
+    }))
  renderTemplate(w, "view", p)
}

To finish up, I modify the view.html to show DisplayBody. I don’t use printf, because that would turn DisplayBody back into a string and ExecuteTemplate would escape it.

wiki.golink
1
2
-<div></div>
+<div></div>

And that completes the extra exercises for Google’s Writing Web Applications tutorial. Hopefully one day this helps someone who gets stuck.

Comments