diff options
author | Andrew Gerrand <adg@golang.org> | 2012-03-27 16:07:46 +1100 |
---|---|---|
committer | Andrew Gerrand <adg@golang.org> | 2012-03-27 16:07:46 +1100 |
commit | d98507f1c4a4dfdd77400138ff38865813d4327f (patch) | |
tree | afac6d5a02a6d0ab62e30b1710440ef801164828 | |
parent | 7a3965417426e4405a6ec81ce486668fa5c36e36 (diff) | |
download | go-d98507f1c4a4dfdd77400138ff38865813d4327f.tar.gz go-d98507f1c4a4dfdd77400138ff38865813d4327f.zip |
doc: update wiki tutorial templates, and template discussion
Fixes #3384.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5915044
-rw-r--r-- | doc/articles/wiki/final.go | 11 | ||||
-rw-r--r-- | doc/articles/wiki/index.html | 393 | ||||
-rw-r--r-- | doc/articles/wiki/wiki.html | 779 |
3 files changed, 58 insertions, 1125 deletions
diff --git a/doc/articles/wiki/final.go b/doc/articles/wiki/final.go index 134ad7e63c..e93cdee479 100644 --- a/doc/articles/wiki/final.go +++ b/doc/articles/wiki/final.go @@ -58,17 +58,10 @@ func saveHandler(w http.ResponseWriter, r *http.Request, title string) { http.Redirect(w, r, "/view/"+title, http.StatusFound) } -var templates = make(map[string]*template.Template) - -func init() { - for _, tmpl := range []string{"edit", "view"} { - t := template.Must(template.ParseFiles(tmpl + ".html")) - templates[tmpl] = t - } -} +var templates = template.Must(template.ParseFiles("edit.html", "view.html")) func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { - err := templates[tmpl].Execute(w, p) + err := templates.ExecuteTemplate(w, tmpl+".html", p) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } diff --git a/doc/articles/wiki/index.html b/doc/articles/wiki/index.html index 99ff3a7c9d..52bf7e798b 100644 --- a/doc/articles/wiki/index.html +++ b/doc/articles/wiki/index.html @@ -1,5 +1,6 @@ <!--{ - "Title": "Writing Web Applications" + "Title": "Writing Web Applications", + "Template": true }--> <h2>Introduction</h2> @@ -36,7 +37,7 @@ Install Go (see the <a href="/doc/install">Installation Instructions</a>). </p> <p> -Make a new directory for this tutorial and cd to it: +Make a new directory for this tutorial inside your <code>GOPATH</code> and cd to it: </p> <pre> @@ -73,12 +74,7 @@ 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> +{{code "doc/articles/wiki/part1.go" `/^type Page/` `/}/`}} <p> The type <code>[]byte</code> means "a <code>byte</code> slice". @@ -95,12 +91,7 @@ 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() error { - filename := p.Title + ".txt" - return ioutil.WriteFile(filename, p.Body, 0600) -} -</pre> +{{code "doc/articles/wiki/part1.go" `/^func.*Page.*save/` `/}/`}} <p> This method's signature reads: "This is a method named <code>save</code> that @@ -134,13 +125,7 @@ read-write permissions for the current user only. (See the Unix man page 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> +{{code "doc/articles/wiki/part1-noerror.go" `/^func loadPage/` `/^}/`}} <p> The function <code>loadPage</code> constructs the file name from @@ -162,16 +147,7 @@ the file might not exist. We should not ignore such errors. Let's modify the function to return <code>*Page</code> and <code>error</code>. </p> -<pre> -func loadPage(title string) (*Page, error) { - filename := title + ".txt" - body, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - return &Page{Title: title, Body: body}, nil -} -</pre> +{{code "doc/articles/wiki/part1.go" `/^func loadPage/` `/^}/`}} <p> Callers of this function can now check the second parameter; if it is @@ -186,14 +162,7 @@ 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> +{{code "doc/articles/wiki/part1.go" `/^func main/` `/^}/`}} <p> After compiling and executing this code, a file named <code>TestPage.txt</code> @@ -227,23 +196,7 @@ This is a sample page. Here's a full working example of a simple web server: </p> -<pre> -package main - -import ( - "fmt" - "net/http" -) - -func handler(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:]) -} - -func main() { - http.HandleFunc("/", handler) - http.ListenAndServe(":8080", nil) -} -</pre> +{{code "doc/articles/wiki/http-sample.go"}} <p> The <code>main</code> function begins with a call to @@ -305,15 +258,9 @@ import ( Let's create a handler to view a wiki page: </p> -<pre> -const lenPath = len("/view/") +{{code "doc/articles/wiki/part2.go" `/^const lenPath/`}} -func viewHandler(w http.ResponseWriter, r *http.Request) { - title := r.URL.Path[lenPath:] - p, _ := loadPage(title) - fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body) -} -</pre> +{{code "doc/articles/wiki/part2.go" `/^func viewHandler/` `/^}/`}} <p> First, this function extracts the page title from <code>r.URL.Path</code>, @@ -342,12 +289,7 @@ 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> +{{code "doc/articles/wiki/part2.go" `/^func main/` `/^}/`}} <p> <a href="part2.go">Click here to view the code we've written so far.</a> @@ -387,14 +329,7 @@ form. 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> +{{code "doc/articles/wiki/final-noclosure.go" `/^func main/` `/^}/`}} <p> The function <code>editHandler</code> loads the page @@ -402,21 +337,7 @@ The function <code>editHandler</code> loads the page and displays an HTML form. </p> -<pre> -func editHandler(w http.ResponseWriter, r *http.Request) { - title := r.URL.Path[lenPath:] - p, err := loadPage(title) - if err != nil { - p = &Page{Title: title} - } - fmt.Fprintf(w, "<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> +{{code "doc/articles/wiki/notemplate.go" `/^func editHandler/` `/^}/`}} <p> This function will work fine, but all that hard-coded HTML is ugly. @@ -450,31 +371,14 @@ Let's create a template file containing the HTML form. Open a new file named <code>edit.html</code>, and add the following lines: </p> -<pre> -<h1>Editing {{.Title |html}}</h1> - -<form action="/save/{{.Title |html}}" method="POST"> -<div><textarea name="body" rows="20" cols="80">{{printf "%s" .Body |html}}</textarea></div> -<div><input type="submit" value="Save"></div> -</form> -</pre> +{{code "doc/articles/wiki/edit.html"}} <p> Modify <code>editHandler</code> to use the template, instead of the hard-coded HTML: </p> -<pre> -func editHandler(w http.ResponseWriter, r *http.Request) { - title := r.URL.Path[lenPath:] - p, err := loadPage(title) - if err != nil { - p = &Page{Title: title} - } - t, _ := template.ParseFiles("edit.html") - t.Execute(w, p) -} -</pre> +{{code "doc/articles/wiki/final-noerror.go" `/^func editHandler/` `/^}/`}} <p> The function <code>template.ParseFiles</code> will read the contents of @@ -509,26 +413,13 @@ 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 |html}}</h1> - -<p>[<a href="/edit/{{.Title |html}}">edit</a>]</p> - -<div>{{printf "%s" .Body |html}}</div> -</pre> +{{code "doc/articles/wiki/view.html"}} <p> Modify <code>viewHandler</code> accordingly: </p> -<pre> -func viewHandler(w http.ResponseWriter, r *http.Request) { - title := r.URL.Path[lenPath:] - p, _ := loadPage(title) - t, _ := template.ParseFiles("view.html") - t.Execute(w, p) -} -</pre> +{{code "doc/articles/wiki/final-noerror.go" `/^func viewHandler/` `/^}/`}} <p> Notice that we've used almost exactly the same templating code in both @@ -536,27 +427,9 @@ handlers. Let's remove this duplication by moving the templating code to its own function: </p> -<pre> -func viewHandler(w http.ResponseWriter, r *http.Request) { - title := r.URL.Path[lenPath:] - p, _ := loadPage(title) - renderTemplate(w, "view", p) -} - -func editHandler(w http.ResponseWriter, r *http.Request) { - title := r.URL.Path[lenPath:] - p, err := loadPage(title) - if err != nil { - p = &Page{Title: title} - } - renderTemplate(w, "edit", p) -} - -func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { - t, _ := template.ParseFiles(tmpl + ".html") - t.Execute(w, p) -} -</pre> +{{code "doc/articles/wiki/final-template.go" `/^func viewHandler/` `/^}/`}} +{{code "doc/articles/wiki/final-template.go" `/^func editHandler/` `/^}/`}} +{{code "doc/articles/wiki/final-template.go" `/^func renderTemplate/` `/^}/`}} <p> The handlers are now shorter and simpler. @@ -572,20 +445,7 @@ 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(w http.ResponseWriter, r *http.Request) { - title, err := getTitle(w, r) - if err != nil { - return - } - p, err := loadPage(title) - if err != nil { - http.Redirect(w, r, "/edit/"+title, http.StatusFound) - return - } - renderTemplate(w, "view", p) -} -</pre> +{{code "doc/articles/wiki/final-noclosure.go" `/^func viewHandler/` `/^}/`}} <p> The <code>http.Redirect</code> function adds an HTTP status code of @@ -599,15 +459,7 @@ header to the HTTP response. The function <code>saveHandler</code> will handle the form submission. </p> -<pre> -func saveHandler(w http.ResponseWriter, r *http.Request) { - title := r.URL.Path[lenPath:] - body := r.FormValue("body") - p := &Page{Title: title, Body: []byte(body)} - p.save() - http.Redirect(w, r, "/view/"+title, http.StatusFound) -} -</pre> +{{code "doc/articles/wiki/final-template.go" `/^func saveHandler/` `/^}/`}} <p> The page title (provided in the URL) and the form's only field, @@ -637,19 +489,7 @@ function and the user will be notified. First, let's handle the errors in <code>renderTemplate</code>: </p> -<pre> -func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { - t, err := template.ParseFiles(tmpl + ".html") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - err = t.Execute(w, p) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } -} -</pre> +{{code "doc/articles/wiki/final-parsetemplate.go" `/^func renderTemplate/` `/^}/`}} <p> The <code>http.Error</code> function sends a specified HTTP response code @@ -661,22 +501,7 @@ Already the decision to put this in a separate function is paying off. Now let's fix up <code>saveHandler</code>: </p> -<pre> -func saveHandler(w http.ResponseWriter, r *http.Request) { - title, err := getTitle(w, r) - if err != nil { - return - } - body := r.FormValue("body") - p := &Page{Title: title, Body: []byte(body)} - err = p.save() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - http.Redirect(w, r, "/view/"+title, http.StatusFound) -} -</pre> +{{code "doc/articles/wiki/final-noclosure.go" `/^func saveHandler/` `/^}/`}} <p> Any errors that occur during <code>p.save()</code> will be reported @@ -687,40 +512,28 @@ to the user. <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. +<code>ParseFiles</code> every time a page is rendered. +A better approach would be to call <code>ParseFiles</code> once at program +initialization, parsing all templates into a single <code>*Template</code>. +Then we can use the +<a href="/pkg/html/template/#Template.ExecuteTemplate"><code>ExecuteTemplate</code></a> +method to render a specific template. </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): +First we create a global variable named <code>templates</code>, and initialize +it with <code>ParseFiles</code>. </p> -<pre> -var templates = make(map[string]*template.Template) -</pre> +{{code "doc/articles/wiki/final.go" `/var templates/`}} <p> -Then we create an <code>init</code> function, which will be called before -<code>main</code> at program initialization. The function -<code>template.Must</code> is a convenience wrapper that panics when passed a -non-nil <code>error</code> value, and otherwise returns the +The function <code>template.Must</code> is a convenience wrapper that panics +when passed a non-nil <code>error</code> value, and otherwise returns the <code>*Template</code> unaltered. 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"} { - t := template.Must(template.ParseFiles(tmpl + ".html")) - templates[tmpl] = t - } -} -</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. @@ -729,18 +542,17 @@ 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>: +We then modify the <code>renderTemplate</code> function to call the +<code>templates.ExecuteTemplate</code> method with the name of the appropriate +template: +</p> -<pre> -func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { - err := templates[tmpl].Execute(w, p) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } -} -</pre> +{{code "doc/articles/wiki/final.go" `/func renderTemplate/` `/^}/`}} + +<p> +Note that the template name is the template file name, so we must +append <code>".html"</code> to the <code>tmpl</code> argument. +</p> <h2>Validation</h2> @@ -755,9 +567,7 @@ 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> +{{code "doc/articles/wiki/final-noclosure.go" `/^var titleValidator/`}} <p> The function <code>regexp.MustCompile</code> will parse and compile the @@ -772,16 +582,7 @@ 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(w http.ResponseWriter, r *http.Request) (title string, err error) { - title = r.URL.Path[lenPath:] - if !titleValidator.MatchString(title) { - http.NotFound(w, r) - err = errors.New("Invalid Page Title") - } - return -} -</pre> +{{code "doc/articles/wiki/final-noclosure.go" `/func getTitle/` `/^}/`}} <p> If the title is valid, it will be returned along with a <code>nil</code> @@ -794,47 +595,9 @@ handler. Let's put a call to <code>getTitle</code> in each of the handlers: </p> -<pre> -func viewHandler(w http.ResponseWriter, r *http.Request) { - title, err := getTitle(w, r) - if err != nil { - return - } - p, err := loadPage(title) - if err != nil { - http.Redirect(w, r, "/edit/"+title, http.StatusFound) - return - } - renderTemplate(w, "view", p) -} - -func editHandler(w http.ResponseWriter, r *http.Request) { - title, err := getTitle(w, r) - if err != nil { - return - } - p, err := loadPage(title) - if err != nil { - p = &Page{Title: title} - } - renderTemplate(w, "edit", p) -} - -func saveHandler(w http.ResponseWriter, r *http.Request) { - title, err := getTitle(w, r) - if err != nil { - return - } - body := r.FormValue("body") - p := &Page{Title: title, Body: []byte(body)} - err = p.save() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - http.Redirect(w, r, "/view/"+title, http.StatusFound) -} -</pre> +{{code "doc/articles/wiki/final-noclosure.go" `/^func viewHandler/` `/^}/`}} +{{code "doc/articles/wiki/final-noclosure.go" `/^func editHandler/` `/^}/`}} +{{code "doc/articles/wiki/final-noclosure.go" `/^func saveHandler/` `/^}/`}} <h2>Introducing Function Literals and Closures</h2> @@ -885,18 +648,7 @@ 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.ResponseWriter, *http.Request, string)) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - title := r.URL.Path[lenPath:] - if !titleValidator.MatchString(title) { - http.NotFound(w, r) - return - } - fn(w, r, title) - } -} -</pre> +{{code "doc/articles/wiki/final.go" `/func makeHandler/` `/^}/`}} <p> The closure returned by <code>makeHandler</code> is a function that takes @@ -917,49 +669,16 @@ Now we can wrap the handler functions with <code>makeHandler</code> in 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> +{{code "doc/articles/wiki/final.go" `/func main/` `/^}/`}} <p> Finally we remove the calls to <code>getTitle</code> from the handler functions, making them much simpler: </p> -<pre> -func viewHandler(w http.ResponseWriter, r *http.Request, title string) { - p, err := loadPage(title) - if err != nil { - http.Redirect(w, r, "/edit/"+title, http.StatusFound) - return - } - renderTemplate(w, "view", p) -} - -func editHandler(w http.ResponseWriter, r *http.Request, title string) { - p, err := loadPage(title) - if err != nil { - p = &Page{Title: title} - } - renderTemplate(w, "edit", p) -} - -func saveHandler(w http.ResponseWriter, r *http.Request, title string) { - body := r.FormValue("body") - p := &Page{Title: title, Body: []byte(body)} - err := p.save() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - http.Redirect(w, r, "/view/"+title, http.StatusFound) -} -</pre> +{{code "doc/articles/wiki/final.go" `/^func viewHandler/` `/^}/`}} +{{code "doc/articles/wiki/final.go" `/^func editHandler/` `/^}/`}} +{{code "doc/articles/wiki/final.go" `/^func saveHandler/` `/^}/`}} <h2>Try it out!</h2> diff --git a/doc/articles/wiki/wiki.html b/doc/articles/wiki/wiki.html deleted file mode 100644 index ef5d902c6c..0000000000 --- a/doc/articles/wiki/wiki.html +++ /dev/null @@ -1,779 +0,0 @@ -<!--{ - "Title": "Writing Web Applications" -}--> - -<h2>Introduction</h2> - -<p> -Covered in this tutorial: -</p> -<ul> -<li>Creating a data structure with load and save methods</li> -<li>Using the <code>net/http</code> package to build web applications -<li>Using the <code>html/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/DOS command-line knowledge</li> -</ul> - -<h2>Getting Started</h2> - -<p> -At present, you need to have a FreeBSD, Linux, OS X, or Windows machine to run Go. -We will use <code>$</code> to represent the command prompt. -</p> - -<p> -Install Go (see the <a href="/doc/install">Installation Instructions</a>). -</p> - -<p> -Make a new directory for this tutorial inside your <code>GOPATH</code> 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> -We import the <code>fmt</code> and <code>ioutil</code> packages from the Go -standard library. 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="/doc/articles/slices_usage_and_internals.html">Slices: usage and -internals</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>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>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>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>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 successfully loaded a Page. If not, it will be an -<code>error</code> that can be handled by the caller (see the -<a href="/ref/spec#Errors">language specification</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> -$ go build wiki.go -$ ./wiki -This is a sample page. -</pre> - -<p> -(If you're using Windows you must type "<code>wiki</code>" without the -"<code>./</code>" to run the program.) -</p> - -<p> -<a href="part1.go">Click here to view the code we've written so far.</a> -</p> - -<h2>Introducing the <code>net/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.ResponseWriter</code> and an <code>http.Request</code> as -its arguments. -</p> - -<p> -An <code>http.ResponseWriter</code> value assembles the HTTP server's response; 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>net/http</code> to serve wiki pages</h2> - -<p> -To use the <code>net/http</code> package, it must be imported: -</p> - -<pre> -import ( - "fmt" - <b>"net/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>w</code>, the <code>http.ResponseWriter</code>. -</p> - -<p> -Again, note the use of <code>_</code> to ignore the <code>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> - -<p> -Open <code>test.txt</code> file in your editor, and save the string "Hello world" (without quotes) -in it. -</p> - -<pre> -$ go build wiki.go -$ ./wiki -</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>html/template</code> package</h2> - -<p> -The <code>html/template</code> package is part of the Go standard library. -We can use <code>html/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>html/template</code> to the list of imports: -</p> - -<pre> -import ( - "http" - "io/ioutil" - "os" - <b>"html/template"</b> -) -</pre> - -<p> -Let's create a template file containing 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.ParseFiles</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> executes the template, writing the -generated HTML to the <code>http.ResponseWriter</code>. -The <code>.Title</code> and <code>.Body</code> dotted identifiers refer to -<code>p.Title</code> and <code>p.Body</code>. -</p> - -<p> -Template directives are enclosed in double curly braces. -The <code>printf "%s" .Body</code> instruction is a function call -that outputs <code>.Body</code> as a string instead of a stream of bytes, -the same as a call to <code>fmt.Printf</code>. -The <code>|html</code> part of each directive pipes the value through the -<code>html</code> formatter before outputting it, which escapes HTML -characters (such as replacing <code>></code> with <code>&gt;</code>), -preventing user data from corrupting the form HTML. -</p> - -<p> -Now that we've removed the <code>fmt.Fprintf</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 <a href="http://localhost:8080/view/APageThatDoesntExist"> -<code>/view/APageThatDoesntExist</code></a>? 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-noclosure.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-noclosure.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>ParseFiles</code> every time a page is rendered. -A better approach would be to call <code>ParseFiles</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.Must</code> is a convenience wrapper that panics when passed a -non-nil <code>error</code> value, and otherwise returns the -<code>*Template</code> unaltered. 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> is distinct from <code>Compile</code> in that it will -panic if the expression compilation fails, while <code>Compile</code> returns -an <code>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="/ref/spec#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(w http.ResponseWriter, r *http.Request, title string) -func editHandler(w http.ResponseWriter, r *http.Request, title string) -func saveHandler(w http.ResponseWriter, 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.ResponseWriter, *http.Request, string)) http.HandlerFunc { - return func(w http.ResponseWriter, 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.ResponseWriter</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>ResponseWriter</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>ResponseWriter</code>, -<code>Request</code>, and <code>title</code> as arguments. -</p> - -<p> -Now we can wrap 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> -$ go build wiki.go -$ ./wiki -</pre> - -<p> -Visiting <a href="http://localhost:8080/view/ANewPage">http://localhost:8080/view/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> |