时间:2023-06-02 02:12:01 | 来源:网站运营
时间:2023-06-02 02:12:01 来源:网站运营
GO实战训练:如何快速搭建一个web应用:GO实战训练:如何快速搭建一个web应用mkdir gowikicd gowikitouch wiki.go在wiki.go中,我们先import两个基本的包package mainimport ( "fmt" "io/ioutil")Page,包含两个成员变量。type Page struct { Title string Body []byte}[]byte是一个byte的切片类型。那为什么我们要使用切片而不是string 类型来定义body呢?是因为我们使用的io包使用的是这个切片类型。Page结构体,我们能够暂时将网页的内容保存在内存中了。但是在用户对wiki页面进行编辑后,怎么将内容永久的保存下来呢?解决这个问题的话,我们需要给Page这个结构体,定义一个save的方法func (p *Page) save() error { filename := p.Title + ".txt" return ioutil.WriteFile(filename, p.Body, 0600)}这个save的方法,有个特殊的参数,也叫做receiver,一个指向Page实例的指针p,意味着这个函数是Page这个结构体的成员函数,可以直接通过dot访问,比如p.save(),并以error作为返回值。0600是一个八进制的数字,作为WriteFile的参数,表示创建一个有读写权限的文件。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}那么到目前为止,我们已经能够在本地加载并且保存相关的网页了,让我们先完成简单的main函数,func main() { p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")} p1.save() p2, _ := loadPage("TestPage") fmt.Println(string(p2.Body))}简单的尝试运行,就可以看到下面的输出了。$ go build wiki.go$ ./wikiThis is a sample Page.那么,怎么将我们的代码变成网站呢?Go的标准库,net/http 可以很轻松的帮我们做到这一点。net/http 这个包GO语言中http这个官方库,先来看一个简单的例子,package mainimport ( "fmt" "log" "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) log.Fatal(http.ListenAndServe(":8080", nil))}main函数中声明了http.HandleFunc这一行代码,告诉httphandler函数将会处理web服务器根目录下的所有请求。然后main函数调用了http.ListenAndServe这个函数,指明我们将会一直监听8080这个端口直到程序结束。handler这个函数,我们这里定义的handler函数其实是http.HandleFunc的一种,它将http.ResponseWriter以及http.Request作为输入参数。 http.ResponseWriter会将HTTP服务器的回复组装在一起,通过对它进行写数据,我们可以将服务器的数据发送给客户端。http.Request是保存客户端请求的数据结构,r.URL.Path是请求中的路径数据,索引[1:]获取了/之后的所有字符。接下来我们直接运行代码,然后在浏览器中输入以下网址,http://localhost:8080/monkeys,我们可以看到以下的输出Hi there, I love monkeys!在初步了解了net/http这个库以后,那么怎么将它和我们的wiki网站制作结合在一起呢?net/http来搭建wiki网站import ( "fmt" "io/ioutil" "net/http")然后我们需要创建一个handler,viewHandler主要处理用户发来查看页面的请求,这类请求的URL会以/view/为开头。func viewHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/view/"):] p, _ := loadPage(title) fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body)}这里我们暂时不去处理loadPage可能产生的错误,之后会一步一步完善。简单讲解一下这个函数的意思,首先viewHandler会从path中抽取出/view/之后的字符,然后加载该名字的HTML的内容,写到response里面返回给客户端。test.txt的文本,并在里面写入任意内容,方便加载。然后我们在浏览器中输入http://localhost:8080/view/test,可以再浏览器页面上看到文本中的内容。func main() { http.HandleFunc("/view/", viewHandler) log.Fatal(http.ListenAndServe(":8080", nil))}func main() { http.HandleFunc("/view/", viewHandler) http.HandleFunc("/edit/", editHandler) http.HandleFunc("/save/", saveHandler) log.Fatal(http.ListenAndServe(":8080", nil))}editHandler函数提供了编辑功能,如果页面不存在的话,我们这里暂时先返回一个空页面给用户。func editHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/edit/"):] p, err := loadPage(title) if err != nil { p = &Page{Title: title} } fmt.Fprintf(w, "<h1>Editing %s</h1>"+ "<form action=/"/save/%s/" method=/"POST/">"+ "<textarea name=/"body/">%s</textarea><br>"+ "<input type=/"submit/" value=/"Save/">"+ "</form>", p.Title, p.Title, p.Body)}目前的话,我们的函数能够正常工作,但是硬编码的HTML还是不推荐的,在这里我们介绍一种更好的方式,利用 html/template的包来优化这段代码,让我们的代码看起来更优雅。htmp/templatehtml/template 是GO的一个标准库。我们可以使用这个包来将HTML的模板保存在另外的文件中,这样的话,如果我们要修改页面布局的话,可以单纯的修改HTML的模板文件,而不用修改GO代码。import ( "html/template" "io/ioutil" "net/http")<h1>Editing {{.Title}}</h1><form action="/save/{{.Title}}" method="POST"><div><textarea name="body" rows="20" cols="80">{{printf "%s" .Body}}</textarea></div><div><input type="submit" value="Save"></div></form>然后对应修改editHandler的代码,来避免硬编码. template.ParseFiles将会读取保存在edit.html中的模板内容,然后返回 *template.Template 类型的数据。然后 t.Execute 将会执行这个template,并将生成的HTML发送给http.ResponseWriter, 模板中 .Title 和 .Body 将会分别持有p中对应的内容。func editHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/edit/"):] p, err := loadPage(title) if err != nil { p = &Page{Title: title} } t, _ := template.ParseFiles("edit.html") t.Execute(w, p)}view的HTML进行一个模板化处理<h1>{{.Title}}</h1><p>[<a href="/edit/{{.Title}}">edit</a>]</p><div>{{printf "%s" .Body}}</div>同样,我们修改viewHandler的代码func viewHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/view/"):] p, _ := loadPage(title) t, _ := template.ParseFiles("view.html") t.Execute(w, p)}viewHandler 以及 editHandler的代码是由部分重复代码的,为了防止”破窗效应“,我们将模板这部分代码抽象出来func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { t, _ := template.ParseFiles(tmpl + ".html") t.Execute(w, p)}对应优化handler的代码func editHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/edit/"):] p, err := loadPage(title) if err != nil { p = &Page{Title: title} } renderTemplate(w, "edit", p)}func saveHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/save/"):] body := r.FormValue("body") p := &Page{Title: title, Body: []byte(body)} p.save() http.Redirect(w, r, "/view/"+title, http.StatusFound)}这里有一点需要注意的是,r.FormValue返回的值是string,我们需要将它强制转换成[]byte类型。关键词:训练,实战