Как лучше всего объединить статические ресурсы в программу Go? [закрыто]

100

Я работаю над небольшим веб-приложением на Go, которое предназначено для использования на компьютере разработчика в качестве инструмента для отладки их приложений / веб-сервисов. Интерфейс программы представляет собой веб-страницу, которая включает не только HTML, но и некоторый JavaScript (для функциональности), изображения и CSS (для стилизации). Я планирую сделать это приложение открытым исходным кодом, поэтому пользователи должны просто иметь возможность запускать Makefile, и все ресурсы будут направляться туда, куда им нужно. Однако я также хотел бы иметь возможность просто распространять исполняемый файл с как можно меньшим количеством файлов / зависимостей. Есть ли хороший способ связать HTML / CSS / JS с исполняемым файлом, чтобы пользователям нужно было загружать только один файл и беспокоиться о нем?


Прямо сейчас в моем приложении обслуживание статического файла выглядит примерно так:

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

Так что это довольно просто: если запрошенный файл существует в моем статическом каталоге, вызовите обработчик, который просто открывает файл и пытается установить товар Content-Typeперед обслуживанием. Я думал, что нет причин, по которым это должно быть основано на реальной файловой системе: если бы были скомпилированные ресурсы, я мог бы просто проиндексировать их по URI запроса и обслуживать их как таковые.

Если нет хорошего способа сделать это, или я лаю не на то дерево, пытаясь сделать это, дайте мне знать. Я просто подумал, что конечный пользователь будет признателен за управление как можно меньшим количеством файлов.

Если подходящих тегов больше, чем , пожалуйста, добавьте их или дайте мне знать.

Джимми Савчук
источник
На самом деле я сегодня только что подумал о том же самом вопросе. Решение, которое я мог бы изучить, - использовать go generateнебольшую утилиту командной строки (упакованную с моим исходным кодом) для преобразования файлов в []byteфрагменты, которые встраиваются как переменные в код, аналогично тому, как stringerэто делается (см. Blog.golang.org / генерировать ).
Ральф

Ответы:

76

Пакет go-bindata выглядит так, как будто это может быть то, что вас интересует.

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

Это позволит вам преобразовать любой статический файл в вызов функции, который может быть встроен в ваш код, и при вызове вернет байтовый фрагмент содержимого файла.

Даниэль
источник
8
В моем случае такое голосование кажется странным самообслуживанием, но я все равно сделаю это: p Для записи, это не пакет, а инструмент командной строки.
jimt
Для протокола, это путь, по которому я пошел со своим проектом. В какой-то момент @jimt представил некоторые новые функции, чтобы сделать вещи более удобными для пользователя, но больше не обеспечивал требуемую степень детализации, поэтому я написал свой собственный инструмент, который имеет меньше функций, но предназначен для моего использования (я использую этот инструмент как своего рода преамбула к процессу сборки): github.com/jimmysawczuk/go-binary
Джимми Савчук
37

Встраивание текстовых файлов

Если мы говорим о текстовых файлах, их можно легко встроить в сам исходный код. Просто используйте обратные кавычки, чтобы объявить stringлитерал следующим образом:

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

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

Совет по оптимизации:

Поскольку в большинстве случаев вам нужно будет только записать ресурс в объект io.Writer, вы также можете сохранить результат []byteпреобразования:

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

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

Единственное, с чем следует быть осторожным, это то, что необработанные строковые литералы не могут содержать символ обратной кавычки (`). Необработанные строковые литералы не могут содержать последовательности (в отличие от интерпретируемых строковых литералов), поэтому, если текст, который вы хотите вставить, действительно содержит обратные кавычки, вы должны разбить необработанный строковый литерал и объединить обратные кавычки как интерпретированные строковые литералы, как в этом примере:

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

На производительность это не влияет, так как эти конкатенации будут выполняться компилятором.

Встраивание двоичных файлов

Сохранение в виде байтового среза

Для двоичных файлов (например, изображений) наиболее компактным (относительно результирующего нативного двоичного файла) и наиболее эффективным было бы иметь содержимое файла как []byteв исходном коде. Это может быть создано сторонними toos / библиотеками, такими как go-bindata .

Если вы не хотите использовать для этого стороннюю библиотеку, вот простой фрагмент кода, который читает двоичный файл и выводит исходный код Go, который объявляет переменную типа, []byteкоторая будет инициализирована точным содержимым файла:

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

Пример вывода, если файл будет содержать байты от 0 до 16 (попробуйте на Go Playground ):

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

Хранение как base64 string

Если файл не «слишком большой» (подходит для большинства изображений / значков), есть и другие жизнеспособные варианты. Вы можете преобразовать содержимое файла в Base64 stringи сохранить его в исходном коде. При запуске приложения ( func init()) или при необходимости вы можете декодировать его в исходное []byteсодержимое. В encoding/base64пакете Go есть хорошая поддержка кодировки Base64 .

Преобразование (двоичного) файла в base64 stringтак же просто, как:

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

Сохраните строку результата base64 в исходном коде, например, как файл const.

Расшифровка - это всего лишь вызов одной функции:

const imgBase64 = "<insert base64 string here>"

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

Хранение как указано string

Более эффективным, чем хранение в формате base64, но может быть больше в исходном коде, является хранение строкового литерала в кавычках двоичных данных. Мы можем получить цитированную форму любой строки с помощью strconv.Quote()функции:

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

Для двоичных данных, содержащих значения от 0 до 64, результат будет выглядеть следующим образом (попробуйте на 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:;<=>?"

(Обратите внимание, что strconv.Quote()к нему добавляются кавычки.)

Вы можете напрямую использовать эту строку в кавычках в своем исходном коде, например:

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

Готово к использованию, расшифровывать не нужно; снятие кавычек выполняется компилятором Go во время компиляции.

Вы также можете сохранить его как байтовый фрагмент, если вам это понадобится:

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
источник
есть ли способ привязать shфайл к исполняемому файлу go?
Касун Сиямбалапития
Я предполагаю, что данные должны быть imgdata в первом фрагменте кода в разделе «сохранение в виде байтового среза».
logical x 2
1
@deusexmachina Вы правы, исправили. Код на детской площадке уже был правильным.
icza
2

также есть экзотический способ - я использую плагин maven для создания проектов GoLang, который позволяет использовать препроцессор JCP для встраивания двоичных блоков и текстовых файлов в исходники. В этом случае код просто выглядит как строка ниже ( и здесь можно найти пример )

var imageArray = []uint8{/*$binfile("./image.png","uint8[]")$*/}
Игорь Мазница
источник
@ Возможно ли привязать каталог, имеющий shили исполняемый файл, как указано выше
Касун Сиямбалапития
@KasunSiyambalapitiya Привязать каталог? Привязать shфайл? Не уверен, что вы имеете в виду. Если вы хотите, чтобы все в каталоге было встроено, я это сделал go-bindata. Например, если я вставлю //go:generate $GOPATH/bin/go-bindata -prefix=data/ -pkg=$GOPACKAGE data/(не сгенерированный) файл go, go generate ./...запустит go-bindata в каталоге пакета, встраивая все в подкаталог данных, но с удаленным префиксом data /.
Марк
1

В качестве популярной альтернативы go-bindataупомянутому в другом ответе mjibson / esc также встраивает произвольные файлы, но особенно удобно обрабатывает деревья каталогов.

robx
источник