From 7bb721b9384bdd196befeaed593b185f7f2a5589 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Tue, 7 Jul 2020 13:49:21 -0400 Subject: all: update references to symbols moved from os to io/fs The old os references are still valid, but update our code to reflect best practices and get used to the new locations. Code compiled with the bootstrap toolchain (cmd/asm, cmd/dist, cmd/compile, debug/elf) must remain Go 1.4-compatible and is excluded. For #41190. Change-Id: I8f9526977867c10a221e2f392f78d7dec073f1bd Reviewed-on: https://go-review.googlesource.com/c/go/+/243907 Trust: Russ Cox Run-TryBot: Russ Cox TryBot-Result: Go Bot Reviewed-by: Rob Pike --- src/net/http/fs.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/net/http/fs.go') diff --git a/src/net/http/fs.go b/src/net/http/fs.go index d718fffba0..0743b5b621 100644 --- a/src/net/http/fs.go +++ b/src/net/http/fs.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "io/fs" "mime" "mime/multipart" "net/textproto" @@ -43,7 +44,7 @@ type Dir string // mapDirOpenError maps the provided non-nil error from opening name // to a possibly better non-nil error. In particular, it turns OS-specific errors -// about opening files in non-directories into os.ErrNotExist. See Issue 18984. +// about opening files in non-directories into fs.ErrNotExist. See Issue 18984. func mapDirOpenError(originalErr error, name string) error { if os.IsNotExist(originalErr) || os.IsPermission(originalErr) { return originalErr @@ -59,7 +60,7 @@ func mapDirOpenError(originalErr error, name string) error { return originalErr } if !fi.IsDir() { - return os.ErrNotExist + return fs.ErrNotExist } } return originalErr @@ -98,8 +99,8 @@ type File interface { io.Closer io.Reader io.Seeker - Readdir(count int) ([]os.FileInfo, error) - Stat() (os.FileInfo, error) + Readdir(count int) ([]fs.FileInfo, error) + Stat() (fs.FileInfo, error) } func dirList(w ResponseWriter, r *Request, f File) { -- cgit v1.2.1 From 7211694a1e3f9eaebff7074944feead968e00e72 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Tue, 7 Jul 2020 09:51:45 -0400 Subject: 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 Run-TryBot: Russ Cox TryBot-Result: Go Bot Reviewed-by: Rob Pike --- src/net/http/fs.go | 133 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 125 insertions(+), 8 deletions(-) (limited to 'src/net/http/fs.go') 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, "
\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}
 }
-- 
cgit v1.2.1