aboutsummaryrefslogtreecommitdiff
path: root/src/net
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2020-07-07 09:51:45 -0400
committerRuss Cox <rsc@golang.org>2020-10-20 18:41:15 +0000
commit7211694a1e3f9eaebff7074944feead968e00e72 (patch)
treec49b8d6eb5a6bc1cf766076fb00c61f897d9ec28 /src/net
parent2a9aa4dcac0c33f7fffefb94a1bc92a17fd7cfd3 (diff)
downloadgo-7211694a1e3f9eaebff7074944feead968e00e72.tar.gz
go-7211694a1e3f9eaebff7074944feead968e00e72.zip
net/http: add FS to convert fs.FS to FileSystem
Two different functions in the http API expect a FileSystem: http.FileSystem and http.NewFileTransport. Add a general converter http.FS to turn an fs.FS into an http.FileSystem for use with either of these functions. (The original plan was to add http.HandlerFS taking an fs.FS directly, but that doesn't help with NewFileTransport.) For #41190. Change-Id: I5f242eafe9b963f4387419a2615bdb487c358f16 Reviewed-on: https://go-review.googlesource.com/c/go/+/243939 Trust: Russ Cox <rsc@golang.org> Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Rob Pike <r@golang.org>
Diffstat (limited to 'src/net')
-rw-r--r--src/net/http/fs.go133
-rw-r--r--src/net/http/fs_test.go37
2 files changed, 162 insertions, 8 deletions
diff --git a/src/net/http/fs.go b/src/net/http/fs.go
index 0743b5b621..a28ae85958 100644
--- a/src/net/http/fs.go
+++ b/src/net/http/fs.go
@@ -87,6 +87,10 @@ func (d Dir) Open(name string) (File, error) {
// A FileSystem implements access to a collection of named files.
// The elements in a file path are separated by slash ('/', U+002F)
// characters, regardless of host operating system convention.
+// See the FileServer function to convert a FileSystem to a Handler.
+//
+// This interface predates the fs.FS interface, which can be used instead:
+// the FS adapter function converts an fs.FS to a FileSystem.
type FileSystem interface {
Open(name string) (File, error)
}
@@ -103,20 +107,52 @@ type File interface {
Stat() (fs.FileInfo, error)
}
+type anyDirs interface {
+ len() int
+ name(i int) string
+ isDir(i int) bool
+}
+
+type fileInfoDirs []fs.FileInfo
+
+func (d fileInfoDirs) len() int { return len(d) }
+func (d fileInfoDirs) isDir(i int) bool { return d[i].IsDir() }
+func (d fileInfoDirs) name(i int) string { return d[i].Name() }
+
+type dirEntryDirs []fs.DirEntry
+
+func (d dirEntryDirs) len() int { return len(d) }
+func (d dirEntryDirs) isDir(i int) bool { return d[i].IsDir() }
+func (d dirEntryDirs) name(i int) string { return d[i].Name() }
+
func dirList(w ResponseWriter, r *Request, f File) {
- dirs, err := f.Readdir(-1)
+ // Prefer to use ReadDir instead of Readdir,
+ // because the former doesn't require calling
+ // Stat on every entry of a directory on Unix.
+ var dirs anyDirs
+ var err error
+ if d, ok := f.(fs.ReadDirFile); ok {
+ var list dirEntryDirs
+ list, err = d.ReadDir(-1)
+ dirs = list
+ } else {
+ var list fileInfoDirs
+ list, err = f.Readdir(-1)
+ dirs = list
+ }
+
if err != nil {
logf(r, "http: error reading directory: %v", err)
Error(w, "Error reading directory", StatusInternalServerError)
return
}
- sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
+ sort.Slice(dirs, func(i, j int) bool { return dirs.name(i) < dirs.name(j) })
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, "<pre>\n")
- for _, d := range dirs {
- name := d.Name()
- if d.IsDir() {
+ for i, n := 0, dirs.len(); i < n; i++ {
+ name := dirs.name(i)
+ if dirs.isDir(i) {
name += "/"
}
// name may contain '?' or '#', which must be escaped to remain
@@ -707,17 +743,98 @@ type fileHandler struct {
root FileSystem
}
+type ioFS struct {
+ fsys fs.FS
+}
+
+type ioFile struct {
+ file fs.File
+}
+
+func (f ioFS) Open(name string) (File, error) {
+ if name == "/" {
+ name = "."
+ } else {
+ name = strings.TrimPrefix(name, "/")
+ }
+ file, err := f.fsys.Open(name)
+ if err != nil {
+ return nil, err
+ }
+ return ioFile{file}, nil
+}
+
+func (f ioFile) Close() error { return f.file.Close() }
+func (f ioFile) Read(b []byte) (int, error) { return f.file.Read(b) }
+func (f ioFile) Stat() (fs.FileInfo, error) { return f.file.Stat() }
+
+var errMissingSeek = errors.New("io.File missing Seek method")
+var errMissingReadDir = errors.New("io.File directory missing ReadDir method")
+
+func (f ioFile) Seek(offset int64, whence int) (int64, error) {
+ s, ok := f.file.(io.Seeker)
+ if !ok {
+ return 0, errMissingSeek
+ }
+ return s.Seek(offset, whence)
+}
+
+func (f ioFile) ReadDir(count int) ([]fs.DirEntry, error) {
+ d, ok := f.file.(fs.ReadDirFile)
+ if !ok {
+ return nil, errMissingReadDir
+ }
+ return d.ReadDir(count)
+}
+
+func (f ioFile) Readdir(count int) ([]fs.FileInfo, error) {
+ d, ok := f.file.(fs.ReadDirFile)
+ if !ok {
+ return nil, errMissingReadDir
+ }
+ var list []fs.FileInfo
+ for {
+ dirs, err := d.ReadDir(count - len(list))
+ for _, dir := range dirs {
+ info, err := dir.Info()
+ if err != nil {
+ // Pretend it doesn't exist, like (*os.File).Readdir does.
+ continue
+ }
+ list = append(list, info)
+ }
+ if err != nil {
+ return list, err
+ }
+ if count < 0 || len(list) >= count {
+ break
+ }
+ }
+ return list, nil
+}
+
+// FS converts fsys to a FileSystem implementation,
+// for use with FileServer and NewFileTransport.
+func FS(fsys fs.FS) FileSystem {
+ return ioFS{fsys}
+}
+
// FileServer returns a handler that serves HTTP requests
// with the contents of the file system rooted at root.
//
+// As a special case, the returned file server redirects any request
+// ending in "/index.html" to the same path, without the final
+// "index.html".
+//
// To use the operating system's file system implementation,
// use http.Dir:
//
// http.Handle("/", http.FileServer(http.Dir("/tmp")))
//
-// As a special case, the returned file server redirects any request
-// ending in "/index.html" to the same path, without the final
-// "index.html".
+// To use an fs.FS implementation, use http.FS to convert it:
+//
+// http.Handle("/", http.FileServer(http.FS(fsys)))
+//
func FileServer(root FileSystem) Handler {
return &fileHandler{root}
}
diff --git a/src/net/http/fs_test.go b/src/net/http/fs_test.go
index de793b331e..c9f324cff6 100644
--- a/src/net/http/fs_test.go
+++ b/src/net/http/fs_test.go
@@ -571,6 +571,43 @@ func testServeFileWithContentEncoding(t *testing.T, h2 bool) {
func TestServeIndexHtml(t *testing.T) {
defer afterTest(t)
+
+ for i := 0; i < 2; i++ {
+ var h Handler
+ var name string
+ switch i {
+ case 0:
+ h = FileServer(Dir("."))
+ name = "Dir"
+ case 1:
+ h = FileServer(FS(os.DirFS(".")))
+ name = "DirFS"
+ }
+ t.Run(name, func(t *testing.T) {
+ const want = "index.html says hello\n"
+ ts := httptest.NewServer(h)
+ defer ts.Close()
+
+ for _, path := range []string{"/testdata/", "/testdata/index.html"} {
+ res, err := Get(ts.URL + path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ b, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ t.Fatal("reading Body:", err)
+ }
+ if s := string(b); s != want {
+ t.Errorf("for path %q got %q, want %q", path, s, want)
+ }
+ res.Body.Close()
+ }
+ })
+ }
+}
+
+func TestServeIndexHtmlFS(t *testing.T) {
+ defer afterTest(t)
const want = "index.html says hello\n"
ts := httptest.NewServer(FileServer(Dir(".")))
defer ts.Close()