Ответ 1
Пакет go.rice" позаботится об этом для вас - встраивает ресурсы в ваши двоичные файлы и предоставляет реализацию http.FileSystem.
У меня есть быстрый вопрос об обслуживании файлов в Go. Существует большой разворачивающий обработчик FileServer, но для моего варианта использования у меня есть только 2 или 3 файла (js и css), которые идут с моим приложением, и я не хочу усложнять развертывание, чтобы подумать об этом.
Как вы думаете, есть простой способ собрать эти два файла в двоичный файл и обслуживать их оттуда. Например, base64 кодирует данные файлов как константы и обрабатывает файлы из констант. Это будет работать в самой простой форме, но я не хочу испытывать боль, делая все, что делает файловый сервер (заголовки, истечения срока, типы mime и т.д.) Самостоятельно. Так будет ли простой способ выпекать эти статические файлы в двоичном виде в той или иной форме и обслуживать их таким образом?
Пакет go.rice" позаботится об этом для вас - встраивает ресурсы в ваши двоичные файлы и предоставляет реализацию http.FileSystem.
Для FileServer
требуется объект FileSystem
в его конструкторе. Обычно вы предоставляете что-то на основе http.Dir
, чтобы сделать это FileSystem
для вас из фактической файловой системы, но ничто не мешает вам реализовать свои собственные:
package main
import "os"
import "time"
import "net/http"
type InMemoryFS map[string]http.File
// Implements FileSystem interface
func (fs InMemoryFS) Open(name string) (http.File, error) {
if f, ok := fs[name]; ok {
return f, nil
}
panic("No file")
}
type InMemoryFile struct {
at int64
Name string
data []byte
fs InMemoryFS
}
func LoadFile(name string, val string, fs InMemoryFS) *InMemoryFile {
return &InMemoryFile{at: 0,
Name: name,
data: []byte(val),
fs: fs}
}
// Implements the http.File interface
func (f *InMemoryFile) Close() error {
return nil
}
func (f *InMemoryFile) Stat() (os.FileInfo, error) {
return &InMemoryFileInfo{f}, nil
}
func (f *InMemoryFile) Readdir(count int) ([]os.FileInfo, error) {
res := make([]os.FileInfo, len(f.fs))
i := 0
for _, file := range f.fs {
res[i], _ = file.Stat()
i++
}
return res, nil
}
func (f *InMemoryFile) Read(b []byte) (int, error) {
i := 0
for f.at < int64(len(f.data)) && i < len(b) {
b[i] = f.data[f.at]
i++
f.at++
}
return i, nil
}
func (f *InMemoryFile) Seek(offset int64, whence int) (int64, error) {
switch whence {
case 0:
f.at = offset
case 1:
f.at += offset
case 2:
f.at = int64(len(f.data)) + offset
}
return f.at, nil
}
type InMemoryFileInfo struct {
file *InMemoryFile
}
// Implements os.FileInfo
func (s *InMemoryFileInfo) Name() string { return s.file.Name }
func (s *InMemoryFileInfo) Size() int64 { return int64(len(s.file.data)) }
func (s *InMemoryFileInfo) Mode() os.FileMode { return os.ModeTemporary }
func (s *InMemoryFileInfo) ModTime() time.Time { return time.Time{} }
func (s *InMemoryFileInfo) IsDir() bool { return false }
func (s *InMemoryFileInfo) Sys() interface{} { return nil }
const HTML = `<html>
Hello world !
</html>
`
const CSS = `
p {
color:red;
text-align:center;
}
`
func main() {
FS := make(InMemoryFS)
FS["foo.html"] = LoadFile("foo.html", HTML, FS)
FS["bar.css"] = LoadFile("bar.css", CSS, FS)
http.Handle("/", http.FileServer(FS))
http.ListenAndServe(":8080", nil)
}
Эта реализация в лучшем случае очень глючна, и вы, вероятно, никогда ее не используете, но она должна показать вам, как интерфейс FileSystem
может быть реализован для произвольных "файлов".
Более надежная (и, конечно, менее опасная) реализация чего-то подобного доступна здесь. Это используется для подделать файловую систему на игровой площадке Go, поэтому она должна быть хорошей ссылкой (намного лучше, чем моя в любом случае).
Легче ли переопределить этот интерфейс FileSystem
или пользовательский FileServer
, как это было предложено, полностью зависит от вас и вашего проекта! Однако я подозреваю, что для обслуживания нескольких предопределенных файлов переопределение обслуживающей части может быть проще, чем эмуляция полной файловой системы.
Я бы сохранил файлы в переменной как обычный текст. Что-то вроде этого:
package main
import (
"fmt"
"log"
"net/http"
)
var files = map[string]string{}
func init() {
files["style.css"] = `
/* css file content */
body { background-color: pink; }
`
}
func init() {
files["index.html"] = `
<!-- Html content -->
<html><head>
<link rel="stylesheet" type="text/css" href="style.css">
</head><body>Hello world!</body></html>
`
}
func main() {
for fileName, content := range files {
contentCpy := content
http.HandleFunc("/"+fileName, func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s\n", contentCpy)
})
}
log.Fatal(http.ListenAndServe(":8080", nil))
}
Таким образом, довольно легко создать свой makefile или построить script, чтобы что-то вроде:
for file in index.html style.css; do echo "package main\nfunc init() { files[\"$file\"] = \`$(cat $file)\` }" | gofmt -s > $file.go; done; go build && ./httptest
Не так сложно делать то, что вы запрашиваете. Вам не нужно, чтобы base64 закодировал его или что-то еще (это просто затруднит вам редактирование.).
Ниже приведен пример вывода файла javascript с правильным типом mime:
package main
import (
"fmt"
"log"
"net/http"
)
const jsFile = `alert('Hello World!');`
func main() {
http.HandleFunc("/file.js", JsHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func JsHandler(w http.ResponseWriter, r *http.Request) {
// Getting the headers so we can set the correct mime type
headers := w.Header()
headers["Content-Type"] = []string{"application/javascript"}
fmt.Fprint(w, jsFile)
}