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.

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