Developing web programs in the Go language [translate]

Source: Internet
Author: User
Tags build wiki readfile
This is a creation in Article, where the information may have evolved or changed.
    • English: http://golang.org/doc/articles/wiki/

Brief introduction

This tutorial will discuss:

    • Create a data structure that supports loading and saving
    • Using the Net/http package to build a Web application
    • Using the Html/template package to process HTML templates
    • Use the RegExp package to validate user input
    • Using closures

Basic knowledge:

    • Have some experience in programming
    • Understanding basic Web Technologies (HTTP, HTML)
    • Some unix/dos command-line knowledge

Begin

Currently, you need a machine that runs FreeBSD, Linux, OS X, or Windows. We will use $ to represent the command prompt.

Install the Go locale (refer to the installation instructions).

To create a new directory for this tutorial, add the new directory to the GOPATH environment variable, and then switch to the new directory on the command line:

$ mkdir gowiki$ cd gowiki

Create a source file named Wiki.go, open it with your favorite editor, and add the following code:

package mainimport (    "fmt"    "io/ioutil")

We imported the FMT and Ioutil packages from the standard library. We will implement more features later, and then we will add more packages to the import declaration.

Data

We now define the data structure. A wiki usually has columns of interrelated pages, each with a title and a body (the contents of the page). Here, we set the page structure to contain the title and two members of the body.

type Page struct {    Title string    Body  []byte}

[]bytethe type represents "a byte slice." (See go slices: usage and nature) we define the body member as []byte not a string type, because we want the type and the library to fit nicely, as we io 'll see later.

Page descriptions are only persisted in memory. But how do you do persistent storage? We can create a save method for the page type:

func (p *Page) save() error {    filename := p.Title + ".txt"    return ioutil.WriteFile(filename, p.Body, 0600)}

The signature of the method reads: "This is a method, called Save, and the receiver P of the method is a pointer to the page type struct. The method has no parameters, but has a return value of type error. ”

This method saves the value of the body member of the page to a text file. For simplicity, we use the value of the title member as the name of the file.

The error value returned by the Save method is consistent with the return type of the WriteFile function (the standard library function that writes a byte slice to the file). The error value returned by the Save method can be used by the program to determine if a file was encountered while writing. If the write file is all right, page.save () returns nil (0 values of the corresponding pointer, interface, and so on).

The third parameter 0600 passed to the WriteFile function is an octal integer value that indicates that the newly created file is read-only to the current user. (For more information, refer to the UNIX manual open (2))

In addition to saving the page, we also need to load the page:

func loadPage(title string) *Page {    filename := title + ".txt"    body, _ := ioutil.ReadFile(filename)    return &Page{Title: title, Body: body}}

The function loadpage constructs the file name from the title parameter, reads the contents of the files into the new variable body, and returns two values: a pointer to the page value constructed by the title and body and an error return value of nil.

A function can return multiple values. Standard library function IO. ReadFile returns []byte and error. In the LoadPage function, the error message is lost; the underscore (_) symbol represented by the "blank identifier" is used to discard the error return value (essentially, no value is assigned).

But what if ReadFile encounters an error? For this example, the file may not yet exist. We can't ignore a similar error. We modify the function to return *page and error.

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}

The caller of this function can detect the second return parameter, or nil indicates a successful loading of the page. Otherwise, the error can be intercepted by the caller (for more information, refer to the language specification).

Now we have a simple data structure and can be saved to a file and loaded from a file page. Let's write a main to test:

func main() {    p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")}    p1.save()    p2, _ := loadPage("TestPage")    fmt.Println(string(p2.Body))}

After compiling and running the program, a file named TestPage.txt is created with the contents of the page body that P1 contains. The contents of the file are then read to P2, and their body members are printed to the screen.

You can compile and run the program like this:

$ go build wiki.go$ ./wikiThis is a sample page.

(If you are using a Windows system, you do not need the "./" in front of "wiki".) )

Click here to view the full code.

Learn about the Net/http package (episode)

Here is the complete code for a brief Web server:

package mainimport (    "fmt"    "net/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)}

The main function starts the call http.HandleFunc , telling the http packet handler function to handle the access request ("/") to the directory.

http.ListenAndServeIt is then called, specifying a listening port of 8080 (": 8080"). (the second parameter, nil, is ignored at this time.) This function blocks until the program terminates.

The type of the function handler is HTTP. Handlerfunc. Its parameters are one http.ResponseWriter and one http.Request .

The parameter http.ResponseWriter summarizes the response of the HTTP server, and the data written to it is sent to the HTTP client.

Parameter http. Request is the data structure that the client requests for data. r.URL.Pathrepresents the URL address of a client request. The [1:] implication is "create a sub-slice from the first character to the end of path." "This ignores the start of the"/"character in the URL path.

If you run the program and access some URL addresses:

http://localhost:8080/monkeys

The program returns a page with the following content:

Hi there, I love monkeys!

Wiki page based on net/http package

The Net/http package needs to be imported before use:

import (    "fmt"    "net/http"    "io/ioutil")

Then we create a viewhandler function that handles the browsing wiki page. It handles all URL addresses prefixed with "/view/".

const lenPath = len("/view/")func viewHandler(w http.ResponseWriter, r *http.Request) {    title := r.URL.Path[lenPath:]    p, _ := loadPage(title)    fmt.Fprintf(w, "

First, the function is from R. Url. In path, remove the title of the page you want to browse. The global constant Lenpath is the length of the URL prefix "/view/". The slice of path is [lenPath:] used to ignore the preceding 6 characters. This is because the URL addresses are prefixed with "/view/", and they are not part of the page title.

The page data is then loaded and formatted into a simple HTML page, written to http.ResponseWriter the type w parameter.

Once again, this _ is used to ignore errors returned by LoadPage error . Just to simplify the code, it's not a good programming practice. We will continue to refine this section later.

To use this function, we need to modify the HTTP initialization code in the main function and use the viewHandler function to process the request for the corresponding/view/address.

func main() {    http.HandleFunc("/view/", viewHandler)    http.ListenAndServe(":8080", nil)}

Click here to view the full code.

We create some test pages (for example, test.txt) and then try to provide a wiki page:

Use the editor to open the Test.txt file, enter "Hello World" content and save (ignore double quotes).

$ go build wiki.go$ ./wiki

If you are using a Windows system, you do not need the "./" Before "wiki".

After starting the Web server, browsing http://localhost:8080/view/test will display a page titled "Test" with the content "Hello world".

Edit Page

A wiki without editing skills is not a real wiki. We continue to create two functions: one edithandler to display the editing page, and the other Savehandler to save the edited page content.

Let's add them to the main() function first:

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

The Edithandler function loads the page and then displays an HTML editing page.

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 works only, but the HTML-related code is ugly. There is, of course, a better way to achieve this.

Using the Html/template Package

Html/template is a package in the standard library. We use the Html/template package to separate the HTML code into a single file, and then we can tweak and refine the edit page without changing the underlying code.

First, we import the Html/template package. Now that we are no longer using the FMT package, we need to remove it.

import (    "html/template"    "http"    "io/ioutil"    "os")

We need to create a template file for the edit page. Create a new edit.html file and enter the following:

Modify the Edithandler function to use a template instead of hard-coded HTML:

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.ParseFiles("edit.html")    t.Execute(w, p)}

The function template.ParseFiles reads the edit.html destination file, and the return value is *template.Template .

The function t.Execute handles the template and writes the generated HTML to the http.ResponseWriter . Where the beginning of the dot and the identifier are to be .Title .Body p.Title p.Body replaced.

The driver statement for the template is the part that is included in the double curly brackets. printf "%s" .Bodyrepresents 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.

It is important to note that we removed fmt.Fprintf the statement, so we also removed "fmt" the import statement for the package.

Now that we have a template-based approach, you can viewHandler create a template file named view.html for the function:

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.RedirectThe function adds http.StatusFound (302) state and repositions.

Save page

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

FormValueThe 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.ErrorThe 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.MustJust 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.

ParseFilesReceives 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 .
MustCompileCompileUnlike 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.
fnVariables 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)    }}

makeHandlerThe 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.)
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.