Search code examples
httpgovirtualhost

How to server static files with virtual hosts functioality in Go


How can I server static files (with FileServer) for a virtual host in Go?

If I have my own handler function, the task seems to be easily solvable [1]:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, world!")
    })
    http.HandleFunc("qa.example.com/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, improved world!")
    })
    http.ListenAndServe(":8080", nil)
}

But what if I need to serve static files (with FileServer) for a virtual host?

This

r.PathPrefix("qa.example.com/").Handler(http.FileServer(http.Dir("/static/qa/")))

does not work — it is just ignored.

What am I doing wrong? Is this approach generally wrong?


Solution

  • package main
    
    import (
        "embed"
        "fmt"
        "net/http"
        "strings"
        "time"
    )
    
    //go:embed *.go
    var f embed.FS
    
    func main() {
        // embed.Fs defaule modtime use now or env value.
        now := time.Now()
        // use mapping host to http.FileSystem
        vhosts := make(map[string]http.FileSystem)
        vhosts["qa.example.com"] = http.FS(f)    // from embed.FS
        vhosts["my.example.com"] = http.Dir(".") // from http.Dir
    
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintf(w, "Hello, world!")
        })
        http.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) {
            // find host
            fs, ok := vhosts[r.Host]
            if !ok {
                w.WriteHeader(404)
                w.Write([]byte("404 not found vhost"))
                return
            }
            // open file from http.FileSystem
            file, err := fs.Open(strings.TrimPrefix(r.URL.Path, "/static/"))
            if err != nil {
                // reference go1.18.3/net/http/fs.go toHTTPError function hander file error.
                w.Write([]byte("check err is 403 or 404 or 500"))
                return
            }
            stat, _ := file.Stat()
            // fix embed modtime is zero.
            modtime := stat.ModTime()
            if modtime.IsZero() {
                modtime = now
            }
            // response
            http.ServeContent(w, r, stat.Name(), modtime, file)
    
        })
    
        http.ListenAndServe(":8080", nil)
    }
    

    run test exec command curl -H "Host: my.example.com" 127.0.0.1:8080/static/01.go, 01.go replacte your static filename.