📜 ⬆️ ⬇️

Go Web Development

The article is based on codelab from Go, but is not limited to it. In the course of reading the article, it will be possible to learn about Go data structures, dynamic arrays, using http, template, regexp libraries to create a web application, templating and filtering input, respectively.
image
To understand the article, you need to be able to program a little bit, not be afraid of the words unix and the web. The basics of the language will be described in the article.

Go installation


So, the first thing that is needed to work with Go is Linux, FreeBSD (OS X), although MinGW for Windows also comes down.
Go needs to be installed, for this you need to do something like the following (instructions are for systems with dpkg):
 $ sudo apt-get install bison ed gawk gcc libc6-dev make # build tools
 $ sudo apt-get install python-setuptools python-dev build-essential # tools for building and installing
 $ sudo easy_install mercurial # if suddenly it is not there yet, it is better not to install from the repository (via apt)
 $ hg clone -r release https://go.googlecode.com/hg/ go
 $ cd go / src
 $ ./all.bash

If all is well, you can add the following to ~ / .bashrc or ~ / .profile :
 export GOROOT = $ HOME / go
 export GOARCH = 386 # or amd64, depending on the OS architecture
 export GOOS = linux
 export GOBIN = $ GOROOT / bin
 PATH = $ PATH: $ GOBIN

When you re-enter the shell and call the compiler ( 8g for i386 or 6g for amd64, then it will be 8g ) we will receive a help message:
 gc: usage 8g [flags] file.go ...

This means that Go is installed and running, you can go directly to the application.

Start. Data structures


Create an application directory:
  $ mkdir ~ / gowiki
 $ cd ~ / gowiki

Create a text editor ( bindings for vim and emacs ) file wiki.go with the following contents:
  package main
 import (
	 fmt
	 "io / ioutil"
	 "os"
 )

By name it is clear that our application will allow us to edit and save pages.
This code imports the fmt, ioutil, and os libraries from the standard Go library. Later we will add some other libraries.

Define several data structures. A wiki is a collection of related pages that have a body and a title. The corresponding data structure will have two fields:
  type page struct {
	 title string
	 body [] byte
 } 

The data type [] byte is a slice of the type byte, an analogue of a dynamic array (more: Effective Go ) The body of the article is saved in [] byte , and not in string for convenience of working with standard libraries.
')
The data structure describes how data is stored in memory. But what if you need to save data for a long time? Implement the save method to save to disk:
  func (p * page) save () os.Error {
	 filename: = p.title + ".txt"
	 return ioutil.WriteFile (filename, p.body, 0600)
 } 

The signature of this function reads: “This is a save method, applicable to a pointer to page, without parameters, and returning a value of type os.Error .”

This method will save the text to a file. For simplicity, we assume that the header is the file name. If this seems to be not safe enough, you can import crypto / md5 and use the md5.New (filename) call.

The return value will be of the os.Error type, corresponding to the return value of the WriteFile call (standard library function for writing a slice to a file). This is done so that in the future you can handle the save to file error. If there are no problems, page.save () returns us nil (zero for pointers, interfaces, and some other types).

The octal constant 0600, the third parameter of the WriteFile call, indicates that the file is saved with read and write rights only for the current user.

It would also be interesting to download the page:
  func loadPage (title string) * page {
	 filename: = title + ".txt"
	 body, _: = ioutil.ReadFile (filename)
	 return & page {title: title, body: body}
 } 

This function gets the file name from the header, reads the contents into a variable of type page, and returns a pointer to it.

Functions in Go can return multiple values. The standard library function io.ReadFile returns [] byte and os.Error . In the loadPage function , errors are not yet processed: the underscore symbol means “do not save this value”.

What happens if ReadFile returns an error? For example, there is no page with this title. This is a significant error, it can not be ignored. Let our function also return two values: * page and os.Error .
  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 you can check the value of the second parameter: if it is nil , then the page has successfully loaded. Otherwise, it will be a value of type os.Error .

So, we have a data structure and methods of loading and unloading. It's time to check how it works:
  func main () {
	 p1: = & page {title: "TestPage", body: [] byte ("Test page.")}
	 p1.save ()
	 p2, _: = loadPage ("TestPage")
	 fmt.Println (string (p2.body))
 } 

After compiling and executing this code, the TestPage.txt file will contain the value p1-> body . After that, this value is loaded into the variable p2 and displayed on the screen.

To build and run the program, do the following:
  $ 8g wiki.go
 $ 8l wiki.8
 $ ./8.out
 This is a sample page.


Http library


The easiest web server on Go is:
  package main
 import (
	 fmt
	 "http"
 )
 func handler (w http.ResponseWriter, r * http.Request) {
	 fmt.Fprintf (w, "Hi% s!", r.URL.Path [1:])
 }
 func main () {
	 http.HandleFunc ("/", handler)
	 http.ListenAndServe (": 8080", nil)
 } 

The main function calls http.HandleFunc , which tells the http library that all sorts of requests ( "/" ) are handled by the handler function.

The next call to http.ListenAndServe , we define that we want to handle requests on all interfaces on port 8080 ( ": 8080" ). The second parameter is not required yet. The program will work in this mode until forced termination.

The handler function is of type http.HandlerFunc . It takes http.ResponseWriter and a pointer to http.Request as parameters.

A value of type http.ResponseWriter generates an http response; writing data there (by calling Fprintf ) we return the contents of the page to the user.

The http.Request data structure is a user request. The string r.URL.Path is the path. The suffix [1:] means “get the Path slice (substring) from the first character to the end,” that is, delete the leading slash.

Starting the browser and opening the URL http: // localhost: 8080 / habrahabr , we will see on the page desired:
  Hi, habrahabr! 


Using http to render pages


Import the http library:
  import (
	 fmt
	 "http"
	 "io / ioutil"
	 "os"
 )

Create a handler to display the article:
 const lenPath = len ("/ view /")

 func viewHandler (w http.ResponseWriter, r * http.Request) {
	 title: = r.URL.Path [lenPath:]
	 p, _: = loadPage (title)
	 fmt.Fprintf (w, "<h1>% s </ h1> <div>% s </ div>", p.title, p.body)
 } 

First, this function retrieves the header from r.URL.Path , the path components of the specified URL. The global constant lenPath is the length of the "/ view /" prefix in the path denoting the viewing of the text of the article in our system. Substring [lenPath:] is allocated, i.e., the title of the article, the prefix is ​​excluded.

The function loads data, complementing it with simple html-tags and writes in w , a parameter of the type http.ResponseWriter .

_ Is again used to ignore a return value of type os.Error. This is done for simplicity and in general it is not good to do so. Below will be described how to handle such errors correctly.

To call this handler, we write a function main , initializing http with the appropriate viewHandler for processing requests along the path / view / .

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

Let's create a test page (in the test.txt file), compile the code and try to display the page:
  $ echo "Hello world"> test.txt
 $ 8g wiki.go
 $ 8l wiki.8
 $ ./8.out 

While our server is running, a page with the title “test” containing the words “Hello world” will be available at http: // localhost: 8080 / view / test .

Change pages


What are these wikis without editing pages? Create two new handlers: editHandler to display the edit form and saveHandler to save the received data.

First, add them to main () :
  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 creates an empty structure if there is no such page), and displays the 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, "<h1> Editing% s </ h1>" +
		 "<form action = \" / save /% s \ "method = \" POST \ ">" +
		 "<text area name = \" body \ ">% s </ text area>" +
		 "<input type = \" submit \ "value = \" Save \ ">" +
		 "</ form>",
		 p.title, p.title, p.body)
 } 

The function works well and correctly, but it looks ugly. The reason is in the hardcode html, but it is fixable.

Library template


The template library is included in the standard Go library. We can use templates to store html markup outside the code so that we can change the markup without recompiling.

First, import the template :

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

Create a form template in the edit.html file, with the following content:

  <h1> Editing {title} </ h1>

 <form action = "/ save / {title}" method = "POST">
 <div> <text area name = "body" rows = "20" cols = "80"> {body | html} </ text area> </ div>
 <div> <input type = "submit" value = "Save"> </ div>
 </ form> 

Edit the editHandler to use the 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)
 } 

The template.ParseFile method will read the edit.html file and display the value of the * template.Template type.

The t.Execute method replaces all {title} and {body} entries with the values ​​of p.title and p.body , and outputs the resulting html to a variable of type http.ResponseWriter.

Notice that the {body | html} construct was found in the template. It means that the parameter will be formatted for output in html, i.e. will be escaped and, for example, > replaced & gt; . This will correctly display the data in the form.

Now there is no fmt.Sprintf call in the program, you can remove fmt from import.

Create a template to display the page, view.html :
 <h1> {title} </ h1> <p> [<a href="/edit/{title}"> edit </a>] </ p> <div> {body} </ div> 

Change the viewHandler accordingly:
  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 code for calling templates is almost the same in both cases. We will get rid of duplication by bringing this code into a separate 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.ParseFile (tmpl + ". html", nil)
	 t.Execute (p, w)
 } 

Now the handlers are shorter and simpler.

Handling Missing Pages


What happens when I go to / view / APageThatDoesntExist ? The program will fall. And all because we have not processed the second value returned by loadPage . If the page does not exist, we will redirect the user to the page for creating a new article:
  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 http.StatusFound (302) and the Location header to the HTTP response.

Saving pages


The saveHandler function processes data from the 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)
 } 

A new page is created with the selected voice and body. The save () method saves the data to a file, the client is redirected to the / view / page.

The value returned by FormValue is of type string . To save to the page structure, we convert it to [] byte by writing [] byte (body) .

Error processing


We ignore errors in our program in several places. This causes the program to crash when an error occurs, so it’s best to return an error message to the user, and the server will continue to work.

First, add error handling to the 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 the selected HTTP status (in this case, “Internal Server Error”) and returns an error message.

Make a similar edit to 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 occurring in p.save () will be passed to the user.

Pattern caching


Our code is not efficient enough: renderTemplate calls ParseFile on every rendering page. It is much better to call ParseFile once for each template when starting the program, saving the resulting values ​​of type * Template into the structure for further use.

First, create a templates map in which we save the * Template values, the key in the map is the name of the template:
  var templates = make (map [string] * template.Template) 

Next, we will create an initialization function that we will call in front of main () . The function template.MustParseFile is a wrapper for ParseFile that does not return an error code; instead, it panics. Indeed, this behavior is valid for the program, because it is not known how to handle an incorrect template.
  func init () {for _, tmpl: = range [] string {"edit", "view"} {templates [tmpl] = template.MustParseFile (tmpl + ". html", nil)}} 

The for loop is used with the range construct and processes the specified patterns.

Next, change the renderTemplate function so that it calls the Execute method of the corresponding template:
  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)
	 }
 } 


Validation


As already noted, our program has serious security bugs. Instead of the name, you can pass an arbitrary path. Add a check to a regular expression.

Import the regexp library. Create a global variable in which to save our RV:
  var titleValidator = regexp.MustCompile ("^ [a-zA-Z0-9] + $") 

The regexp.MustCompile function will compile a regular expression and return regexp.Regexp . MustCompile , like template.MustParseFile , differs from Compile in that it panics in case of an error, while Compile returns an error code.

Now, we will build a function that extracts the title from the URL, and checks its PB 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 header is correct, the value nil will be returned with it. Otherwise, “404 Not Found” will be displayed to the user, and an error will be returned to the handler.

Add a getTitle call to each of the handlers:
  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)
 } 


Functional types and closures


Error checking and returns generates a rather monotonous code, it would be nice to write it only once. This is possible, for example, if you wrap the functions that return errors into the corresponding call, functional types will help us with this.

Rewrite handlers by adding the title 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 now define a wrapper function that accepts the type of function defined above and returns http.HandlerFunc (to transfer it to 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 function returned is a closure because uses values ​​defined outside of it (in this case it is the fn variable, the handler).

Now let's transfer the code from getTitle :
  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 accepts parameters like http.ResponseWriter and http.Request (that is, a function like http.HandlerFunc). This closure extracts the title from the URL and checks its PB titleValidator . If the header is incorrect, an error will be sent to the ResponseWriter (call http.NotFound ). Otherwise, the corresponding handler fn will be called.

Add a wrapper call to the main () function:
  func main () {
	 http.HandleFunc ("/ view /", makeHandler (viewHandler))
	 http.HandleFunc ("/ edit /", makeHandler (editHandler))
	 http.HandleFunc ("/ save /", makeHandler (saveHandler))
	 http.ListenAndServe (": 8080", nil)
 } 

Finally, it’s possible to make them much 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)
 } 

This is what should end up

Re-compile the code and run our application:
 $ 8g wiki.go
 $ 8l wiki.8
 $ ./8.out

At http: // localhost: 8080 / view / ANewPage there will be a page with a form. You can save the page and go to it.

Note The textarea in the code had to be broken in order not to wreak habraparser.

Source: https://habr.com/ru/post/122095/


All Articles