From: Jimmy Zelinskie
-Create a file named
-We import the
-The type
-The
This method's signature reads: "This is a method named
-This method will save the wiki.go, open it in your favorite editor, and
+Create a file named wiki.go, open it in your favorite editor, and
add the following lines:
fmt and ioutil packages from the Go
-standard library. Later, as we implement additional functionality, we will
+We import the fmt and ioutil packages from the Go
+standard library. Later, as we implement additional functionality, we will
add more packages to this import declaration.
[]byte means "a byte slice".
+The type []byte means "a byte slice".
(See Slices: usage and
internals for more on slices.)
The Body element is a []byte rather than
@@ -86,8 +86,8 @@ libraries we will use, as you'll see below.
Page struct describes how page data will be stored in memory.
-But what about persistent storage? We can address that by creating a
+The Page struct describes how page data will be stored in memory.
+But what about persistent storage? We can address that by creating a
save method on Page:
save that
takes as its receiver p, a pointer to Page . It takes
-no parameters, and returns a value of type error."
+no parameters, and returns a value of type error."
Page's Body to a text
+This method will save the Page's Body to a text
file. For simplicity, we will use the Title as the file name.
WriteFile (a standard library function
that writes a byte slice to a file). The save method returns the
error value, to let the application handle it should anything go wrong while
writing the file. If all goes well, Page.save() will return
-nil (the zero-value for pointers, interfaces, and some other
+nil (the zero-value for pointers, interfaces, and some other
types).
-The octal integer constant 0600, passed as the third parameter to
+The octal integer literal 0600, passed as the third parameter to
WriteFile, indicates that the file should be created with
read-write permissions for the current user only. (See the Unix man page
open(2) for details.)
-We will want to load pages, too: +In addition to saving pages, we will want to load pages, too:
{{code "doc/articles/wiki/part1-noerror.go" `/^func loadPage/` `/^}/`}}
The function loadPage constructs the file name from
-Title, reads the file's contents into a new
-Page, and returns a pointer to that new page.
+the title parameter, reads the file's contents into a new
+variable body, and returns two values: a pointer to a
+Page literal constructed with the proper title and body
+values and nil for the error value.
-Functions can return multiple values. The standard library function
-io.ReadFile returns []byte and error.
+Functions can return multiple values. The standard library function
+io.ReadFile returns []byte and error.
In loadPage, error isn't being handled yet; the "blank identifier"
represented by the underscore (_) symbol is used to throw away the
-error return value (in essence, assigning the value to nothing).
+error return value (in essence, assigning the value to nothing).
@@ -152,7 +154,7 @@ function to return *Page and error.
Callers of this function can now check the second parameter; if it is
nil then it has successfully loaded a Page. If not, it will be an
-error that can be handled by the caller (see the
+error that can be handled by the caller (see the
language specification for details).
-You can compile and run the program like this: +You can compile and run the program like this:
@@ -182,7 +184,7 @@ This is a sample page.
-(If you're using Windows you must type "wiki" without the
+(If you're using Windows you must type "wiki" without the
"./" to run the program.)
-The main function begins with a call to
-http.HandleFunc, which tells the http package to
-handle all requests to the web root ("/") with
-handler.
+The main function begins with a call to
+http.HandleFunc, which tells the http package to
+handle all requests to the web root ("/") with
+handler.
@@ -219,20 +221,20 @@ its arguments.
-An http.ResponseWriter value assembles the HTTP server's response; by writing
+An http.ResponseWriter value assembles the HTTP server's response; by writing
to it, we send data to the HTTP client.
An http.Request is a data structure that represents the client
-HTTP request. The string r.URL.Path is the path component
-of the request URL. The trailing [1:] means
-"create a sub-slice of Path from the 1st character to the end."
+HTTP request. r.URL.Path is the path component
+of the request URL. The trailing [1:] means
+"create a sub-slice of Path from the 1st character to the end."
This drops the leading "/" from the path name.
-If you run this program and access the URL: +If you run this program and access the URL:
http://localhost:8080/monkeys
@@ -249,13 +251,14 @@ To use the net/http package, it must be imported:
import ( "fmt" - "net/http" "io/ioutil" + "net/http" )
-Let's create a handler to view a wiki page:
+Let's create a handler, viewHandler that will allow users to
+view a wiki page. It will handle URLs prefixed with "/view/".
First, this function extracts the page title from r.URL.Path,
-the path component of the request URL. The global constant
+the path component of the request URL. The global constant
lenPath is the length of the leading "/view/"
component of the request path.
-The Path is re-sliced with [lenPath:] to drop the
-first 6 characters of the string. This is because the path will invariably
-begin with "/view/", which is not part of the page title.
+The Path is re-sliced with [lenPath:] to drop the
+first 6 characters of the string. This is because the path will invariably
+begin with "/view/", which is not part of the page's title.
-The function then loads the page data, formats the page with a string of simple
-HTML, and writes it to w, the http.ResponseWriter.
+The function then loads the page data, formats the page with a string of simple
+HTML, and writes it to w, the http.ResponseWriter.
-Again, note the use of _ to ignore the error
+Again, note the use of _ to ignore the error
return value from loadPage. This is done here for simplicity
and generally considered bad practice. We will attend to this later.
-To use this handler, we create a main function that
-initializes http using the viewHandler to handle
+To use this handler, we rewrite our main function to
+initialize http using the viewHandler to handle
any requests under the path /view/.
+(If you're using Windows you must type "wiki" without the
+"./" to run the program.)
+
With this web server running, a visit to http://localhost:8080/view/test
@@ -326,14 +334,14 @@ form.
-First, we add them to main():
+First, we add them to main():
-The function editHandler loads the page
-(or, if it doesn't exist, create an empty Page struct),
+The function editHandler loads the page
+(or, if it doesn't exist, create an empty Page struct),
and displays an HTML form.
html/template package@@ -354,20 +362,20 @@ underlying Go code.
-First, we must add html/template to the list of imports:
+First, we must add html/template to the list of imports. We
+also won't be using fmt anymore, so we have to remove that.
import ( "html/template" - "http" "io/ioutil" - "os" + "net/http" )
-Let's create a template file containing the HTML form.
+Let's create a template file containing the HTML form.
Open a new file named edit.html, and add the following lines:
-The function template.ParseFiles will read the contents of
-edit.html and return a *template.Template.
+The function template.ParseFiles will read the contents of
+edit.html and return a *template.Template.
@@ -405,12 +413,7 @@ HTML.
-Now that we've removed the fmt.Fprintf statement, we can remove
-"fmt" from the import list.
-
-While we're working with templates, let's create a template for our
+Since we're working with templates now, let's create a template for our
viewHandler called view.html:
-The handlers are now shorter and simpler.
+If we comment out the registration of our unimplemented save handler in
+main, we can once again build and test our program.
+Click here to view the code we've written so far.
What if you visit
-/view/APageThatDoesntExist? The program will crash. This is
-because it ignores the error return value from loadPage. Instead,
-if the requested Page doesn't exist, it should redirect the client to the edit
-Page so the content may be created:
+/view/APageThatDoesntExist? You'll see a page containing
+HTML. This is because it ignores the error return value from
+loadPage and continues to try and fill out the template
+with no data. Instead, if the requested Page doesn't exist, it should
+redirect the client to the edit Page so the content may be created:
-The http.Redirect function adds an HTTP status code of
+The http.Redirect function adds an HTTP status code of
http.StatusFound (302) and a Location
header to the HTTP response.
-The function saveHandler will handle the form submission.
+The function saveHandler will handle the submission of forms
+located on the edit pages. After uncommenting the related line in
+main, let's implement the the handler:
-The page title (provided in the URL) and the form's only field,
-Body, are stored in a new Page.
+The page title (provided in the URL) and the form's only field,
+Body, are stored in a new Page.
The save() method is then called to write the data to a file,
and the client is redirected to the /view/ page.
The value returned by FormValue is of type string.
-We must convert that value to []byte before it will fit into
-the Page struct. We use []byte(body) to perform
+We must convert that value to []byte before it will fit into
+the Page struct. We use []byte(body) to perform
the conversion.
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. +have unintended behavior. 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 function exactly how we want and the user can be notified.
@@ -493,7 +501,7 @@ First, let's handle the errors in renderTemplate:
{{code "doc/articles/wiki/final-parsetemplate.go" `/^func renderTemplate/` `/^}/`}}
-The http.Error function sends a specified HTTP response code
+The http.Error 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.
saveHandler:
-{{code "doc/articles/wiki/final-noclosure.go" `/^func saveHandler/` `/^}/`}}
+{{code "doc/articles/wiki/part3-errorhandling.go" `/^func saveHandler/` `/^}/`}}
-Any errors that occur during p.save() will be reported
+Any errors that occur during p.save() will be reported
to the user.
-There is an inefficiency in this code: renderTemplate calls
-ParseFiles every time a page is rendered.
+There is an inefficiency in this code: renderTemplate calls
+ParseFiles every time a page is rendered.
A better approach would be to call ParseFiles once at program
initialization, parsing all templates into a single *Template.
Then we can use the
@@ -536,10 +544,10 @@ can't be loaded the only sensible thing to do is exit the program.
-A for loop is used with a range 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.
+A for loop is used with a range 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.
@@ -571,25 +579,27 @@ Then we can create a global variable to store our validation regexp: {{code "doc/articles/wiki/final-noclosure.go" `/^var titleValidator/`}}
-The function regexp.MustCompile will parse and compile the
-regular expression, and return a regexp.Regexp.
+The function regexp.MustCompile will parse and compile the
+regular expression, and return a regexp.Regexp.
MustCompile is distinct from Compile in that it will
panic if the expression compilation fails, while Compile returns
-an error as a second parameter.
+an error as a second parameter.
-Now, let's write a function that extracts the title string from the request
-URL, and tests it against our TitleValidator expression:
+Now, let's write a function, getTitle, that extracts the title
+string from the request URL, and tests it against our
+TitleValidator expression:
If the title is valid, it will be returned along with a nil
-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.
+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. To create a new error, we have to import the errors
+package.
@@ -604,10 +614,10 @@ Let's put a call to getTitle in each of the handlers:
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 -function -literals provide a powerful means of abstracting functionality +What if we could wrap each of the handlers in a function that does this +validation and error checking? Go's +function +literals provide a powerful means of abstracting functionality that can help us here.
@@ -654,19 +664,19 @@ Now we can take the code fromgetTitle and use it here
The closure returned by makeHandler is a function that takes
an http.ResponseWriter and http.Request (in other
-words, an http.HandlerFunc).
+words, an http.HandlerFunc).
The closure extracts the title from the request path, and
validates it with the TitleValidator regexp. If the
title is invalid, an error will be written to the
-ResponseWriter using the http.NotFound function.
+ResponseWriter using the http.NotFound function.
If the title is valid, the enclosed handler function
fn will be called with the ResponseWriter,
Request, and title as arguments.
-Now we can wrap the handler functions with makeHandler in
-main, before they are registered with the http
+Now we can wrap the handler functions with makeHandler in
+main, before they are registered with the http
package:
Visiting http://localhost:8080/view/ANewPage -should present you with the page edit form. You should then be able to +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.
@@ -710,11 +720,11 @@ Here are some simple tasks you might want to tackle on your own:tmpl/ and page data in data/.
-/view/FrontPage.[PageName] to <a href="/view/PageName">PageName</a>.
(hint: you could use regexp.ReplaceAllFunc to do this)
diff --git a/doc/articles/wiki/part3-errorhandling.go b/doc/articles/wiki/part3-errorhandling.go
new file mode 100644
index 0000000000..945aa1e391
--- /dev/null
+++ b/doc/articles/wiki/part3-errorhandling.go
@@ -0,0 +1,75 @@
+// 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.
+
+package main
+
+import (
+ "html/template"
+ "io/ioutil"
+ "net/http"
+)
+
+type Page struct {
+ Title string
+ Body []byte
+}
+
+func (p *Page) save() error {
+ filename := p.Title + ".txt"
+ return ioutil.WriteFile(filename, p.Body, 0600)
+}
+
+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
+}
+
+const lenPath = len("/view/")
+
+func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
+ t, _ := template.ParseFiles(tmpl + ".html")
+ t.Execute(w, p)
+}
+
+func viewHandler(w http.ResponseWriter, r *http.Request) {
+ title := r.URL.Path[lenPath:]
+ 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 := r.URL.Path[lenPath:]
+ p, err := loadPage(title)
+ if err != nil {
+ p = &Page{Title: title}
+ }
+ renderTemplate(w, "edit", p)
+}
+
+func saveHandler(w http.ResponseWriter, r *http.Request) {
+ title := r.URL.Path[lenPath:]
+ 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)
+}
+
+func main() {
+ http.HandleFunc("/view/", viewHandler)
+ http.HandleFunc("/edit/", editHandler)
+ http.HandleFunc("/save/", saveHandler)
+ http.ListenAndServe(":8080", nil)
+}
diff --git a/doc/articles/wiki/part3.go b/doc/articles/wiki/part3.go
new file mode 100644
index 0000000000..7fe4351af9
--- /dev/null
+++ b/doc/articles/wiki/part3.go
@@ -0,0 +1,59 @@
+// 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.
+
+package main
+
+import (
+ "html/template"
+ "io/ioutil"
+ "net/http"
+)
+
+type Page struct {
+ Title string
+ Body []byte
+}
+
+func (p *Page) save() error {
+ filename := p.Title + ".txt"
+ return ioutil.WriteFile(filename, p.Body, 0600)
+}
+
+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
+}
+
+const lenPath = len("/view/")
+
+func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
+ t, _ := template.ParseFiles(tmpl + ".html")
+ t.Execute(w, p)
+}
+
+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 main() {
+ http.HandleFunc("/view/", viewHandler)
+ http.HandleFunc("/edit/", editHandler)
+ //http.HandleFunc("/save/", saveHandler)
+ http.ListenAndServe(":8080", nil)
+}