代码之家  ›  专栏  ›  技术社区  ›  Feras

Golang提供来自内存的静态文件

  •  18
  • Feras  · 技术社区  · 11 年前

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

    您认为有没有一种简单的方法可以将这些文件构建成二进制文件并从那里为它们提供服务。例如,base64将文件的数据编码为常量,并根据常量为文件提供服务器。这将以最简单的形式工作,但我不想独自完成文件服务器所做的一切(头、表达式、mime类型等)。那么,有没有一种简单的方法可以将这些静态文件以某种形式烘焙成二进制文件并以这种方式提供它们?

    4 回复  |  直到 11 年前
        1
  •  21
  •   val    11 年前

    这个 FileServer 需要 FileSystem 对象。通常,你会提供一些基于 http.Dir 要做到这一点 文件系统 对于实际的文件系统,但没有什么可以阻止您实现自己的:

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

    此实施是 非常 最好是bug,你可能永远不会使用它,但它应该向你展示 文件系统 接口可以为任意“文件”实现。

    一个更可信(当然也不那么危险)的类似实现是可用的 here 。这是过去的 fake the filesystem 所以它应该是一个很好的参考(无论如何比我的好得多)。

    重新实现这一点是否更简单 文件系统 接口或自定义 文件服务器 正如其他人所建议的,这完全取决于您和您的项目!然而,我怀疑,对于服务几个预定义的文件,重写服务部分可能比模拟完整的文件系统更容易。

        2
  •  8
  •   Greg    11 年前

    go.rice 包为您处理了这一点——在二进制文件中嵌入资源,并提供http.FileSystem实现。

        3
  •  5
  •   ANisus    11 年前

    按照你的要求做并不困难。您不必对其进行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)
    }
    
        4
  •  4
  •   creack    11 年前

    我会将文件以纯文本形式存储在变量中。 类似于:

    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