實作 - 從零開始用 Golang 寫網頁 - 10 Form 表單請求

[實作] 從零開始用 Golang 寫網頁 : 10 Form 表單請求

通常在向後端提交資料會使用以下兩種方式 :

  • Form 表單請求
  • AJAX 請求

傳統上,我們常常會使用 Form 表單請求 來傳輸資料到後端,但缺點是此方法往往會重刷整個頁面,造成額外的網頁頻寬消耗,而使用 AJAX 請求 則可以減少頁面重刷的次數

這邊先實作 Form 表單請求 的方法,下一章則會將其修改為 AJAX 請求 來比較兩者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{{ block "content" . }}

<h1 class='text-center'>{{ .Title }}</h1>
<form action='/todo/' method='POST'>
<div class='row'>
<div class='offset-lg-1 col-lg-8 offset-md-1 col-md-7' style='margin-bottom: 5pt;'>

<input type='text' class='form-control' name='todo' placeholder='Something to do.'>
</div>

<div class='col-lg-3 col-md-4'>
<button class='btn btn-primary'>Add</button>
</div>
</div>
</form>


{{ range $t := .TODOs }}
<form action='/todo/' method='POST'>
<div class='row'>
<div class='offset-lg-1 col-lg-8 offset-md-1 col-md-7 todo' style='margin-top: 5pt; margin-bottom: 5pt;'>
<label class='col-form-label'>{{ $t.Item }}</label>
<inupt name='index' name='index' value='{{ $t.Index }}' hidden>
</div>
</div>
</form>
{{ end }}

{{ if .Message }}
<div class='row'>
<div class='offset-lg-2 col-lg-8 offset-md-1 col-md-10 col-sm-12'>
<div class="alert alert-warning" role="alert">
{{ .Message }}
</div>
</div>
</div>
{{ end }}

{{ end }}

HTML

  • 輸入字串後,按下 Add 按鈕傳送表單並將抓取到的資料印在下方

    • 使用 POST 請求方法
    • action='/todo/' 則對應到網頁程式的路徑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package main

import (
"fmt"
"net/http"
"os"

"github.com/julienschmidt/httprouter"
log "github.com/sirupsen/logrus"
"github.com/urfave/negroni"

negronilogrus "github.com/meatballhat/negroni-logrus"

"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)

// TODO represents single TODO item.
type TODO struct {
Item string
Index uint
}

// TODOModel represents the model of a TODO list.
type TODOModel struct {
ID uint `gorm:"PRIMARY_KEY,AUTO_INCREMENT"`
Todo string
}

// TableName set the name of the table.
func (TODOModel) TableName() string {
return "todos"
}

var db *gorm.DB

func init() {
var err error

db, err = gorm.Open("sqlite3", ":memory:")
if err != nil {
log.Fatal(err)
}

if !db.HasTable(&TODOModel{}) {
db.CreateTable(&TODOModel{})
}
}

func main() {

defer db.Close()

host := "127.0.0.1"
port := "8080"
output := "D:\\Desktop\\GO\\log\\log.txt"

args := os.Args[1:]

for {
if len(args) < 2 {
break
} else if args[0] == "-h" || args[0] == "--host" {
host = args[1]
args = args[2:]
} else if args[0] == "-p" || args[0] == "--port" {
port = args[1]
args = args[2:]
} else if args[0] == "-l" || args[0] == "--log" {
output = args[1]
args = args[2:]
} else {
log.Fatal(fmt.Sprintf("Unknown parameter : %s", args[0]))
}
}

mux := httprouter.New()

mux.ServeFiles("/js/*filepath", http.Dir("static/js"))
mux.ServeFiles("/css/*filepath", http.Dir("static/css"))

mux.GET("/myexercise/todolist", todolistindexHandler)
mux.POST("/todo/", addTODOHandler)

mux.NotFound = http.HandlerFunc(notFound)

mux.PanicHandler = errHandler

l := log.New()

var f *os.File
var err error

if output != "" {
f, err = os.Create(output)
if err != nil {
log.Fatal(err)
}

defer f.Close()
l.SetOutput(f)
}

n := negroni.Classic()
n.Use(negronilogrus.NewMiddlewareFromLogger(l, "web"))
n.UseHandler(mux)

server := http.Server{
Addr: fmt.Sprintf("%s:%s", host, port),
Handler: n,
}

fmt.Printf("服務器即將開啟, 訪問地址 http://%s:%s\n", host, port)
l.Println(fmt.Sprintf("服務器即將開啟, 訪問地址 http://%s:%s", host, port))
l.Fatal(server.ListenAndServe())
}

init

  • 這邊主要創建資料庫並初始化,使用 SQLite 並將資料存在記憶體中

    • 在關閉程式後資料就會歸零,不會留下實體資料,適合用於展示,不適用於實務上

main

  • defer db.Close() 關閉資料庫

  • 將對應的路徑分配給對應的函式

    • todolistindexHandler 處理頁面的顯示
    • addTODOHandler 處理傳來的 Form 表單
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
func todolistindexHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
t := template.Must(template.ParseFiles("./views/todolist/layout.html", "./views/head.html", "./views/todolist/todolist copy.html"))

var msg string
if r.Header.Get("Message") != "" {
msg = r.Header.Get("Message")

r.Header.Set("Message", "")
}

rows, err := db.Table("todos").Select("*").Rows()
if err != nil {
msg = "Unable to retrieve database"
}

var todos []TODO

todos = make([]TODO, 0)

for rows.Next() {
var todo struct {
ID uint
Todo string `gorm:"todo"`
}

db.ScanRows(rows, &todo)

todos = append(todos, TODO{
Index: todo.ID,
Item: todo.Todo,
})
}

data := struct {
Title string
TODOs []TODO
Message string
}{
Title: "代辦事項",
TODOs: todos,
Message: msg,
}

err = t.ExecuteTemplate(w, "layout", data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

func addTODOHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
r.ParseForm()

todo := r.FormValue("todo")

if todo == "" {
r.Header.Set("Message", "Empty TODO item")
} else {
db.Table("todos").Create(struct {
Todo string `gorm:"todo"`
}{
Todo: todo,
})
}

http.Redirect(w, r, "/myexercise/todolist", http.StatusSeeOther)
}

todolistindexHandler

  • 最開始先檢查 header 中是否有錯誤訊息,有的話記錄起來並清空

  • 接著對資料庫做查詢 db.Table("todos").Select("*").Rows(),取出資料

  • rows.Next() 走訪每一個結果,並使用 db.ScanRows() 將該資料掃出,最後用 append() 放入變數中

addTODOHandler

  • 這邊處理接收到的表單,按下 Add 後會進入 /todo/ 路徑並匹配此函式

  • 使用 r.ParseForm() 解析表單

    • 當解析到的變數為空時,將錯誤訊息寫入 header,反之則將其寫入資料庫
  • 最後使用 http.Redirect() 將頁面導回原本的頁面

    • http.StatusSeeOther 代表此請求的回應可以在其他頁面被找到,需使用 GET 方法訪問該頁面

結果

  • Add 後的資料就會顯示於下方

tags: 實作 Golang 網站 Form 表單
Author: Kenny Li
Link: https://kennyliblog.nctu.me/2020/09/22/Golang-Web10/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.