diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/net/http/fs.go | 133 | ||||
| -rw-r--r-- | src/net/http/fs_test.go | 37 |
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() |
