Golang serve static files from memory

前端 未结 4 1857
忘了有多久
忘了有多久 2020-12-08 11:54

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

相关标签:
4条回答
  • 2020-12-08 12:03

    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 "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'.

    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.

    0 讨论(0)
  • 2020-12-08 12:14

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

    0 讨论(0)
  • 2020-12-08 12:16

    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 (
        "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)
    }
    
    0 讨论(0)
  • 2020-12-08 12:19

    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:

    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

    0 讨论(0)
提交回复
热议问题