The driver statement for the template is the part that is included in the double curly brackets. printf "%s" .Body
represents the .Body
output bit string
Instead of a byte string, fmt.Printf
a function-like effect. html/template
can guarantee the output of a valid HTML string,
(>)
special symbols such as these are automatically replaced with >
equivalent encodings, ensuring that the original HTML structure is not broken.
You also need to adjust the viewHandler
function:
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)}
Observe that you can see if the template is very similar in the way it is. So we're going to make the template independent of the big one function:
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)}
Now the processing function is much clearer and shorter.
Handling pages that do not exist
What happens if you visit /view/APageThatDoesntExist
? The program crashes.
This is because the program ignores the loadPage
returned error message. In order to handle a situation where the page does not exist,
The program redirects to the edit page of a new page:
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)}
http.Redirect
The function adds http.StatusFound (302)
state and repositions.
Save page
saveHandler
The function is used to process the submitted form.
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)}
The title of the page (URL provided) and the contents of the form are saved as a new page.
The calling save()
method writes the page to a file and then redirects to the /view/
page.
FormValue
The return value returned by the method is a string type. We need to first convert to []byte
, and then populate to Page
Structural body. We use []byte(body)
statements to make casts.
Error handling
The preceding code basically ignores error handling. This is not a good way to handle it, because an error can cause the program to crash.
A good way to handle this is to intercept the error and display the error-related information to the user. This way, even if an error occurs, the server
Can run correctly and users can receive error messages.
First, I'll deal with renderTemplate
the errors in the first place:
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) }}
http.Error
The function returns a specific error code (this is the "Server error" type) and the error message.
It seems that the decision to separate the template processing into a function is the right decision.
Here's The fix saveHandler
:
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)}
p.save()
The error message that occurs is also reported to the user.
Cache templates
The previous implementation has a performance flaw: The renderTemplate
function is called every time ParseFiles
.
A better idea of optimization is to use only one call at the time of initialization ParseFiles
, and all the templates to be processed
Put *Template
it in one. You can then use ExecuteTemplate
the specified template for rendering.
First create a Fame templates
global variable and then initialize it with ParseFiles
.
var templates = template.Must(template.ParseFiles("edit.html", "view.html"))
template.Must
Just a simple wrapper, when passing a non- nil
error is throwing an panic
exception.
It is appropriate to throw an exception here: If the template does not load properly, the simple way to do this is to exit the program.
ParseFiles
Receives any number of string arguments as the name of the template file, and resolves those files to a base file name
The template. If we need more templates, we can add the template file name directly to the ParseFiles
parameter.
The function is then modified renderTemplate
to invoke templates.ExecuteTemplate
the specified template for rendering:
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { err := templates.ExecuteTemplate(w, tmpl+".html", p) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) }}
It is important to note that the template name is the name of the template file, so the ". html" suffix name is added here.
Verify
You may as well find that this program has a serious security flaw: the user can read and write any independent path on the server.
To mitigate this risk, we write a function that validates the legitimacy of the title in the form of a regular expression.
First, you import the "regexp"
package. Then create a global variable to hold the regular expression for validation:
var titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")
The function regexp.MustCompile
parses and compiles the regular expression, returning regexp.Regexp
.
MustCompile
Compile
Unlike some, an MustCompile
exception is thrown when an error is encountered panic
.
An error Compile
is returned by the second return value when an error is encountered.
Now, let's write a function getTitle
that extracts the caption from the requested URL and tests if it is a valid expression:
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}
If the title is valid, an nil
error value is returned. If the title is not valid, the function outputs "404 Not Found" error.
Let's getTitle
apply to each handler:
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)}
Value and closure of letters
Each handler function introduces a lot of duplicated code in order to increase the error error. If it is possible to set the function of each handler
Error handling wrapper to a function? The Go language's closure function provides a powerful tool that can be used just right here.
In the first step, we rewrite each handler function to add a caption string parameter:
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)
We then top a wrapper function, the parameter type is consistent with the type of handler defined earlier, and finally returns
http.HandlerFunc
( http.HandleFunc
parameter type for adaptation):
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' }}
The function returned here is a closure because it refers to the value of a local variable defined outside it.
In this case, the variable fn
( makeHandler
the function's only argument) is held by the closure function.
fn
Variables will correspond to our save, edit and view handler functions.
Now we can getTitle
move the code here (with some changes in the details):
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) }}
makeHandler
The return is a holding http.ResponseWriter
and http.Request
parameter closure function
(In fact http.HandlerFunc
, it is the type). The closure function extracts the title of the page and TitleValidator
validates
Whether the title conforms to the regular expression. If it is an invalid caption, the http.NotFound
output error response will be used.
If it is a valid caption, then the fn
handler function will be called.
Now we can main
use the makeHandler
wrapper-specific handler function when the function is registered:
func main() { http.HandleFunc("/view/", makeHandler(viewHandler)) http.HandleFunc("/edit/", makeHandler(editHandler)) http.HandleFunc("/save/", makeHandler(saveHandler)) http.ListenAndServe(":8080", nil)}
Finally We remove the calls to GetTitle from the handler functions, making them much simpler:
Finally we delete the getTitle
call to the handler, and it's easier to process the code:
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)}
Look at the page effect!
Click here to view the final version of the code.
Recompile the code and run:
$ go build wiki.go$ ./wiki
Browsing http://localhost:8080/view/ANewPage
will see the edit page.
You can enter some text, click ' Save ' to save, and then redirect to the newly created page.
Other tasks
You can also choose some simple extension tasks according to your own interests:
- Save the template to the
tmpl/
directory, save the data to the data/
directory.
- Add a handler for the root directory to redirect to
/view/FrontPage
.
- Spruce up the page templates by making them valid HTML and adding some CSS rules.
- Refine the page templates, let them output valid HTML, and add some CSS rules.
[PageName]
<a href="/view/PageName">PageName</a>
The link between pages is implemented by converting the bits.
(Hint: You can use regexp.ReplaceAllFunc
it to implement this feature.)