+3

[GO] Làm một trang web đơn giản dùng Go [Phần 2]

Chào các bạn, ở phần trước mình đã giới thiệu với các bạn về việc tạo một trang web đơn giản, hôm nay mình xin được trình bày tiếp về việc xử lý data trong trang web đó (CRUD) dùng Go.

1: Edit Pages

Một Pages không phải là một Pages mà không có khả năng chỉnh sửa trang. Chúng ta hãy tạo ra hai hàm mới: một là editHandler để hiển thị một trang 'edit page' và một hàm khác là saveHandler để lưu dữ liệu được nhập thông qua biểu mẫu. Đầu tiên, chúng ta thêm vào main ():

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

Hàm editHandler load trang (hoặc, nếu nó không tồn tại, tạo một Page trống), và hiển thị một form HTML.

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)
}

Chức năng này sẽ hoạt động tốt, nhưng đây là những đoạn HTML cứng. Tất nhiên, có một cách tốt hơn. mình sẽ giới thiệu ngay bây giờ. Đầu tiên, chúng ta phải thêm html/template vào danh sách imports. Chúng ta cũng sẽ không sử dụng fmt nữa, vì vậy chúng ta phải gỡ bỏ điều đó.

import (
  "html/template"
  "io/ioutil"
  "net/http"
)

Chúng ta hãy tạo ra một template file chưa form HTML. Mở tệp mới có tên edit.html và thêm các dòng sau:

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

Sửa đổi editHandler để sử dụng template, thay vì hard-code HTML:

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)
}

Function template.ParseFiles sẽ đọc nội dung của file edit.html và trả về *template.Template Method t.Execute thực thi template, viết mã HTML được tạo ra tới http.ResponseWriter.

Vì chúng ta đang làm việc với các template bây giờ, chúng ta hãy tạo một template cho viewHandler của chúng tôi được gọi là view.html:

<h1>{{.Title}}</h1>
<p>[<a href="/edit/{{.Title}}">edit</a>]</p>
<div>{{printf "%s" .Body}}</div>

Sửa đổi hàm viewHandler cho phù hợp:

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)
}

Lưu ý rằng chúng tôi đã sử dụng gần như chính xác cùng mã templating trong cả hai xử lý. Hãy loại bỏ trùng lặp này bằng cách di chuyển mã templating đến chức năng riêng của nó:

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
  t, _ := template.ParseFiles(tmpl + ".html")
  t.Execute(w, p)
}

Sau đó sửa viewHandle như sau:

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)
}

2: Saving Pages

Hàm saveHandler sẽ xử lý việc đệ trình các biểu mẫu được đặt trên trang chỉnh sửa. Sau khi sửa lại dòng liên quan trong main, hãy thực hiện xử lý:

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)
}

Title page (được cung cấp trong URL) và trường duy nhất của Template, Body, được lưu trữ trong Page mới. Method save() sau đó được gọi để ghi dữ liệu vào một file, và client được chuyển đến /view/page. Giá trị trả về bởi FormValue là kiểu chuỗi. Chúng ta phải chuyển đổi giá trị đó sang []byte trước khi nó khớp với Page struct. Chúng ta sử dụng []byte (body) để thực hiện chuyển đổi.

3: Xử lý lỗi

Có một số nơi trong chương trình của chúng ta các lỗi đang bị bỏ qua. Đây là một điều rất không tốt vì khi lỗi xảy ra thì chương trình sẽ có hành vi không mong muốn. Một giải pháp tốt hơn là để xử lý các lỗi và trả lại một thông báo lỗi cho người dùng. Bằng cách đó nếu một cái gì đó không sai, máy chủ sẽ hoạt động chính xác như chúng ta muốn và người dùng có thể được thông báo. Trước tiên, hãy xử lý lỗi trong renderTemplate:

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)
  }
}

Hàm http.Error gửi một mã phản hồi HTTP được chỉ định (trong trường hợp này là "Internal Server Error") và thông báo lỗi. Chúng ta sẽ đặt nó vào một chức năng riêng biệt là trả hết.

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 {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }
  http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

Bất kỳ lỗi nào xảy ra trong quá trình p.save() sẽ được báo cáo cho người dùng.

4: Kết luận

Ở phần này mình đã giới thiệu thêm cho các bạn về việc tạo mới và sửa một data cũng như xử lý lỗi trong trang như thế nào với Golang, phần sau mình sẽ giới thiệu phần cuối về validation và dùng các thư viện css hoặc boostrap với Go. Cảm ơn các bạn đã theo dõi series giới thiệu về Golang của mình.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí