Go тілі: нөлден өз блогыңызға дейін 49-сабақ (барлығы 50)
Іске қосар алдында: үміт емес, тексеру парағы
Go курсының қырық тоғызыншы сабағы. Таймаутсыз сервер қосылымды шексіз ұстайды — өлшенді: баяу клиент оны бос емес қылды, ал бірінші болып біз бас тарттық. Қоса жұмыс істеп тұрған блогтан алынған дерекқор көшірмесі, іздеуге арналған сипаттама әрі он үш тармақ.
Не үшін керек
Блог шығарылды, домен жұмыс істейді, мекенжай жолағында құлып бар. Бір нәрсе қалды: сілтемені тірі адамдарға берер алдында тізімнен өту.
Тізім — бюрократия емес. Ондағы әр тармақ біреу бір күні оны өткізіп жіберіп, кейін түнде жөндегендіктен тұр. Үш тармақты дәл қазір жабамыз, өйткені олар блогта әлі жоқ.
Бірден тұтас
Жаңа қалта, go mod init sabaq46. Бағдарлама баяу клиентті бейнелейді: ол қосылым ашады әрі тақырыпты жүз миллисекундта бір байттан жібереді, оны ешқашан аяқтамайды.
package main
import (
"fmt"
"net"
"net/http"
"time"
)
// Сервер қосылымды өзі жапқанша қанша күтуге дайынбыз. Осы уақытта жаппаса —
// ешқашан жаппайды деген сөз.
const patience = 2 * time.Second
func main() {
fmt.Println("== баяу клиент: таймаутсыз сервер")
fmt.Println(hold(&http.Server{Handler: page()}))
fmt.Println()
fmt.Println("== сол клиент: ReadHeaderTimeout қойылған сервер")
fmt.Println(hold(&http.Server{
Handler: page(),
ReadHeaderTimeout: 300 * time.Millisecond,
}))
fmt.Println()
fmt.Println("== серверді бос емес қылуға осындай қосылым қанша керек")
fmt.Println("машинада горутиналарға қанша жады болса, сонша —")
fmt.Println("бір баяу жол бір горутинаны қалағанша ұстап тұрады.")
}
func page() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "бет")
})
}
// hold қосылым ашады, тақырыпты байттап жібереді әрі кім бірінші берілетінін
// қарайды: сервер қосылымды жаба ма, әлде біздің шыдамымыз бітеді ме.
func hold(srv *http.Server) string {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return "портты алу мүмкін болмады: " + err.Error()
}
defer l.Close()
go srv.Serve(l)
defer srv.Close()
conn, err := net.Dial("tcp", l.Addr().String())
if err != nil {
return "қосылу мүмкін болмады: " + err.Error()
}
defer conn.Close()
start := time.Now()
fmt.Fprint(conn, "GET / HTTP/1.1\r\nHost: blog\r\n")
// Ешқашан бітпейтін тақырып: 100 мс сайын бір байттан.
go func() {
for {
if _, err := fmt.Fprint(conn, "X"); err != nil {
return
}
time.Sleep(100 * time.Millisecond)
}
}()
conn.SetReadDeadline(time.Now().Add(patience))
buf := make([]byte, 64)
n, err := conn.Read(buf)
waited := time.Since(start).Round(50 * time.Millisecond)
switch {
case err == nil && n > 0:
return fmt.Sprintf("сервер %v өткенде жауап берді: %q", waited, string(buf[:n]))
case isTimeout(err):
return fmt.Sprintf("қосылым тұр: біз %v күттік те, бірінші болып бас тарттық", waited)
default:
return fmt.Sprintf("сервер қосылымды %v өткенде жапты", waited)
}
}
// isTimeout — біздің өз шыдамымыз бітті ме, әлде басқа нәрсе ме.
func isTimeout(err error) bool {
var ne net.Error
if e, ok := err.(net.Error); ok {
ne = e
}
return ne != nil && ne.Timeout()
}
Шығатыны:
== баяу клиент: таймаутсыз сервер
қосылым тұр: біз 2s күттік те, бірінші болып бас тарттық
== сол клиент: ReadHeaderTimeout қойылған сервер
сервер 300ms өткенде жауап берді: "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain; charset=utf-"
== серверді бос емес қылуға осындай қосылым қанша керек
машинада горутиналарға қанша жады болса, сонша —
бір баяу жол бір горутинаны қалағанша ұстап тұрады.
Талдау
Таймаутсыз сервер мәңгі күтеді
Бірінші блок — сөз айналымы емес: қосылым біз күтуге келіскенше тұрды. Сервер емес, біз бас тарттық.
Бұл — ескі әрі әлі күнге жұмыс істейтін шабуыл: осындай мың қосылым ашып, бірде-бірін аяқтамау. Әрқайсысы горутинаны, жадты әрі кезектегі орынды алады — әрі сервер «құламай» тұрып, тірі оқырмандарға жауап беруді қояды.
Екінші блокта сол клиент ReadHeaderTimeout: 300ms-ке тіреледі: 300 мс өткенде сервер өзі 400 Bad Request деп жауап беріп, қосылымды жабады.
Серверді жасағанда бір ғана бөлік:
srv := &http.Server{
Handler: handler,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 1 << 16,
}
Әрқайсысы нені білдіреді:
ReadHeaderTimeout— тақырыптарды қанша күтеміз. Мұнда жомарт болудың реті жоқ: тақырыптар кішкентай әрі бірден келеді;ReadTimeout— денесімен қоса бүкіл сұраныс. Нашар байланыспен сурет жүктелетін уақыттан үлкен болуы керек;WriteTimeout— біздің жағымыз: осы уақытта жазылмаған жауап мүлде жазылмайды;IdleTimeout— қосылымды сұраныстар арасында қанша ашық ұстаймыз. Браузерлер оларды қайта пайдаланады, сондықтан мұнда минуттар;MaxHeaderBytes— бұдан үлкен тақырып оқырман емес, қате немесе шабуыл.
Біз бастаған http.ListenAndServe бұлардың бірде-бірін қоймайды. Сабақ үшін бұл қалыпты, интернет үшін — жоқ.
Дерекқор көшірмесін файлды көшіріп алуға болмайды
Блогта болмаған екінші тармақ: сақтық көшірме. Азғыруы — cp blog.db copy.db. Олай істеуге болмайды: көшірудің ортасына түскен жазу жартыларды келіспей қалдырады, әрі көшірме мүлде ашылмауы мүмкін.
SQLite-тің өз жауабы бар:
vacuum into 'copy.db'
Ол оқу транзакциясының астында беттерді аралап, тұтас дерекқорды жазады — жұмыс істеп тұрған блогта, оны тоқтатпай. 34-қадамда бұл — Backup әдісі мен -backup жалаушасы.
Көшірмені көзбен емес, pragma integrity_check сұрауымен тексеру керек — ол ok дейді немесе бұзылғандарды тізеді.
Әрі сақтық көшірменің басты ережесі: бір рет те жайып көрмеген көшірме — көшірме емес, үміт. Айына бір рет оны ашып, мақалалардың орнында екенін қараңыз.
Іздеу көрсететін сипаттама
Үшінші тармақ — өз атың мен белгің туралы сабақтың қарызы. Беттерде <meta name="description"> жоқ еді, ал бұл — іздеу жүйесі сілтеменің астында көрсететін жол. Ол болмаса — мәтіннің кездейсоқ бөлігін көрсетеді.
Қадамда бұл — үлгі блогы: тізім беті өз сипаттамасын береді, мақала беті сөз бойынша қиылған мәтіннің басын алады:
"short": short,
Байт бойынша қиюға болмайды — қазақ әрпін қақ бөледі; сөздің ортасынан қию ұсқынсыз. Сондықтан short руналарды санайды әрі соңғы бос орында тоқтайды.
Жұмыс істеп тұрған блогта шыққаны:
== іздеу көрсететін сипаттама
басты бет: тізім үлгісіндегі <meta name="description"> — 70 таңба
мақала: мақала мәтінінің басы, сөз бойынша қиылған — 37 таңба
== жұмыс істеп тұрған блогта алынған дерекқор көшірмесі
$ ./blog -backup copy.db
көшірме жасалды
blog.db integrity_check=ok, мақала=3
copy.db integrity_check=ok, мақала=3
Тізімнің өзі
Он үш тармақ. Әрқайсысының қасында — бір минутта қалай тексеру керегі.
Қауіпсіздік
- Құпиясөздер — тек хешпен.
select * from usersқұпиясөзге ұқсас ештеңе көрсетпеуі керек. - Формалар CSRF-тен қорғалған. Форманы жетонсыз жіберіңіз —
403болуы керек. - Бөтен мәтін экрандалады. Мақала тақырыбына
<script>alert(1)</script>жазыңыз — бетте терезе емес, әріптер тұруы керек. Content-Security-Policy,X-Content-Type-Options,Referrer-Policyтақырыптары тұр:curl -Iкөрсетеді.- HTTPS жұмыс істейді,
httpбағытталады:curl -I http://сіздің-домен. - git-те құпия жоқ:
git log -p | grep -i "password\|token"ештеңе таппауы керек.
Төзімділік
- Сервер таймауттары қойылған — жоғарыда өлшенді.
- Жүктеу көлемі шектелген:
http.MaxBytesHandler, шектен үлкен файл жіберіп көріңіз. - Блог ұқыпты тоқтайды:
kill -TERM, шығу коды0. - Қызмет блогты құлағаннан әрі қайта жүктелгеннен кейін көтереді:
systemctl enable --now.
Дерек пен адамдар
- Дерекқор көшірмесі алынады әрі жайып көру арқылы тексерілген.
- Беттерде
<title>мен<meta name="description">бар. - Журнал жазылады әрі оқылады: сұраныс кезінде
journalctl -u blog -f.
Бірде-бір тармақ жаңа білім талап етпейді — бұның бәрі сабақтарда болды. Тізім басқа нәрсе үшін керек: сілтемені беруге ақыры бел буған кеште жадқа сенбеу үшін.
Сабақ картасы
Өз сөзіңізбен айтыңыз
Қарамай, дауыстап немесе қағазға жауап беріңіз. Жауаптар — сабақтың соңында.
ReadHeaderTimeout-сыз сервер «құламаса» да, неге қауіпті?vacuum intocp blog.db copy.db-дан несімен жақсы?- Бір рет те ашылмаған сақтық көшірменің несі дұрыс емес?
Тапсырма
Міндетті. Блогыңызда он үш тармақтан өтіп, нені сәйкес келмегенін жазып алыңыз. Үш жаңа тармақты жабыңыз: сервер таймауттары, vacuum into арқылы дерекқор көшірмесі әрі беттердің сипаттамасы.
Мұның бәрі step-34 ішінде жасалған — өзіңіз жасағаннан кейін салыстырыңыз.
Қалауыңызша.
- Көшірмені кесте бойынша жасаңыз:
systemd timerнемесеcron-дағы жол, әрі көшірмелерді атында күні бар бөлек қалтаға салыңыз. - Кешегі көшірмені қасында басқа портта жайып, блогтың содан көтерілетініне көз жеткізіңіз.
robots.txtпен сайт картасын қосыңыз: іздеу жүйесіне оңай, ал сізге — бір бет код.
Блогта бұл қайда тұрады
34-қадам үш қарызды жабады: серверде таймауттар, дерекқорда көшірме, беттерде сипаттама пайда болды.
Блогта бұдан басқа қарыз жоқ. Қалғаны — қарыз емес, келесі қадам: пікірлер, жобалар, бірнеше автор, әлеуметтік желіге арналған сурет. Бұл туралы — курстың соңғы сабағында.
Жауаптар
Жауаптарды көрсету
- Өйткені баяу қосылымдар горутиналар мен жадты алады, әрі сервер «жұмыс істеп тұрып» тірі оқырмандарға жауап беруді қояды. Өлшенді: таймаутсыз қосылым біз бас тартқанша тұрды.
- Өйткені
cpдәл сол сәтте жазылып жатқан файлды көшіреді: жартылары келіспей қалуы мүмкін, әрі көшірме ашылмайды.vacuum intoблогты тоқтатпай, оқу транзакциясының астында тұтас дерекқорды жазады. - Ол ашылмауы мүмкін, ал сіз мұны дәл ол керек болған күні білесіз. Көшірмені жайып көру арқылы тексереді — әйтпесе бұл көшірме емес, үміт.
Пікірлер (0)
Пікір қалдыру үшін кіріңіз →
Әзірге пікір жоқ. Бірінші болыңыз.