1. Introduction
Technologies involved in this example:
- Create a data type that contains the load and save functions
- Create web programs based on HTTP packages
- Template-based HTML template Technology
- Use Regexp package to verify user input
- Use closures
Assume that you have the following knowledge:
- Basic programming experience
- Basic Web Application Technology (HTTP, HTML)
- Unix Command Line
2. Start
First, you must have a Linux, OS X, or FreeBSD system that can run the go program. If not, you can install a Virtual Machine (such as virtualbox) or virtual private server.
Install the go environment: (See installation instructions ).
Create a new directory and enter the directory:
$ mkdir ~/gowiki $ cd ~/gowiki
Create a wiki. Go file, open it in your favorite editor, and add the following code:
package main import ( "fmt" "io/ioutil" "os" )
We import FMT, ioutil, and OS packages from the go standard library. Later, when other functions are implemented, we will import more packages as needed.
3. Data Structure
First, we define a structure type to save data. The wiki system consists of a group of interconnected Wiki pages. Each wiki page contains the content and title. We define a wiki page as a structure page, as follows:
type page struct { titlestring body[]byte }
Type [] Byte indicates a byte slice. (Refer to objective go for more information about slices.) The reason why the member body is defined as [] Byte rather than string is that [] Byte can directly use the IO Package function.
The structure page describes the storage mode of a page in memory. However, to save data to a disk, you also need to add the Save Method to the page type:
func (p *page) save() os.Error { filename := p.title + ".txt" return ioutil.WriteFile(filename, p.body, 0600) }
The signature of the type method can be interpreted as follows: "save is a page-type method, and the caller of the method is the page-type pointer Variable P. This member function has no parameters. The returned value IS OS. error, indicating the error message ."
This method saves the body part of the page structure to a text file. For simplicity, we use title as the name of a text file.
The return value type of method save IS OS. error, which corresponds to the return value of writefile (standard library function, writing byte slice to the file. You can determine the type of the error by returning the OS. error value. If no error exists, nil (zero value of pointer, interface, and Other types) is returned ).
The third parameter of writefile is 0600 of the 8 th percentile, indicating that only the current user has the read and write permissions for the newly created file. (Refer to the Unix manual open (2 ))
The following function loads a page:
func loadPage(title string) *page { filename := title + ".txt" body, _ := ioutil.ReadFile(filename) return &page{title: title, body: body} }
The loadpage function reads the content of the page from the corresponding file based on the page title and constructs a new page variable-corresponding to a page.
Functions (and member methods) in go can return multiple values. Io. readfile in the standard library returns an [] byte error message of the OS. Error type. In the previous code, we used the underscore "_" to discard the error message.
However, readfile may have errors. For example, the requested file does not exist. Therefore, we add an error message to the return value of the function.
func loadPage(title string) (*page, os.Error) { filename := title + ".txt" body, err := ioutil.ReadFile(filename) if err != nil { return nil, err } return &page{title: title, body: body}, nil }
Now the caller can detect the second return value. If it is nil, the page is loaded successfully. Otherwise, the caller can obtain an OS. Error object. (For more information about the error, see OS package documentation)
Now we have a simple data structure that can be saved to or loaded from a file. Create a main function to test related functions.
func main() { p1 := &page{title: "TestPage", body: []byte("This is a sample page.")} p1.save() p2, _ := loadPage("TestPage") fmt.Println(string(p2.body)) }
Compile and run the program. A testpage.txt file is created to save the page content corresponding to P1. Then, read the page content from the file to P2 and print the P2 value to the screen.
You can use the following command to compile and run the program:
$ 8g wiki.go $ 8l wiki.8 $ ./8.out This is a sample page.
(The 8g and 8l commands correspond to goarch = 386. For the amd64 system, 6 GB and 6 l can be used)
Click here to view our current code.
4. Use an http package
The following is a complete Web Server example:
package main import ( "fmt" "http" ) func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:]) } func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) }
In the main function, HTTP. handlefunc sets all the handler functions for the root directory request to handler.
Then call HTTP. listenandserve to start listening on port 8080 (the second parameter can be ignored temporarily ). Then the program will be blocked until it exits.
The handler function is of the HTTP. handlerfunc type. It contains two types of parameters: http. Conn and HTTP. Request.
HTTP. Conn corresponds to the HTTP connection of the server. We can use it to send data to the client.
Parameters of the HTTP. Request type correspond to a client request. Here, R. url. path is the request address, which is a string type variable. We use [1:] to create an slice on the path, corresponding to the path name after.
After the program is started, access the following address through a browser:
http://localhost:8080/monkeys
The following output is displayed:
Hi there, I love monkeys!
5. Provides Wiki pages based on HTTP
To use an http package, import it first:
import ( "fmt" "http" "io/ioutil" "os" )
Then create a function for viewing the Wiki:
const lenPath = len("/view/") func viewHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[lenPath:] p, _ := loadPage(title) fmt.Fprintf(w, "
First, this function parses the page title from R. url. Path (the path part of the request URL. The global constant lenpath stores the length of "/View/", which is the prefix of the Request Path. The path always starts with "/View/" and removes the first six characters to get the page title.
Load the page data, format it as a simple HTML string, and write it to C. C is an HTTP. Conn parameter.
Note that the underscore "_" is used to ignore the returned value of the loadpage OS. Error. This is not a good practice. It is here to keep it simple. We will consider this issue later.
To use this handler, we create a main function. It uses viewhandler to initialize HTTP and forward all requests starting with/View/to viewhandler for processing.
func main() { http.HandleFunc("/view/", viewHandler) http.ListenAndServe(":8080", nil) }
Click here to view our current code.
Let's create some page data (for example, as test.txt), compile and run it.
$ echo "Hello world" > test.txt $ 8g wiki.go $ 8l wiki.8 $ ./8.out
When the server is running, accessing http: // localhost: 8080/View/test will display a page titled "test" with the content "Hello World ".
6. Edit page
The editing function is indispensable for wiki. Now, we create two new processing functions (handler): edithandler displays the "Edit page" form (Form), and savehandler saves the data in the form (Form.
First, add them to the main () function:
func main() { http.HandleFunc("/view/", viewHandler) http.HandleFunc("/edit/", editHandler) http.HandleFunc("/save/", saveHandler) http.ListenAndServe(":8080", nil) }
The edithandler function loads a page (or, if the page does not exist, creates an empty page structure) and displays it as an HTML form (Form ).
func editHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[lenPath:] p, err := loadPage(title) if err != nil { p = &page{title: title} } fmt.Fprintf(w, "
This function can work, but the hardcoded HTML is very ugly. Of course, we have a better solution.
7. template package
The template package is part of the Go language standard library. We use template to store HTML in a separate file. You can change the layout of the editing page without modifying the relevant go code.
First, we must add the template to the import list:
import ( "http" "io/ioutil" "os" "template" )
Create a template file that contains HTML forms. Open a new file named edit.html and add the following line:
Modify edithandler and replace hardcoded HTML with a template.
func editHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[lenPath:] p, err := loadPage(title) if err != nil { p = &page{title: title} } t, _ := template.ParseFile("edit.html", nil) t.Execute(p, w) }
When template.parsefilereads the content of edit.html, the * template. template type data is returned.
The method T. Execute replaces all {Title} and {body} in the template with the values of P. Title and P. Body, and writes the results to HTTP. Conn.
Note: In the preceding template, {body | HTML} is used }. | Before the HTML request template engine outputs the body value, it first uploads it to the HTML formatter to escape HTML characters (for example, replacing with> ). In this way, user data can be prevented from breaking the HTML form.
Since we have deleted the FMT. sprintf statement, we can delete "FMT" from the import list ".
With the template technology, we can create a template for viewhandlerand name it view.html.
Modify viewhandler:
func viewHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[lenPath:] p, _ := loadPage(title) t, _ := template.ParseFile("view.html", nil) t.Execute(p, w) }
Note: The two handler functions use almost identical template processing code. We can write the template processing code into a separate function to eliminate duplicates.
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.ParseFile(tmpl+".html", nil) t.Execute(p, w) }
Now, the handler code is shorter and simpler.
8. Process nonexistent pages
What happens when you access/View/apagethatdoesntexist? The program will crash. Because the error returned by loadpage is ignored. When the request page does not exist, you should redirect the client to the Edit page so that the new page will be created.
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) }
The HTTP. Redirect function adds the HTTP status code HTTP. statusfound (302) and header location to the HTTP response.
9. Storage page
The savehandler function processes form submission.
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 page title (in the URL) and the unique field, body in the form, are stored in a new page. Call the SAVE () method to write the data to the file and redirect the customer to the/View/page.
The type returned by formvalue is string. Before adding it to the page structure, we must convert it to the [] Byte type. We use [] Byte (body) for conversion.
10. handle errors
In our program, errors are ignored in several places. This is a bad way, especially when an error occurs, the program will crash. A better solution is to handle errors and return error messages to users. In this way, when an error occurs, the server can continue to run and the user will be notified.
First, we should handle the error in rendertemplate:
func renderTemplate(w http.ResponseWriter, tmpl string, p *page) { t, err := template.ParseFile(tmpl+".html", nil) if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) return } err = t.Execute(p, w) if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) } }
The HTTP. Error Function sends a specific HTTP response code ("internal server error" Here) and error message.
Now, let's fix the savehandler:
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.String(), http.StatusInternalServerError) return } http.Redirect(w, r, "/view/"+title, http.StatusFound) }
Any errors in P. Save () will be reported to the user.
11. template Cache
There is an inefficiency in the Code: every time a page is displayed, rendertemplate calls parsefile. A better way is to call parsefile for each template once during program initialization, save the result as * template type value, and use it later.
First, we create a Global Map named templates. Templates is used to store * template-type values, and string indexes are used.
Then, we create an init function, which will be called during program initialization before the main function. The template. mustparsefile function is an encapsulation of parsefile. It does not return error codes, but throws a panic error when an error occurs. Panic throws are suitable here. If the template cannot be loaded, the only meaningful thing the program can do is to exit.
func init() { for _, tmpl := range []string{"edit", "view"} { templates[tmpl] = template.MustParseFile(tmpl+".html", nil) } }
Use a for loop with a range statement to access every element in a constant array. This constant array contains the names of all templates we want to load. If you want to add more templates, you only need to add the Template Name to the array.
Modify the rendertemplate function and call the execute method on the corresponding template in templates:
func renderTemplate(w http.ResponseWriter, tmpl string, p *page) { err := templates[tmpl].Execute(p, w) if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) } }
12. Verify
You may have discovered a serious security vulnerability in the program: You can provide any path to perform read/write operations on the server. To eliminate this problem, we use regular expressions to verify the page title.
First, add "Regexp" to the import list. Then create a global variable to store our validation regular expression:
The Regexp. mustcompile function parses and compiles regular expressions, and returns a Regexp. Regexp object. Similar to template. mustparsefile, when an expression compilation error occurs, mustcompile throws an error, and compile returns an OS. Error in its second return parameter.
Now, we write a function that parses the page title from the request URL and uses titlevalidator for verification:
func getTitle(w http.ResponseWriter, r *http.Request) (title string, err os.Error) { title = r.URL.Path[lenPath:] if !titleValidator.MatchString(title) { http.NotFound(w, r) err = os.NewError("Invalid Page Title") } return }
If the title is valid, it returns an Nil error value. If it is invalid, it writes "404 Not Found" error to the HTTP connection and returns an error object.
Modify all processing functions and use gettitle to obtain the page title:
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.String(), http.StatusInternalServerError) return } http.Redirect(w, r, "/view/"+title, http.StatusFound) }
13. Function text and Closure
Capturing errors in handler is similar repetitive code. What should we do if we want to encapsulate the code that captures errors into a function? Go function text provides powerful abstraction capabilities to help us achieve this.
First, we need to rewrite the definition of each processing function so that they can accept the title string:
Define an encapsulation function, accept the function type defined above, and return HTTP. handlerfunc (which can be transferred to the function HTTP. handlefunc ).
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 returned function is called a closure because it contains values defined outside it. Here, the variable FN (the unique parameter of makehandler) is included by the closure. FN is our processing function, save, edit, or view.
We can copy the gettitle code here (with some minor changes ):
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) } }
The closure returned by makehandler is a function that has two parameters: http. Conn and HTTP. Request (therefore, it is HTTP. handlerfunc ). The closure resolves the title from the Request Path and uses titlevalidator to verify the title. If the title is invalid, use the HTTP. notfound function to write the error to Conn. If the title is valid, the encapsulated processing function FN will be called. The parameters are Conn, request, and title.
In the main function, we use makehandler to encapsulate all the processing functions:
func main() { http.HandleFunc("/view/", makeHandler(viewHandler)) http.HandleFunc("/edit/", makeHandler(editHandler)) http.HandleFunc("/save/", makeHandler(saveHandler)) http.ListenAndServe(":8080", nil) }
Finally, we can delete the gettitle in the processing function to make the processing function simpler.
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.String(), http.StatusInternalServerError) return } http.Redirect(w, r, "/view/"+title, http.StatusFound) }
14. Try it!
Click here to view the final code
Re-compile the code and run the program:
$ 8g wiki.go $ 8l wiki.8 $ ./8.out
An edit form is displayed when you access http: // localhost: 8080/View/anewpage. You can enter some text versions and click "save" to redirect to the new page.
15. Other tasks
Here are some simple tasks that you can solve by yourself:
- Store the template file in the tmpl/directory and the page data in the data/directory.
- Add a handler to redirect requests to the root directory to/View/Frontpage.
- Modify the page template to make it a valid HTML file. Add CSS rules.
- Implementation Page Link. Change [pagename] to <a href = "/View/pagename"> pagename </a>. (Note: You can use Regexp. replaceallfunc to achieve this effect)