mirror of
https://github.com/golang/go
synced 2024-11-22 03:04:41 -07:00
Wiki codelab, complete with tests.
R=r, rsc, gri CC=golang-dev https://golang.org/cl/887045
This commit is contained in:
parent
b2183701c0
commit
78d9a6074d
29
doc/codelab/wiki/Makefile
Normal file
29
doc/codelab/wiki/Makefile
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
# Use of this source code is governed by a BSD-style
|
||||||
|
# license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
include ../../../src/Make.$(GOARCH)
|
||||||
|
|
||||||
|
all: index.html
|
||||||
|
|
||||||
|
# ugly hack to deal with whitespaces in $GOROOT
|
||||||
|
nullstring :=
|
||||||
|
space := $(nullstring) # a space at the end
|
||||||
|
QUOTED_GOROOT:=$(subst $(space),\ ,$(GOROOT))
|
||||||
|
|
||||||
|
include $(QUOTED_GOROOT)/src/Make.common
|
||||||
|
|
||||||
|
CLEANFILES+=index.html srcextract.bin htmlify.bin
|
||||||
|
|
||||||
|
index.html: srcextract.bin htmlify.bin
|
||||||
|
awk '/^!/{system(substr($$0,2)); next} {print}' "$$@" < wiki.html > index.html
|
||||||
|
|
||||||
|
test: final.bin
|
||||||
|
./test.sh
|
||||||
|
rm -f final.6 final.bin
|
||||||
|
|
||||||
|
%.bin: %.$O
|
||||||
|
$(QUOTED_GOBIN)/$(LD) -o $@ $<
|
||||||
|
%.$O:
|
||||||
|
$(QUOTED_GOBIN)/$(GC) $*.go
|
||||||
|
|
6
doc/codelab/wiki/edit.html
Normal file
6
doc/codelab/wiki/edit.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<h1>Editing {title}</h1>
|
||||||
|
|
||||||
|
<form action="/save/{title}" method="POST">
|
||||||
|
<div><textarea name="body" rows="20" cols="80">{body|html}</textarea></div>
|
||||||
|
<div><input type="submit" value="Save"></div>
|
||||||
|
</form>
|
100
doc/codelab/wiki/final-noclosure.go
Normal file
100
doc/codelab/wiki/final-noclosure.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"http"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"template"
|
||||||
|
)
|
||||||
|
|
||||||
|
type page struct {
|
||||||
|
title string
|
||||||
|
body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) save() os.Error {
|
||||||
|
filename := p.title + ".txt"
|
||||||
|
return ioutil.WriteFile(filename, p.body, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadPage(title string) (*page, os.Error) {
|
||||||
|
filename := title + ".txt"
|
||||||
|
body, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &page{title: title, body: body}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func viewHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title, err := getTitle(c, r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(c, "/edit/"+title, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
renderTemplate(c, "view", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func editHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title, err := getTitle(c, r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
p = &page{title: title}
|
||||||
|
}
|
||||||
|
renderTemplate(c, "edit", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title, err := getTitle(c, r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body := r.FormValue("body")
|
||||||
|
p := &page{title: title, body: []byte(body)}
|
||||||
|
err = p.save()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c, err.String(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(c, "/view/"+title, http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderTemplate(c *http.Conn, tmpl string, p *page) {
|
||||||
|
t, err := template.ParseFile(tmpl+".html", nil)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c, err.String(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = t.Execute(p, c)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c, err.String(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lenPath = len("/view/")
|
||||||
|
|
||||||
|
var titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")
|
||||||
|
|
||||||
|
func getTitle(c *http.Conn, r *http.Request) (title string, err os.Error) {
|
||||||
|
title = r.URL.Path[lenPath:]
|
||||||
|
if !titleValidator.MatchString(title) {
|
||||||
|
http.NotFound(c, r)
|
||||||
|
err = os.NewError("Invalid Page Title")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/view/", viewHandler)
|
||||||
|
http.HandleFunc("/edit/", editHandler)
|
||||||
|
http.HandleFunc("/save/", saveHandler)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
52
doc/codelab/wiki/final-noerror.go
Normal file
52
doc/codelab/wiki/final-noerror.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"http"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"template"
|
||||||
|
)
|
||||||
|
|
||||||
|
type page struct {
|
||||||
|
title string
|
||||||
|
body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) save() os.Error {
|
||||||
|
filename := p.title + ".txt"
|
||||||
|
return ioutil.WriteFile(filename, p.body, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadPage(title string) (*page, os.Error) {
|
||||||
|
filename := title + ".txt"
|
||||||
|
body, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &page{title: title, body: body}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const lenPath = len("/view/")
|
||||||
|
|
||||||
|
func editHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
p = &page{title: title}
|
||||||
|
}
|
||||||
|
t, _ := template.ParseFile("edit.html", nil)
|
||||||
|
t.Execute(p, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func viewHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
p, _ := loadPage(title)
|
||||||
|
t, _ := template.ParseFile("view.html", nil)
|
||||||
|
t.Execute(p, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/view/", viewHandler)
|
||||||
|
http.HandleFunc("/edit/", editHandler)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
90
doc/codelab/wiki/final-parsetemplate.go
Normal file
90
doc/codelab/wiki/final-parsetemplate.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"http"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"template"
|
||||||
|
)
|
||||||
|
|
||||||
|
type page struct {
|
||||||
|
title string
|
||||||
|
body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) save() os.Error {
|
||||||
|
filename := p.title + ".txt"
|
||||||
|
return ioutil.WriteFile(filename, p.body, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadPage(title string) (*page, os.Error) {
|
||||||
|
filename := title + ".txt"
|
||||||
|
body, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &page{title: title, body: body}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func viewHandler(c *http.Conn, r *http.Request, title string) {
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(c, "/edit/"+title, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
renderTemplate(c, "view", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func editHandler(c *http.Conn, r *http.Request, title string) {
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
p = &page{title: title}
|
||||||
|
}
|
||||||
|
renderTemplate(c, "edit", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveHandler(c *http.Conn, r *http.Request, title string) {
|
||||||
|
body := r.FormValue("body")
|
||||||
|
p := &page{title: title, body: []byte(body)}
|
||||||
|
err := p.save()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c, err.String(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(c, "/view/"+title, http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderTemplate(c *http.Conn, tmpl string, p *page) {
|
||||||
|
t, err := template.ParseFile(tmpl+".html", nil)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c, err.String(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = t.Execute(p, c)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c, err.String(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lenPath = len("/view/")
|
||||||
|
|
||||||
|
var titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")
|
||||||
|
|
||||||
|
func makeHandler(fn func(*http.Conn, *http.Request, string)) http.HandlerFunc {
|
||||||
|
return func(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
if !titleValidator.MatchString(title) {
|
||||||
|
http.NotFound(c, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fn(c, r, title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/view/", makeHandler(viewHandler))
|
||||||
|
http.HandleFunc("/edit/", makeHandler(editHandler))
|
||||||
|
http.HandleFunc("/save/", makeHandler(saveHandler))
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
64
doc/codelab/wiki/final-template.go
Normal file
64
doc/codelab/wiki/final-template.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"http"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"template"
|
||||||
|
)
|
||||||
|
|
||||||
|
type page struct {
|
||||||
|
title string
|
||||||
|
body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) save() os.Error {
|
||||||
|
filename := p.title + ".txt"
|
||||||
|
return ioutil.WriteFile(filename, p.body, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadPage(title string) (*page, os.Error) {
|
||||||
|
filename := title + ".txt"
|
||||||
|
body, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &page{title: title, body: body}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const lenPath = len("/view/")
|
||||||
|
|
||||||
|
func editHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
p = &page{title: title}
|
||||||
|
}
|
||||||
|
renderTemplate(c, "view", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func viewHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
p, _ := loadPage(title)
|
||||||
|
renderTemplate(c, "edit", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
body := r.FormValue("body")
|
||||||
|
p := &page{title: title, body: []byte(body)}
|
||||||
|
p.save()
|
||||||
|
http.Redirect(c, "/view/"+title, http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderTemplate(c *http.Conn, tmpl string, p *page) {
|
||||||
|
t, _ := template.ParseFile(tmpl+".html", nil)
|
||||||
|
t.Execute(p, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/view/", viewHandler)
|
||||||
|
http.HandleFunc("/edit/", editHandler)
|
||||||
|
http.HandleFunc("/save/", saveHandler)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
93
doc/codelab/wiki/final.go
Normal file
93
doc/codelab/wiki/final.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"http"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"template"
|
||||||
|
)
|
||||||
|
|
||||||
|
type page struct {
|
||||||
|
title string
|
||||||
|
body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) save() os.Error {
|
||||||
|
filename := p.title + ".txt"
|
||||||
|
return ioutil.WriteFile(filename, p.body, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadPage(title string) (*page, os.Error) {
|
||||||
|
filename := title + ".txt"
|
||||||
|
body, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &page{title: title, body: body}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func viewHandler(c *http.Conn, r *http.Request, title string) {
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(c, "/edit/"+title, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
renderTemplate(c, "view", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func editHandler(c *http.Conn, r *http.Request, title string) {
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
p = &page{title: title}
|
||||||
|
}
|
||||||
|
renderTemplate(c, "edit", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveHandler(c *http.Conn, r *http.Request, title string) {
|
||||||
|
body := r.FormValue("body")
|
||||||
|
p := &page{title: title, body: []byte(body)}
|
||||||
|
err := p.save()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c, err.String(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(c, "/view/"+title, http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
var templates = make(map[string]*template.Template)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for _, tmpl := range []string{"edit", "view"} {
|
||||||
|
templates[tmpl] = template.MustParseFile(tmpl+".html", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderTemplate(c *http.Conn, tmpl string, p *page) {
|
||||||
|
err := templates[tmpl].Execute(p, c)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c, err.String(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lenPath = len("/view/")
|
||||||
|
|
||||||
|
var titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")
|
||||||
|
|
||||||
|
func makeHandler(fn func(*http.Conn, *http.Request, string)) http.HandlerFunc {
|
||||||
|
return func(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
if !titleValidator.MatchString(title) {
|
||||||
|
http.NotFound(c, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fn(c, r, title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/view/", makeHandler(viewHandler))
|
||||||
|
http.HandleFunc("/edit/", makeHandler(editHandler))
|
||||||
|
http.HandleFunc("/save/", makeHandler(saveHandler))
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
12
doc/codelab/wiki/htmlify.go
Normal file
12
doc/codelab/wiki/htmlify.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"template"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
b, _ := ioutil.ReadAll(os.Stdin)
|
||||||
|
template.HTMLFormatter(os.Stdout, b, "")
|
||||||
|
}
|
15
doc/codelab/wiki/http-sample.go
Normal file
15
doc/codelab/wiki/http-sample.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handler(c *http.Conn, r *http.Request) {
|
||||||
|
fmt.Fprintf(c, "Hi there, I love %s!", r.URL.Path[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", handler)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
997
doc/codelab/wiki/index.html
Normal file
997
doc/codelab/wiki/index.html
Normal file
@ -0,0 +1,997 @@
|
|||||||
|
<div class="content">
|
||||||
|
|
||||||
|
<h1>Writing Web Applications</h1>
|
||||||
|
|
||||||
|
<h2>Introduction</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Covered in this codelab:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Creating a data structure with load and save methods</li>
|
||||||
|
<li>Using the <code>http</code> package to build web applications
|
||||||
|
<li>Using the <code>template</code> package to process HTML templates</li>
|
||||||
|
<li>Using the <code>regexp</code> package to validate user input</li>
|
||||||
|
<li>Using closures</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Assumed knowledge:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Programming experience</li>
|
||||||
|
<li>Understanding of basic web technologies (HTTP, HTML)</li>
|
||||||
|
<li>Some UNIX command-line knowledge</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Getting Started</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
At present, you need to have a Linux, OS X, or FreeBSD machine to run Go. If
|
||||||
|
you don't have access to one, you could set up a Linux Virtual Machine (using
|
||||||
|
<a href="http://www.virtualbox.org/">VirtualBox</a> or similar) or a
|
||||||
|
<a href="http://www.google.com/search?q=virtual+private+server">Virtual
|
||||||
|
Private Server</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Install Go (see the <a href="http://golang.org/doc/install.html">Installation Instructions</a>).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Make a new directory for this codelab and cd to it:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
$ mkdir ~/gowiki
|
||||||
|
$ cd ~/gowiki
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Create a file named <code>wiki.go</code>, open it in your favorite editor, and
|
||||||
|
add the following lines:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Both <code>fmt</code> and <code>ioutil</code> are built-in packages that
|
||||||
|
we'll be using. Later, as we implement additional functionality, we will add
|
||||||
|
more packages to this <code>import</code> declaration.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Data Structures</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Let's start by defining the data structures. A wiki consists of a series of
|
||||||
|
interconnected pages, each of which has a title and a body (the page content).
|
||||||
|
Here, we define <code>page</code> as a struct with two fields representing
|
||||||
|
the title and body.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
type page struct {
|
||||||
|
title string
|
||||||
|
body []byte
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The type <code>[]byte</code> means "a <code>byte</code> slice".
|
||||||
|
(See <a href="http://golang.org/doc/effective_go.html#slices">Effective Go</a>
|
||||||
|
for more on slices.)
|
||||||
|
The <code>body</code> element is a <code>[]byte</code> rather than
|
||||||
|
<code>string</code> because that is the type expected by the <code>io</code>
|
||||||
|
libraries we will use, as you'll see below.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code>page</code> struct describes how page data will be stored in memory.
|
||||||
|
But what about persistent storage? We can address that by creating a
|
||||||
|
<code>save</code> method on <code>page</code>:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func (p *page) save() os.Error {
|
||||||
|
filename := p.title + ".txt"
|
||||||
|
return ioutil.WriteFile(filename, p.body, 0600)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This method's signature reads: "This is a method named <code>save</code> that
|
||||||
|
takes as its receiver <code>p</code>, a pointer to <code>page</code> . It takes
|
||||||
|
no parameters, and returns a value of type <code>os.Error</code>."
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This method will save the <code>page</code>'s <code>body</code> to a text
|
||||||
|
file. For simplicity, we will use the <code>title</code> as the file name.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code>save</code> method returns an <code>os.Error</code> value because
|
||||||
|
that is the return type of <code>WriteFile</code> (a standard library function
|
||||||
|
that writes a byte slice to a file). The <code>save</code> method returns the
|
||||||
|
error value, to let the application handle it should anything go wrong while
|
||||||
|
writing the file. If all goes well, <code>page.save()</code> will return
|
||||||
|
<code>nil</code> (the zero-value for pointers, interfaces, and some other
|
||||||
|
types).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The octal integer constant <code>0600</code>, passed as the third parameter to
|
||||||
|
<code>WriteFile</code>, indicates that the file should be created with
|
||||||
|
read-write permissions for the current user only. (See the Unix man page
|
||||||
|
<code>open(2)</code> for details.)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
We will want to load pages, too:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func loadPage(title string) *page {
|
||||||
|
filename := title + ".txt"
|
||||||
|
body, _ := ioutil.ReadFile(filename)
|
||||||
|
return &page{title: title, body: body}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The function <code>loadPage</code> constructs the file name from
|
||||||
|
<code>title</code>, reads the file's contents into a new
|
||||||
|
<code>page</code>, and returns a pointer to that new <code>page</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Functions can return multiple values. The standard library function
|
||||||
|
<code>io.ReadFile</code> returns <code>[]byte</code> and <code>os.Error</code>.
|
||||||
|
In <code>loadPage</code>, error isn't being handled yet; the "blank identifier"
|
||||||
|
represented by the underscore (<code>_</code>) symbol is used to throw away the
|
||||||
|
error return value (in essence, assigning the value to nothing).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
But what happens if <code>ReadFile</code> encounters an error? For example,
|
||||||
|
the file might not exist. We should not ignore such errors. Let's modify the
|
||||||
|
function to return <code>*page</code> and <code>os.Error</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func loadPage(title string) (*page, os.Error) {
|
||||||
|
filename := title + ".txt"
|
||||||
|
body, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &page{title: title, body: body}, nil
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Callers of this function can now check the second parameter; if it is
|
||||||
|
<code>nil</code> then it has succesfully loaded a page. If not, it will be an
|
||||||
|
<code>os.Error</code> that can be handled by the caller (see the <a
|
||||||
|
href="http://golang.org/pkg/os/#Error">os package documentation</a> for
|
||||||
|
details).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
At this point we have a simple data structure and the ability to save to and
|
||||||
|
load from a file. Let's write a <code>main</code> function to test what we've
|
||||||
|
written:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func main() {
|
||||||
|
p1 := &page{title: "TestPage", body: []byte("This is a sample page.")}
|
||||||
|
p1.save()
|
||||||
|
p2, _ := loadPage("TestPage")
|
||||||
|
fmt.Println(string(p2.body))
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
After compiling and executing this code, a file named <code>TestPage.txt</code>
|
||||||
|
would be created, containing the contents of <code>p1</code>. The file would
|
||||||
|
then be read into the struct <code>p2</code>, and its <code>body</code> element
|
||||||
|
printed to the screen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You can compile and run the program like this:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
$ 8g wiki.go
|
||||||
|
$ 8l wiki.8
|
||||||
|
$ ./8.out
|
||||||
|
This is a sample page.
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
(The <code>8g</code> and <code>8l</code> commands are applicable to
|
||||||
|
<code>GOARCH=386</code>. If you're on an <code>amd64</code> system,
|
||||||
|
subtitute 6's for the 8's.)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="part1.go">Click here to view the code we've written so far.</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Introducing the <code>http</code> package (an interlude)</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Here's a full working example of a simple web server:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handler(c *http.Conn, r *http.Request) {
|
||||||
|
fmt.Fprintf(c, "Hi there, I love %s!", r.URL.Path[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", handler)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code>main</code> function begins with a call to
|
||||||
|
<code>http.HandleFunc</code>, which tells the <code>http</code> package to
|
||||||
|
handle all requests to the web root (<code>"/"</code>) with
|
||||||
|
<code>handler</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
It then calls <code>http.ListenAndServe</code>, specifying that it should
|
||||||
|
listen on port 8080 on any interface (<code>":8080"</code>). (Don't
|
||||||
|
worry about its second parameter, <code>nil</code>, for now.)
|
||||||
|
This function will block until the program is terminated.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The function <code>handler</code> is of the type <code>http.HandlerFunc</code>.
|
||||||
|
It takes an <code>http.Conn</code> and <code>http.Request</code> as its
|
||||||
|
arguments.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
An <code>http.Conn</code> is the server end of an HTTP connection; by writing
|
||||||
|
to it, we send data to the HTTP client.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
An <code>http.Request</code> is a data structure that represents the client
|
||||||
|
HTTP request. The string <code>r.URL.Path</code> is the path component
|
||||||
|
of the request URL. The trailing <code>[1:]</code> means
|
||||||
|
"create a sub-slice of <code>Path</code> from the 1st character to the end."
|
||||||
|
This drops the leading "/" from the path name.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If you run this program and access the URL:
|
||||||
|
</p>
|
||||||
|
<pre>http://localhost:8080/monkeys</pre>
|
||||||
|
<p>
|
||||||
|
the program would present a page containing:
|
||||||
|
</p>
|
||||||
|
<pre>Hi there, I love monkeys!</pre>
|
||||||
|
|
||||||
|
<h2>Using <code>http</code> to serve wiki pages</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To use the <code>http</code> package, it must be imported:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
<b>"http"</b>
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Let's create a handler to view a wiki page:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
const lenPath = len("/view/")
|
||||||
|
|
||||||
|
func viewHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
p, _ := loadPage(title)
|
||||||
|
fmt.Fprintf(c, "<h1>%s</h1><div>%s</div>", p.title, p.body)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First, this function extracts the page title from <code>r.URL.Path</code>,
|
||||||
|
the path component of the request URL. The global constant
|
||||||
|
<code>lenPath</code> is the length of the leading <code>"/view/"</code>
|
||||||
|
component of the request path.
|
||||||
|
The <code>Path</code> is re-sliced with <code>[lenPath:]</code> to drop the
|
||||||
|
first 6 characters of the string. This is because the path will invariably
|
||||||
|
begin with <code>"/view/"</code>, which is not part of the page title.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The function then loads the page data, formats the page with a string of simple
|
||||||
|
HTML, and writes it to <code>c</code>, the <code>http.Conn</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Again, note the use of <code>_</code> to ignore the <code>os.Error</code>
|
||||||
|
return value from <code>loadPage</code>. This is done here for simplicity
|
||||||
|
and generally considered bad practice. We will attend to this later.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To use this handler, we create a <code>main</code> function that
|
||||||
|
initializes <code>http</code> using the <code>viewHandler</code> to handle
|
||||||
|
any requests under the path <code>/view/</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/view/", viewHandler)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="part2.go">Click here to view the code we've written so far.</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Let's create some page data (as <code>test.txt</code>), compile our code, and
|
||||||
|
try serving a wiki page:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
$ echo "Hello world" > test.txt
|
||||||
|
$ 8g wiki.go
|
||||||
|
$ 8l wiki.8
|
||||||
|
$ ./8.out
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
With this web server running, a visit to <code><a
|
||||||
|
href="http://localhost:8080/view/test">http://localhost:8080/view/test</a></code>
|
||||||
|
should show a page titled "test" containing the words "Hello world".
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Editing pages</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
A wiki is not a wiki without the ability to edit pages. Let's create two new
|
||||||
|
handlers: one named <code>editHandler</code> to display an 'edit page' form,
|
||||||
|
and the other named <code>saveHandler</code> to save the data entered via the
|
||||||
|
form.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First, we add them to <code>main()</code>:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/view/", viewHandler)
|
||||||
|
http.HandleFunc("/edit/", editHandler)
|
||||||
|
http.HandleFunc("/save/", saveHandler)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The function <code>editHandler</code> loads the page
|
||||||
|
(or, if it doesn't exist, create an empty <code>page</code> struct),
|
||||||
|
and displays an HTML form.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func editHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
p = &page{title: title}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(c, "<h1>Editing %s</h1>"+
|
||||||
|
"<form action=\"/save/%s\" method=\"POST\">"+
|
||||||
|
"<textarea name=\"body\">%s</textarea><br>"+
|
||||||
|
"<input type=\"submit\" value=\"Save\">"+
|
||||||
|
"</form>",
|
||||||
|
p.title, p.title, p.body)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This function will work fine, but all that hard-coded HTML is ugly.
|
||||||
|
Of course, there is a better way.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>The <code>template</code> package</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code>template</code> package is part of the Go standard library. We can
|
||||||
|
use <code>template</code> to keep the HTML in a separate file, allowing
|
||||||
|
us to change the layout of our edit page without modifying the underlying Go
|
||||||
|
code.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First, we must add <code>template</code> to the list of imports:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
import (
|
||||||
|
"http"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
<b>"template"</b>
|
||||||
|
)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Let's create a template file containg the HTML form.
|
||||||
|
Open a new file named <code>edit.html</code>, and add the following lines:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<h1>Editing {title}</h1>
|
||||||
|
|
||||||
|
<form action="/save/{title}" method="POST">
|
||||||
|
<div><textarea name="body" rows="20" cols="80">{body|html}</textarea></div>
|
||||||
|
<div><input type="submit" value="Save"></div>
|
||||||
|
</form>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Modify <code>editHandler</code> to use the template, instead of the hard-coded
|
||||||
|
HTML:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func editHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
p = &page{title: title}
|
||||||
|
}
|
||||||
|
t, _ := template.ParseFile("edit.html", nil)
|
||||||
|
t.Execute(p, c)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The function <code>template.ParseFile</code> will read the contents of
|
||||||
|
<code>edit.html</code> and return a <code>*template.Template</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The method <code>t.Execute</code> replaces all occurrences of
|
||||||
|
<code>{title}</code> and <code>{body}</code> with the values of
|
||||||
|
<code>p.title</code> and <code>p.body</code>, and writes the resultant
|
||||||
|
HTML to the <code>http.Conn</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Note that we've used <code>{body|html}</code> in the above template.
|
||||||
|
The <code>|html</code> part asks the template engine to pass the value
|
||||||
|
<code>body</code> through the <code>html</code> formatter before outputting it,
|
||||||
|
which escapes HTML characters (such as replacing <code>></code> with
|
||||||
|
<code>&gt;</code>).
|
||||||
|
This will prevent user data from corrupting the form HTML.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now that we've removed the <code>fmt.Sprintf</code> statement, we can remove
|
||||||
|
<code>"fmt"</code> from the <code>import</code> list.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
While we're working with templates, let's create a template for our
|
||||||
|
<code>viewHandler</code> called <code>view.html</code>:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<h1>{title}</h1>
|
||||||
|
|
||||||
|
<p>[<a href="/edit/{title}">edit</a>]</p>
|
||||||
|
|
||||||
|
<div>{body}</div>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Modify <code>viewHandler</code> accordingly:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func viewHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
p, _ := loadPage(title)
|
||||||
|
t, _ := template.ParseFile("view.html", nil)
|
||||||
|
t.Execute(p, c)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Notice that we've used almost exactly the same templating code in both
|
||||||
|
handlers. Let's remove this duplication by moving the templating code
|
||||||
|
to its own function:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func viewHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
p, _ := loadPage(title)
|
||||||
|
renderTemplate(c, "edit", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func editHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
p = &page{title: title}
|
||||||
|
}
|
||||||
|
renderTemplate(c, "view", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderTemplate(c *http.Conn, tmpl string, p *page) {
|
||||||
|
t, _ := template.ParseFile(tmpl+".html", nil)
|
||||||
|
t.Execute(p, c)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The handlers are now shorter and simpler.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Handling non-existent pages</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
What if you visit <code>/view/APageThatDoesntExist</code>? The program will
|
||||||
|
crash. This is because it ignores the error return value from
|
||||||
|
<code>loadPage</code>. Instead, if the requested page doesn't exist, it should
|
||||||
|
redirect the client to the edit page so the content may be created:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func viewHandler(c *http.Conn, r *http.Request, title string) {
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(c, "/edit/"+title, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
renderTemplate(c, "view", p)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code>http.Redirect</code> function adds an HTTP status code of
|
||||||
|
<code>http.StatusFound</code> (302) and a <code>Location</code>
|
||||||
|
header to the HTTP response.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Saving pages</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The function <code>saveHandler</code> will handle the form submission.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func saveHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
body := r.FormValue("body")
|
||||||
|
p := &page{title: title, body: []byte(body)}
|
||||||
|
p.save()
|
||||||
|
http.Redirect(c, "/view/"+title, http.StatusFound)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The page title (provided in the URL) and the form's only field,
|
||||||
|
<code>body</code>, are stored in a new <code>page</code>.
|
||||||
|
The <code>save()</code> method is then called to write the data to a file,
|
||||||
|
and the client is redirected to the <code>/view/</code> page.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The value returned by <code>FormValue</code> is of type <code>string</code>.
|
||||||
|
We must convert that value to <code>[]byte</code> before it will fit into
|
||||||
|
the <code>page</code> struct. We use <code>[]byte(body)</code> to perform
|
||||||
|
the conversion.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Error handling</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
There are several places in our program where errors are being ignored. This
|
||||||
|
is bad practice, not least because when an error does occur the program will
|
||||||
|
crash. A better solution is to handle the errors and return an error message
|
||||||
|
to the user. That way if something does go wrong, the server will continue to
|
||||||
|
function and the user will be notified.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First, let's handle the errors in <code>renderTemplate</code>:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func renderTemplate(c *http.Conn, tmpl string, p *page) {
|
||||||
|
t, err := template.ParseFile(tmpl+".html", nil)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c, err.String(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = t.Execute(p, c)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c, err.String(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code>http.Error</code> function sends a specified HTTP response code
|
||||||
|
(in this case "Internal Server Error") and error message.
|
||||||
|
Already the decision to put this in a separate function is paying off.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now let's fix up <code>saveHandler</code>:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func saveHandler(c *http.Conn, r *http.Request, title string) {
|
||||||
|
body := r.FormValue("body")
|
||||||
|
p := &page{title: title, body: []byte(body)}
|
||||||
|
err := p.save()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c, err.String(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(c, "/view/"+title, http.StatusFound)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Any errors that occur during <code>p.save()</code> will be reported
|
||||||
|
to the user.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Template caching</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
There is an inefficiency in this code: <code>renderTemplate</code> calls
|
||||||
|
<code>ParseFile</code> every time a page is rendered.
|
||||||
|
A better approach would be to call <code>ParseFile</code> once for each
|
||||||
|
template at program initialization, and store the resultant
|
||||||
|
<code>*Template</code> values in a data structure for later use.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First we create a global map named <code>templates</code> in which to store
|
||||||
|
our <code>*Template</code> values, keyed by <code>string</code>
|
||||||
|
(the template name):
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
var templates = make(map[string]*template.Template)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Then we create an <code>init</code> function, which will be called before
|
||||||
|
<code>main</code> at program initialization. The function
|
||||||
|
<code>template.MustParseFile</code> is a convenience wrapper around
|
||||||
|
<code>ParseFile</code> that does not return an error code; instead, it panics
|
||||||
|
if an error is encountered. A panic is appropriate here; if the templates can't
|
||||||
|
be loaded the only sensible thing to do is exit the program.
|
||||||
|
</p
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func init() {
|
||||||
|
for _, tmpl := range []string{"edit", "view"} {
|
||||||
|
templates[tmpl] = template.MustParseFile(tmpl+".html", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
A <code>for</code> loop is used with a <code>range</code> statement to iterate
|
||||||
|
over an array constant containing the names of the templates we want parsed.
|
||||||
|
If we were to add more templates to our program, we would add their names to
|
||||||
|
that array.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
We then modify our <code>renderTemplate</code> function to call
|
||||||
|
the <code>Execute</code> method on the appropriate <code>Template</code> from
|
||||||
|
<code>templates</code>:
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func renderTemplate(c *http.Conn, tmpl string, p *page) {
|
||||||
|
err := templates[tmpl].Execute(p, c)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c, err.String(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2>Validation</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
As you may have observed, this program has a serious security flaw: a user
|
||||||
|
can supply an arbitrary path to be read/written on the server. To mitigate
|
||||||
|
this, we can write a function to validate the title with a regular expression.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First, add <code>"regexp"</code> to the <code>import</code> list.
|
||||||
|
Then we can create a global variable to store our validation regexp:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
var titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The function <code>regexp.MustCompile</code> will parse and compile the
|
||||||
|
regular expression, and return a <code>regexp.Regexp</code>.
|
||||||
|
<code>MustCompile</code>, like <code>template.MustParseFile</code>,
|
||||||
|
is distinct from <code>Compile</code> in that it will panic if
|
||||||
|
the expression compilation fails, while <code>Compile</code> returns an
|
||||||
|
<code>os.Error</code> as a second parameter.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now, let's write a function that extracts the title string from the request
|
||||||
|
URL, and tests it against our <code>titleValidator</code> expression:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func getTitle(c *http.Conn, r *http.Request) (title string, err os.Error) {
|
||||||
|
title = r.URL.Path[lenPath:]
|
||||||
|
if !titleValidator.MatchString(title) {
|
||||||
|
http.NotFound(c, r)
|
||||||
|
err = os.NewError("Invalid Page Title")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If the title is valid, it will be returned along with a <code>nil</code>
|
||||||
|
error value. If the title is invalid, the function will write a
|
||||||
|
"404 Not Found" error to the HTTP connection, and return an error to the
|
||||||
|
handler.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Let's put a call to <code>getTitle</code> in each of the handlers:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func viewHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title, err := getTitle(c, r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(c, "/edit/"+title, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
renderTemplate(c, "view", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func editHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title, err := getTitle(c, r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
p = &page{title: title}
|
||||||
|
}
|
||||||
|
renderTemplate(c, "edit", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title, err := getTitle(c, r)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body := r.FormValue("body")
|
||||||
|
p := &page{title: title, body: []byte(body)}
|
||||||
|
err = p.save()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c, err.String(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(c, "/view/"+title, http.StatusFound)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2>Introducing Function Literals and Closures</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Catching the error condition in each handler introduces a lot of repeated code.
|
||||||
|
What if we could wrap each of the handlers in a function that does this
|
||||||
|
validation and error checking? Go's
|
||||||
|
<a href="http://golang.org/doc/go_spec.html#Function_declarations">function
|
||||||
|
literals</a> provide a powerful means of abstracting functionality
|
||||||
|
that can help us here.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First, we re-write the function definition of each of the handlers to accept
|
||||||
|
a title string:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func viewHandler(c, *http.Conn, r *http.Request, title string)
|
||||||
|
func editHandler(c, *http.Conn, r *http.Request, title string)
|
||||||
|
func saveHandler(c, *http.Conn, r *http.Request, title string)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now let's define a wrapper function that <i>takes a function of the above
|
||||||
|
type</i>, and returns a function of type <code>http.HandlerFunc</code>
|
||||||
|
(suitable to be passed to the function <code>http.HandleFunc</code>):
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func makeHandler(fn func (*http.Conn, *http.Request, string)) http.HandlerFunc {
|
||||||
|
return func(c *http.Conn, r *http.Request) {
|
||||||
|
// Here we will extract the page title from the Request,
|
||||||
|
// and call the provided handler 'fn'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The returned function is called a closure because it encloses values defined
|
||||||
|
outside of it. In this case, the variable <code>fn</code> (the single argument
|
||||||
|
to <code>makeHandler</code>) is enclosed by the closure. The variable
|
||||||
|
<code>fn</code> will be one of our save, edit, or view handlers.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now we can take the code from <code>getTitle</code> and use it here
|
||||||
|
(with some minor modifications):
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func makeHandler(fn func(*http.Conn, *http.Request, string)) http.HandlerFunc {
|
||||||
|
return func(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
if !titleValidator.MatchString(title) {
|
||||||
|
http.NotFound(c, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fn(c, r, title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The closure returned by <code>makeHandler</code> is a function that takes
|
||||||
|
an <code>http.Conn</code> and <code>http.Request</code> (in other words,
|
||||||
|
an <code>http.HandlerFunc</code>).
|
||||||
|
The closure extracts the <code>title</code> from the request path, and
|
||||||
|
validates it with the <code>titleValidator</code> regexp. If the
|
||||||
|
<code>title</code> is invalid, an error will be written to the
|
||||||
|
<code>Conn</code> using the <code>http.NotFound</code> function.
|
||||||
|
If the <code>title</code> is valid, the enclosed handler function
|
||||||
|
<code>fn</code> will be called with the <code>Conn</code>,
|
||||||
|
<code>Request</code>, and <code>title</code> as arguments.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now we can wwrap the handler functions with <code>makeHandler</code> in
|
||||||
|
<code>main</code>, before they are registered with the <code>http</code>
|
||||||
|
package:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/view/", makeHandler(viewHandler))
|
||||||
|
http.HandleFunc("/edit/", makeHandler(editHandler))
|
||||||
|
http.HandleFunc("/save/", makeHandler(saveHandler))
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Finally we remove the calls to <code>getTitle</code> from the handler functions,
|
||||||
|
making them much simpler:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func viewHandler(c *http.Conn, r *http.Request, title string) {
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(c, "/edit/"+title, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
renderTemplate(c, "view", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func editHandler(c *http.Conn, r *http.Request, title string) {
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
p = &page{title: title}
|
||||||
|
}
|
||||||
|
renderTemplate(c, "edit", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveHandler(c *http.Conn, r *http.Request, title string) {
|
||||||
|
body := r.FormValue("body")
|
||||||
|
p := &page{title: title, body: []byte(body)}
|
||||||
|
err := p.save()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(c, err.String(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(c, "/view/"+title, http.StatusFound)
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2>Try it out!</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="final.go">Click here to view the final code listing.</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Recompile the code, and run the app:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
$ 8g wiki.go
|
||||||
|
$ 8l wiki.8
|
||||||
|
$ ./8.out
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Visiting <a href="http://localhost:8080/ANewPage">http://localhost:8080/ANewPage</a>
|
||||||
|
should present you with the page edit form. You should then be able to
|
||||||
|
enter some text, click 'Save', and be redirected to the newly created page.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Other tasks</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Here are some simple tasks you might want to tackle on your own:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Store templates in <code>tmpl/</code> and page data in <code>data/</code>.
|
||||||
|
<li>Add a handler to make the web root redirect to
|
||||||
|
<code>/view/FrontPage</code>.</li>
|
||||||
|
<li>Spruce up the page templates by making them valid HTML and adding some
|
||||||
|
CSS rules.</li>
|
||||||
|
<li>Implement inter-page linking by converting instances of
|
||||||
|
<code>[PageName]</code> to <br>
|
||||||
|
<code><a href="/view/PageName">PageName</a></code>.
|
||||||
|
(hint: you could use <code>regexp.ReplaceAllFunc</code> to do this)
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
55
doc/codelab/wiki/notemplate.go
Normal file
55
doc/codelab/wiki/notemplate.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"http"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type page struct {
|
||||||
|
title string
|
||||||
|
body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) save() os.Error {
|
||||||
|
filename := p.title + ".txt"
|
||||||
|
return ioutil.WriteFile(filename, p.body, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadPage(title string) (*page, os.Error) {
|
||||||
|
filename := title + ".txt"
|
||||||
|
body, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &page{title: title, body: body}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const lenPath = len("/view/")
|
||||||
|
|
||||||
|
func viewHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
p, _ := loadPage(title)
|
||||||
|
fmt.Fprintf(c, "<h1>%s</h1><div>%s</div>", p.title, p.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func editHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
p, err := loadPage(title)
|
||||||
|
if err != nil {
|
||||||
|
p = &page{title: title}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(c, "<h1>Editing %s</h1>"+
|
||||||
|
"<form action=\"/save/%s\" method=\"POST\">"+
|
||||||
|
"<textarea name=\"body\">%s</textarea><br>"+
|
||||||
|
"<input type=\"submit\" value=\"Save\">"+
|
||||||
|
"</form>",
|
||||||
|
p.title, p.title, p.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/view/", viewHandler)
|
||||||
|
http.HandleFunc("/edit/", editHandler)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
30
doc/codelab/wiki/part1-noerror.go
Normal file
30
doc/codelab/wiki/part1-noerror.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type page struct {
|
||||||
|
title string
|
||||||
|
body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) save() os.Error {
|
||||||
|
filename := p.title + ".txt"
|
||||||
|
return ioutil.WriteFile(filename, p.body, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadPage(title string) *page {
|
||||||
|
filename := title + ".txt"
|
||||||
|
body, _ := ioutil.ReadFile(filename)
|
||||||
|
return &page{title: title, body: body}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
p1 := &page{title: "TestPage", body: []byte("This is a sample page.")}
|
||||||
|
p1.save()
|
||||||
|
p2 := loadPage("TestPage")
|
||||||
|
fmt.Println(string(p2.body))
|
||||||
|
}
|
33
doc/codelab/wiki/part1.go
Normal file
33
doc/codelab/wiki/part1.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type page struct {
|
||||||
|
title string
|
||||||
|
body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) save() os.Error {
|
||||||
|
filename := p.title + ".txt"
|
||||||
|
return ioutil.WriteFile(filename, p.body, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadPage(title string) (*page, os.Error) {
|
||||||
|
filename := title + ".txt"
|
||||||
|
body, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &page{title: title, body: body}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
p1 := &page{title: "TestPage", body: []byte("This is a sample page.")}
|
||||||
|
p1.save()
|
||||||
|
p2, _ := loadPage("TestPage")
|
||||||
|
fmt.Println(string(p2.body))
|
||||||
|
}
|
40
doc/codelab/wiki/part2.go
Normal file
40
doc/codelab/wiki/part2.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"http"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type page struct {
|
||||||
|
title string
|
||||||
|
body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *page) save() os.Error {
|
||||||
|
filename := p.title + ".txt"
|
||||||
|
return ioutil.WriteFile(filename, p.body, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadPage(title string) (*page, os.Error) {
|
||||||
|
filename := title + ".txt"
|
||||||
|
body, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &page{title: title, body: body}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const lenPath = len("/view/")
|
||||||
|
|
||||||
|
func viewHandler(c *http.Conn, r *http.Request) {
|
||||||
|
title := r.URL.Path[lenPath:]
|
||||||
|
p, _ := loadPage(title)
|
||||||
|
fmt.Fprintf(c, "<h1>%s</h1><div>%s</div>", p.title, p.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/view/", viewHandler)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
73
doc/codelab/wiki/srcextract.go
Normal file
73
doc/codelab/wiki/srcextract.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"go/parser"
|
||||||
|
"go/printer"
|
||||||
|
"go/ast"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
srcFn = flag.String("src", "", "source filename")
|
||||||
|
getName = flag.String("name", "", "func/type name to output")
|
||||||
|
html = flag.Bool("html", true, "output HTML")
|
||||||
|
showPkg = flag.Bool("pkg", false, "show package in output")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// handle input
|
||||||
|
flag.Parse()
|
||||||
|
if *srcFn == "" || *getName == "" {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
// load file
|
||||||
|
file, err := parser.ParseFile(*srcFn, nil, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Exit(err)
|
||||||
|
}
|
||||||
|
// create printer
|
||||||
|
p := &printer.Config{
|
||||||
|
Mode: 0,
|
||||||
|
Tabwidth: 8,
|
||||||
|
Styler: nil,
|
||||||
|
}
|
||||||
|
if *html {
|
||||||
|
p.Mode = printer.GenHTML
|
||||||
|
}
|
||||||
|
// create filter
|
||||||
|
filter := func(name string) bool {
|
||||||
|
return name == *getName
|
||||||
|
}
|
||||||
|
// filter
|
||||||
|
if !ast.FilterFile(file, filter) {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
p.Fprint(b, file)
|
||||||
|
// drop package declaration
|
||||||
|
if !*showPkg {
|
||||||
|
for {
|
||||||
|
c, err := b.ReadByte()
|
||||||
|
if c == '\n' || err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// drop leading newlines
|
||||||
|
for {
|
||||||
|
b, err := b.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if b != '\n' {
|
||||||
|
os.Stdout.Write([]byte{b})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// output
|
||||||
|
b.WriteTo(os.Stdout)
|
||||||
|
}
|
24
doc/codelab/wiki/test.sh
Executable file
24
doc/codelab/wiki/test.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#1/bin/bash
|
||||||
|
|
||||||
|
./final.bin &
|
||||||
|
wiki_pid=$!
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
kill $wiki_pid
|
||||||
|
rm -f test_*.out Test.txt
|
||||||
|
exit ${1:-1}
|
||||||
|
}
|
||||||
|
trap cleanup INT
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
curl -s -o test_edit.out http://localhost:8080/edit/Test
|
||||||
|
cmp test_edit.out test_edit.good || cleanup 1
|
||||||
|
curl -s -o /dev/null -d body=some%20content http://localhost:8080/save/Test
|
||||||
|
cmp Test.txt test_Test.txt.good || cleanup 1
|
||||||
|
curl -s -o test_view.out http://localhost:8080/view/Test
|
||||||
|
cmp test_view.out test_view.good || cleanup 1
|
||||||
|
|
||||||
|
echo "Passed"
|
||||||
|
cleanup 0
|
||||||
|
|
1
doc/codelab/wiki/test_Test.txt.good
Normal file
1
doc/codelab/wiki/test_Test.txt.good
Normal file
@ -0,0 +1 @@
|
|||||||
|
some content
|
6
doc/codelab/wiki/test_edit.good
Normal file
6
doc/codelab/wiki/test_edit.good
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<h1>Editing Test</h1>
|
||||||
|
|
||||||
|
<form action="/save/Test" method="POST">
|
||||||
|
<div><textarea name="body" rows="20" cols="80"></textarea></div>
|
||||||
|
<div><input type="submit" value="Save"></div>
|
||||||
|
</form>
|
5
doc/codelab/wiki/test_view.good
Normal file
5
doc/codelab/wiki/test_view.good
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<h1>Test</h1>
|
||||||
|
|
||||||
|
<p>[<a href="/edit/Test">edit</a>]</p>
|
||||||
|
|
||||||
|
<div>some content</div>
|
5
doc/codelab/wiki/view.html
Normal file
5
doc/codelab/wiki/view.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<h1>{title}</h1>
|
||||||
|
|
||||||
|
<p>[<a href="/edit/{title}">edit</a>]</p>
|
||||||
|
|
||||||
|
<div>{body}</div>
|
783
doc/codelab/wiki/wiki.html
Normal file
783
doc/codelab/wiki/wiki.html
Normal file
@ -0,0 +1,783 @@
|
|||||||
|
<div class="content">
|
||||||
|
|
||||||
|
<h1>Writing Web Applications</h1>
|
||||||
|
|
||||||
|
<h2>Introduction</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Covered in this codelab:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Creating a data structure with load and save methods</li>
|
||||||
|
<li>Using the <code>http</code> package to build web applications
|
||||||
|
<li>Using the <code>template</code> package to process HTML templates</li>
|
||||||
|
<li>Using the <code>regexp</code> package to validate user input</li>
|
||||||
|
<li>Using closures</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Assumed knowledge:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Programming experience</li>
|
||||||
|
<li>Understanding of basic web technologies (HTTP, HTML)</li>
|
||||||
|
<li>Some UNIX command-line knowledge</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Getting Started</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
At present, you need to have a Linux, OS X, or FreeBSD machine to run Go. If
|
||||||
|
you don't have access to one, you could set up a Linux Virtual Machine (using
|
||||||
|
<a href="http://www.virtualbox.org/">VirtualBox</a> or similar) or a
|
||||||
|
<a href="http://www.google.com/search?q=virtual+private+server">Virtual
|
||||||
|
Private Server</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Install Go (see the <a href="http://golang.org/doc/install.html">Installation Instructions</a>).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Make a new directory for this codelab and cd to it:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
$ mkdir ~/gowiki
|
||||||
|
$ cd ~/gowiki
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Create a file named <code>wiki.go</code>, open it in your favorite editor, and
|
||||||
|
add the following lines:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Both <code>fmt</code> and <code>ioutil</code> are built-in packages that
|
||||||
|
we'll be using. Later, as we implement additional functionality, we will add
|
||||||
|
more packages to this <code>import</code> declaration.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Data Structures</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Let's start by defining the data structures. A wiki consists of a series of
|
||||||
|
interconnected pages, each of which has a title and a body (the page content).
|
||||||
|
Here, we define <code>page</code> as a struct with two fields representing
|
||||||
|
the title and body.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=part1.go -name=page
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The type <code>[]byte</code> means "a <code>byte</code> slice".
|
||||||
|
(See <a href="http://golang.org/doc/effective_go.html#slices">Effective Go</a>
|
||||||
|
for more on slices.)
|
||||||
|
The <code>body</code> element is a <code>[]byte</code> rather than
|
||||||
|
<code>string</code> because that is the type expected by the <code>io</code>
|
||||||
|
libraries we will use, as you'll see below.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code>page</code> struct describes how page data will be stored in memory.
|
||||||
|
But what about persistent storage? We can address that by creating a
|
||||||
|
<code>save</code> method on <code>page</code>:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=part1.go -name=save
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This method's signature reads: "This is a method named <code>save</code> that
|
||||||
|
takes as its receiver <code>p</code>, a pointer to <code>page</code> . It takes
|
||||||
|
no parameters, and returns a value of type <code>os.Error</code>."
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This method will save the <code>page</code>'s <code>body</code> to a text
|
||||||
|
file. For simplicity, we will use the <code>title</code> as the file name.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code>save</code> method returns an <code>os.Error</code> value because
|
||||||
|
that is the return type of <code>WriteFile</code> (a standard library function
|
||||||
|
that writes a byte slice to a file). The <code>save</code> method returns the
|
||||||
|
error value, to let the application handle it should anything go wrong while
|
||||||
|
writing the file. If all goes well, <code>page.save()</code> will return
|
||||||
|
<code>nil</code> (the zero-value for pointers, interfaces, and some other
|
||||||
|
types).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The octal integer constant <code>0600</code>, passed as the third parameter to
|
||||||
|
<code>WriteFile</code>, indicates that the file should be created with
|
||||||
|
read-write permissions for the current user only. (See the Unix man page
|
||||||
|
<code>open(2)</code> for details.)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
We will want to load pages, too:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=part1-noerror.go -name=loadPage
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The function <code>loadPage</code> constructs the file name from
|
||||||
|
<code>title</code>, reads the file's contents into a new
|
||||||
|
<code>page</code>, and returns a pointer to that new <code>page</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Functions can return multiple values. The standard library function
|
||||||
|
<code>io.ReadFile</code> returns <code>[]byte</code> and <code>os.Error</code>.
|
||||||
|
In <code>loadPage</code>, error isn't being handled yet; the "blank identifier"
|
||||||
|
represented by the underscore (<code>_</code>) symbol is used to throw away the
|
||||||
|
error return value (in essence, assigning the value to nothing).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
But what happens if <code>ReadFile</code> encounters an error? For example,
|
||||||
|
the file might not exist. We should not ignore such errors. Let's modify the
|
||||||
|
function to return <code>*page</code> and <code>os.Error</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=part1.go -name=loadPage
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Callers of this function can now check the second parameter; if it is
|
||||||
|
<code>nil</code> then it has succesfully loaded a page. If not, it will be an
|
||||||
|
<code>os.Error</code> that can be handled by the caller (see the <a
|
||||||
|
href="http://golang.org/pkg/os/#Error">os package documentation</a> for
|
||||||
|
details).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
At this point we have a simple data structure and the ability to save to and
|
||||||
|
load from a file. Let's write a <code>main</code> function to test what we've
|
||||||
|
written:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=part1.go -name=main
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
After compiling and executing this code, a file named <code>TestPage.txt</code>
|
||||||
|
would be created, containing the contents of <code>p1</code>. The file would
|
||||||
|
then be read into the struct <code>p2</code>, and its <code>body</code> element
|
||||||
|
printed to the screen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You can compile and run the program like this:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
$ 8g wiki.go
|
||||||
|
$ 8l wiki.8
|
||||||
|
$ ./8.out
|
||||||
|
This is a sample page.
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
(The <code>8g</code> and <code>8l</code> commands are applicable to
|
||||||
|
<code>GOARCH=386</code>. If you're on an <code>amd64</code> system,
|
||||||
|
subtitute 6's for the 8's.)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="part1.go">Click here to view the code we've written so far.</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Introducing the <code>http</code> package (an interlude)</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Here's a full working example of a simple web server:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./htmlify.bin < http-sample.go
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code>main</code> function begins with a call to
|
||||||
|
<code>http.HandleFunc</code>, which tells the <code>http</code> package to
|
||||||
|
handle all requests to the web root (<code>"/"</code>) with
|
||||||
|
<code>handler</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
It then calls <code>http.ListenAndServe</code>, specifying that it should
|
||||||
|
listen on port 8080 on any interface (<code>":8080"</code>). (Don't
|
||||||
|
worry about its second parameter, <code>nil</code>, for now.)
|
||||||
|
This function will block until the program is terminated.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The function <code>handler</code> is of the type <code>http.HandlerFunc</code>.
|
||||||
|
It takes an <code>http.Conn</code> and <code>http.Request</code> as its
|
||||||
|
arguments.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
An <code>http.Conn</code> is the server end of an HTTP connection; by writing
|
||||||
|
to it, we send data to the HTTP client.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
An <code>http.Request</code> is a data structure that represents the client
|
||||||
|
HTTP request. The string <code>r.URL.Path</code> is the path component
|
||||||
|
of the request URL. The trailing <code>[1:]</code> means
|
||||||
|
"create a sub-slice of <code>Path</code> from the 1st character to the end."
|
||||||
|
This drops the leading "/" from the path name.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If you run this program and access the URL:
|
||||||
|
</p>
|
||||||
|
<pre>http://localhost:8080/monkeys</pre>
|
||||||
|
<p>
|
||||||
|
the program would present a page containing:
|
||||||
|
</p>
|
||||||
|
<pre>Hi there, I love monkeys!</pre>
|
||||||
|
|
||||||
|
<h2>Using <code>http</code> to serve wiki pages</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To use the <code>http</code> package, it must be imported:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
<b>"http"</b>
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Let's create a handler to view a wiki page:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=part2.go -name=lenPath
|
||||||
|
|
||||||
|
!./srcextract.bin -src=part2.go -name=viewHandler
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First, this function extracts the page title from <code>r.URL.Path</code>,
|
||||||
|
the path component of the request URL. The global constant
|
||||||
|
<code>lenPath</code> is the length of the leading <code>"/view/"</code>
|
||||||
|
component of the request path.
|
||||||
|
The <code>Path</code> is re-sliced with <code>[lenPath:]</code> to drop the
|
||||||
|
first 6 characters of the string. This is because the path will invariably
|
||||||
|
begin with <code>"/view/"</code>, which is not part of the page title.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The function then loads the page data, formats the page with a string of simple
|
||||||
|
HTML, and writes it to <code>c</code>, the <code>http.Conn</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Again, note the use of <code>_</code> to ignore the <code>os.Error</code>
|
||||||
|
return value from <code>loadPage</code>. This is done here for simplicity
|
||||||
|
and generally considered bad practice. We will attend to this later.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To use this handler, we create a <code>main</code> function that
|
||||||
|
initializes <code>http</code> using the <code>viewHandler</code> to handle
|
||||||
|
any requests under the path <code>/view/</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=part2.go -name=main
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="part2.go">Click here to view the code we've written so far.</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Let's create some page data (as <code>test.txt</code>), compile our code, and
|
||||||
|
try serving a wiki page:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
$ echo "Hello world" > test.txt
|
||||||
|
$ 8g wiki.go
|
||||||
|
$ 8l wiki.8
|
||||||
|
$ ./8.out
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
With this web server running, a visit to <code><a
|
||||||
|
href="http://localhost:8080/view/test">http://localhost:8080/view/test</a></code>
|
||||||
|
should show a page titled "test" containing the words "Hello world".
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Editing pages</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
A wiki is not a wiki without the ability to edit pages. Let's create two new
|
||||||
|
handlers: one named <code>editHandler</code> to display an 'edit page' form,
|
||||||
|
and the other named <code>saveHandler</code> to save the data entered via the
|
||||||
|
form.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First, we add them to <code>main()</code>:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final-noclosure.go -name=main
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The function <code>editHandler</code> loads the page
|
||||||
|
(or, if it doesn't exist, create an empty <code>page</code> struct),
|
||||||
|
and displays an HTML form.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=notemplate.go -name=editHandler
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This function will work fine, but all that hard-coded HTML is ugly.
|
||||||
|
Of course, there is a better way.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>The <code>template</code> package</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code>template</code> package is part of the Go standard library. We can
|
||||||
|
use <code>template</code> to keep the HTML in a separate file, allowing
|
||||||
|
us to change the layout of our edit page without modifying the underlying Go
|
||||||
|
code.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First, we must add <code>template</code> to the list of imports:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
import (
|
||||||
|
"http"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
<b>"template"</b>
|
||||||
|
)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Let's create a template file containg the HTML form.
|
||||||
|
Open a new file named <code>edit.html</code>, and add the following lines:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./htmlify.bin < edit.html
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Modify <code>editHandler</code> to use the template, instead of the hard-coded
|
||||||
|
HTML:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final-noerror.go -name=editHandler
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The function <code>template.ParseFile</code> will read the contents of
|
||||||
|
<code>edit.html</code> and return a <code>*template.Template</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The method <code>t.Execute</code> replaces all occurrences of
|
||||||
|
<code>{title}</code> and <code>{body}</code> with the values of
|
||||||
|
<code>p.title</code> and <code>p.body</code>, and writes the resultant
|
||||||
|
HTML to the <code>http.Conn</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Note that we've used <code>{body|html}</code> in the above template.
|
||||||
|
The <code>|html</code> part asks the template engine to pass the value
|
||||||
|
<code>body</code> through the <code>html</code> formatter before outputting it,
|
||||||
|
which escapes HTML characters (such as replacing <code>></code> with
|
||||||
|
<code>&gt;</code>).
|
||||||
|
This will prevent user data from corrupting the form HTML.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now that we've removed the <code>fmt.Sprintf</code> statement, we can remove
|
||||||
|
<code>"fmt"</code> from the <code>import</code> list.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
While we're working with templates, let's create a template for our
|
||||||
|
<code>viewHandler</code> called <code>view.html</code>:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./htmlify.bin < view.html
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Modify <code>viewHandler</code> accordingly:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final-noerror.go -name=viewHandler
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Notice that we've used almost exactly the same templating code in both
|
||||||
|
handlers. Let's remove this duplication by moving the templating code
|
||||||
|
to its own function:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final-template.go -name=viewHandler
|
||||||
|
|
||||||
|
!./srcextract.bin -src=final-template.go -name=editHandler
|
||||||
|
|
||||||
|
!./srcextract.bin -src=final-template.go -name=renderTemplate
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The handlers are now shorter and simpler.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Handling non-existent pages</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
What if you visit <code>/view/APageThatDoesntExist</code>? The program will
|
||||||
|
crash. This is because it ignores the error return value from
|
||||||
|
<code>loadPage</code>. Instead, if the requested page doesn't exist, it should
|
||||||
|
redirect the client to the edit page so the content may be created:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final.go -name=viewHandler
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code>http.Redirect</code> function adds an HTTP status code of
|
||||||
|
<code>http.StatusFound</code> (302) and a <code>Location</code>
|
||||||
|
header to the HTTP response.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Saving pages</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The function <code>saveHandler</code> will handle the form submission.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final-template.go -name=saveHandler
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The page title (provided in the URL) and the form's only field,
|
||||||
|
<code>body</code>, are stored in a new <code>page</code>.
|
||||||
|
The <code>save()</code> method is then called to write the data to a file,
|
||||||
|
and the client is redirected to the <code>/view/</code> page.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The value returned by <code>FormValue</code> is of type <code>string</code>.
|
||||||
|
We must convert that value to <code>[]byte</code> before it will fit into
|
||||||
|
the <code>page</code> struct. We use <code>[]byte(body)</code> to perform
|
||||||
|
the conversion.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Error handling</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
There are several places in our program where errors are being ignored. This
|
||||||
|
is bad practice, not least because when an error does occur the program will
|
||||||
|
crash. A better solution is to handle the errors and return an error message
|
||||||
|
to the user. That way if something does go wrong, the server will continue to
|
||||||
|
function and the user will be notified.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First, let's handle the errors in <code>renderTemplate</code>:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final-parsetemplate.go -name=renderTemplate
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The <code>http.Error</code> function sends a specified HTTP response code
|
||||||
|
(in this case "Internal Server Error") and error message.
|
||||||
|
Already the decision to put this in a separate function is paying off.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now let's fix up <code>saveHandler</code>:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final.go -name=saveHandler
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Any errors that occur during <code>p.save()</code> will be reported
|
||||||
|
to the user.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Template caching</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
There is an inefficiency in this code: <code>renderTemplate</code> calls
|
||||||
|
<code>ParseFile</code> every time a page is rendered.
|
||||||
|
A better approach would be to call <code>ParseFile</code> once for each
|
||||||
|
template at program initialization, and store the resultant
|
||||||
|
<code>*Template</code> values in a data structure for later use.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First we create a global map named <code>templates</code> in which to store
|
||||||
|
our <code>*Template</code> values, keyed by <code>string</code>
|
||||||
|
(the template name):
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final.go -name=templates
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Then we create an <code>init</code> function, which will be called before
|
||||||
|
<code>main</code> at program initialization. The function
|
||||||
|
<code>template.MustParseFile</code> is a convenience wrapper around
|
||||||
|
<code>ParseFile</code> that does not return an error code; instead, it panics
|
||||||
|
if an error is encountered. A panic is appropriate here; if the templates can't
|
||||||
|
be loaded the only sensible thing to do is exit the program.
|
||||||
|
</p
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final.go -name=init
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
A <code>for</code> loop is used with a <code>range</code> statement to iterate
|
||||||
|
over an array constant containing the names of the templates we want parsed.
|
||||||
|
If we were to add more templates to our program, we would add their names to
|
||||||
|
that array.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
We then modify our <code>renderTemplate</code> function to call
|
||||||
|
the <code>Execute</code> method on the appropriate <code>Template</code> from
|
||||||
|
<code>templates</code>:
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final.go -name=renderTemplate
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2>Validation</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
As you may have observed, this program has a serious security flaw: a user
|
||||||
|
can supply an arbitrary path to be read/written on the server. To mitigate
|
||||||
|
this, we can write a function to validate the title with a regular expression.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First, add <code>"regexp"</code> to the <code>import</code> list.
|
||||||
|
Then we can create a global variable to store our validation regexp:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final-noclosure.go -name=titleValidator
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The function <code>regexp.MustCompile</code> will parse and compile the
|
||||||
|
regular expression, and return a <code>regexp.Regexp</code>.
|
||||||
|
<code>MustCompile</code>, like <code>template.MustParseFile</code>,
|
||||||
|
is distinct from <code>Compile</code> in that it will panic if
|
||||||
|
the expression compilation fails, while <code>Compile</code> returns an
|
||||||
|
<code>os.Error</code> as a second parameter.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now, let's write a function that extracts the title string from the request
|
||||||
|
URL, and tests it against our <code>titleValidator</code> expression:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final-noclosure.go -name=getTitle
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If the title is valid, it will be returned along with a <code>nil</code>
|
||||||
|
error value. If the title is invalid, the function will write a
|
||||||
|
"404 Not Found" error to the HTTP connection, and return an error to the
|
||||||
|
handler.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Let's put a call to <code>getTitle</code> in each of the handlers:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final-noclosure.go -name=viewHandler
|
||||||
|
|
||||||
|
!./srcextract.bin -src=final-noclosure.go -name=editHandler
|
||||||
|
|
||||||
|
!./srcextract.bin -src=final-noclosure.go -name=saveHandler
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2>Introducing Function Literals and Closures</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Catching the error condition in each handler introduces a lot of repeated code.
|
||||||
|
What if we could wrap each of the handlers in a function that does this
|
||||||
|
validation and error checking? Go's
|
||||||
|
<a href="http://golang.org/doc/go_spec.html#Function_declarations">function
|
||||||
|
literals</a> provide a powerful means of abstracting functionality
|
||||||
|
that can help us here.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First, we re-write the function definition of each of the handlers to accept
|
||||||
|
a title string:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func viewHandler(c, *http.Conn, r *http.Request, title string)
|
||||||
|
func editHandler(c, *http.Conn, r *http.Request, title string)
|
||||||
|
func saveHandler(c, *http.Conn, r *http.Request, title string)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now let's define a wrapper function that <i>takes a function of the above
|
||||||
|
type</i>, and returns a function of type <code>http.HandlerFunc</code>
|
||||||
|
(suitable to be passed to the function <code>http.HandleFunc</code>):
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
func makeHandler(fn func (*http.Conn, *http.Request, string)) http.HandlerFunc {
|
||||||
|
return func(c *http.Conn, r *http.Request) {
|
||||||
|
// Here we will extract the page title from the Request,
|
||||||
|
// and call the provided handler 'fn'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The returned function is called a closure because it encloses values defined
|
||||||
|
outside of it. In this case, the variable <code>fn</code> (the single argument
|
||||||
|
to <code>makeHandler</code>) is enclosed by the closure. The variable
|
||||||
|
<code>fn</code> will be one of our save, edit, or view handlers.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now we can take the code from <code>getTitle</code> and use it here
|
||||||
|
(with some minor modifications):
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final.go -name=makeHandler
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The closure returned by <code>makeHandler</code> is a function that takes
|
||||||
|
an <code>http.Conn</code> and <code>http.Request</code> (in other words,
|
||||||
|
an <code>http.HandlerFunc</code>).
|
||||||
|
The closure extracts the <code>title</code> from the request path, and
|
||||||
|
validates it with the <code>titleValidator</code> regexp. If the
|
||||||
|
<code>title</code> is invalid, an error will be written to the
|
||||||
|
<code>Conn</code> using the <code>http.NotFound</code> function.
|
||||||
|
If the <code>title</code> is valid, the enclosed handler function
|
||||||
|
<code>fn</code> will be called with the <code>Conn</code>,
|
||||||
|
<code>Request</code>, and <code>title</code> as arguments.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now we can wwrap the handler functions with <code>makeHandler</code> in
|
||||||
|
<code>main</code>, before they are registered with the <code>http</code>
|
||||||
|
package:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final.go -name=main
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Finally we remove the calls to <code>getTitle</code> from the handler functions,
|
||||||
|
making them much simpler:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
!./srcextract.bin -src=final.go -name=viewHandler
|
||||||
|
|
||||||
|
!./srcextract.bin -src=final.go -name=editHandler
|
||||||
|
|
||||||
|
!./srcextract.bin -src=final.go -name=saveHandler
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h2>Try it out!</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="final.go">Click here to view the final code listing.</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Recompile the code, and run the app:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
$ 8g wiki.go
|
||||||
|
$ 8l wiki.8
|
||||||
|
$ ./8.out
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Visiting <a href="http://localhost:8080/ANewPage">http://localhost:8080/ANewPage</a>
|
||||||
|
should present you with the page edit form. You should then be able to
|
||||||
|
enter some text, click 'Save', and be redirected to the newly created page.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Other tasks</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Here are some simple tasks you might want to tackle on your own:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Store templates in <code>tmpl/</code> and page data in <code>data/</code>.
|
||||||
|
<li>Add a handler to make the web root redirect to
|
||||||
|
<code>/view/FrontPage</code>.</li>
|
||||||
|
<li>Spruce up the page templates by making them valid HTML and adding some
|
||||||
|
CSS rules.</li>
|
||||||
|
<li>Implement inter-page linking by converting instances of
|
||||||
|
<code>[PageName]</code> to <br>
|
||||||
|
<code><a href="/view/PageName">PageName</a></code>.
|
||||||
|
(hint: you could use <code>regexp.ReplaceAllFunc</code> to do this)
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
Loading…
Reference in New Issue
Block a user