Jaki jest najlepszy sposób łączenia zasobów statycznych w programie Go? [Zamknięte]

100

Pracuję nad małą aplikacją internetową w Go, która ma być używana jako narzędzie na komputerze dewelopera do pomocy w debugowaniu ich aplikacji / usług internetowych. Interfejs programu to strona internetowa, która zawiera nie tylko HTML, ale także JavaScript (dla funkcjonalności), obrazy i CSS (dla stylizacji). Planuję udostępnienie tej aplikacji na zasadach open source, więc użytkownicy powinni po prostu mieć możliwość uruchomienia pliku Makefile, a wszystkie zasoby zostaną umieszczone tam, gdzie muszą. Chciałbym jednak również móc po prostu dystrybuować plik wykonywalny z jak najmniejszą liczbą plików / zależności. Czy istnieje dobry sposób na dołączenie kodu HTML / CSS / JS do pliku wykonywalnego, tak aby użytkownicy musieli pobrać tylko jeden plik i martwić się o niego?


W tej chwili w mojej aplikacji udostępnianie statycznego pliku wygląda trochę tak:

// called via http.ListenAndServe
func switchboard(w http.ResponseWriter, r *http.Request) {

    // snipped dynamic routing...

    // look for static resource
    uri := r.URL.RequestURI()
    if fp, err := os.Open("static" + uri); err == nil {
        defer fp.Close()
        staticHandler(w, r, fp)
        return
    }

    // snipped blackhole route
}

Jest to więc całkiem proste: jeśli żądany plik istnieje w moim katalogu statycznym, wywołaj procedurę obsługi, która po prostu otwiera plik i próbuje ustawić towar Content-Typeprzed podaniem. Pomyślałem, że nie ma powodu, aby było to oparte na prawdziwym systemie plików: gdyby były skompilowane zasoby, mógłbym je po prostu zindeksować przez identyfikator URI żądania i służyć jako takie.

Jeśli nie ma dobrego sposobu na zrobienie tego lub szczekam na niewłaściwe drzewo, próbując to zrobić, daj mi znać. Pomyślałem, że użytkownik końcowy doceniłby jak najmniejszą liczbę plików do zarządzania.

Jeśli są bardziej odpowiednie tagi niż , proszę, dodaj je lub daj mi znać.

Jimmy Sawczuk
źródło
Właściwie pomyślałem dzisiaj o dokładnie tym samym pytaniu. Rozwiązaniem, które mógłbym zbadać, jest użycie go generatemałego narzędzia wiersza poleceń (spakowanego z moim kodem źródłowym) do konwersji plików na []byteplasterki, które są osadzone jako zmienne w kodzie, podobnie jak stringerto się dzieje (patrz blog.golang.org / generuj ).
Ralph,

Odpowiedzi:

76

Wygląda na to, że pakiet go-bindata może być tym, co Cię interesuje.

https://github.com/go-bindata/go-bindata

Umożliwi to przekonwertowanie dowolnego pliku statycznego na wywołanie funkcji, które można osadzić w kodzie, a po wywołaniu zwróci fragment bajtu zawartości pliku.

Daniel
źródło
8
Głosowanie za tym wydaje się w moim przypadku dziwnie samoobsługowe, ale i tak to zrobię: p Jednak dla przypomnienia, nie jest to pakiet, ale narzędzie wiersza poleceń.
jimt
Tak dla przypomnienia, to droga, którą obrałem w swoim projekcie. W pewnym momencie @jimt wprowadził kilka nowych funkcji, aby uczynić rzeczy bardziej przyjaznymi dla użytkownika, ale nie zapewniał już takiej szczegółowości, jakiej potrzebowałem, więc napisałem własne narzędzie, które ma mniej funkcji, ale jest przeznaczone do mojego przypadku (używam tego narzędzia jako swego rodzaju preambuła do procesu budowania): github.com/jimmysawczuk/go-binary
Jimmy Sawczuk
37

Osadzanie plików tekstowych

Jeśli mówimy o plikach tekstowych, można je łatwo osadzić w samym kodzie źródłowym. Po prostu użyj cudzysłowów wstecz, aby zadeklarować stringliterał w następujący sposób:

const html = `
<html>
<body>Example embedded HTML content.</body>
</html>
`

// Sending it:
w.Write([]byte(html))  // w is an io.Writer

Wskazówka dotycząca optymalizacji:

Ponieważ w większości przypadków wystarczy zapisać zasób do pliku io.Writer, możesz również zapisać wynik []bytekonwersji:

var html = []byte(`
<html><body>Example...</body></html>
`)

// Sending it:
w.Write(html)  // w is an io.Writer

Jedyną rzeczą, na którą musisz uważać, jest to, że surowe literały ciągów nie mogą zawierać znaku cudzysłowu (`). Surowe literały ciągów nie mogą zawierać sekwencji (w przeciwieństwie do zinterpretowanych literałów ciągów), więc jeśli tekst, który chcesz osadzić, zawiera cudzysłowy wsteczne, musisz przerwać nieprzetworzony literał ciągu i połączyć cudzysłowy jako zinterpretowane literały ciągów, jak w tym przykładzie:

var html = `<p>This is a back quote followed by a dot: ` + "`" + `.</p>`

Nie ma to wpływu na wydajność, ponieważ te konkatenacje będą wykonywane przez kompilator.

Osadzanie plików binarnych

Przechowywanie jako kawałek bajtu

W przypadku plików binarnych (np. Obrazów) najbardziej zwartym (pod względem wynikowego natywnego pliku binarnego) i najbardziej wydajnym byłoby umieszczenie zawartości pliku []bytew kodzie źródłowym. Może to zostać wygenerowane przez zewnętrzne biblioteki toos / biblioteki, takie jak go-bindata .

Jeśli nie chcesz do tego używać biblioteki innej firmy, oto prosty fragment kodu, który odczytuje plik binarny i wyświetla kod źródłowy Go, który deklaruje zmienną typu, []bytektóra zostanie zainicjowana z dokładną zawartością pliku:

imgdata, err := ioutil.ReadFile("someimage.png")
if err != nil {
    panic(err)
}

fmt.Print("var imgdata = []byte{")
for i, v := range imgdata {
    if i > 0 {
        fmt.Print(", ")
    }
    fmt.Print(v)
}
fmt.Println("}")

Przykładowe dane wyjściowe, jeśli plik zawierałby bajty od 0 do 16 (spróbuj w Go Playground ):

var imgdata = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}

Przechowywanie jako base64 string

Jeśli plik nie jest „zbyt duży” (większość obrazów / ikon kwalifikuje się), istnieją również inne przydatne opcje. Możesz przekonwertować zawartość pliku na Base64 stringi zapisać ją w kodzie źródłowym. Podczas uruchamiania aplikacji ( func init()) lub w razie potrzeby możesz zdekodować ją do oryginalnej []bytetreści. Go ma dobre wsparcie dla kodowania Base64 w encoding/base64pakiecie.

Konwersja pliku (binarnego) do formatu base64 stringjest tak prosta, jak:

data, err := ioutil.ReadFile("someimage.png")
if err != nil {
    panic(err)
}
fmt.Println(base64.StdEncoding.EncodeToString(data))

Przechowuj wynikowy ciąg base64 w swoim kodzie źródłowym, np. Jako plik const.

Dekodowanie to tylko jedno wywołanie funkcji:

const imgBase64 = "<insert base64 string here>"

data, err := base64.StdEncoding.DecodeString(imgBase64) // data is of type []byte

Przechowywanie zgodnie z notowaniem string

Bardziej wydajne niż przechowywanie jako base64, ale może być dłuższe w kodzie źródłowym, jest przechowywanie cytowanego literału ciągu danych binarnych. Możemy otrzymać cytowaną postać dowolnego ciągu za pomocą strconv.Quote()funkcji:

data, err := ioutil.ReadFile("someimage.png")
if err != nil {
    panic(err)
}
fmt.Println(strconv.Quote(string(data))

W przypadku danych binarnych zawierających wartości od 0 do 64 tak wyglądałoby dane wyjściowe (wypróbuj na Go Playground ):

"\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?"

(Zwróć uwagę, że strconv.Quote()dołącza i poprzedza cudzysłów).

Możesz bezpośrednio użyć tego cytowanego ciągu w swoim kodzie źródłowym, na przykład:

const imgdata = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?"

Jest gotowy do użycia, nie trzeba go dekodować; usuwanie cytatów jest wykonywane przez kompilator Go w czasie kompilacji.

Możesz również zapisać go jako fragment bajtu, jeśli potrzebujesz go w ten sposób:

var imgdata = []byte("\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?")
icza
źródło
czy istnieje sposób na powiązanie shpliku z plikiem wykonywalnym go?
Kasun Siyambalapitiya
Wydaje mi się, że dane powinny być imgdata w pierwszym fragmencie kodu w sekcji „przechowywanie jako fragment bajtu”.
logiczne x 2
1
@deusexmachina Masz rację, naprawiłeś to. Kod na placu zabaw był już poprawny.
icza
2

jest też egzotyczny sposób - używam wtyczki maven do budowania projektów GoLang i pozwala ona na użycie preprocesora JCP do osadzania bloków binarnych i plików tekstowych w źródłach. W przypadku kod wygląda jak linia poniżej ( a przykład można znaleźć tutaj )

var imageArray = []uint8{/*$binfile("./image.png","uint8[]")$*/}
Igor Maznitsa
źródło
@ czy można powiązać katalog zawierający shplik wykonywalny lub plik wykonywalny jak powyżej
Kasun Siyambalapitiya
@KasunSiyambalapitiya Bind a directory? Powiązać shplik? Nie wiem co masz na myśli. Jeśli chcesz, aby wszystko w katalogu zostało osadzone, jest to coś, z czym zrobiłem go-bindata. Na przykład, jeśli wstawię //go:generate $GOPATH/bin/go-bindata -prefix=data/ -pkg=$GOPACKAGE data/(nie wygenerowany) plik go, go generate ./...uruchomię go-bindata w katalogu pakietu, osadzając wszystko w podkatalogu danych, ale z usuniętym prefiksem „data /”.
Mark
1

Jako popularna alternatywa dla go-bindatawspomnianej w innej odpowiedzi, mjibson / esc również osadza dowolne pliki, ale szczególnie wygodnie obsługuje drzewa katalogów.

robx
źródło