-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
httpfs: implement http.FileSystem interfaces
Implements the http.FileSystem interfaces. This allows using http.FileServer with a BillyFS. Signed-off-by: Christian Stewart <[email protected]>
- Loading branch information
Showing
4 changed files
with
200 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package httpfs | ||
|
||
import ( | ||
"errors" | ||
"io/fs" | ||
"net/http" | ||
) | ||
|
||
// Dir implements the HTTP directory. | ||
type Dir struct { | ||
// fs is the base filesysetm | ||
fs BillyFs | ||
// path is the path to this dir | ||
path string | ||
} | ||
|
||
// NewDir constructs the Dir from a Billy Dir. | ||
func NewDir(fs BillyFs, path string) *Dir { | ||
return &Dir{fs: fs, path: path} | ||
} | ||
|
||
func (f *Dir) Stat() (fs.FileInfo, error) { | ||
return f.fs.Stat(f.path) | ||
} | ||
|
||
// Readdir reads the directory contents. | ||
func (f *Dir) Readdir(count int) ([]fs.FileInfo, error) { | ||
ents, err := f.fs.ReadDir(f.path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if count > 0 && count > len(ents) { | ||
ents = ents[:count] | ||
} | ||
return ents, err | ||
} | ||
|
||
func (f *Dir) Read(p []byte) (n int, err error) { | ||
return 0, errors.New("not a file") | ||
} | ||
|
||
func (f *Dir) Seek(offset int64, whence int) (int64, error) { | ||
return 0, errors.New("not a file") | ||
} | ||
|
||
func (f *Dir) Close() error { | ||
// no-op. | ||
return nil | ||
} | ||
|
||
// _ is a type assertion | ||
var _ http.File = ((*Dir)(nil)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package httpfs | ||
|
||
import ( | ||
"errors" | ||
"io/fs" | ||
"net/http" | ||
|
||
"github.com/go-git/go-billy/v5" | ||
) | ||
|
||
// File implements the HTTP file. | ||
type File struct { | ||
// File is the billy file | ||
billy.File | ||
// path is the path to File | ||
path string | ||
// fs is the filesystem | ||
fs BillyFs | ||
} | ||
|
||
// NewFile constructs the File from a Billy File. | ||
func NewFile(fs BillyFs, path string) (*File, error) { | ||
f, err := fs.Open(path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &File{File: f, path: path, fs: fs}, nil | ||
} | ||
|
||
func (f *File) Readdir(count int) ([]fs.FileInfo, error) { | ||
// ENOTDIR | ||
return nil, errors.New("not a directory") | ||
} | ||
|
||
func (f *File) Stat() (fs.FileInfo, error) { | ||
return f.fs.Stat(f.path) | ||
} | ||
|
||
// _ is a type assertion | ||
var _ http.File = ((*File)(nil)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package httpfs | ||
|
||
import ( | ||
"net/http" | ||
"path" | ||
"strings" | ||
|
||
"github.com/go-git/go-billy/v5" | ||
) | ||
|
||
// BillyFs is the set of required billy filesystem interfaces. | ||
type BillyFs interface { | ||
billy.Basic | ||
billy.Dir | ||
} | ||
|
||
// FileSystem implements the HTTP filesystem. | ||
type FileSystem struct { | ||
// fs is the billy filesystem | ||
fs BillyFs | ||
// prefix is the filesystem prefix for HTTP | ||
prefix string | ||
} | ||
|
||
// NewFileSystem constructs the FileSystem from a Billy FileSystem. | ||
// | ||
// Prefix is a path prefix to prepend to file paths for HTTP. | ||
// The prefix is trimmed from the paths when opening files. | ||
func NewFileSystem(fs BillyFs, prefix string) *FileSystem { | ||
if len(prefix) != 0 { | ||
prefix = path.Clean(prefix) | ||
} | ||
return &FileSystem{fs: fs, prefix: prefix} | ||
} | ||
|
||
// Open opens the file at the given path. | ||
func (f *FileSystem) Open(name string) (http.File, error) { | ||
name = path.Clean(name) | ||
if len(f.prefix) != 0 { | ||
name = strings.TrimPrefix(name, f.prefix) | ||
name = path.Clean(name) | ||
} | ||
if strings.HasPrefix(name, "/") { | ||
name = name[1:] | ||
} | ||
|
||
fi, err := f.fs.Stat(name) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if fi.IsDir() { | ||
return NewDir(f.fs, name), nil | ||
} | ||
return NewFile(f.fs, name) | ||
} | ||
|
||
// _ is a type assertion | ||
var _ http.FileSystem = ((*FileSystem)(nil)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package httpfs | ||
|
||
import ( | ||
"bytes" | ||
"io/ioutil" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/go-git/go-billy/v5/memfs" | ||
"github.com/go-git/go-billy/v5/util" | ||
) | ||
|
||
// TestFileSystem tests the HTTP filesystem. | ||
func TestFileSystem(t *testing.T) { | ||
mfs := memfs.New() | ||
|
||
err := mfs.MkdirAll("./stuff", 0755) | ||
if err != nil { | ||
t.Fatal(err.Error()) | ||
} | ||
|
||
data := []byte("hello world!\n") | ||
err = util.WriteFile(mfs, "./stuff/test.txt", data, 0755) | ||
if err != nil { | ||
t.Fatal(err.Error()) | ||
} | ||
|
||
var hfs http.FileSystem = NewFileSystem(mfs, "/test") | ||
|
||
mux := http.NewServeMux() | ||
mux.Handle("/", http.FileServer(hfs)) | ||
|
||
req := httptest.NewRequest("GET", "/test/stuff/test.txt", nil) | ||
rw := httptest.NewRecorder() | ||
mux.ServeHTTP(rw, req) | ||
|
||
res := rw.Result() | ||
if res.StatusCode != 200 { | ||
t.Fatalf("status code: %d", res.StatusCode) | ||
} | ||
|
||
readData, err := ioutil.ReadAll(res.Body) | ||
if err != nil { | ||
t.Fatal(err.Error()) | ||
} | ||
if !bytes.Equal(readData, data) { | ||
t.Fail() | ||
} | ||
} |