Golang从内存中提供静态文件。

huangapple go评论78阅读模式
英文:

Golang serve static files from memory

问题

关于在Go语言中提供文件的问题,我有一个快速的问题。有一个非常方便的FileServer处理程序,但是对于我的用例,我只有2或3个文件(js和css)与我的应用程序一起,并且我不想为了这些文件而复杂化部署过程。

你认为有没有一种简单的方法将这些文件构建到二进制文件中,并从那里提供它们。例如,将文件的数据作为常量进行base64编码,并从常量中提供文件。这在最简单的形式下可以工作,但是我不想自己处理文件服务器所做的一切(头部、过期时间、MIME类型等)。所以,有没有一种简单的方法以某种形式将这些静态文件嵌入到二进制文件中,并以这种方式提供它们?

英文:

I have a quick question about serving files in 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.

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. 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. So would there be an easy way to bake those static files into the binary in some form and serve them that way?

答案1

得分: 21

FileServer在其构造函数中需要一个FileSystem对象。通常情况下,你可以提供基于http.Dir的东西,以便从实际文件系统中为你创建FileSystem,但是没有什么可以阻止你自己实现:

package main

import "os"
import "time"
import "net/http"

type InMemoryFS map[string]http.File

// 实现FileSystem接口
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}
}

// 实现http.File接口
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
}

// 实现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,你应该永远不要使用它,但它应该向你展示了如何为任意的“文件”实现FileSystem接口。

一个更可信(并且肯定更安全)的类似实现可以在这里找到。这是用于在Go playground上模拟文件系统的实现,所以它应该是一个很好的参考(比我的好多了)。

无论是重新实现这个FileSystem接口还是像其他人建议的那样自定义FileServer,完全取决于你和你的项目!不过,我怀疑对于提供几个预定义文件,重新编写服务部分可能比模拟一个完整的文件系统更容易。

英文:

The FileServer requires a FileSystem object in its constructor. 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:

package main
import &quot;os&quot;
import &quot;time&quot;
import &quot;net/http&quot;
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(&quot;No file&quot;)
}
type InMemoryFile struct {
at   int64
Name string
data []byte
fs   InMemoryFS
}
func LoadFile(name string, val string, fs InMemoryFS) *InMemoryFile {
return &amp;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 &amp;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 &lt; int64(len(f.data)) &amp;&amp; i &lt; 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 = `&lt;html&gt;
Hello world !
&lt;/html&gt;
`
const CSS = `
p {
color:red;
text-align:center;
} 
`
func main() {
FS := make(InMemoryFS)
FS[&quot;foo.html&quot;] = LoadFile(&quot;foo.html&quot;, HTML, FS)
FS[&quot;bar.css&quot;] = LoadFile(&quot;bar.css&quot;, CSS, FS)
http.Handle(&quot;/&quot;, http.FileServer(FS))
http.ListenAndServe(&quot;:8080&quot;, 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'.

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

Whether it is simpler to reimplement this FileSystem interface or a custom FileServer as other suggested, is entirely up to you and your project ! I suspect however that for serving a couple of predefined files, rewriting the serving part might be easier than emulating a full file-system.

答案2

得分: 8

go.rice”包可以为您处理这个问题-将资源嵌入到您的二进制文件中,并提供一个http.FileSystem实现。

英文:

The "go.rice" package takes care of this for you - embedding resources in your binaries, and providing an http.FileSystem implementation.

答案3

得分: 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) {
	// 获取头部信息以设置正确的MIME类型
	headers := w.Header()
	headers["Content-Type"] = []string{"application/javascript"}
	fmt.Fprint(w, jsFile)
}

希望对你有帮助!

英文:

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

Below is an example of how to output a javascript file with correct mime type:

package main
import (
&quot;fmt&quot;
&quot;log&quot;
&quot;net/http&quot;
)
const jsFile = `alert(&#39;Hello World!&#39;);`
func main() {
http.HandleFunc(&quot;/file.js&quot;, JsHandler)
log.Fatal(http.ListenAndServe(&quot;:8080&quot;, nil))
}
func JsHandler(w http.ResponseWriter, r *http.Request) {
// Getting the headers so we can set the correct mime type
headers := w.Header()
headers[&quot;Content-Type&quot;] = []string{&quot;application/javascript&quot;}
fmt.Fprint(w, jsFile)
}

答案4

得分: 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

希望对你有帮助!

英文:

I would store the files in variable as plain text.
Something like this:

package main
import (
&quot;fmt&quot;
&quot;log&quot;
&quot;net/http&quot;
)
var files = map[string]string{}
func init() {
files[&quot;style.css&quot;] = `
/* css file content */
body { background-color: pink; }
`
}
func init() {
files[&quot;index.html&quot;] = `
&lt;!-- Html content --&gt;
&lt;html&gt;&lt;head&gt;
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;style.css&quot;&gt;
&lt;/head&gt;&lt;body&gt;Hello world!&lt;/body&gt;&lt;/html&gt;
`
}
func main() {
for fileName, content := range files {
contentCpy := content
http.HandleFunc(&quot;/&quot;+fileName, func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, &quot;%s\n&quot;, contentCpy)
})
}
log.Fatal(http.ListenAndServe(&quot;:8080&quot;, nil))
}

That way, it is pretty easy to have your makefile or build script so something like:

for file in index.html style.css; do echo &quot;package main\nfunc init() { files[\&quot;$file\&quot;] = \`$(cat $file)\` }&quot; | gofmt -s &gt; $file.go; done; go build &amp;&amp; ./httptest

huangapple
  • 本文由 发表于 2014年2月6日 14:07:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/21595295.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定