Srautinis maišymas ir saugyklos sąsaja
Autentifikacija baigta; dabar programa įgyja savo objektą — dokumentus. Ši pamoka yra apie įkėlimą, ir ji iškart perteikia dvi mintis: saugyklos sluoksnį už sąsajos (ta pati jungtis, kurią ką tik sukūrei el. paštui) ir failą, kuris maišomas jį rašant, niekada nekaupiant viso atmintyje.
Saugykla už sąsajos — jungtis antrą kartą. Kur gyvena baitai — sprendimas, kurį norėsi keisti vėliau: dev rašo į ./uploads, produkcija nori objektų saugyklos (S3, R2, Railway tomo). Todėl saugykla slypi už trijų metodų sąsajos, lygiai kaip el. paštas:
type Store interface {
Save(ctx context.Context, r io.Reader) (key string, size int64, sha256hex string, err error)
Open(ctx context.Context, key string) (io.ReadCloser, error)
Delete(ctx context.Context, key string) error
}
LocalStore yra dev realizacija. Įstatyti S3Store vėliau — konstruktoriaus pakeitimas main faile, ne perrašymas: „produkcijoje tai objektų saugykla; čia tai aplankas diske." Šią formą matei su email.Sender; pamatyti ją antrą kartą ir yra esmė — taip infrastruktūros pasirinkimas nenuteka į kiekvieną valdiklį.
Maišyk rašydamas — vienu perėjimu, be kaupimo. Naivus įkėlimas nuskaito visą failą į []byte, jį sumaišo, tada rašo. 25 MiB failas tampa 25 MiB atminties vienam lygiagrečiam įkėlimui — ir viskas griūva vos atkeliavus dviem dideliems failams kartu. Sprendimas — io.MultiWriter:
h := sha256.New()
size, err := io.Copy(io.MultiWriter(f, h), r) // vienas nuskaitymas; baitai šakojasi į failą IR maišytuvą
Kiekvienas iš užklausos nuskaitytas baitas rašomas į failą ir paduodamas maišytuvui tuo pačiu perėjimu. Atmintis lieka plokščia, koks bebūtų failas, o kai io.Copy grįžta, turi ir dydį, ir SHA-256 — dovanai, nuskaitęs baitus lygiai vieną kartą. Ta maiša atsiduria dokumento eilutėje; būtent ji vėliau leidžia parašui pririšti tikslius pasirašytus baitus.
Saugojimo raktas nėra eilutės ID. Saugykla generuoja savo raktą — 16 atsitiktinių baitų kaip 32 šešioliktainiai ženklai — atsietą nuo dokumento UUID. Ir kiekvienas Open/Delete patikrina tą raktą regex'u prieš liesdamas diską:
var keyPattern = regexp.MustCompile(`^[a-f0-9]{32}$`)
Saugojimo raktas ateina iš duomenų bazės ir prijungiamas prie aplanko kelio. Jei raktas kada nors būtų ../../etc/passwd, filepath.Join mielai išeitų iš įkėlimų aplanko. Kiekvieno rakto patikra pagal ^[a-f0-9]{32}$ prieš naudojimą reiškia, kad raktas gali įvardyti tik failą bazinio aplanko viduje — klastojimas neįmanomas iš principo, ne dėl vilties, kad duomenys švarūs.
Griežta dydžio riba. http.MaxBytesReader apvynioja užklausos kūną, kad klientas negalėtų srautu siųsti neriboto failo ir išsemti disko; viskas virš 25 MiB nukertama, o valdiklis grąžina 413. Riba gyvena konfigūracijoje (MaxUploadBytes), nuskaitoma kartą.
HTMX daro įkėlimą gyvą. Įkėlimo forma siunčia su hx-post + hx-encoding="multipart/form-data"; atsakymas — tik atnaujintas dokumentų sąrašo fragmentas, pakeičiamas vietoje, be viso puslapio perkrovimo. <progress> elementas, sujungtas su htmx xhr:progress įvykiais, rodo tikrą įkėlimo juostą. Tai — pagrindinis šio kurso argumentas apčiuopiamai: serverio pusėje atvaizduojama programa, jokio SPA, o sąveika jaučiasi betarpiška.
Ši pamoka:
storage.Storesąsaja +LocalStore,documentslentelė ir dvi jos užklausos, konfigūracija ir sujungimas.- Įkėlimo valdiklis ir skydelis, rodantis, ką įkėlei — nuo pradžios iki galo, su gyva progreso juosta.