Apa cara terbaik untuk menggabungkan sumber daya statis dalam program Go? [Tutup]

100

Saya sedang mengerjakan aplikasi web kecil di Go yang dimaksudkan untuk digunakan sebagai alat pada mesin pengembang untuk membantu men-debug aplikasi / layanan web mereka. Antarmuka ke program adalah halaman web yang tidak hanya mencakup HTML, tetapi beberapa JavaScript (untuk fungsionalitas), gambar dan CSS (untuk gaya). Saya berencana untuk membuat aplikasi ini menjadi sumber terbuka, jadi pengguna harus dapat menjalankan Makefile dan semua sumber daya akan pergi ke mana pun mereka harus pergi. Namun, saya juga ingin dapat mendistribusikan file yang dapat dieksekusi dengan sesedikit mungkin file / dependensi. Apakah ada cara yang baik untuk memaketkan HTML / CSS / JS dengan yang dapat dieksekusi, sehingga pengguna hanya perlu mengunduh dan mengkhawatirkan satu file?


Saat ini, di aplikasi saya, menyajikan file statis terlihat seperti ini:

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

Jadi cukup sederhana: jika file yang diminta ada di direktori statis saya, panggil handler, yang akan membuka file tersebut dan mencoba mengatur kebaikan Content-Typesebelum disajikan. Pemikiran saya adalah bahwa tidak ada alasan ini perlu didasarkan pada sistem file yang sebenarnya: jika ada sumber daya yang dikompilasi, saya dapat mengindeksnya dengan URI permintaan dan melayani mereka seperti itu.

Jika tidak ada cara yang baik untuk melakukan ini, atau saya menggonggong pohon yang salah dengan mencoba melakukan ini, beri tahu saya. Saya baru saja membayangkan pengguna akhir akan menghargai sesedikit mungkin file untuk dikelola.

Jika ada lebih banyak tag yang sesuai daripada , jangan ragu untuk menambahkannya atau beri tahu saya.

Jimmy Sawczuk
sumber
Saya sebenarnya baru saja memikirkan pertanyaan yang persis sama hari ini. Solusi yang mungkin saya jelajahi adalah menggunakan go generateutilitas baris perintah kecil (dikemas dengan kode sumber saya) untuk mengonversi file menjadi []byteirisan yang disematkan sebagai variabel dalam kode, mirip dengan bagaimana stringermelakukannya (lihat blog.golang.org / hasilkan ).
Ralph

Jawaban:

76

Paket go-bindata sepertinya sesuai dengan minat Anda.

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

Ini akan memungkinkan Anda untuk mengonversi file statis apa pun menjadi panggilan fungsi yang dapat disematkan dalam kode Anda dan akan mengembalikan potongan byte dari konten file saat dipanggil.

Daniel
sumber
8
Upvoting ini nampaknya anehnya melayani diri sendiri dalam kasus saya, tetapi saya akan tetap melakukannya: p Sebagai catatan, ini bukan paket, tetapi alat baris perintah.
jimt
Sekadar catatan, ini adalah jalan yang saya ambil dengan proyek saya. Pada titik tertentu @jimt memperkenalkan beberapa fitur baru untuk membuat segalanya lebih ramah pengguna tetapi tidak lagi memberikan perincian yang saya butuhkan, jadi saya menulis alat saya sendiri yang memiliki lebih sedikit fitur tetapi dirancang untuk kasus penggunaan saya (saya menggunakan alat ini sebagai semacam pembukaan proses pembangunan): github.com/jimmysawczuk/go-binary
Jimmy Sawczuk
37

Menyematkan File Teks

Jika kita berbicara tentang file teks, mereka dapat dengan mudah disematkan di kode sumber itu sendiri. Cukup gunakan tanda kutip belakang untuk menyatakan stringliteral seperti ini:

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

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

Tip pengoptimalan:

Karena sebagian besar waktu Anda hanya perlu menulis sumber daya ke io.Writer, Anda juga dapat menyimpan hasil []bytekonversi:

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

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

Satu-satunya hal yang harus Anda perhatikan adalah literal string mentah tidak boleh berisi karakter kutipan belakang (`). Literal string mentah tidak boleh berisi urutan (tidak seperti literal string yang ditafsirkan), jadi jika teks yang ingin Anda sematkan memang berisi tanda kutip balik, Anda harus menghentikan literal string mentah dan menggabungkan tanda kutip kembali sebagai literal string yang ditafsirkan, seperti dalam contoh ini:

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

Performa tidak terpengaruh, karena penggabungan ini akan dijalankan oleh kompilator.

Menyematkan File Biner

Menyimpan sebagai potongan byte

Untuk file biner (mis. Gambar) yang paling ringkas (berkenaan dengan biner asli yang dihasilkan) dan paling efisien adalah memiliki konten file sebagai []bytedalam kode sumber Anda. Ini dapat dibuat oleh toos / library pihak ketiga seperti go-bindata .

Jika Anda tidak ingin menggunakan pustaka pihak ketiga untuk ini, berikut cuplikan kode sederhana yang membaca file biner, dan mengeluarkan kode sumber Go yang mendeklarasikan variabel jenis []byteyang akan diinisialisasi dengan konten file yang tepat:

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

Contoh keluaran jika file akan berisi byte dari 0 hingga 16 (coba di Go Playground ):

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

Menyimpan sebagai base64 string

Jika file tidak "terlalu besar" (kebanyakan gambar / ikon memenuhi syarat), ada opsi lain yang layak juga. Anda dapat mengonversi konten file ke Base64 stringdan menyimpannya di kode sumber Anda. Saat aplikasi dimulai ( func init()) atau saat diperlukan, Anda bisa mendekodekannya ke []bytekonten asli . Go memiliki dukungan yang bagus untuk pengkodean Base64 dalam encoding/base64paket.

Mengonversi file (biner) ke base64 stringsemudah:

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

Simpan hasil string base64 dalam kode sumber Anda, misalnya sebagai const.

Mendekode itu hanya satu panggilan fungsi:

const imgBase64 = "<insert base64 string here>"

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

Menyimpan seperti dikutip string

Lebih efisien daripada menyimpan sebagai base64, tetapi mungkin lebih lama dalam kode sumber adalah menyimpan literal string yang dikutip dari data biner. Kita bisa mendapatkan bentuk kutipan dari string apa pun menggunakan strconv.Quote()fungsi:

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

Untuk data biner yang berisi nilai-nilai dari 0 hingga 64, seperti inilah keluarannya (coba di 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:;<=>?"

(Perhatikan bahwa strconv.Quote()menambahkan dan menambahkan tanda kutip padanya.)

Anda dapat langsung menggunakan string yang dikutip ini di kode sumber Anda, misalnya:

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

Ini siap digunakan, tidak perlu memecahkan kodenya; penghapusan kutipan dilakukan oleh kompilator Go, pada waktu kompilasi.

Anda juga dapat menyimpannya sebagai potongan byte jika Anda membutuhkannya seperti itu:

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
sumber
apakah ada cara untuk mengikat shfile ke file yang dapat dieksekusi?
Kasun Siyambalapitiya
Saya kira data harus imgdata dalam potongan kode pertama di bawah bagian "menyimpan sebagai potongan byte".
logis x 2
1
@deusexmachina Anda benar, perbaiki. Kode di taman bermain sudah benar.
icza
2

juga ada beberapa cara eksotis - saya menggunakan plugin maven untuk membangun proyek GoLang dan memungkinkan untuk menggunakan preprocessor JCP untuk menanamkan blok biner dan file teks ke dalam sumber. Dalam kode kasus hanya terlihat seperti baris di bawah ini ( dan beberapa contoh dapat ditemukan di sini )

var imageArray = []uint8{/*$binfile("./image.png","uint8[]")$*/}
Igor Maznitsa
sumber
@Apakah mungkin untuk mengikat direktori yang memiliki shatau dapat dieksekusi seperti di atas
Kasun Siyambalapitiya
@KasunSyambalapitiya Mengikat direktori? Ikat shfile? Tidak yakin apa yang kamu maksud. Jika Anda ingin semua yang ada di direktori disematkan, ini adalah sesuatu yang telah saya lakukan go-bindata. Misalnya, jika saya memasukkan //go:generate $GOPATH/bin/go-bindata -prefix=data/ -pkg=$GOPACKAGE data/file go (tidak dihasilkan), go generate ./...akan menjalankan go-bindata di dir paket, menyematkan semuanya di subdir data tetapi dengan awalan 'data /' dihapus.
Tandai
1

Sebagai alternatif populer untuk go-bindatadisebutkan dalam jawaban lain, mjibson / esc juga menyematkan file arbitrer, tetapi menangani pohon direktori dengan nyaman.

robx
sumber