2

How can I compose the default Go HTTP file server (serve if exists, show file listing otherwise) with additional HTML?

Sample http.go with default file server:

package main

import "net/http"

func main() {
    http.Handle("/", http.FileServer(http.Dir(".")))
    http.ListenAndServe(":8090", nil)
}

Loading the default page (http://localhost:8090) gives something like:

<pre><a href="LICENSE">LICENSE</a>
<a href="README.md">README.md</a>
<a href="studio.jpg">studio.jpg</a>
</pre>

I found it is declared at fs.go.

I want to keep that section, but with my own header and footer (preferably without copying the dirList function and making small changes):

<title>My files</title>

<pre><a href="LICENSE">LICENSE</a>
<a href="README.md">README.md</a>
<a href="studio.jpg">studio.jpg</a>
</pre>

<p>And that's all, folks!</p>
kozmo
  • 4,024
  • 3
  • 30
  • 48
ubndpdhsc
  • 43
  • 5

1 Answers1

0

Based on this answer, you can implement own FileSystem for a FileServer

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

type InMemoryFS map[string]http.File

type InMemoryFile struct {
    at   int64
    Name string
    data []byte
    fs   InMemoryFS
}

func NewFile(name string, data []byte) *InMemoryFile {
    return &InMemoryFile{at: 0,
        Name: name,
        data: data,
        fs:   make(InMemoryFS)}
}

// 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) {
    return nil, 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 }
// CustomFsDecorator: is `http.FileSystem` decorator
type CustomFsDecorator struct {
    http.FileSystem
}

func (fs CustomFsDecorator) Open(name string) (http.File, error) {
    file, err := fs.FileSystem.Open(name)
    if err != nil {
        return nil, err
    }
    info, err := file.Stat()
    if err != nil {
        return nil, err
    }
    if info.IsDir() {
        return file, nil
    }

    b, err := io.ReadAll(file)
    if err != nil {
        return nil, err
    }
    buf := new(bytes.Buffer)

    // add header's lines
    _, err = buf.Write([]byte("<title>My files</title>\n"))
    if err != nil {
        return nil, err
    }
    _, err = buf.Write(b)
    if err != nil {
        return nil, err
    }

    // add footer's lines
    _, err = buf.Write([]byte("\n<p>And that's all, folks!</p>"))
    if err != nil {
        return nil, err
    }

    return NewFile(info.Name(), buf.Bytes()), nil
}
func Test(t *testing.T) {
    cfsys := CustomFsDecorator{FileSystem: http.Dir("./static")}
    fsys := http.FileServer(cfsys)

    req := httptest.NewRequest(http.MethodGet, "/some.html", nil)
    w := httptest.NewRecorder()
    fsys.ServeHTTP(w, req)
    res := w.Result()
    defer func() {
        _ = res.Body.Close()
    }()

    data, err := io.ReadAll(res.Body)
    if err != nil {
        t.Errorf("expected error to be nil got %v", err)
    }
    fmt.Println(string(data))
}

<title>My files</title>
<pre><a href="LICENSE">LICENSE</a>
<a href="README.md">README.md</a>
<a href="studio.jpg">studio.jpg</a>
</pre>
<p>And that's all, folks!</p>

PLAYGROUND

kozmo
  • 4,024
  • 3
  • 30
  • 48