前言昨天我们简单的了解了 今天我们来简单的实现一个 web app包含内容我们将接触以下几部分
初始化项目mkdir gowikicd gowiki go mod init example/gowiki echo > main.go // 编码可能是`utf-16`,需要留意 数据结构type Page struct {Title string Body []byte } 用于定义页面的数据结构 这里 关于切片,可以看这篇:https://golang.google.cn/doc/articles/slices_usage_and_internals.html 然后我们再给这个 filename := p.Title + ".txt" return os.WriteFile(filename, p.Body, 0600) } 和之前学的
这个方法是用来将当前的内容写入 这里就回到了我们前面留的问题: 因为这里要调用 既然有 filename := title + ".txt" body, err := os.ReadFile(filename) if err != nil { return nil, err } return &Page{Title: title, Body: body}, nil } 这里的写法 而 扯远了。。。回到我们的代码中,我们来试下这两个方法是否正常 func main() {p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")} p1.save() p2, _ := loadPage("TestPage") fmt.Println(string(p2.Body)) } 然后我们运行下 ![]() img_save_and_load_Page_success 正常 net/http[1]我们前面简单的接触过 我们先来看下基础用法 //go:build ignorepackage main import ( "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)) }
至于 然后我们来接入到我们的项目中 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) } func main() { http.HandleFunc("/view/", viewHandler) log.Fatal(http.ListenAndServe(":8080", nil)) }
然后浏览器/ ![]() img_access_testPage 正常 编辑页面现在我们基本打通访问的流程了,那么就可以接收输入了。 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) } func main() { http.HandleFunc("/view/", viewHandler) http.HandleFunc("/edit/", editHandler) log.Fatal(http.ListenAndServe(":8080", nil)) } 提供一个 我们再来实现这一部分 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)} err := p.save() if err != nil { fmt.Fprintf(w, "save error: %s", err) return } fmt.Fprintf(w, "save success") } func main() { http.HandleFunc("/view/", viewHandler) http.HandleFunc("/edit/", editHandler) http.HandleFunc("/save/", saveHandler) log.Fatal(http.ListenAndServe(":8080", nil)) }
现在 ![]() img_test_edit 表现正常 html/template[2]前面我们用的都是 所以我们这里用另一个包来处理。 底层估计也是使用的 <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> 然后我们来调整下 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) }
我们再来处理 <p>[<a href="/edit/{{.Title}}">edit</a>]</p> <div>{{printf "%s" .Body}}</div> 然后调整下 title := r.URL.Path[len("/view/"):] p, _ := loadPage(title) t, _ := template.ParseFiles("view.html") t.Execute(w, p) } 这里面还有一些重复的代码,咱把它抽出来 func viewHandler(w http.ResponseWriter, r *http.Request) {title := r.URL.Path[len("/view/"):] p, _ := loadPage(title) renderTemplate(w, "view", 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} } renderTemplate(w, "edit", p) } func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { t, _ := template.ParseFiles(tmpl + ".html") t.Execute(w, p) } 然后我们重新运行下 ![]() img_use_template 正常 处理不存在的页面现在我们仅注册了几个路径,如果用户访问的是一个没有注册的路径,比如 我们来调整下 title := r.URL.Path[len("/view/"):] p, err := loadPage(title) if err != nil { http.Redirect(w, r, "/edit/"+title, http.StatusFound) return } renderTemplate(w, "view", p) }
我们重新跑下 ![]() img_use_redirect 直接重定向到 完善save我们前面写的 我们来完善一下,让它保存之后直接去到 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) } 然后我们重新跑下 ![]() img_redirect_to_view_after_save 表现正常 错误处理前面写的代码大多都没有关注这一块的内容,实际上错误处理是非常重要的一部分。 我们先来处理模板这块的 func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {t, err := template.ParseFiles(tmpl + ".html") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } err = t.Execute(w, p) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } 当文档解析发生错误的时候直接返回对应的错误信息以及服务器 然后是 title := r.URL.Path[len("/save/"):] body := r.FormValue("body") p := &Page{Title: title, Body: []byte(body)} err := p.save() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, "/view/"+title, http.StatusFound) }
模板缓存我们在 我们这里直接省事都处理了。 var templates = template.Must(template.ParseFiles("edit.html", "view.html"))func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { err := templates.ExecuteTemplate(w, tmpl+".html", p) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }
这里就涉及到了一些抽象语法树( 关于抽象语法树相关的知识如果感兴趣可以去看下我之前分析 扯远了,我们回到代码中,现在每次都只会去处理抽象语法树,而不会再次解析模板。 校验目前我们的路径就只支持 func getTitle(w http.ResponseWriter, r *http.Request) (string, error) { m := validPath.FindStringSubmatch(r.URL.Path) if m == nil { http.NotFound(w, r) return "", errors.New("invalid Page Title") } return m[2], nil // The title is the second subexpression. } 这里使用的是
我们直接来用下,如果请求路径不符合 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.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, "/view/"+title, http.StatusFound) } ![]() img_404_not_found 闭包
我们来调整下之前我们的函数字面量,我们实际上存在一些重复的逻辑可以通过闭包的方式来优化。 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.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, "/view/"+title, http.StatusFound) } var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$") func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { m := validPath.FindStringSubmatch(r.URL.Path) if m == nil { http.NotFound(w, r) return } fn(w, r, m[2]) } } func main() { http.HandleFunc("/view/", makeHandler(viewHandler)) http.HandleFunc("/edit/", makeHandler(editHandler)) http.HandleFunc("/save/", makeHandler(saveHandler)) log.Fatal(http.ListenAndServe(":8080", nil)) } 我们将 实际上这是一个高阶函数,接受一个函数并且返回一个函数。 这里优化的点是校验的逻辑,这样就没必要去每个函数里面写了。 当然,也不是非得这么写才能优化。实际上我们可以通过别的方式去实现,比如写前置路由拦截等。 最终代码- The Go Programming Language (google.cn) 总结这只是一个非常简单的 参考
|
万奢网手机版
官网微博:万奢网服务平台