5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-03 15:49:58 +08:00
wails/v3/internal/assetserver/asset_fileserver.go

162 lines
4.0 KiB
Go

package assetserver
import (
"bytes"
"context"
"embed"
"errors"
"fmt"
"io"
iofs "io/fs"
"net/http"
"os"
"path"
"strings"
)
const (
indexHTML = "index.html"
)
type assetFileServer struct {
fs iofs.FS
err error
}
func newAssetFileServerFS(vfs iofs.FS) http.Handler {
subDir, err := findPathToFile(vfs, indexHTML)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
msg := "no `index.html` could be found in your Assets fs.FS"
if embedFs, isEmbedFs := vfs.(embed.FS); isEmbedFs {
rootFolder, _ := findEmbedRootPath(embedFs)
msg += fmt.Sprintf(", please make sure the embedded directory '%s' is correct and contains your assets", rootFolder)
}
err = errors.New(msg)
}
} else {
vfs, err = iofs.Sub(vfs, path.Clean(subDir))
}
return &assetFileServer{fs: vfs, err: err}
}
func (d *assetFileServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
ctx := req.Context()
url := req.URL.Path
err := d.err
if err == nil {
filename := path.Clean(strings.TrimPrefix(url, "/"))
d.logInfo(ctx, "Handling request", "url", url, "file", filename)
err = d.serveFSFile(rw, req, filename)
if os.IsNotExist(err) {
rw.WriteHeader(http.StatusNotFound)
return
}
}
if err != nil {
d.logError(ctx, "Unable to handle request", "url", url, "err", err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
// serveFile will try to load the file from the fs.FS and write it to the response
func (d *assetFileServer) serveFSFile(rw http.ResponseWriter, req *http.Request, filename string) error {
if d.fs == nil {
return os.ErrNotExist
}
file, err := d.fs.Open(filename)
if err != nil {
if s := path.Ext(filename); s == "" {
filename = filename + ".html"
file, err = d.fs.Open(filename)
if err != nil {
return err
}
} else {
return err
}
}
defer file.Close()
statInfo, err := file.Stat()
if err != nil {
return err
}
url := req.URL.Path
isDirectoryPath := url == "" || url[len(url)-1] == '/'
if statInfo.IsDir() {
if !isDirectoryPath {
// If the URL doesn't end in a slash normally a http.redirect should be done, but that currently doesn't work on
// WebKit WebViews (macOS/Linux).
// So we handle this as a specific error
return fmt.Errorf("a directory has been requested without a trailing slash, please add a trailing slash to your request")
}
filename = path.Join(filename, indexHTML)
file, err = d.fs.Open(filename)
if err != nil {
return err
}
defer file.Close()
statInfo, err = file.Stat()
if err != nil {
return err
}
} else if isDirectoryPath {
return fmt.Errorf("a file has been requested with a trailing slash, please remove the trailing slash from your request")
}
var buf [512]byte
var n int
if _, haveType := rw.Header()[HeaderContentType]; !haveType {
// Detect MimeType by sniffing the first 512 bytes
n, err = file.Read(buf[:])
if err != nil && err != io.EOF {
return err
}
// Do the custom MimeType sniffing even though http.ServeContent would do it in case
// of an io.ReadSeeker. We would like to have a consistent behaviour in both cases.
if contentType := GetMimetype(filename, buf[:n]); contentType != "" {
rw.Header().Set(HeaderContentType, contentType)
}
}
if fileSeeker, _ := file.(io.ReadSeeker); fileSeeker != nil {
if _, err := fileSeeker.Seek(0, io.SeekStart); err != nil {
return fmt.Errorf("seeker can't seek")
}
http.ServeContent(rw, req, statInfo.Name(), statInfo.ModTime(), fileSeeker)
return nil
}
rw.Header().Set(HeaderContentLength, fmt.Sprintf("%d", statInfo.Size()))
// Write the first 512 bytes used for MimeType sniffing
_, err = io.Copy(rw, bytes.NewReader(buf[:n]))
if err != nil {
return err
}
// Copy the remaining content of the file
_, err = io.Copy(rw, file)
return err
}
func (d *assetFileServer) logInfo(ctx context.Context, message string, args ...interface{}) {
logInfo(ctx, "[AssetFileServerFS] "+message, args...)
}
func (d *assetFileServer) logError(ctx context.Context, message string, args ...interface{}) {
logError(ctx, "[AssetFileServerFS] "+message, args...)
}