时间: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
这一行代码,告诉http
handler函数将会处理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/template
html/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
类型。关键词:训练,实战