storage

Memória és helyi lemez

A két driver, ami a gyökér storage modulban szállít, teszthez és egy-node-os telepítéshez.

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
Roota 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
PublicBaseURLa Disk.URL alapja: egy reverse proxy vagy CDN a Root előtt, ha a tartalom publikusan kiszolgált
BaseURLaz az alap URL, amire a helyi driver saját aláírt linkjei mutatnak, azaz ahova a Handler-ét mountolod
Secreta 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

main.go
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.
Copyright © 2026