Search code examples
httpgofileserver

Good way to disable directory listing with http.FileServer in Go


If you use the http.FileServer in Go like:

func main() {
    port := flag.String("p", "8100", "port to serve on")
    directory := flag.String("d", ".", "the directory of static file to host")
    flag.Parse()

    http.Handle("/", http.FileServer(http.Dir(*directory)))

    log.Printf("Serving %s on HTTP port: %s\n", *directory, *port)
    log.Fatal(http.ListenAndServe(":"+*port, nil))
}

Then accessing a directory will give you a listing of files. Often this is disabled for web services and instead responds with 404 and I would like this behaviour too.

http.FileServer has no options for this AFAIK and I have seen a proposed way to solve this here https://groups.google.com/forum/#!topic/golang-nuts/bStLPdIVM6w what they do is wrapping the http.FileSystem type and implementing an own Open method. However this doesn't give a 404 when the path is a directory, it just gives a blank page, and it's unclear how to modify it to accomodate this. This is what they do:

type justFilesFilesystem struct {
    fs http.FileSystem
}

func (fs justFilesFilesystem) Open(name string) (http.File, error) {
    f, err := fs.fs.Open(name)
    if err != nil {
        return nil, err
    }
    return neuteredReaddirFile{f}, nil
}

type neuteredReaddirFile struct {
    http.File
}

func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) {
    return nil, nil
}

func main() {
    fs := justFilesFilesystem{http.Dir("/tmp/")}
    http.ListenAndServe(":8080", http.FileServer(fs))
}

Note: if you make Readdir return nil, os.ErrNotExist then you get a 500 response with "Error reading directory" - not 404.

Any ideas on how to neatly present a 404 and still preserving the feature of automatically finding an index.html if present?


Solution

  • This behavior can be changed if you substitute not a Readdir method, but the Stat.
    Please take a look at working code below. It supports serving of index.html files if they are inside of requested directory and returns 404 in case there is no index.html and it is a directory.

        package main
    
        import (
            "io"
            "net/http"
            "os"
        )
    
        type justFilesFilesystem struct {
            fs               http.FileSystem
            // readDirBatchSize - configuration parameter for `Readdir` func  
            readDirBatchSize int
        }
    
        func (fs justFilesFilesystem) Open(name string) (http.File, error) {
            f, err := fs.fs.Open(name)
            if err != nil {
                return nil, err
            }
            return neuteredStatFile{File: f, readDirBatchSize: fs.readDirBatchSize}, nil
        }
    
        type neuteredStatFile struct {
            http.File
            readDirBatchSize int
        }
    
        func (e neuteredStatFile) Stat() (os.FileInfo, error) {
            s, err := e.File.Stat()
            if err != nil {
                return nil, err
            }
            if s.IsDir() {
            LOOP:
                for {
                    fl, err := e.File.Readdir(e.readDirBatchSize)
                    switch err {
                    case io.EOF:
                        break LOOP
                    case nil:
                        for _, f := range fl {
                            if f.Name() == "index.html" {
                                return s, err
                            }
                        }
                    default:
                        return nil, err
                    }
                }
                return nil, os.ErrNotExist
            }
            return s, err
        }
    
        func main() {
            fs := justFilesFilesystem{fs: http.Dir("/tmp/"), readDirBatchSize: 2}
            fss := http.FileServer(fs)
            http.ListenAndServe(":8080", fss)
        }