Was ist der beste Weg, um statische Ressourcen in einem Go-Programm zu bündeln? [geschlossen]

100

Ich arbeite an einer kleinen Webanwendung in Go, die als Tool auf dem Computer eines Entwicklers verwendet werden soll, um das Debuggen seiner Anwendungen / Webdienste zu unterstützen. Die Schnittstelle zum Programm ist eine Webseite, die nicht nur HTML, sondern auch JavaScript (für die Funktionalität), Bilder und CSS (für das Styling) enthält. Ich plane, diese Anwendung als Open-Sourcing-Lösung zu verwenden, damit Benutzer einfach ein Makefile ausführen können und alle Ressourcen dorthin gelangen, wo sie benötigt werden. Ich möchte aber auch einfach eine ausführbare Datei mit so wenig Dateien / Abhängigkeiten wie möglich verteilen können. Gibt es eine gute Möglichkeit, HTML / CSS / JS mit der ausführbaren Datei zu bündeln, sodass Benutzer nur eine Datei herunterladen und sich um sie kümmern müssen?


Im Moment sieht das Bereitstellen einer statischen Datei in meiner App ungefähr so ​​aus:

// 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
}

Es ist also ziemlich einfach: Wenn die angeforderte Datei in meinem statischen Verzeichnis vorhanden ist, rufen Sie den Handler auf, der die Datei einfach öffnet und versucht, Content-Typevor dem Serving ein Gut festzulegen. Mein Gedanke war, dass es keinen Grund gibt, warum dies auf dem realen Dateisystem basieren muss: Wenn es kompilierte Ressourcen gäbe, könnte ich sie einfach anhand der Anforderungs-URI indizieren und als solche dienen.

Wenn es keinen guten Weg gibt, dies zu tun, oder ich den falschen Baum belle, indem ich versuche, dies zu tun, lassen Sie es mich wissen. Ich dachte nur, der Endbenutzer würde es begrüßen, wenn so wenig Dateien wie möglich verwaltet werden könnten.

Wenn es passendere Tags als gibt , zögern Sie nicht, sie hinzuzufügen oder lassen Sie es mich wissen.

Jimmy Sawczuk
quelle
Ich habe heute eigentlich nur an genau die gleiche Frage gedacht. Die Lösung, die ich möglicherweise untersuchen möchte, besteht darin go generate, die Dateien mit einem kleinen Befehlszeilenprogramm (zusammen mit meinem Quellcode) in []byteSlices zu konvertieren, die als Variablen in den Code eingebettet sind, ähnlich wie dies der stringerFall ist (siehe blog.golang.org) / generieren ).
Ralph

Antworten:

76

Das go-bindata-Paket scheint das zu sein, woran Sie interessiert sind.

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

Damit können Sie jede statische Datei in einen Funktionsaufruf konvertieren, der in Ihren Code eingebettet werden kann, und beim Aufruf ein Byte-Slice des Dateiinhalts zurückgeben.

Daniel
quelle
8
Das Upvoting scheint in meinem Fall seltsam eigennützig zu sein, aber ich werde es trotzdem tun: p Für die Aufzeichnung ist es jedoch kein Paket, sondern ein Befehlszeilentool.
Jimt
Nur zur Veranschaulichung, dies ist der Weg, den ich mit meinem Projekt eingeschlagen habe. Irgendwann hat @jimt einige neue Funktionen eingeführt, um die Benutzerfreundlichkeit zu verbessern, aber nicht mehr die von mir benötigte Granularität bereitzustellen. Deshalb habe ich mein eigenes Tool geschrieben, das weniger Funktionen enthält, aber für meinen Anwendungsfall konzipiert ist (ich verwende dieses Tool als eine Art von Präambel des Erstellungsprozesses): github.com/jimmysawczuk/go-binary
Jimmy Sawczuk
37

Einbetten von Textdateien

Wenn es sich um Textdateien handelt, können diese problemlos in den Quellcode selbst eingebettet werden. Verwenden Sie einfach die hinteren Anführungszeichen, um das stringLiteral wie folgt zu deklarieren :

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

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

Optimierungstipp:

Da Sie die Ressource in den meisten Fällen nur in eine schreiben müssen io.Writer, können Sie auch das Ergebnis einer []byteKonvertierung speichern :

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

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

Sie müssen nur vorsichtig sein, dass rohe String-Literale das hintere Anführungszeichen (`) nicht enthalten dürfen. Raw-String-Literale dürfen keine Sequenzen enthalten (im Gegensatz zu den interpretierten String-Literalen). Wenn der einzubettende Text also Anführungszeichen enthält, müssen Sie das Raw-String-Literal aufbrechen und Back-Anführungszeichen als interpretierte String-Literale verketten, wie in diesem Beispiel:

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

Die Leistung wird nicht beeinträchtigt, da diese Verkettungen vom Compiler ausgeführt werden.

Einbetten von Binärdateien

Speichern als Byte-Slice

Für Binärdateien (z. B. Bilder) wäre es am kompaktesten (in Bezug auf die resultierende native Binärdatei) und am effizientesten, den Inhalt der Datei []bytein Ihrem Quellcode zu haben. Dies kann von Toos / Bibliotheken von Drittanbietern wie go-bindata generiert werden .

Wenn Sie hierfür keine Bibliothek eines Drittanbieters verwenden möchten, finden Sie hier ein einfaches Codefragment, das eine Binärdatei liest und Go-Quellcode ausgibt, der eine Variable vom Typ deklariert []byte, die mit dem genauen Inhalt der Datei initialisiert wird:

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("}")

Beispielausgabe, wenn die Datei Bytes von 0 bis 16 enthalten würde (versuchen Sie es auf dem Go Playground ):

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

Speichern als base64 string

Wenn die Datei nicht "zu groß" ist (die meisten Bilder / Symbole sind qualifiziert), gibt es auch andere praktikable Optionen. Sie können den Inhalt der Datei in eine Base64 konvertieren stringund in Ihrem Quellcode speichern. Beim Start der Anwendung ( func init()) oder bei Bedarf können Sie sie in den ursprünglichen []byteInhalt dekodieren . Go bietet eine gute Unterstützung für die Base64-Codierung im encoding/base64Paket.

Das Konvertieren einer (binären) Datei in base64 stringist so einfach wie:

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

Speichern Sie die Ergebnis-Base64-Zeichenfolge in Ihrem Quellcode, z const. B. als .

Das Dekodieren ist nur ein Funktionsaufruf:

const imgBase64 = "<insert base64 string here>"

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

Lagerung wie angegeben string

Effizienter als Base64 speichern, aber mehr im Quellcode kann sein wird , um die Speicherung zitierte Stringliteral der binären Daten. Wir können die zitierte Form jeder Zeichenfolge mit der folgenden strconv.Quote()Funktion erhalten:

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

Bei Binärdaten mit Werten von 0 bis 64 würde die Ausgabe folgendermaßen aussehen (versuchen Sie es auf dem 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:;<=>?"

(Beachten Sie, dass strconv.Quote()ein Anführungszeichen angehängt und vorangestellt wird.)

Sie können diese Zeichenfolge in Anführungszeichen direkt in Ihrem Quellcode verwenden, zum Beispiel:

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:;<=>?"

Es ist gebrauchsfertig und muss nicht dekodiert werden. Das Aufheben der Anführungszeichen erfolgt durch den Go-Compiler zur Kompilierungszeit.

Sie können es auch als Byte-Slice speichern, wenn Sie es so benötigen:

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
quelle
Gibt es eine Möglichkeit, eine shDatei an eine ausführbare Datei zu binden ?
Kasun Siyambalapitiya
Ich denke, Daten sollten imgdata im ersten Code-Snippet unter "Speichern als Byte-Slice" sein.
logisch x 2
1
@deusexmachina Du hast recht, behoben. Der Code auf dem Spielplatz war bereits korrekt.
icza
2

Es gibt auch eine exotische Möglichkeit: Ich verwende das Maven-Plugin , um GoLang-Projekte zu erstellen, und es ermöglicht die Verwendung des JCP-Präprozessors zum Einbetten von Binärblöcken und Textdateien in Quellen. Im Fallcode sehen Sie einfach wie in der Zeile unten aus ( und ein Beispiel finden Sie hier )

var imageArray = []uint8{/*$binfile("./image.png","uint8[]")$*/}
Igor Maznitsa
quelle
@ ist es möglich, ein Verzeichnis mit einer shoder einer ausführbaren Datei wie oben zu binden
Kasun Siyambalapitiya
@KasunSiyambalapitiya Ein Verzeichnis binden? Eine shDatei binden ? Nicht sicher was du meinst. Wenn Sie möchten, dass alles in einem Verzeichnis eingebettet wird, habe ich dies getan go-bindata. Wenn ich beispielsweise //go:generate $GOPATH/bin/go-bindata -prefix=data/ -pkg=$GOPACKAGE data/eine (nicht generierte) go-Datei go generate ./...eingebe , wird go-bindata im Paketverzeichnis ausgeführt und alles in ein Daten-Unterverzeichnis eingebettet, wobei das Präfix 'data /' entfernt wird.
Mark
1

Als beliebte Alternative zu einer go-bindataanderen Antwort bettet mjibson / esc auch beliebige Dateien ein, behandelt Verzeichnisbäume jedoch besonders bequem.

robx
quelle