Memória és helyi lemez
Két Driver-implementáció szállít a gyökér storage modulon belül, a modulon túl semmilyen extra függőség nélkül: a storage/memory, egy in-process fake teszthez, és a storage/local, egy fájlrendszer-alapú driver, ami egy-node-os telepítéshez vagy helyi fejlesztéshez elég jó. Mindkettő ugyanazt a Driver szerződést teljesíti, mint az S3-kompatibilis driver, így a Disk ellen írt kód nem változik, amikor közöttük váltasz.
storage/memory: teszthez
import "github.com/gp-system/storage/memory"
driver := memory.New()
driver := memory.New(memory.WithBaseURL("https://cdn.test"))
disk := storage.NewDisk(driver, storage.WithName("test"))
A memory.New egy in-process map mögé épített Driver-t ad vissza: nincs fájlrendszer, nincs hálózat, nincs konténer. A WithBaseURL állítja be az alapot, amiből az URL linkeket épít (default: egy belső placeholder séma); nélküle az URL is működik, csak nem néz ki valódi linknek. Ez a driver a választás egy service-réteg unit teszthez:
func TestUploadStoresImageAndURL(t *testing.T) {
disk := storage.NewDisk(memory.New(memory.WithBaseURL("https://cdn.test")))
svc := service.NewProductImageService(disk, repo)
url, err := svc.Upload(ctx, "p42", strings.NewReader("png-bytes"), storage.PutOptions{ContentType: "image/png"})
require.NoError(t, err)
require.Equal(t, "https://cdn.test/products/p42/image", url)
require.True(t, disk.Exists(ctx, "products/p42/image"))
}
Ha saját Driver-t írsz e kettő helyett, futtasd le a storage/storagetest suite-ját rajta, hogy megerősítsd, ugyanúgy viselkedik.
storage/local: fájlrendszer-alapú, aláírt URL-ekkel
import "github.com/gp-system/storage/local"
cfg := local.Config{
Root: "/var/data/uploads",
PublicBaseURL: "https://cdn.example.com",
BaseURL: "https://api.example.com/files",
Secret: os.Getenv("STORAGE_LOCAL_SECRET"),
}
driver, err := local.New(cfg)
disk := storage.NewDisk(driver, storage.WithName("uploads"))
| Mező | Jelentés |
|---|---|
Root | a könyvtár, amiből az objektumok olvasódnak és amibe íródnak; minden path ez alatt oldódik fel, és a ValidPath elutasít mindent, ami kiszökne belőle |
PublicBaseURL | a Disk.URL alapja: egy reverse proxy vagy CDN a Root előtt, ha a tartalom publikusan kiszolgált |
BaseURL | az az alap URL, amire a helyi driver saját aláírt linkjei mutatnak, azaz ahova a Handler-ét mountolod |
Secret | a HMAC-kulcs, amivel az aláírt URL-ek generálódnak és ellenőrződnek |
Az aláírt-URL Handler
A storage/local nem csak generálja az aláírt URL-eket, ki is szolgálja őket: a driver.Handler() egy http.Handler-t ad vissza, amit a BaseURL által jelzett path-ra mountolsz, és ez az, ami a beérkező kéréseken tényleg kikényszeríti az aláírást és a lejáratot:
mux := http.NewServeMux()
mux.Handle("/files/", http.StripPrefix("/files/", driver.Handler()))
Egy érvényes, le nem járt aláírás nélküli kérés elutasításra kerül, mielőtt a fájlrendszerhez hozzáérne. Ettől van értelme a TemporaryURL/TemporaryUploadURL-nek egy olyan driveren, ami mögött semmilyen felhő-infrastruktúra nincs: a HMAC-aláírás (a path, egy lejárati időbélyeg és a Secret fölött) az egész hozzáférés-vezérlési mechanizmus, úgyhogy a Secret-et kezeld úgy, mint bármely más credentialt (env-változó, sosem commitolva), és forgasd, ha kiszivárog.
Önálló példa
package main
import (
"context"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/gp-system/storage"
"github.com/gp-system/storage/local"
)
func main() {
driver, err := local.New(local.Config{
Root: "./data",
PublicBaseURL: "http://localhost:8080/files",
BaseURL: "http://localhost:8080/files",
Secret: os.Getenv("STORAGE_LOCAL_SECRET"),
})
if err != nil {
log.Fatal(err)
}
disk := storage.NewDisk(driver, storage.WithName("local"))
ctx := context.Background()
if err := disk.Put(ctx, "hello.txt", strings.NewReader("hello, disk"), storage.PutOptions{}); err != nil {
log.Fatal(err)
}
signed, err := disk.TemporaryURL(ctx, "hello.txt", storage.TemporaryURLOptions{TTL: 5 * time.Minute})
if err != nil {
log.Fatal(err)
}
log.Println("aláírt link:", signed)
mux := http.NewServeMux()
mux.Handle("/files/", http.StripPrefix("/files/", driver.Handler()))
log.Fatal(http.ListenAndServe(":8080", mux))
}
Kapcsolódó oldalak
- Áttekintés: a Driver/Disk/Manager modell, és a dekorátorok, amikkel ezek a driverek komponálhatók.
- Disk API: a
Disk-en keresztül elérhető teljes metódus-készlet. - S3: a driver, amire egy production, több-instance-os telepítéshez váltani érdemes.
- Tesztelés: a konformancia-suite, amin mindkét driver átmegy.