简体   繁体   English

Golang从内存中提供静态文件

[英]Golang serve static files from memory

I have a quick question about serving files in Go. 我有一个关于在Go中提供文件的快速问题。 There is the great timesaving FileServer handler, but for my use case, I only have 2 or 3 files (js and css) that go with my app and I dont want to complicate the deployment to have to think about those. 有很大的省时FileServer处理程序,但对于我的用例,我只有2或3个文件(js和css)与我的应用程序一起使用,我不想让部署复杂化,不得不考虑这些。

Do you think there is an easy way to build those couple of files into the binary and serve them from there. 你认为有一种简单的方法可以将这些文件构建到二进制文件中并从那里提供它们。 For example base64 encode the data of the files as constants and server the files from the constants. 例如,base64将文件的数据编码为常量,并从常量中为文件提供服务。 This would work in its most simple form, but I dont want to go through the pain of doing everything that a file server does (headers, expiries, mime-types, etc) on my own. 这将以最简单的形式工作,但我不想经历我自己做文件服务器所做的一切(标题,expiries,mime-types等)的痛苦。 So would there be an easy way to bake those static files into the binary in some form and serve them that way? 那么有一种简单的方法可以将这些静态文件以某种形式烘焙到二进制文件中并以这种方式提供它们吗?

The FileServer requires a FileSystem object in its constructor. FileServer在其构造函数中需要FileSystem对象。 Usually, you would provide something based on http.Dir to make that FileSystem for you from the actual file system, but nothing prevents you from implementing your own: 通常情况下,你将提供基于什么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)
}

This implementation is very buggy at best, and you should probably never ever use it, but it should show you how the FileSystem interface can be implemented for arbitrary 'files'. 这个实现是非常错误的最好的,你应该永远使用它,但它应该告诉你如何将FileSystem接口可以任意“文件”来实现。

A more credible (and certainly less dangerous) implementation of something similar is available here . 一个更可信的(当然不那么危险)实施的类似的东西,请点击这里 This is the one used to fake the filesystem on Go playground, so it should be a good reference (much better than mine anyway). 这是用于在Go操场上伪造文件系统的那个 ,所以它应该是一个很好的参考(比我的好多了)。

Whether it is simpler to reimplement this FileSystem interface or a custom FileServer as other suggested, is entirely up to you and your project ! 是否更容易重新实现此FileSystem接口或其他建议的自定义FileServer ,完全取决于您和您的项目! I suspect however that for serving a couple of predefined files, rewriting the serving part might be easier than emulating a full file-system. 但我怀疑,为了提供几个预定义文件,重写服务部分可能比模拟完整的文件系统更容易。

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

It is not very difficult to do what you request. 做你要求的事情并不是很困难。 You don't have to base64 encode it or anything (it will just make it harder for you to edit.). 你不必对它进行base64编码或任何事情(它只会让你更难编辑。)。

Below is an example of how to output a javascript file with correct mime type: 下面是如何输出具有正确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)
}

I would store the files in variable as plain text. 我将文件存储在变量中作为纯文本。 Something like this: 像这样的东西:

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

That way, it is pretty easy to have your makefile or build script so something like: 这样,你可以很容易地使用makefile或build脚本:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM