| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027 |
- <!DOCTYPE html>
- <!-- saved from url=(0035)http://golang.org/doc/codelab/wiki/ -->
- <html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>Codelab: Writing Web Applications - The Go Programming Language</title>
- <link rel="stylesheet" href="./Codelab Writing Web Applications - The Go Programming Language_files/all.css" type="text/css" media="all" charset="utf-8">
- <!--[if lt IE 8]>
- <link rel="stylesheet" href="/doc/ie.css" type="text/css">
- <![endif]-->
- <script type="text/javascript" async="" src="./Codelab Writing Web Applications - The Go Programming Language_files/ga.js"></script><script type="text/javascript" src="./Codelab Writing Web Applications - The Go Programming Language_files/godocs.js"></script>
- <script type="text/javascript">
- var _gaq = _gaq || [];
- _gaq.push(["_setAccount", "UA-11222381-2"]);
- _gaq.push(["_trackPageview"]);
- </script>
- </head>
- <body id="top">
- <div id="container">
- <div id="topnav">
- <h1 id="title">The Go Programming Language</h1>
- <div id="nav-main">
- <ul>
- <li><a href="http://golang.org/">Home</a></li><li><a href="http://golang.org/doc/install.html">Getting Started</a></li><li><a href="http://golang.org/doc/docs.html">Documentation</a></li><li><a href="http://golang.org/doc/contrib.html">Contributing</a></li><li><a href="http://golang.org/doc/community.html">Community</a></li>
- </ul>
- <div class="quickref">
- <form method="GET" action="http://golang.org/search">
- References:
- <a href="http://golang.org/pkg/">Packages</a> <span class="sep">|</span>
- <a href="http://golang.org/cmd/">Commands</a> <span class="sep">|</span>
- <a href="http://golang.org/doc/go_spec.html">Specification</a>
- <input id="search" type="search" name="q" value="" class="inactive" placeholder="code search" results="0">
- </form>
- </div>
- </div>
- <a id="logo-box" href="http://golang.org/"></a>
- </div>
- <div id="content">
- <!-- Menu is HTML-escaped elsewhere -->
- <h1 id="generatedHeader">Codelab: Writing Web Applications</h1>
- <!-- The Table of Contents is automatically inserted in this <div>.
- Do not delete this <div>. -->
- <div id="nav"><table class="unruled"><tbody><tr><td class="first"><dl><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_13">Introduction</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_23">Getting Started</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_39">Data Structures</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_87">Introducing the http package (an interlude)</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_111">Using http to serve wiki pages</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_139">Editing Pages</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_153">The template package</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_191">Handling non-existent pages</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_199">Saving Pages</a></dt></dl></td><td><dl><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_209">Error handling</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_225">Template caching</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_242">Validation</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_262">Introducing Function Literals and Closures</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_290">Try it out!</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_300">Other tasks</a></dt></dl></td></tr></tbody></table></div>
- <!-- Content is HTML-escaped elsewhere -->
- <!-- Codelab: Writing Web Applications -->
- <h2 id="tmp_13">Introduction<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></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><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 id="tmp_23">Getting Started<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></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"
- "os"
- )
- </pre>
- <p>
- We import the <code>fmt</code>, <code>ioutil</code> and <code>os</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 id="tmp_39">Data Structures<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></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 successfully 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,
- substitute 6's for the 8's.)
- </p>
- <p>
- <a href="http://golang.org/doc/codelab/wiki/part1.go">Click here to view the code we've written so far.</a>
- </p>
- <h2 id="tmp_87">Introducing the <code>http</code> package (an interlude)<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
- <p>
- Here's a full working example of a simple web server:
- </p>
- <pre>package main
- import (
- "fmt"
- "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>
- <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 id="tmp_111">Using <code>http</code> to serve wiki pages<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
- <p>
- To use the <code>http</code> package, it must be imported:
- </p>
- <pre>import (
- "fmt"
- <b>"http"</b>
- "io/ioutil"
- "os"
- )
- </pre>
- <p>
- Let's create a handler to view a wiki page:
- </p>
- <pre>const lenPath = len("/view/")
- 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>
- <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>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="http://golang.org/doc/codelab/wiki/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 id="tmp_139">Editing Pages<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></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(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>
- <p>
- This function will work fine, but all that hard-coded HTML is ugly.
- Of course, there is a better way.
- </p>
-
- <h2 id="tmp_153">The <code>template</code> package<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></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 containing 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(w http.ResponseWriter, 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(w, p)
- }
- </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.ResponseWriter</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.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><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(w http.ResponseWriter, r *http.Request) {
- title := r.URL.Path[lenPath:]
- p, _ := loadPage(title)
- t, _ := template.ParseFile("view.html", nil)
- t.Execute(w, p)
- }
- </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(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.ParseFile(tmpl+".html", nil)
- t.Execute(w, p)
- }
- </pre>
- <p>
- The handlers are now shorter and simpler.
- </p>
- <h2 id="tmp_191">Handling non-existent pages<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></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(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>
- <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 id="tmp_199">Saving Pages<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
- <p>
- 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>
- <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 id="tmp_209">Error handling<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></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(w http.ResponseWriter, tmpl string, p *Page) {
- t, err := template.ParseFile(tmpl+".html", nil)
- if err != nil {
- http.Error(w, err.String(), http.StatusInternalServerError)
- return
- }
- err = t.Execute(w, p)
- if err != nil {
- http.Error(w, 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(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.String(), http.StatusInternalServerError)
- return
- }
- http.Redirect(w, r, "/view/"+title, http.StatusFound)
- }
- </pre>
- <p>
- Any errors that occur during <code>p.save()</code> will be reported
- to the user.
- </p>
- <h2 id="tmp_225">Template caching<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></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>:
- </p><pre>func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
- err := templates[tmpl].Execute(w, p)
- if err != nil {
- http.Error(w, err.String(), http.StatusInternalServerError)
- }
- }
- </pre>
- <h2 id="tmp_242">Validation<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></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(w http.ResponseWriter, r *http.Request) (title string, err os.Error) {
- title = r.URL.Path[lenPath:]
- if !titleValidator.MatchString(title) {
- http.NotFound(w, 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(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.String(), http.StatusInternalServerError)
- return
- }
- http.Redirect(w, r, "/view/"+title, http.StatusFound)
- }
- </pre>
- <h2 id="tmp_262">Introducing Function Literals and Closures<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></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(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>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>
- <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>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(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.String(), http.StatusInternalServerError)
- return
- }
- http.Redirect(w, r, "/view/"+title, http.StatusFound)
- }
- </pre>
- <h2 id="tmp_290">Try it out!<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
- <p>
- <a href="http://golang.org/doc/codelab/wiki/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/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 id="tmp_300">Other tasks<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></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><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>
- <div id="site-info">
- <p>release.r58.1 8699. Except as noted, this content is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">Creative Commons Attribution 3.0 License</a>.</p>
- </div>
- </div>
- <script type="text/javascript">
- (function() {
- var ga = document.createElement("script"); ga.type = "text/javascript"; ga.async = true;
- ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js";
- var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s);
- })();
- </script>
- <!-- generated at Wed Jul 13 14:07:27 EST 2011 -->
- </body></html>
|