Shanraq.org Shanraq.org
Go-дағы үлгілер: html/template және жол емес, бет
IT

Go тілі: нөлден өз блогыңызға дейін 22-сабақ (барлығы 50)

Go-дағы үлгілер: html/template және жол емес, бет

Go курсының жиырма екінші сабағы. HTML кодтан бөлек файлға көшеді: нүкте — деректің аты, тілім бойынша range, if пен else. html/template бөтен мәтінді неге өзі залалсыздандырады, үлгілерді неге іске қосылғанда бір рет талдайды және бетті неге бірден жауапқа емес, буферге жинайды.

Не үшін керек

Осы уақытқа дейін өңдеушілер жол жазып келді: fmt.Fprintln(w, "…"). Бір бетті солай жинауға болады. Онды — жоқ: код ішіндегі HTML-ді редактор боямайды, тексермейді, ал алғашқы кестеде-ақ ол тырнақшалар ботқасына айналады.

Бөлейік: белгілеу файлға, деректер құрылымға, ал қосуды үлгілеушіге тапсырамыз.

Термин туралы бірден. Тесіктері бар дайын пішін қазақша үлгі деп аталады — қазақ Уикипедиясында да үлгілер кеңістігі «Үлгі» деп аталады. Ағылшынша ол template, әрі кодта да, құжаттамада да сол сөз тұрады, сондықтан екеуін де біліп қойған жөн.

Бірден тұтас

Жаңа қалта, go mod init sabaq20. Ішінде — templates қалтасы, онда list.html:

<!doctype html>
<html lang="kk">
  <head><meta charset="utf-8"><title>{{ .Title }}</title></head>
  <body>
    <h1>{{ .Title }}</h1>
    {{ if .Articles }}
    <ul>
      {{ range .Articles }}
      <li><a href="/read/{{ .Slug }}">{{ .Title }}</a> — {{ .ReadingTime }} мин</li>
      {{ end }}
    </ul>
    {{ else }}
    <p>Әзірге мақала жоқ.</p>
    {{ end }}
  </body>
</html>

Қасында main.go:

package main

import (
	"bytes"
	"embed"
	"html/template"
	"log"
	"net/http"
)

//go:embed templates/*.html
var files embed.FS

type Article struct {
	Slug  string
	Title string
	Words int
}

func (a Article) ReadingTime() int { return (a.Words + 199) / 200 }

type page struct {
	Title    string
	Articles []Article
}

func main() {
	tpl := template.Must(template.ParseFS(files, "templates/*.html"))

	blog := []Article{
		{Slug: "dala", Title: "Дала туралы", Words: 400},
		{Slug: "shanyraq", Title: "Шаңырақ деген не", Words: 1000},
		{Slug: "xss", Title: `<script>alert("бұзылды")</script>`, Words: 150},
	}

	http.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
		var buf bytes.Buffer
		if err := tpl.ExecuteTemplate(&buf, "list.html", page{Title: "Менің блогым", Articles: blog}); err != nil {
			log.Println("template:", err)
			http.Error(w, "500", http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		buf.WriteTo(w)
	})

	log.Println("http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Іске қоспас бұрын — ішінде <script> жатқан үшінші тақырыптың орнында не болатынын дауыстап айтыңыз. Содан кейін go run . жіберіп, localhost:8080 ашыңыз да, беттің бастапқы кодын қараңыз:

    <li><a href="/read/dala">Дала туралы</a> — 2 мин</li>
    <li><a href="/read/shanyraq">Шаңырақ деген не</a> — 5 мин</li>
    <li><a href="/read/xss">&lt;script&gt;alert(&#34;бұзылды&#34;)&lt;/script&gt;</a> — 1 мин</li>

Үшінші жол — сабақтың ең маңыздысы. Скрипт орындалған да, бетті бұзған да жоқ: ол мәтін болып келді.

Талдау

Шаблон — бұл тесіктері бар HTML

list.html файлы — редактор түсінетін кәдімгі белгілеу. Қос фигуралық жақшадағының бәрін үлгілеуші деректермен алмастырады:

{{ .Title }}

Нүкте — сіз Execute-ке бергеніңіз. .Title — сол құрылымның өрісі. Өріс аттары бас әріптен басталуы керек, әйтпесе үлгі оларды көрмейді: ол басқа пакетте тұр, ал бас әріп ережесі мұнда да жұмыс істейді.

Елестетіп көріңіз. Бос орындары бар бланк. Пішін алдын ала басылған әрі бәріне ортақ, ал бос бағандарға әр жолы өзіңдікін жазады.

range және if

{{ range .Articles }} … {{ end }}
{{ if .Articles }} … {{ else }} … {{ end }}

range ішінде нүктенің мәні өзгереді: енді бұл бүкіл бет емес, кезекті мақала. Сондықтан ішінде .Title жазады, ал бұл мақаланың тақырыбы.

if бостықты тексереді: бос тілім, бос жол және нөл жалған болып саналады. Осыдан «әзірге бос» деген {{ else }} шығады — мақаласы жоқ жаңа блогта сынбайтын бет.

Тек өріс емес, әдіс те

{{ .ReadingTime }} мин

ReadingTime — өріс емес, құрылымдар сабағындағы әдіс. Шаблон оны жақшасыз, өзі шақырады. Ештеңе қабылдамайтын және бір мән қайтаратын кез келген әдіс жарайды.

Логиканы әдістерде ұстап, үлгіге дайын сандар берген жөн. Есеп жүргізілетін үлгі тез арада екінші бағдарламаға айналады — жөндеушісі де, тесті де жоқ тілде.

Экрандау — html/template алудың басты себебі

Стандартты кітапханада екі үлгілеуші бар. Айырмасы бір жолдан көрінеді:

text/template:  <li><script>alert("бұзылды")</script></li>
html/template:  <li>&lt;script&gt;alert(&#34;бұзылды&#34;)&lt;/script&gt;</li>

text/template сол күйінде қояды. html/template HTML жинап жатқанын түсінеді де, бөтен мәтінді залалсыздандырады: скрипт скрипттің мәтініне айналады.

Бұл — шабуылдардың тұтас бір түрінен қорғаныс: зиянкес мақала тақырыбына не пікірге <script> жазады, ал бет оны әр оқырманда орындайды. Сіз html/template алып, экрандауды қолмен өшірмей тұрғанда, бұл есік жабық.

Елестетіп көріңіз. Кіреберістегі залалсыздандыру. Сәлемдемені лақтырып та тастамайды, орныңызға оқып та бермейді — оны жай ғана ішіндегісі зиян келтіре алмайтындай өңдейді.

Экрандау орынға байланысты: мәтінде, атрибутта, <script> ішінде және сілтеме мекенжайында ережелер әртүрлі, ал үлгілеуші оларды өзі ажыратады.

HTML-дің HTML күйінде керек кезі

Экрандау кейде кедергі келтіреді — әрі дәл бір рет орынды. Мақаланың денесін Markdown-да жазып, HTML-ге өзіңіз айналдырасыз; оны жол күйінде беру деген — оқырманға мәтіннің орнына тег көрсету деген сөз:

жол болып: &lt;p&gt;&lt;b&gt;Markdown&lt;/b&gt;-нан азат жол&lt;/p&gt;
сенімді:   <p><b>Markdown</b>-нан азат жол</p>

Айырма — түрде. Кәдімгі жол экрандалады, ал template.HTML түріндегі мәнді үлгі сол күйінде қояды:

type page struct {
	Body template.HTML
}

Ал мұнда бұзуға болмайтын жалғыз ереже бар: template.HTML ішіне тек өзіңіз жасағанды салады. Оқырманның мәтіні, формадағы өріс, бөтен API-дан келген үзінді — ешқашан. Бұл түр қорғанысты өшіреді, әрі оны үнсіз өшіреді.

Әр сұраныста емес, бір рет талдау

tpl := template.Must(template.ParseFS(files, "templates/*.html"))

Шаблондарды талдау — файлдармен және мәтінмен жұмыс. Оны әр сұраныста істеу — оқырманның уақытын босқа шығындау.

template.Must — «талданбаса, бірден құла» дегеннің қысқа жазуы. Бұл — panic орынды болатын нақ сол жағдай: іске қосылғандағы сынған үлгі оқырмандағы ақ беттен жақсы.

Ал //go:embed үлгі файлдарын жиналған бағдарламаның ішіне салады. Бағдарлама мен қасындағы қалтаның орнына бір файл — әрі серверді белгілеусіз жариялау мүмкіндігі жоқ.

Неге бірден жауапқа емес, буферге

Бүтін абзацқа тұратын нәзік тұс осы.

Execute жинау барысында жаза береді. Қате ортасында шықса — мысалы, жоқ өріске жүгінсеңіз — беттің бір бөлігі 200 кодымен бірге оқырманға кетіп қойған болады. Өзіңіз тексеріңіз:

<p>басы</p>
қате: template: c:2:3: executing "c" at <.Nope>: can't evaluate field Nope in type main.A

Бірінші жол оқырманда, оны кері қайтару мүмкін емес — бұл HTTP сабағындағы ереже. Сондықтан bytes.Buffer-ге жинап, содан кейін ғана береді: шықса — түгел жазамыз, шықпаса — 500 және журналға жазба.

Елестетіп көріңіз. Тағамды дайын күйінде шығарады. Аспаз оны залға бөлшектеп тасымайды және бірдеңе күйіп кетсе, жартысын кері алып кетпейді: асханада түгел жинап, залға бір рет шығады.

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

Сабақ картасы: үлгі мен деректер бір бетке қосылады

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

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

  1. {{ range .Articles }} ішіндегі нүкте нені білдіреді және оның мәні неге өзгереді?
  2. Оқырман мақаланы <script>alert(1)</script> деп атады. Қалғандары не көреді?
  3. Бірден w-ға жазуға болатын болса, бетті буферге не үшін жинайды?

Тапсырма

Міндетті. Екінші бетті жасаңыз — /read/{slug} мекенжайы бойынша бір мақала. article.html үлгісін ашыңыз, мақаланы тілімнен мекенжайы бойынша тауып алыңыз, ал ондай болмаса — өз бетіңізбен, ол да үлгіден, 404 беріңіз. Тақырып пен оқу уақытын құрылымнан, денені Body өрісінен алыңыз.

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

  • Тақырыбы <b>қалың</b> мақала қосып, тегтердің мәтін болып көрінетініне көз жеткізіңіз.
  • html/template-ті text/template-ке ауыстырып, сол бетті қараңыз. Кейін кері қайтарыңыз.
  • Буферді алып тастап, үлгіде жоқ өріске жүгініңіз де, браузердің не алатынын қараңыз.

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

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

Ортақ макетті — бас жағын, аяқ жағын және мәзірді — әр бетте қайталамас үшін бөлек үлгіге шығарады. Бұл өткен сабақтағы ораманың дәл сол тәсілі, тек белгілеу үшін.

Жауаптар

Жауаптарды көрсету
  1. Нүкте — дәл қазір қойылып жатқан дерек. Бетте бұл — бүкіл құрылым, ал range ішінде — тілімнің кезекті элементі. Сондықтан сыртта .Title беттің тақырыбын, ал ішінде мақаланың тақырыбын білдіреді.
  2. Орындалған скриптті емес, <script>alert(1)</script> деген мәтінді: html/template қойған нәрсесінің бәрін экрандайды әрі қою орнына қарай керек тәсілді таңдайды. Оны text/template-тің орнына алудың себебі де осы.
  3. Себебі Execute жол-жөнекей жазады, ал ортадағы қате оқырманды беттің жартысымен және енді өзгертуге келмейтін 200 кодымен қалдырады. Буферге жинасаңыз, не дайынын түгел бересіз, не адал түрде 500 қайтарасыз.

Дереккөздер

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

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

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

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

Пікірлер (0)

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