From dad1228cc378f5860a111201ed24ba88cf992a73 Mon Sep 17 00:00:00 2001 From: Jimmy Zelinskie Date: Thu, 11 Oct 2012 13:07:34 +1100 Subject: [PATCH] doc/articles/wiki: numerous fixes Fixes #3733 Fixes #2149 Updated Syntax Added part3.go example program Added part3-errorhandling.go example program Improved wording in some places R=golang-dev, adg, minux.ma CC=golang-dev https://golang.org/cl/6636048 --- doc/articles/wiki/index.html | 208 ++++++++++++----------- doc/articles/wiki/part3-errorhandling.go | 75 ++++++++ doc/articles/wiki/part3.go | 59 +++++++ 3 files changed, 243 insertions(+), 99 deletions(-) create mode 100644 doc/articles/wiki/part3-errorhandling.go create mode 100644 doc/articles/wiki/part3.go diff --git a/doc/articles/wiki/index.html b/doc/articles/wiki/index.html index 6c45d7178e..b7706777d3 100644 --- a/doc/articles/wiki/index.html +++ b/doc/articles/wiki/index.html @@ -46,7 +46,7 @@ $ cd gowiki

-Create a file named 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:

@@ -60,8 +60,8 @@ import (

-We import the 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.

@@ -77,7 +77,7 @@ the title and body. {{code "doc/articles/wiki/part1.go" `/^type Page/` `/}/`}}

-The type []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.

-The 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:

@@ -96,11 +96,11 @@ But what about persistent storage? We can address that by creating a

This method's signature reads: "This is a method named 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."

-This method will save the 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.

@@ -110,35 +110,37 @@ that is the return type of 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).

@@ -172,7 +174,7 @@ printed to the screen.

-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.)

@@ -199,10 +201,10 @@ Here's a full working example of a simple web server: {{code "doc/articles/wiki/http-sample.go"}}

-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/".

{{code "doc/articles/wiki/part2.go" `/^const lenPath/`}} @@ -264,28 +267,28 @@ Let's create a handler to view a wiki page:

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/.

@@ -310,6 +313,11 @@ $ go build wiki.go $ ./wiki +

+(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():

{{code "doc/articles/wiki/final-noclosure.go" `/^func 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.

@@ -343,7 +351,7 @@ and displays an HTML form. This function will work fine, but all that hard-coded HTML is ugly. Of course, there is a better way.

- +

The 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:

@@ -381,8 +389,8 @@ HTML: {{code "doc/articles/wiki/final-noerror.go" `/^func editHandler/` `/^}/`}}

-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:

@@ -428,28 +431,31 @@ handlers. Let's remove this duplication by moving the templating code to its own function:

+{{code "doc/articles/wiki/final-template.go" `/^func renderTemplate/` `/^}/`}} {{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/` `/^}/`}}

-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.

Handling non-existent pages

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:

-{{code "doc/articles/wiki/final-noclosure.go" `/^func viewHandler/` `/^}/`}} +{{code "doc/articles/wiki/part3-errorhandling.go" `/^func viewHandler/` `/^}/`}}

-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.

@@ -457,22 +463,24 @@ header to the HTTP response.

Saving Pages

-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:

{{code "doc/articles/wiki/final-template.go" `/^func saveHandler/` `/^}/`}}

-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.

@@ -481,9 +489,9 @@ 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.

@@ -502,18 +510,18 @@ Already the decision to put this in a separate function is paying off. Now let's fix up 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.

Template caching

-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:

{{code "doc/articles/wiki/final-noclosure.go" `/func getTitle/` `/^}/`}}

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 from getTitle 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:

@@ -698,7 +708,7 @@ $ ./wiki

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: