Go тілі: нөлден өз блогыңызға дейін 26-сабақ (барлығы 50)
Go-дағы баптау: флагтар, орта айнымалылары және кодтан тыс құпия сөз
Go курсының жиырма алтыншы сабағы. Мекенжай, дерекқор файлы және қолтаңба кілті кодтан кетеді. flag пакеті мен -h беретін анықтама, орта айнымалысын флагтың әдепкі мәні ету, бос айнымалының берілмегеннен несімен өзгешелігі және міндетті баптау болмаса неге іске қосылғанда құлау керек.
Не үшін керек
Блог кодында :8080 мен қалтаға апаратын жол жазулы тұр. Өз компьютеріңізде жалғыз жұмыс істегенде бұл байқалмайды. Мәселе бағдарлама басқа жерде іске қосылуға тиіс күні басталады.
Серверде порт басқа. Басқа компьютерде 80-порт бос болмауы мүмкін. Тесттерде өз порты керек. Әрбір осындай айырма — не кодты түзетіп қайта жинау, не сырттан баптау.
Оның үстіне жақында дерекқор пайда болады, онымен бірге — құпия сөз. Git-ке түскен құпия сөз тарихта мәңгі қалады: жолды өшіру жеткіліксіз, ол кез келген ескі коммитте көрініп тұрады. Сондықтан баптауды сыртқа шығаруды шығаратын нәрсе пайда болғанға дейін үйрену керек.
Бірден тұтас
Жаңа қалта, go mod init sabaq24, main.go:
package main
import (
"errors"
"flag"
"fmt"
"log/slog"
"net/http"
"os"
_ "modernc.org/sqlite"
)
var log = slog.New(slog.NewTextHandler(os.Stdout, nil))
// Бағдарламаның барлық баптауы бір жерде. Іске қосылғанда бір рет жиналады,
// әрі қарай оларды тек оқиды.
type config struct {
addr string
dbPath string
secret string
dev bool
}
// Орта айнымалысының мәні, ал айнымалы жоқ болса — қосалқысы.
func env(name, fallback string) string {
if v, ok := os.LookupEnv(name); ok {
return v
}
return fallback
}
// Үш дереккөз бірден. Әдепкі мән кодта тұр, орта айнымалысы оны басады, ал
// флаг екеуін де басады — өйткені айнымалы флаг үшін әдепкі мәнге айналады.
func load() (config, error) {
var c config
flag.StringVar(&c.addr, "addr", env("BLOG_ADDR", ":8080"), "мекенжай мен порт")
flag.StringVar(&c.dbPath, "db", env("BLOG_DB_PATH", "blog.db"), "SQLite дерекқор файлы")
flag.StringVar(&c.secret, "secret", env("BLOG_SECRET", ""), "cookie қолтаңбасына арналған кілт")
flag.BoolVar(&c.dev, "dev", env("BLOG_DEV", "") == "1", "әзірлеуге арналған толық жауаптар")
flag.Parse()
if c.secret == "" {
return c, errors.New("қолтаңба кілті берілмеген: -secret немесе BLOG_SECRET")
}
return c, nil
}
func main() {
c, err := load()
if err != nil {
fmt.Fprintln(os.Stderr, "баптау:", err)
os.Exit(1)
}
// Кілтті журналға жартылай да жазбайды: тек ұзындығын басамыз.
log.Info("іске қосыламыз", "мекенжай", c.addr, "дерекқор", c.dbPath,
"кілт берілді", true, "кілт ұзындығы", len(c.secret), "әзірлеу", c.dev)
mux := http.NewServeMux()
mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Менің блогым")
})
if err := http.ListenAndServe(c.addr, mux); err != nil {
log.Error("сервер тоқтады", "қате", err)
os.Exit(1)
}
}
Оны бөлек файл етіп жинаңыз — сонда анықтамада бағдарламаның аты көрінеді:
go build -o sabaq24 .
./sabaq24
баптау: қолтаңба кілті берілмеген: -secret немесе BLOG_SECRET
Талдау
Баптау — бытыраңқы os.Getenv емес, құрылым
Азғырық қарапайым: мән керек болған жерде os.Getenv("BLOG_ADDR") деп жаза салу. Олай істемеу керек, әрі себебі әдемілікте емес.
Жиналған құрылым «не бапталады өзі» деген сұраққа типке бір қараумен жауап береді. Бытыраңқы шақырулар оған мүлде жауап бермейді: тізімді білу үшін бүкіл кодты іздеуге тура келеді, әрі сонда да бірдеңені жіберіп аласыз.
Екіншіден: бір рет жиналған құрылым бір рет тексеріледі. os.Getenv әр сұраныста шақырылса, айнымалы атындағы қате іске қосылғанда емес, бір аптадан кейін нағыз серверде шығады.
Елестетіп көріңіз. Пәтердің кілттері әр қалтада және шкафтағы күртеде бір-бірден емес, қалтадағы бір шоқта. Ұқыпты болғаны үшін емес, шығар алдында бәрі орнында ма екенін бір қимылмен тексергің келгені үшін.
Флагтар: flag пакеті және -h не беретіні
flag.StringVar(&c.addr, "addr", ":8080", "мекенжай мен порт")
flag.Parse()
StringVar мәнді тікелей құрылым өрісіне салады. Төрт аргумент: қайда салу, флагтың аты қалай, флаг болмаса не алу, және адамға арналған сипаттама.
flag.Parse() міндетті: оған дейін айнымалыларда әдепкі мәндер жатады. Оны барлық флаг жарияланғаннан кейін бір рет шақырады.
Сипаттама әдемілік үшін емес. Ол анықтамаға айналады:
./sabaq24 -h
Usage of ./sabaq24:
-addr string
мекенжай мен порт (default ":8080")
-db string
SQLite дерекқор файлы (default "blog.db")
-dev
әзірлеуге арналған толық жауаптар
-secret string
cookie қолтаңбасына арналған кілт
Бұл анықтаманы сіз ол үшін бір жол да жазбай алдыңыз. Әрі байқаңыз: әдепкі мәндер көрсетілген, ал -secret-те ол жоқ — өйткені қолтаңба кілтінде орынды әдепкі мән болмайды.
Флаг атындағы қате де өңделген:
./sabaq24 -port 9000
flag provided but not defined: -port
Usage of ./sabaq24:
Бағдарлама түсінбеген флагпен іске қосылмайды, ал шығу коды 2 болады. Бұл дұрыс мінез: -port дегенді үнсіз елемеу нашарырақ болар еді.
Орта айнымалысы — флагтың әдепкі мәні
Сабақтың ең ыңғайлы тәсілі бір жолға сыяды:
flag.StringVar(&c.addr, "addr", env("BLOG_ADDR", ":8080"), "мекенжай мен порт")
Үшінші аргумент — тұрақты емес, функция шақыруы. Сондықтан басымдық реті бір де бір if-сіз өзінен-өзі шығады:
- флаг та жоқ, айнымалы да жоқ — кодтағы
:8080алынады; - айнымалы бар — сол алынады;
- флаг бар — сол алынады, айнымалыда не жатса да.
Тексеріңіз:
BLOG_ADDR=:8290 BLOG_SECRET=k4z-secret-key ./sabaq24
level=INFO msg="іске қосыламыз" мекенжай=:8290 дерекқор=blog.db "кілт берілді"=true "кілт ұзындығы"=14 әзірлеу=false
Журнал үзінділерінен уақыт алынып тасталды: ол әр іске қосылғанда өзгереді.
BLOG_ADDR=:8290 BLOG_SECRET=k4z-secret-key ./sabaq24 -addr :8390
level=INFO msg="іске қосыламыз" мекенжай=:8390 дерекқор=blog.db "кілт берілді"=true "кілт ұзындығы"=14 әзірлеу=false
Айнымалы :8290 деді, флаг :8390 деді, флаг жеңді.
Бос және «берілмеген» — әртүрлі нәрсе
Сондықтан env ішінде os.Getenv емес, os.LookupEnv тұр.
os.Getenv айнымалы жоқта да, бар боп бос тұрғанда да бос жол қайтарады. Ажырату мүмкін емес. os.LookupEnv екінші мән қайтарады — ат мүлде жарияланған ба:
if v, ok := os.LookupEnv(name); ok {
return v
}
Айырма бірден көрінеді. Айнымалы берілмеген:
мекенжай=:8080
Айнымалы бос берілген — BLOG_ADDR= ./sabaq24 …:
мекенжай=""
Екінші жағдай біріншімен бірдей емес: net/http үшін бос мекенжай «барлық мекенжай, әдепкі порт» дегенді білдіреді, яғни сексенінші. Скриптте BLOG_ADDR= деп жазған адам «сол күйінде қалдыр» дегісі келді, ал басқа порт алды.
Getenv болса, мұны байқамас та едіңіз. LookupEnv-те бос мән бос болып қалады да, анық берілгендей әрекет етеді — яғни дәл қалай берілсе, солай.
Міндетті баптау жоқ — іске қосылғанда құлаймыз
if c.secret == "" {
return c, errors.New("қолтаңба кілті берілмеген: -secret немесе BLOG_SECRET")
}
Мекенжай мен дерекқор файлында орынды әдепкі мән бар, ал қолтаңба кілтінде — жоқ әрі бола алмайды. Бағдарлама онсыз жұмыс істемеуі керек. Демек, бұл туралы оқырманның бірінші сұранысында емес, бірінші секундта білу керек.
Мұндай құлауды пайдалы ететін үш ұсақ нәрсе. Хабарлама кәдімгі шығысқа емес, os.Stderr-ге кетеді — журнал мен қатені бөледі. Шығу коды 0 емес, 1 — сол бойынша systemd не docker іске қосылу сәтсіз болғанын түсінеді де, бәрі жақсы деп көрсетпейді. Әрі мәтінде мәнді берудің екі тәсілі де аталған, адам бастапқы кодқа үңілмеуі үшін.
Кілт кодқа да, журналға да түспейді
Қолтаңба кілті — нағыз құпия: оны білген адам cookie-ді жасанды жасап, бөтеннің атынан кіре алады. Кодпен түсінікті — ол онда жоқ. Бірақ ұмытылатын екінші ағу бар: журнал.
Түзету үшін «кілт: abc123» деп жазуға азғырады. Біз тек көмектесетінін әрі ештеңе шығармайтынын басамыз:
log.Info("іске қосыламыз", "кілт берілді", true, "кілт ұзындығы", len(c.secret))
"кілт берілді"=true "кілт ұзындығы"=14
Кілттің алынғанын әрі қиылмағанын түсінуге осы жетеді. Кілттің өзі жоқ. Ал журналдар сақтау жүйелеріне кетеді, скриншотқа және қолдаумен хат алысуға түседі.
Мұнда тәсілді емес, жалпы ережені есте сақтаған жөн: құпияны не мүлде баспайды, не одан қалпына келтіру мүмкін емес нәрсені басады — ұзындығын, бар екенінің белгісін. «Құпия сөздің жартысын» емес: жартысы әдетте жеткілікті болып қалады.
Әрі бүгін енгізуге тұрарлық ереже: нағыз кілттер жатқан баптау файлын .env деп атайды да, бірден .gitignore-ға қосады. Бірінші коммиттен кейін емес — оған дейін.
Нені қайда береді
Флагтар өз компьютерінде ыңғайлы: терминал тарихында көрінеді, оңай ауыстырылады, ештеңе экспорттаудың қажеті жоқ.
Орта айнымалылары серверде ыңғайлы: оларды бағдарламаны іске қосатын нәрсе береді — systemd, docker, хостинг, — әрі олар ps-тің кәдімгі шығысына да, пәрмендер тарихына да түспейді. Флагпен берілген кілт машинаның кез келген пайдаланушысына ps-те көрінеді; айнымалымен берілген — жоқ.
Айнымалыларды жасырын сақтау орны деп ойламауыңыз үшін ескерту. Бұл қорғаныс емес, тек көзге аз түсу: ps eww процестің ортасын өзіңізге көрсетеді, ал root оны бәрінде көреді — тексерілген. Құпияның нағыз қорғанысы — .env файлының құқықтары әрі осы машинада процесс іске қоса алатын адамның құқықтары.
Содан кәдімгі бөліну шығады: өз компьютерінде флагтар, серверде айнымалылар. Бағдарламаға бәрібір — ол екеуін де оқиды.
Сабақ картасы
Өз сөзіңізбен айтыңыз
Қарамай, дауыстап немесе қағазға жауап беріңіз. Жауаптар — сабақтың соңында.
- Орта айнымалысын неге
flag.StringVar-ға үшінші аргумент етіп береді, флагпен қолмен салыстырмайды? os.LookupEnvos.Getenv-тен несімен өзгеше және айырма қашан көрінеді?- Кодта жоқ болса, құпия сөзді журналға жазу неге қауіпті?
Тапсырма
Міндетті. config-ке журнал деңгейін қосыңыз. Оны info не debug жолымен беріп, slog.Level-ге айналдырыңыз. Белгісіз мән — үнсіз info емес, іске қосылғандағы қате. Статика жолын баптаудың қажеті де, жөні де жоқ: ол статика сабағында embed арқылы бағдарламаға кірістірілген.
Қалауыңызша.
- Нұсқаны басып шығарып, бағдарламаны
0кодымен аяқтайтын-versionфлагын қосыңыз. BLOG_ADDR=дегенді бос қылып, сервердің қай портта болатынын қараңыз.LookupEnv-тіGetenv-ке ауыстырып, бағдарламаның өзгеше әрекет ететін жағдайын табыңыз.
Блогта бұл қайда тұрады
Баптау бір жерге жиналды, дерекқор жолы да солардың арасында. blog.db файлын әзірге ешкім ашпайды — онымен келесі модуль айналысады, әрі ондағы config-ті өзгертудің қажеті болмайды.
Қарыздар. Баптау файлы бізде жоқ: блогқа флагтар мен айнымалылар жетеді, ал yaml не toml баптау отыз болғанда керек болады. Тексеру әзірге біреу — «кілт бос емес»; мекенжай үшін пішінді, ал кілт ұзындығы үшін ең азын тексерген жөн еді. Әрі flag міндетті флагты білмейді: -secret міндетті болғаны — оны Parse-тан кейін өзіміз тексергендіктен.
Жауаптар
Жауаптарды көрсету
- Өйткені сонда басымдық реті өзінен-өзі шығады: флагтың әдепкі мәні айнымалыдан есептеледі, ал флаг берілсе, оны басады. Қолмен салыстыру — әр баптауға артық
ifжәне кім кімді басатынын шатастыру оңай жер. Getenvберілмеген айнымалыға да, бос берілгенге де бос жол қайтарады — бұл жағдайлар ажыратылмайды.LookupEnvекінші мән ретінде «айнымалы жарияланған» белгісін қайтарады. Айырма бос мән әдейі берілгенде көрінеді:Getenv-те бағдарлама әдепкі мәнді қояды,LookupEnv-те — сұрағандай, бос күйінде қалдырады.- Өйткені журнал көрінгеннен ұзақ өмір сүреді әрі алысырақ саяхаттайды: ол журнал сақтау жүйесіне кетеді, хат алысудағы скриншотқа түседі, оны серверге қолжетімі жоқ адам оқиды. Қосылу жолын құпия сөзсіз басады — бағдарламаның қайда қосылғанын түсінуге қалғаны жетеді.
Пікірлер (0)
Пікір қалдыру үшін кіріңіз →
Әзірге пікір жоқ. Бірінші болыңыз.