Shanraq.org Shanraq.org
Go-дағы форма (form): оқырман жіберген дерек
IT

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>&lt;script&gt;alert(1)&lt;/script&gt;</li>

Бұл — өткен сабақтағы html/template. Айырмасы мынада: бүгінге дейін дерек сізден келді, енді — бөтен адамнан, сондықтан қорғаныс шын мәнінде қажет болды.

Сабақ картасы

Сабақ картасы: оқырман дерек жібереді, өңдеуші бағыттаумен жауап береді

Өз сөзіңізбен айтыңыз

Қарамай, дауыстап немесе қағазға жауап беріңіз. Жауаптар — сабақтың соңында.

  1. HTML-дегі required неге өңдеушідегі тексеруді алмастырмайды?
  2. /add өңдеушісі бетті өзі салса, ал оқырман F5 бассa, не болады?
  3. Оқырман өрісті толтырмай форманы жіберді. Бұл r.FormValue("title") үшін қалай көрінеді?

Тапсырма

Міндетті. Формаға екінші өріс қосыңыз — мақаланың мәтіні, <textarea name="body">. Оның бос емесін және 5000 рунадан ұзын емесін серверде тексеріңіз. Қате болса, адам енгізген екі жолды да қайтарыңыз, біреуін ғана емес. Мақала мәтінін тізімде тақырыптың астында көрсетіңіз.

Қалауыңызша.

  • Форманы curl арқылы title өрісінсіз мүлде жіберіңіз де, мұның бос өрістен айырмасын қараңыз.
  • 303 орнына кәдімгі бет шығаруды қойыңыз, мақала қосыңыз да, F5 басыңыз. Браузердің не сұрайтынын оқыңыз.
  • Формаға name="title" деген екі өрісті әртүрлі мәнмен қойып, r.FormValue қайсысын қайтаратынын біліңіз.

Блогта бұл қайда тұрады

Блог қабылдауды үйренді. Әрі қарай — статика: CSS, қаріп, сурет, яғни сайт жұмыс істеп қана қоймай, көрінетін болуы үшін.

Бүгін алған үш қарыз бар, оларды кейін қайтарамыз.

blog — кәдімгі айнымалы, ал оны өңдеушілер өзгертеді, өңдеушілер қатар орындалады. Бір секундта «Қосу» басқан екі оқырман — бұл жарыс. Ол қатар орындау сабағында, сол жерде go run -race да бар.

Тілім жадта тұр. Серверді қайта қосасыз — мақала жоқ. Дерекқор төртінші модульде күтіп тұр.

Ең бастысы: бөтен бет сіздің оқырманыңыздың атынан сіздің мекенжайыңызға форма жібере алады. Мұны CSRF дейді, одан бөлек қорғанады. Қауіпсіздік сабағының формалардан кейін тұрғаны кездейсоқ емес — әуелі бұзуға болатын нәрсені жасау керек еді.

Жауаптар

Жауаптарды көрсету
  1. Өйткені required браузерде тұр, ал сұраныс кез келген жерден келуі мүмкін: curl-дан, бөтен бағдарламадан, басқа сайттағы формадан. Браузердегі тексеру адамға қателеспеуге көмектеседі; дұрыс емес деректен тек өңдеуші қорғайды.
  2. Дәл сондай екінші мақала пайда болады. /add мекенжайындағы бет — форманы жіберуге берілген жауап, оны жаңарту үшін форманы қайта жіберу керек. Сондықтан деректі өзгерткен өңдеуші 303 қайтарып, браузерді қалағанша жаңартуға болатын бетке бағыттайды.
  3. Ол бос жол қайтарады — өріс мүлде болмағандағыдай; бірін екіншісінен FormValue ажыратқызбайды. Айырмасы керек болса, r.ParseForm() шақырып, r.PostForm.Has("title") деп сұраңыз.

Дереккөздер

Мәтінде қате не теру қатесі кездессе, бізге айтыңыз

Тапсырманы тексеру

Алдымен VS Code-та шешіп, іске қосыңыз — редактор қатені сол жерде көрсетеді. Дайын шешімді осында қойыңыз. Тексеретін — модель: ол қатені атап көрсетеді, бірақ дайын жауапты бермейді.

Тексеру үшін кіру керек. Кіру

Пікірлер (0)

Әзірге пікір жоқ. Бірінші болыңыз.