Summary of 0.

With the basic syntax of Go in hand, let’s try to write a simple wiki app. It is a Web application project (web application).

This article covers the following technical points:

  1. Define a struct type and implement “read” and “save” methods by manipulating files
  2. Build Web applications using NET/HTTP packages
  3. Use the HTML /template package to process HTML templates
  4. Validate user input using regEXP package regular expressions
  5. closure

We are expected to proceed in steps:

  • The first stage: implement basic functions, such as text storage, web page viewing, editing and so on.
  • Stage 2: Improvements, handling non-existent pages, improved error handling, and template caching.
  • Stage 3: Refactoring, regular expression validation and closure for refactoring

Structure of this paper:

1. Stage 1: Implement basic Functions Present Functions 1.1 Before You Start 1.2 Defining Data Types and Implementing "Read" and "Save" methods 1.2.1 Saving articles 1.2.2 Reading Articles 1.3 Implementing Web Applications 1.3.1 Processing Requests: Viewing Articles 1.3.2 Processing Requests: Edit article 1.3.3 Save: Processing form 2. Stage 2: Improved, handling non-existent pages, improved error handling, and Template Cache 2.1 Handling non-existent pages 2.2 Exception Handling 2.2.1 Failed to Read templates and Performing Template Conversion 2.2.2 Failed to Convert Templates 2.2.3 Failed to Save articles 2.3 Optimized Template Cache 3. Stage 3: Reconstruction, regular expression validation and reconstruction using closures 3.1 Regular expression validation 3.2 Introduction of functions and closures 3.3 Reconstruction of template binding HTML redundancy 4. The complete codeCopy the code

1. Stage 1: Implement basic functions

1.1 Before You Start

Let’s say our app is called “GoWiki” and create a folder

$ mkdir gowiki
$ cd gowiki
Copy the code

Import the package to use

package main

import (
        "fmt"
    "io/ioutil"
)
Copy the code

1.2 Define data types and implement “read” and “save” methods

An article should have “title” and “content”, so we define a structure called Page, which has title and file content fields.

Type Page struct {Title string // Title Body []byte //Copy the code

This Page is stored in memory format, so how to implement persistent storage, I use Go operation file function to achieve.

  • When you save an article, you write it to a file.
  • To read an article is to read a file

1.2.1 Save the article

/* save page to file */ func (p * page) save() error{fileName := p.tip + ".txt" return ioutil.writefile (fileName, p.box, 0x600) }Copy the code

As above, use the title of the article as the file name. Ioutil. WriteFile is the method for writing files. 0x600 is a constant, indicating that read and write permissions are required.

1.2.2 Reading articles

The title of our article is read as filename according to this rule.

func loadPage(title string) (*Page,error){ fileName := title + ".txt" body,err := ioutil.ReadFile(fileName) if err ! = nil { return nil,err } return &Page{fileName, body},nil }Copy the code

Outil. ReadFile is the method to read the file. It returns two return values: the contents of the file and the error that may have occurred. If the read succeeds, err is empty. We check whether the file was read successfully by checking whether err is nil. When the reading is complete, we build a Page object as the return value.

1.3 Implementing Web Applications

We need three handles for corresponding, viewing, editing and saving respectively. Let’s start with the main function, which looks like this:

func main(){
  http.HandleFunc("/view/",viewHandler)
  http.HandleFunc("/edit/",editHandler)
  http.HandleFunc("/save/",saveHandler)
  http.ListenAndServe(":8080",nil)
}
Copy the code

So let’s implement viewHandler, editHandler, saveHandler one by one.

1.3.1 Handling requests: View the article

Write a viewHandler, receiving such web request ignored in front of the domain name http://localhost:8080/view/ttt and the corresponding oral confession is/view/TTT REST style URL, so the TTT here said the title of the article. The concrete implementation is as follows:

func viewHandler(w http.ResponseWriter, r *http.Request){ fmt.Println("path:",r.URL.Path) title := r.URL.Path[len("/view/"):] p,_ := loadPage(title) // T,_ := template.parsefiles ("view.html") // Note that template t.execute (w, p)} is used here.Copy the code

1. Get TTT from the URL as the title, and then call the loadPage method we did in the previous section to save it to a specific file. 2. Construct a template template that specifies a local HTML file path. Using the constructed template, Execute the Execute method, passing in the write stream (w), and parameters (page object).

The code for view.html, which is a concrete HTML implementation, works as a “binding” mechanism.

<h1>{{.Title}}</h1>

<p>[<a href="/edit/{{.Title}}">edit</a>]</p>

<div>{{printf "%s" .Body}}</div>
Copy the code

Notice the {{.title}} notation above, which is a special way of saying, read the Title property and put it here. Note the {{printf “%s”.Body}} expression that prints the value of the Body property as a string. It’s very similar to printf.

1.3.2 Handling requests: Edit articles

Similar to the above, write a viewHandler, receiving such http://localhost:8080/edit/ttt web page request from/edit/TTT is to handle the request.

func editHandler(w http.ResponseWriter, r *http.Request){ fmt.Println("path:",r.URL.Path) title := r.URL.Path[len("/edit/"):] p,err := loadPage(title) if err ! = nil { p = &Page{Title:title} } t,_ := template.ParseFiles("edit.html") t.Execute(w, p) }Copy the code

Edit.html is implemented as follows:

<h1>Editing {{.Title}}</h1>

<form action="/save/{{.Title}}" method="POST">
  <div><textarea name="body" rows="20" cols="80">{{printf "%s" .Body}}</textarea></div>
  <div><input type="submit" value="Save"></div>

</form>
Copy the code

1.3.3 Save: Process the form

Similar to the above, write a viewHandler, receiving such http://localhost:8080/edit/ttt web page request is to process request/edit/TTT.

func saveHandler(w http.ResponseWriter, R * http.request){fmt.println ("path:", r.ul.path) title := r.ul.path [len("/save/"):] body := r.fermValue ("body") // Note P := &page {title,[]byte(body)} // Redirect(w, r, "/view/"+title, http.StatusFound) }Copy the code

At this point, you have a simple Web application with the ability to view and edit articles, even if it looks rudimentary.

2. Stage 2: Improvement, handling non-existent pages, improving error handling, and template caching

2.1 Processing pages that do not exist

Let’s go back to the viewHandler method implementation:

T,_ := template.parsefiles ("view.html").Execute(w, p).Copy the code

When entering the page “/view/ non-existent page “in the url, a blank page will be displayed. Attempts to read the physical file will fail because the article does not exist. While the application doesn’t crash, the response is a bad user experience.

Let’s improve it by jumping directly to the edit page when the specified article does not exist. Use http.redirect () to Redirect. The code is as follows:

// p,_ := loadPage(title) // Old code, comment out p,err := loadPage(title) // If err! =nil {// If an exception occurs, HTTP.Redirect(w, r, "/edit/"+title, http.StatusFound) return } t,_ := template.ParseFiles("view.html") t.Execute(w, p)Copy the code

The first two arguments to the http.redirect () function are w http.responseWriter and r * http.request, which means write to the stream and Request. No need to say more. The third parameter is the destination page to jump to, and the fourth parameter is the HTTP response code.

Again, enter http://localhost:8080/view/sssss in the browser, if the SSSSS article does not exist, will jump to http://localhost:8080/edit/sssss. You can see the url change in the browser.

2.2 Exception Handling

Much of the code we wrote above uses “white space identifiers” (the “_” underscore symbol) to hide hiding, which is bad, because once an exception occurs, you don’t know what the problem is. We fix it. When such an exception occurs, we identify it and inform the user of the exception.

2.2.1 Exceptions when the Template fails to be read and when the template is converted

Exception when reading template failed

The viewHandler implementation above has the following code

T,_ := template.parsefiles ("edit.html") //Copy the code

A blank identifier hides an exception, let’s fix it:

T,err := template.parsefiles ("view.html") // If err! = nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } t.Execute(w, p)Copy the code

HTTP. Error () function is to write the first parameter to the inflow, the second parameter is the Error string of the specification, the third parameter is the HTTP status code, HTTP. StatusInternalServerError said 500, service internal anomalies.

So, when the template file does not exist, it will return a 500 exception response, with an error message.

2.2.2 Exceptions during Template Conversion

Let’s continue with the code above. The template execution method t.execute (w, p) also needs to be handled if an exception occurs and the web page cannot be returned correctly.

err = t.Execute(w, p) if err ! = nil { http.Error(w, err.Error(), http.StatusInternalServerError) }Copy the code

The return value of t.execute (w, p) is an error. If it is not nil, we call http.error () to tell the user that an exception has occurred.

2.2.3 Abnormal failure to save articles

In saveHandler, there is the following code that calls the save method without handling the judgment that the save method exception occurred.

P := &page {title,[]byte(body)} // Convert body string to byte p.ave ()Copy the code

Similarly, we receive the return value from the save() method and judge.

err := p.save() if err ! = nil { http.Error(w, err.Error(), http.StatusInternalServerError) return }Copy the code

After modification, when save() fails, the user is notified of the failure.

2.3 Optimizing template Caching

Recall from the above code that we parsed the method that constructed the template. We called this method in the viewHandler function:

 t,_ := template.ParseFiles("edit.html") 
Copy the code

Since the viewHandler function is called every time the view article page is opened, it will cause the template to be parsed and constructed every time, however, there is no need to waste each time the template is created. We can call it once in a global variable, for example:

var templates = template.Must(template.ParseFiles("edit.html", "view.html"))
Copy the code

The template.Must function is a handy wrapper that crashes when passed an error value. There should be panic; If you can’t load the template, quit the program.

Sample code:

Var templates = template.Must(template.parsefiles ("view.html","edit.html")) /* Build web app processing */ func viewHandler(w http.ResponseWriter, r *http.Request){ fmt.Println("path:",r.URL.Path) title := r.URL.Path[len("/view/"):] p,err := loadPage(title) if err ! =nil { http.Redirect(w, r, "/edit/"+title, http.StatusFound) return } // t,err := template.ParseFiles("view.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) // } err = templates.ExecuteTemplate(w, "view.html", p) if err ! = nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }Copy the code

Templates. ExecuteTemplate function the second argument to the presentation template, the template file name. Its return value is of type error.

Template cache modification:

  • Global variables replace local variables
  • Template. Must replaces the template.ParseFiles method.
  • Templates. Replace the template ExecuteTemplate. ParseFiles

Now, phase two is complete. We still have some things to do, like some user validation.

3. Stage 3: Refactoring, regular expression validation and closure for refactoring

As you may have noticed, this program has a flaw that users can reach arbitrary pages and article titles are arbitrary. It may produce undesirable results, so let’s use regular expressions to do some validation.

Import regular expression packages: Import regexp and errors packages

3.1 Regular expression Verification

Constructing regular expressions

var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")
Copy the code

MustCompile takes a string of regular expressions as an argument, and an illegal string triggers a panic.

Write a judgment method, example:

func getTitle(w http.ResponseWriter, r *http.Request) (string, error) { m := validPath.FindStringSubmatch(r.URL.Path) if m == nil { http.NotFound(w,r) return "", Errors. New("Invalid Page Title")} return m[2],nilCopy the code
  • The regular ^ / edit | save | view)/([a zA – Z0-9] +) $, first in brackets the edit | save | view said that any one of the three strings are matching. The second parentheses indicate that regular strings and numbers are accepted.
  • ValidPath FindStringSubmatch to determine whether legitimate, if is empty, is that don’t match. If a match is identified, the second argument is title
  • Calling http.notfound (w,r) returns: 404 page NotFound

Now we call getTitle in, with the following code:

Func viewHandler(w http.responseWriter,r * http.request){FMT.Println("path:", r.ul.path) Title,err := getTitle(w,r) if err! = nil{return} // With the re, Path[len("/view/"):] p,err := loadPage(title) if err! =nil { http.Redirect(w, r, "/edit/"+title, http.StatusFound) return } err = templates.ExecuteTemplate(w, "view.html", p) if err ! = nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }Copy the code

When this is done, if the user enters a url that does not start with /edit/, /save/, or /view/, or an invalid title string, they will receive a 404, page found.

3.2 Introducing functions and closures

In the above method, we wrote a getTitle(), which needs to be called in viewHandler, editHandler and saveHandler. It is very tedious to write a method every time and judge err, so we removed the common part to avoid code redundancy and repetition.

Functions in Go can be passed as arguments in the function, and we can use this feature to implement the call proxy of the function.

Let’s first modify the function signature of viewHandler and other three methods:

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)
Copy the code

Above we added the title argument, we want to get the title first, and then pass the value of the title into this function.

We write a makeHandler method that constructs an appropriate Handler as a return value of type http.handlerfunc.

func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc { return func(w http.ResponseWriter, R * HTTP Request) {/ / attention here # 1 m: = validPath. FindStringSubmatch (r.U RL. Path) if m = = nil {HTTP. NotFound (w, R) return {fn(w, r, m[2])}Copy the code

There is one caveat to the above code:

Return func(argument)Copy the code

This is a closure, where an anonymous function is built and passed as a return value.

Inside this closure, you can use arguments to the makeHandler function it’s in directly. In this case, we put the code for the getTitle method above here, first verifying that the URL is valid line, using the re to get the title value. Finally, fn is passed as an argument, and the fn function is called.

You will notice that the signature of the fn function is exactly the same as the viewHandler function signature we just modified. Yes, the function will be passed here as an argument.

3.3 Reconstructing template binding HTML redundancy

The viewHandler and editHandler above both template binding HTML code, there is also duplicate code, we will deal with it, and to make the parameter name more semantic, the original code:

err = templates.ExecuteTemplate(w, "edit.html", p) if err ! = nil { http.Error(w, err.Error(), http.StatusInternalServerError) }Copy the code

After the refactoring:

func renderTemplate(w http.ResponseWriter, templateName string, p *Page){ err := templates.ExecuteTemplate(w, templateName+".html", p) if err ! = nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }Copy the code

When you need to display HTML, call it in viewHandler like this:

  renderTemplate(w,"edit",p)
Copy the code

At this point, the refactoring is complete.

4. Complete code

Easy to read, the final completed code is as follows:

package main import( "fmt" "io/ioutil" "net/http" "html/template" "regexp" "errors" ) type Page struct { Title string Body []byte} /* Save page to file */ func (p * page) save() error{fileName := p.tip + ".txt" return ioutil.WriteFile(fileName, p.Body, 0x600) } func loadPage(title string) (*Page,error){ fileName := title + ".txt" body,err := ioutil.ReadFile(fileName) if err ! = nil { return nil,err } return &Page{title, Body}, nil} / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / var / / * regular validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$") func getTitle(w http.ResponseWriter, r *http.Request) (string, error) { m := validPath.FindStringSubmatch(r.URL.Path) if m == nil { http.NotFound(w,r) return "", Errors. The New (" Invalid Page Title ")} return m [2], nil / / Title in the second position} / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / var templates = Template. Must(template.parsefiles ("view.html","edit.html")) /* Request processing; */ 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.fermValue ("body") // Note that body is a string p := &Page{title,[]byte(body)} // Convert the body string to bytes err := p.save() if err ! = nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, "/view/"+title, http.StatusFound) } func renderTemplate(w http.ResponseWriter, templateName string, p *Page){ err := templates.ExecuteTemplate(w, templateName+".html", p) if err ! = nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request){ fmt.Println("path:",r.URL.Path) title,err := getTitle(w,r) if err ! = nil{ return } fn(w,r,title) } } func main(){ http.HandleFunc("/view/",makeHandler(viewHandler)) http.HandleFunc("/edit/",makeHandler(editHandler)) http.HandleFunc("/save/",makeHandler(saveHandler)) fmt.Println("server running!" ) http.ListenAndServe(":8080",nil) }Copy the code

END