Голанг обслуживает статические файлы из памяти

У меня есть быстрый вопрос об обслуживании файлов в Go. Существует большой разворачивающий обработчик FileServer, но для моего варианта использования у меня есть только 2 или 3 файла (js и css), которые идут с моим приложением, и я не хочу усложнять развертывание, чтобы подумать об этом.

Как вы думаете, есть простой способ собрать эти два файла в двоичный файл и обслуживать их оттуда. Например, base64 кодирует данные файлов как константы и обрабатывает файлы из констант. Это будет работать в самой простой форме, но я не хочу испытывать боль, делая все, что делает файловый сервер (заголовки, истечения срока, типы mime и т.д.) Самостоятельно. Так будет ли простой способ выпекать эти статические файлы в двоичном виде в той или иной форме и обслуживать их таким образом?

Ответы

Ответ 1

Пакет go.rice" позаботится об этом для вас - встраивает ресурсы в ваши двоичные файлы и предоставляет реализацию http.FileSystem.

Ответ 2

Для 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, как это было предложено, полностью зависит от вас и вашего проекта! Однако я подозреваю, что для обслуживания нескольких предопределенных файлов переопределение обслуживающей части может быть проще, чем эмуляция полной файловой системы.

Ответ 3

Я бы сохранил файлы в переменной как обычный текст. Что-то вроде этого:

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

Ответ 4

Не так сложно делать то, что вы запрашиваете. Вам не нужно, чтобы 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)
}