Golang从内存中提供静态文件

时间:2014-02-06 06:07:40

标签: http static go assets

我有一个关于在Go中提供文件的快速问题。有很大的省时FileServer处理程序,但对于我的用例,我只有2或3个文件(js和css)与我的应用程序一起使用,我不想让部署复杂化,不得不考虑这些。

您认为有一种简单的方法可以将这些文件构建到二进制文件中并从那里提供它们。例如,base64将文件的数据编码为常量,并从常量中为文件提供服务。这将以最简单的形式工作,但我不想经历我自己做文件服务器所做的一切(标题,expiries,mime-types等)的痛苦。那么有一种简单的方法可以将这些静态文件以某种形式烘焙到二进制文件中并以这种方式提供它们吗?

4 个答案:

答案 0 :(得分:19)

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

此实现最多只是非常错误,您可能永远不会使用它,但它应该向您展示如何为任意&#39;文件实现FileSystem接口#39 ;.

可以使用类似的更可靠(并且肯定不那么危险)的实现here。这是Go操场上常用的fake the filesystem,所以它应该是一个很好的参考(比我的好多了)。

重新实现此FileSystem界面或其他建议的自定义FileServer是否更简单,完全取决于您和您的项目!但我怀疑,为了提供几个预定义文件,重写服务部分可能比模拟完整的文件系统更容易。

答案 1 :(得分:8)

go.rice”包为您解决了这个问题 - 在二进制文件中嵌入资源,并提供http.FileSystem实现。

答案 2 :(得分:5)

按要求做事并不是很困难。您不必对它进行base64编码或任何操作(这将使您更难编辑。)。

以下是如何输出具有正确mime类型的javascript文件的示例:

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

答案 3 :(得分:4)

我会将文件作为纯文本存储在变量中。 像这样:

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或构建脚本非常容易,例如:

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