Go тілі: нөлден өз блогыңызға дейін 23-сабақ (барлығы 50)
Go-дағы форма (form): оқырман жіберген дерек
Go курсының жиырма үшінші сабағы. Дерек алғаш рет кері бағытта жүреді — оқырманнан серверге. Әдіс пен өріс аттары, r.FormValue, браузердегі тексеру неге қорғаныс емес және бірдеңені өзгерткен өңдеуші неге бет емес, бағыттау қайтарады: әйтпесе F5 дәл сондай екінші мақала жасайды.
Не үшін керек
Жиырма екі сабақ бойы оқырман тек алып келді. Сайтпен істей алатыны біреу ғана болды — мекенжайды ашу.
Форма — дерек кері бағытта жүретін алғашқы орын. Әрі сенім бітетін алғашқы орын: сырттан келген нәрсе кез келген нәрсе болуы мүмкін, ал оны тексеру сізге қалады.
Форма (form) — HTML-дегі <form> тегі. Қазақшада оған дәл балама жоқ, сондықтан ағылшын сөзін сол күйінде аламыз: адам толтыратын, содан кейін серверге кететін бланк.
Бірден тұтас
Жаңа қалта, go mod init sabaq21. Ішінде — templates қалтасы, онда list.html:
<!doctype html>
<html lang="kk">
<head><meta charset="utf-8"><title>{{ .Title }}</title></head>
<body>
<h1>{{ .Title }}</h1>
<form method="post" action="/add">
<label for="title">Мақаланың тақырыбы</label>
<input id="title" name="title" value="{{ .Draft }}" required maxlength="80">
<button type="submit">Қосу</button>
</form>
{{ if .Err }}<p class="err">{{ .Err }}</p>{{ end }}
<ul>
{{ range .Articles }}<li>{{ .Title }}</li>{{ end }}
</ul>
</body>
</html>
Қасында main.go:
package main
import (
"bytes"
"embed"
"html/template"
"log"
"net/http"
"strings"
"unicode/utf8"
)
//go:embed templates/*.html
var files embed.FS
type Article struct{ Title string }
type page struct {
Title string
Articles []Article
Draft string
Err string
}
var (
tpl = template.Must(template.ParseFS(files, "templates/*.html"))
blog = []Article{{Title: "Дала туралы"}}
)
func show(w http.ResponseWriter, code int, p page) {
p.Title = "Менің блогым"
p.Articles = blog
// Буферге жинап, содан кейін ғана береміз — үлгілер сабағындағы ереже.
var buf bytes.Buffer
if err := tpl.ExecuteTemplate(&buf, "list.html", p); err != nil {
log.Println("үлгі:", err)
http.Error(w, "серверде қате", http.StatusInternalServerError)
return
}
w.WriteHeader(code)
if _, err := buf.WriteTo(w); err != nil {
log.Println("үлгі:", err)
}
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
show(w, http.StatusOK, page{})
})
mux.HandleFunc("POST /add", func(w http.ResponseWriter, r *http.Request) {
title := strings.TrimSpace(r.FormValue("title"))
switch {
case title == "":
show(w, http.StatusBadRequest, page{Err: "тақырып бос"})
return
case utf8.RuneCountInString(title) > 80:
show(w, http.StatusBadRequest, page{Draft: title, Err: "тақырып тым ұзын"})
return
}
blog = append(blog, Article{Title: title})
http.Redirect(w, r, "/", http.StatusSeeOther)
})
log.Println("http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
go run ., http://localhost:8080 ашыңыз, тақырып жазып «Қосу» басыңыз. Мақала тізімге қосылды, ал браузер жолындағы мекенжай сол күйі қалды — /add емес, /. Неге олай екенін төменде талдаймыз; әуелі мұны көрген жөн.
Талдау
Форма — бұл әдіс, мекенжай және өріс аттары
<form method="post" action="/add">
<input name="title">
Үш нәрсе, әрі үшеуін де түсіну керек.
action — сұраныс қайда кетеді. method — қалай кетеді. name — сервер мәнді табатын кілт. Соңғысы көрінгеннен маңыздырақ: name-сіз өрісті браузер мүлде жібермейді. «Бос жібереді» емес — жібермейді. Өңдеушіде бәрі дұрыс тұрғанда форманың «істемей тұруының» ең жиі себебі осы.
id мен label for жіберуге қатысы жоқ. Олар адам жазуды басып өріске түсуі үшін, ал экраннан оқитын бағдарлама бұл қандай өріс екенін айтуы үшін керек.
GET көрсетеді, POST өзгертеді
Бұл ереже Go туралы емес, HTTP туралы.
GET — көрсетуді сұрау. Оны қалағанша қайталауға болады: бетті жаңартқанда браузер өзі қайталайды, іздеу жүйесі сұрамай ашады, «артқа» ешбір сұрақсыз қайтарады. Ештеңе өзгермеуі керек.
POST — өзгертуді сұрау: сақтау, өшіру, төлеу.
Осыдан керек болмай тұрып есте қалатын ереже шығады: GET арқылы істейтін «мақаланы өшіру» сілтемесі — қате. Сайтыңыздың сілтемелерімен жай ғана жүріп өтетін кез келген робот оны ашады, сонда мақалалар өздігінен жоғала бастайды.
Әдіс бағыттар сабағындағыдай, бағыттың өзінде тұрады:
mux.HandleFunc("POST /add", …)
Енді /add мекенжайын браузерден ашуға болмайды: ол GET, оған өңдеуші жоқ, сондықтан ServeMux өзі 405 Method Not Allowed деп жауап береді.
r.FormValue жол қайтарады — тек жол
title := strings.TrimSpace(r.FormValue("title"))
FormValue сұраныстың денесін талдап, ат бойынша мәнді алады. r.ParseForm() бөлек шақырудың қажеті жоқ — FormValue оны өзі істейді.
Оның үш қасиетін алдын ала білген жөн.
Ол жол қайтарады. Сан емес, күн емес — жол. Қалғанын өзіңіз аласыз: strconv.Atoi және оның туыстары.
Ол бір мән қайтарады, бірінші мәнді, сондай аты бар өріс бірнешеу болса да.
Бос өріс пен жоқ өріс ол үшін ажыратылмайды: екеуінде де "". Айырмасы маңызды болса — r.ParseForm() мен r.PostForm.Has("title") алыңыз.
Мұндағы TrimSpace әшекей емес. Үш бос орыннан тұратын жол бос емес, ал одан шыққан тақырып бос.
Тексеру серверде, әрқашан
Формада required пен maxlength бар. Браузер солар бойынша бос өрісті жіберткізбейді. Бұл — адамға жеңілдік, тек сол ғана.
Сұраныс браузерден келмеуі мүмкін:
curl -i -X POST -d "title= " http://localhost:8080/add
Мұнда required де, maxlength те жоқ. Тек сіздің өңдеушіңіз бар:
HTTP/1.1 400 Bad Request
Елестетіп көріңіз. «Кірмеңіз» деген жазу мен есіктегі құлып — бір нәрсе емес. Браузердегі тексеру — жазу: сыпайы, пайдалы, әрі оны есікпен кірмек болғандар ғана көреді.
Ұзындықты байтпен емес, рунамен санаймыз:
utf8.RuneCountInString(title) > 80
Бұл — жолдар сабағындағы ереже: len байт қайтарады, сонда қырық қазақ әрпінен тұратын тақырып күтпеген жерден «тым ұзын» боп шығады.
POST → бағыттау → GET
Сабақтағы ең маңыздысы.
/add өңдеушісі сақтағаннан кейін жай ғана бет салды деп елестетіңіз. Оқырман тізімді көрді, риза болды — және F5 басты. Браузер: «Деректі қайта жіберейін бе?» — деп сұрайды да жібереді. Дәл сондай екінші мақала пайда болады. Үшінші. /add мекенжайындағы беттің форманы қайта жібермей жаңаратын жолы жоқ, өйткені ол беттің өзі — форманы жіберуге берілген жауап.
Бір жолмен емделеді:
http.Redirect(w, r, "/", http.StatusSeeOther)
Бірдеңені өзгерткен өңдеуші бет салмайды. Ол браузерді бет салатын мекенжайға бағыттайды. Браузер / бетіне енді GET әдісімен барады, ал мекенжай жолында / қалады. Енді F5 — кәдімгі GET, қайталайтын ештеңе жоқ.
303 See Other коды сөзбе-сөз: «осы мекенжайға қара, GET әдісімен». 301 емес (ол — мәңгілікке көшу, әрі браузер оны есінде сақтайды) және 302 емес (ол әдіс жағынан тарихи екіұшты).
Елестетіп көріңіз. Дүкендегі чек. Бір рет төлейсіз, ал чекті қалағанша қарауға болады, оған қараудан шоттан ақша қайта шешілмейді.
Қате де — жауап
show(w, http.StatusBadRequest, page{Draft: title, Err: "тақырып тым ұзын"})
Бірден екі нәрсе.
Біріншісі: 200 емес, 400. Сұраныс дұрыс болмады, ал жауап мұны айтуға міндетті — бетте адамға сөзбен ғана емес, кодқа да.
Екіншісі: Draft: title. Үлгіде бұл — value="{{ .Draft }}", яғни өріс толтырылған күйінде қайтады. Адам сексен бес әріп жазды; оны бәрін қайта тергізу — тексеру емес, дөрекілік.
Ал бөтен кодтан қорғаныс жұмыс істеп тұр
Формаға мынаны жазыңыз:
<script>alert(1)</script>
Бетте мәтін пайда болады. Дәл мәтін:
<li><script>alert(1)</script></li>
Бұл — өткен сабақтағы html/template. Айырмасы мынада: бүгінге дейін дерек сізден келді, енді — бөтен адамнан, сондықтан қорғаныс шын мәнінде қажет болды.
Сабақ картасы
Өз сөзіңізбен айтыңыз
Қарамай, дауыстап немесе қағазға жауап беріңіз. Жауаптар — сабақтың соңында.
- HTML-дегі
requiredнеге өңдеушідегі тексеруді алмастырмайды? /addөңдеушісі бетті өзі салса, ал оқырман F5 бассa, не болады?- Оқырман өрісті толтырмай форманы жіберді. Бұл
r.FormValue("title")үшін қалай көрінеді?
Тапсырма
Міндетті. Формаға екінші өріс қосыңыз — мақаланың мәтіні, <textarea name="body">. Оның бос емесін және 5000 рунадан ұзын емесін серверде тексеріңіз. Қате болса, адам енгізген екі жолды да қайтарыңыз, біреуін ғана емес. Мақала мәтінін тізімде тақырыптың астында көрсетіңіз.
Қалауыңызша.
- Форманы
curlарқылыtitleөрісінсіз мүлде жіберіңіз де, мұның бос өрістен айырмасын қараңыз. 303орнына кәдімгі бет шығаруды қойыңыз, мақала қосыңыз да, F5 басыңыз. Браузердің не сұрайтынын оқыңыз.- Формаға
name="title"деген екі өрісті әртүрлі мәнмен қойып,r.FormValueқайсысын қайтаратынын біліңіз.
Блогта бұл қайда тұрады
Блог қабылдауды үйренді. Әрі қарай — статика: CSS, қаріп, сурет, яғни сайт жұмыс істеп қана қоймай, көрінетін болуы үшін.
Бүгін алған үш қарыз бар, оларды кейін қайтарамыз.
blog — кәдімгі айнымалы, ал оны өңдеушілер өзгертеді, өңдеушілер қатар орындалады. Бір секундта «Қосу» басқан екі оқырман — бұл жарыс. Ол қатар орындау сабағында, сол жерде go run -race да бар.
Тілім жадта тұр. Серверді қайта қосасыз — мақала жоқ. Дерекқор төртінші модульде күтіп тұр.
Ең бастысы: бөтен бет сіздің оқырманыңыздың атынан сіздің мекенжайыңызға форма жібере алады. Мұны CSRF дейді, одан бөлек қорғанады. Қауіпсіздік сабағының формалардан кейін тұрғаны кездейсоқ емес — әуелі бұзуға болатын нәрсені жасау керек еді.
Жауаптар
Жауаптарды көрсету
- Өйткені
requiredбраузерде тұр, ал сұраныс кез келген жерден келуі мүмкін:curl-дан, бөтен бағдарламадан, басқа сайттағы формадан. Браузердегі тексеру адамға қателеспеуге көмектеседі; дұрыс емес деректен тек өңдеуші қорғайды. - Дәл сондай екінші мақала пайда болады.
/addмекенжайындағы бет — форманы жіберуге берілген жауап, оны жаңарту үшін форманы қайта жіберу керек. Сондықтан деректі өзгерткен өңдеуші303қайтарып, браузерді қалағанша жаңартуға болатын бетке бағыттайды. - Ол бос жол қайтарады — өріс мүлде болмағандағыдай; бірін екіншісінен
FormValueажыратқызбайды. Айырмасы керек болса,r.ParseForm()шақырып,r.PostForm.Has("title")деп сұраңыз.
Пікірлер (0)
Пікір қалдыру үшін кіріңіз →
Әзірге пікір жоқ. Бірінші болыңыз.