From dad1228cc378f5860a111201ed24ba88cf992a73 Mon Sep 17 00:00:00 2001
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)
+}
--
2.48.1