Go Language Writing Web program

Source: Internet
Author: User
Tags install go readfile response code
This is a creation in Article, where the information may have evolved or changed.

1. Introduction

This example involves the technique of:

    • Create a data type that contains the load and save functions
    • Create a Web program based on an HTTP package
    • HTML template technology based on templates package
    • Validating user input with the RegExp package
    • Using closures

Suppose the reader has the following knowledge:

    • BASIC Programming experience
    • Basic Techniques for Web programs (HTTP, HTML)
    • UNIX command Line

2. Start

First of all, to have a Linux, OS X, or FreeBSD system, you can run go program. If not, you can install a virtual machine (for example, VirtualBox), or Vsan Server.

Install GO environment: (Please refer to installation instructions).

Create a new directory and enter the directory:

  $ mkdir ~/gowiki  $ cd ~/gowiki

Create a Wiki.go file, open it with 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 features are implemented, we will import more packages as needed.

3. Data structure

Let's first define a struct type that holds the data. The wiki system consists of a set of interconnected wiki pages, each of which contains content and titles. We define the wiki page as the structure page, as follows:

  Type page struct {  titlestring  body[]byte  }

The type []byte represents a byte slice. (Refer to effective go For more information about slices) member body is defined as []byte instead of string because []byte can use the functionality of the IO package directly.

The structure page describes how a page is stored in memory. However, if you want to save the data to 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 a type method can be interpreted like this: "Save is a method of page type, the caller of the method is a pointer variable p of page type." The member function has no parameters and the return value is OS. Error, which represents the fault message. ”

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

The return value type of the Save method is OS. Error, corresponding to the return value of the WriteFile (standard library function, which writes a byte slice to a file). By returning to the OS. An error value that can be used to determine the type of occurrence. If there are no errors, then return nil (pointers, interfaces, and some other types of 0 values).

The third parameter of the WriteFile is octal 0600, which indicates that only the current user has read and write access to the newly created file. (Refer to 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 function LoadPage reads the contents of the page from the corresponding file according to the page title, and constructs a new page variable-corresponding to one.

Functions in Go (and Member methods) can return multiple values. IO in the standard library. ReadFile returns to the OS while returning []byte]. Error message of the type error. We dropped the error message with an underscore "_" in the preceding code.

However, ReadFile may have errors, such as 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
  }

The caller can now detect the second return value, or nil to indicate a successful loading of the page. Otherwise, the caller can get an OS. The Error object. (For more information on errors, refer to OS package documentation)

Now we have a simple data structure that can be saved to a file or loaded from a file. We create a main function to test the 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))  }

After compiling the program, a TestPage.txt file is created to hold the P1 corresponding page content. Then, the page content is read from the file to P2, and the value of P2 is printed to the screen.

You can compile a run program with a command similar to the following:

  $8g wiki.go  $8l wiki.8  $./8.out This is  a sample page.

(Commands 8g and 8l correspond to goarch=386.) If the AMD64 system, 6g and 6l can be used)

Click here to view our current code.

4. Using the HTTP Package

The following is a complete example of a Web server:

  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 request to handler.

Then call HTTP. Listenandserve, start listening on port 8080 (the second parameter can be ignored temporarily). The program then blocks until it exits.

The function handler is HTTP. The Handlerfunc type, which contains HTTP. Conn and http.request two types of parameters.

where HTTP. Conn The HTTP connection of the corresponding server, which we can send data to the client.

Type is HTTP. The parameter for request corresponds to a client request. where R. Url. Path is the requested address, which is a variable of type string. We create a slice on path with [1:], corresponding to the path name after "/".

After starting the program, access the following address via the browser:

  Http://localhost:8080/monkeys

You see the following output:

  Hi there, I love monkeys!

5. Provide wiki pages based on HTTP

To use an HTTP package, import it first:

  Import (  "FMT"  "http" "Io/ioutil" "  os"  )

Then create a function to browse 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, "

%s

%s ", P.title, P.body) }

First, this function is from R. Url. Path (the path portion of the request URL) resolves the page title. The global constant Lenpath the length of "/view/", which is the prefix portion of the request path. Path always starts with "/view/", removing the previous 6 characters to get the page title.

The page data is then loaded, formatted as a simple HTML string, written to C, and C is an HTTP. The parameters of the conn type.

Note that the underscore "_" is used here to ignore the Os.error return value of the loadpage. This is not a good practice, here is to keep it simple. We will consider this question later.

In order to use this handler function (handler), we create a main function. It initializes HTTP with Viewhandler and forwards all requests beginning with/view/to Viewhandler 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, run.

  $ echo "Hello World" > Test.txt  $8g wiki.go  $8l wiki.8  $./8.out

When the server is running, Access Http://localhost:8080/view/test will display a page titled "Test" with the content "Hello world".

6. Edit Page

Editing functionality is indispensable for wikis. Now, we create two new handler functions (handler): Edithandler Displays the "Edit Page" form (form), Savehandler saves the data in the 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 the page (or, if the page does not exist, creates an empty page structure) and appears 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, "

Editing%s

"+ " ", P.title, P.title, p.body) }

This function works, but hard-coded HTML can be very ugly. Of course, we have a better way.

7. Template Package

The template package is a part of the Go Language standard library. We use the template to store HTML in a separate file, and you can change the layout of the editing page without modifying the associated go code.

First, we must add the template to the import list:

  Import (  "http"  "Io/ioutil" "  os" "  template"  )

Create a template file that contains an HTML form. Open a new file named Edit.html and add the following line:

  

Editing {Title}

Modify Edithandler to replace hard-coded 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, _: = Templat E.parsefile ("edit.html", nil)  T.execute (P, W)  }

function template. Parsefile reads the contents of the edit.html and returns *template. The data for the template type.

Method T. Execute replaces all {title} and {body} in the template with the values of P.title and p.body, and writes the result to HTTP. Conn.

Note that in the above template we use the {body|html}. |html section to request that the template engine, before outputting the value of the body, to the HTML formatter (formatter), to escape HTML characters (such as > Replace >). By doing this, you can prevent user data from destroying the form HTML.

Now that we have deleted the FMT. sprintf statement, we can delete the "FMT" in the Import list.

Using template technology, we can create a template for Viewhandler, named View.html.

  

{title}

[Edit]

{Body}

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 that the almost identical template processing code is used in two processing functions (handler), and we can write the template processing code as a separate function to eliminate duplication.

  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)  }

The processing function (handler) code is now much shorter and simpler.

8. Handling non-existent pages

What happens when you visit/view/apagethatdoesntexist? The program will crash. Because we ignored the error returned by LoadPage. 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)  }

function http. Redirect adds the HTTP status code http.statusfound (302) and the header location to the HTTP response.

9. Storage page

The function Savehandler handles the 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 in the form, body, are stored in a new page. The Save () method is then called to write the data to the file, and the client is redirected to the/view/page.

The type of the Formvalue return value is string, and we must convert it to the []byte type] before adding it to the page structure. We use []byte (body) to perform the conversion.

10. Error Handling

In our program, there are several places where errors are ignored. This is a bad way to do this, especially after the error occurs and the program crashes. A better solution is to handle the error and return the error message to the user. By doing so, the server can continue to run when the error occurs and the user will be notified.

First, we deal with errors 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)  }  }

function http. Error sends a specific HTTP response code (in this case, "Internal Server Error") and an error message.

Now, let's fix 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 that occur in P.save () are reported to the user.

11. Template Caching

There is a low-efficiency place in the code: each time a page is displayed, Rendertemplate calls Parsefile. A better practice is to call parsefile once for each template when the program is initialized, save the result as a value of type *template, and use it later.

First, we create a global map named templates. The templates is used to store values of type *template, using a string index.

Then we create an init function, which is called when the program initializes, before the main function. function template. Mustparsefile is an encapsulation of parsefile, which does not return an error code, but throws (panic) an error when the error occurs. Throwing an error (panic) is appropriate here, and if the template does not load, 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 each element in a constant array that contains the names of all the templates we want to load. If we want to add more templates, just add the template name to the array.

Modify the Rendertemplate function and the corresponding template in the templates to raise the Execute method:

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

You may have found that there is a serious security vulnerability in the program: the user can provide any path to perform read and write operations on the server. To eliminate this problem, we use regular expressions to verify the title of the page.

First, add "RegExp" to the import list. Then create a global variable to store our validation regular expression:

function RegExp. Mustcompile parses and compiles the regular expression, returning a regexp. The RegExp object. and template. Mustparsefile similar, when an expression compiles an error, Mustcompile throws an error, and compile returns a os.error in its second return parameter.

Now, we write a function that parses the page header from the request URL resolution and validates it with Titlevalidator:

  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 caption is valid, it returns a nil error value. If it is not valid, it writes "404 Not Found" error to the HTTP connection, and returns an Error object.

Modify all the processing functions and use GetTitle to get 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)  I F Err! = Nil {  p = &page{title:title}  }  rendertemplate (w, "edit", p)  }    func Savehandler (W H Ttp. 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 closures

The catch error in the handler function (handler) is similar to repeating code. What should we do if we want to encapsulate the code that catches the error as a function? The function text of Go provides a powerful abstraction that can help us do this.

First, we rewrite the definition of each handler function so that they accept the caption string:

Define a wrapper function that accepts the function type defined above and returns HTTP. Handlerfunc (can be passed to function Http.handlefunc).

  Func Makehandler (fn func (http. Responsewriter, *http. Request, String)) http. Handlerfunc {  return func (w http. Responsewriter, R *http. Request) {  //Here we'll extract the page title from the Request,  //And call the provided handler ' FN '  }
  }

The returned function is called a closure because it contains values that are defined outside of it. Here, the variable fn (the only parameter of Makehandler) is included in the closure. FN is our handler function, save, edit, or view.

We can copy the GetTitle code here (there are 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, using Titlevalidator to verify the title. If the title is invalid, use the function http. NotFound writes the error to Conn. If the title is valid, the encapsulated handler FN will be called, with the parameters conn, Request, and title.

In the main function, we encapsulate all the processing functions with Makehandler:

  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, making 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}  }  r Endertemplate (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

Recompile the code and run the program:

  $8g wiki.go  $8l wiki.8  $./8.out

An edit form will appear when you visit http://localhost:8080/view/ANewPage. You can enter a few pages and click "Save" to redirect to the new page.

15. Other Tasks

Here are some simple tasks that you can solve yourself:

    • The template file is stored in the tmpl/directory, and the page data is stored in the data/directory.
    • Add a handler function (handler) to redirect requests to the root directory to/view/frontpage.
    • decorates the page template so that it becomes a valid HTML file. Add a CSS rule.
    • Implement in-page links. Modify [PageName] to PageName. (Tip: You can use RegExp.) Replaceallfunc to achieve this effect)
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.