mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 18:10:48 +08:00

* [website] Fix devserver default value doc * [v2] Add support for AssetsHandler AssetsHandler is a http.Handler delegate, which gets called as a fallback for all Non-GET requests and for GET requests for which the Assets didn’t find the file. Known Limitations on Linux: - All requests are GET requests - No request headers - No request body - No response status code, only StatusOK will be returned - No response headers Known Limitations on Windows: - Request body is leaking memory. Seems to be a bug in WebView2, investigation angoing. Most of these limitations on Linux will be fixed in the future with adding support for Webkit2Gtk 2.36.0+. * [v2, linux] Add response streaming support The complete response won’t be held anymore in memory and will be streamed to WebKit2. Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
167 lines
3.8 KiB
Go
167 lines
3.8 KiB
Go
package assetserver
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
_ "embed"
|
|
"fmt"
|
|
"io"
|
|
iofs "io/fs"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/wailsapp/wails/v2/internal/fs"
|
|
"github.com/wailsapp/wails/v2/internal/logger"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
)
|
|
|
|
//go:embed defaultindex.html
|
|
var defaultHTML []byte
|
|
|
|
const (
|
|
indexHTML = "index.html"
|
|
)
|
|
|
|
type assetHandler struct {
|
|
fs iofs.FS
|
|
handler http.Handler
|
|
|
|
logger *logger.Logger
|
|
|
|
servingFromDisk bool
|
|
}
|
|
|
|
func NewAsssetHandler(ctx context.Context, options *options.App) (http.Handler, error) {
|
|
vfs := options.Assets
|
|
if vfs != nil {
|
|
if _, err := vfs.Open("."); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
subDir, err := fs.FindPathToFile(vfs, indexHTML)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vfs, err = iofs.Sub(vfs, path.Clean(subDir))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
result := &assetHandler{
|
|
fs: vfs,
|
|
handler: options.AssetsHandler,
|
|
|
|
// Check if we have been given a directory to serve assets from.
|
|
// If so, this means we are in dev mode and are serving assets off disk.
|
|
// We indicate this through the `servingFromDisk` flag to ensure requests
|
|
// aren't cached in dev mode.
|
|
servingFromDisk: ctx.Value("assetdir") != nil,
|
|
}
|
|
|
|
if _logger := ctx.Value("logger"); _logger != nil {
|
|
result.logger = _logger.(*logger.Logger)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (d *assetHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|
handler := d.handler
|
|
if strings.EqualFold(req.Method, http.MethodGet) {
|
|
filename := strings.TrimPrefix(req.URL.Path, "/")
|
|
if filename == "" {
|
|
filename = indexHTML
|
|
}
|
|
|
|
d.logDebug("[AssetHandler] Loading file '%s'", filename)
|
|
if err := d.serveFSFile(rw, filename); err != nil {
|
|
if os.IsNotExist(err) {
|
|
if handler != nil {
|
|
d.logDebug("[AssetHandler] File '%s' not found, serving '%s' by AssetHandler", filename, req.URL)
|
|
handler.ServeHTTP(rw, req)
|
|
} else {
|
|
rw.WriteHeader(http.StatusNotFound)
|
|
}
|
|
} else {
|
|
d.logError("[AssetHandler] Unable to load file '%s': %s", filename, err)
|
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
} else if handler != nil {
|
|
d.logDebug("[AssetHandler] No GET request, serving '%s' by AssetHandler", req.URL)
|
|
handler.ServeHTTP(rw, req)
|
|
} else {
|
|
rw.WriteHeader(http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
// serveFile will try to load the file from the fs.FS and write it to the response
|
|
func (d *assetHandler) serveFSFile(rw http.ResponseWriter, filename string) error {
|
|
if d.fs == nil {
|
|
return os.ErrNotExist
|
|
}
|
|
|
|
file, err := d.fs.Open(filename)
|
|
if err != nil && d.servingFromDisk {
|
|
for tries := 0; tries < 50; tries++ {
|
|
file, err = d.fs.Open(filename)
|
|
if err != nil {
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
if filename == indexHTML && os.IsNotExist(err) {
|
|
return serveFile(rw, filename, defaultHTML)
|
|
}
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
statInfo, err := file.Stat()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rw.Header().Set(HeaderContentLength, fmt.Sprintf("%d", statInfo.Size()))
|
|
|
|
var buf [512]byte
|
|
n, err := file.Read(buf[:])
|
|
if err != nil && err != io.EOF {
|
|
return err
|
|
}
|
|
|
|
// Detect MimeType by sniffing the first 512 bytes
|
|
if contentType := GetMimetype(filename, buf[:n]); contentType != "" {
|
|
rw.Header().Set(HeaderContentType, contentType)
|
|
}
|
|
|
|
// Write the first bytes
|
|
_, 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 *assetHandler) logDebug(message string, args ...interface{}) {
|
|
if d.logger != nil {
|
|
d.logger.Debug("[AssetHandler] "+message, args...)
|
|
}
|
|
}
|
|
|
|
func (d *assetHandler) logError(message string, args ...interface{}) {
|
|
if d.logger != nil {
|
|
d.logger.Error("[AssetHandler] "+message, args...)
|
|
}
|
|
}
|