diff --git a/v3/examples/binding/main.go b/v3/examples/binding/main.go index 15a448bb..aa0fc8d3 100644 --- a/v3/examples/binding/main.go +++ b/v3/examples/binding/main.go @@ -17,7 +17,7 @@ func main() { &GreetService{}, }, Assets: application.AssetOptions{ - FS: assets, + Handler: application.AssetFileServerFS(assets), }, Mac: application.MacOptions{ ApplicationShouldTerminateAfterLastWindowClosed: true, diff --git a/v3/examples/contextmenus/main.go b/v3/examples/contextmenus/main.go index 4cb7977d..1a47cb40 100644 --- a/v3/examples/contextmenus/main.go +++ b/v3/examples/contextmenus/main.go @@ -17,7 +17,7 @@ func main() { Name: "Context Menu Demo", Description: "A demo of the Context Menu API", Assets: application.AssetOptions{ - FS: assets, + Handler: application.AssetFileServerFS(assets), }, Mac: application.MacOptions{ ApplicationShouldTerminateAfterLastWindowClosed: true, diff --git a/v3/examples/dev/main.go b/v3/examples/dev/main.go index acfcc8ba..478702f5 100644 --- a/v3/examples/dev/main.go +++ b/v3/examples/dev/main.go @@ -16,7 +16,7 @@ func main() { Name: "dev", Description: "A demo of using raw HTML & CSS", Assets: application.AssetOptions{ - FS: assets, + Handler: application.AssetFileServerFS(assets), }, Mac: application.MacOptions{ ApplicationShouldTerminateAfterLastWindowClosed: true, diff --git a/v3/examples/drag-n-drop/main.go b/v3/examples/drag-n-drop/main.go index a0feae58..abaaa04a 100644 --- a/v3/examples/drag-n-drop/main.go +++ b/v3/examples/drag-n-drop/main.go @@ -18,7 +18,7 @@ func main() { Name: "Drag-n-drop Demo", Description: "A demo of the Drag-n-drop API", Assets: application.AssetOptions{ - FS: assets, + Handler: application.AssetFileServerFS(assets), }, Mac: application.MacOptions{ ApplicationShouldTerminateAfterLastWindowClosed: true, diff --git a/v3/examples/events/main.go b/v3/examples/events/main.go index 8ec5a2bc..1fe96a96 100644 --- a/v3/examples/events/main.go +++ b/v3/examples/events/main.go @@ -19,7 +19,7 @@ func main() { Name: "Events Demo", Description: "A demo of the Events API", Assets: application.AssetOptions{ - FS: assets, + Handler: application.AssetFileServerFS(assets), }, Mac: application.MacOptions{ ApplicationShouldTerminateAfterLastWindowClosed: true, diff --git a/v3/examples/frameless/main.go b/v3/examples/frameless/main.go index 684c7eaf..c59b6702 100644 --- a/v3/examples/frameless/main.go +++ b/v3/examples/frameless/main.go @@ -17,7 +17,7 @@ func main() { Name: "Frameless Demo", Description: "A demo of frameless windows", Assets: application.AssetOptions{ - FS: assets, + Handler: application.AssetFileServerFS(assets), }, Mac: application.MacOptions{ ApplicationShouldTerminateAfterLastWindowClosed: true, diff --git a/v3/examples/oauth/main.go b/v3/examples/oauth/main.go index 449dbab9..14818c47 100644 --- a/v3/examples/oauth/main.go +++ b/v3/examples/oauth/main.go @@ -3,12 +3,13 @@ package main import ( "embed" _ "embed" + "log" + "os" + "github.com/markbates/goth" "github.com/markbates/goth/providers/github" "github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/plugins/oauth" - "log" - "os" ) //go:embed assets @@ -34,7 +35,7 @@ func main() { ApplicationShouldTerminateAfterLastWindowClosed: true, }, Assets: application.AssetOptions{ - FS: assets, + Handler: application.AssetFileServerFS(assets), }, Plugins: map[string]application.Plugin{ "github.com/wailsapp/wails/v3/plugins/oauth": oAuthPlugin, diff --git a/v3/examples/plugins/main.go b/v3/examples/plugins/main.go index e113a3d1..99f273d0 100644 --- a/v3/examples/plugins/main.go +++ b/v3/examples/plugins/main.go @@ -46,7 +46,7 @@ func main() { "start_at_login": start_at_login.NewPlugin(start_at_login.Config{}), }, Assets: application.AssetOptions{ - FS: assets, + Handler: application.AssetFileServerFS(assets), }, }) diff --git a/v3/examples/screen/main.go b/v3/examples/screen/main.go index 3f80e210..32c04baf 100644 --- a/v3/examples/screen/main.go +++ b/v3/examples/screen/main.go @@ -26,7 +26,7 @@ func main() { WebviewBrowserPath: "", }, Assets: application.AssetOptions{ - FS: assets, + Handler: application.AssetFileServerFS(assets), }, }) diff --git a/v3/examples/server/main.go b/v3/examples/server/main.go index f9d8633e..ca683ed4 100644 --- a/v3/examples/server/main.go +++ b/v3/examples/server/main.go @@ -30,7 +30,7 @@ func main() { }), }, Assets: application.AssetOptions{ - FS: assets, + Handler: application.AssetFileServerFS(assets), }, }) go func() { diff --git a/v3/examples/wml/main.go b/v3/examples/wml/main.go index 84f48a7c..aa7a54fb 100644 --- a/v3/examples/wml/main.go +++ b/v3/examples/wml/main.go @@ -20,7 +20,7 @@ func main() { ApplicationShouldTerminateAfterLastWindowClosed: true, }, Assets: application.AssetOptions{ - FS: assets, + Handler: application.AssetFileServerFS(assets), }, }) diff --git a/v3/internal/assetserver/assethandler.go b/v3/internal/assetserver/asset_fileserver.go similarity index 52% rename from v3/internal/assetserver/assethandler.go rename to v3/internal/assetserver/asset_fileserver.go index 253dd269..d32bfd8f 100644 --- a/v3/internal/assetserver/assethandler.go +++ b/v3/internal/assetserver/asset_fileserver.go @@ -2,12 +2,13 @@ package assetserver import ( "bytes" + "context" "embed" "errors" "fmt" "io" + "io/fs" iofs "io/fs" - "log/slog" "net/http" "os" "path" @@ -18,91 +19,53 @@ const ( indexHTML = "index.html" ) -type assetHandler struct { - fs iofs.FS - handler http.Handler - - logger *slog.Logger - - retryMissingFiles bool +type assetFileServer struct { + fs iofs.FS + err error } -func NewDefaultAssetHandler(options *Options) (http.Handler, error) { - - vfs := options.Assets - if vfs != nil { - if _, err := vfs.Open("."); err != nil { - return nil, err - } - - 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) - } - - return nil, fmt.Errorf(msg) +func newAssetFileServerFS(vfs fs.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) } - return nil, err + err = fmt.Errorf(msg) } - - vfs, err = iofs.Sub(vfs, path.Clean(subDir)) - if err != nil { - return nil, err - } - } - - var result http.Handler = &assetHandler{ - fs: vfs, - handler: options.Handler, - logger: options.Logger, - } - - if middleware := options.Middleware; middleware != nil { - result = middleware(result) - } - - return result, nil -} - -func (d *assetHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - url := req.URL.Path - handler := d.handler - if strings.EqualFold(req.Method, http.MethodGet) { - filename := path.Clean(strings.TrimPrefix(url, "/")) - - d.logInfo("Handling request", "url", url, "file", filename) - if err := d.serveFSFile(rw, req, filename); err != nil { - if os.IsNotExist(err) { - if handler != nil { - d.logInfo("File not found. Deferring to AssetHandler", "filename", filename, "url", url) - handler.ServeHTTP(rw, req) - err = nil - } else { - rw.WriteHeader(http.StatusNotFound) - err = nil - } - } - - if err != nil { - d.logError("Unable to handle request '%s': %s", url, err) - http.Error(rw, err.Error(), http.StatusInternalServerError) - } - } - } else if handler != nil { - d.logInfo("Non-GET request. Deferring to AssetHandler", "url", url) - handler.ServeHTTP(rw, req) } else { - rw.WriteHeader(http.StatusMethodNotAllowed) + 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 *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, filename string) error { +func (d *assetFileServer) serveFSFile(rw http.ResponseWriter, req *http.Request, filename string) error { if d.fs == nil { return os.ErrNotExist } @@ -182,10 +145,10 @@ func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, fi return err } -func (d *assetHandler) logInfo(message string, args ...interface{}) { - d.logger.Debug("[AssetHandler] "+message, args...) +func (d *assetFileServer) logInfo(ctx context.Context, message string, args ...interface{}) { + logInfo(ctx, "[AssetFileServerFS] "+message, args...) } -func (d *assetHandler) logError(message string, args ...interface{}) { - d.logger.Error("[AssetHandler] "+message, args...) +func (d *assetFileServer) logError(ctx context.Context, message string, args ...interface{}) { + logError(ctx, "[AssetFileServerFS] "+message, args...) } diff --git a/v3/internal/assetserver/assetserver.go b/v3/internal/assetserver/assetserver.go index 08947e92..415f33cf 100644 --- a/v3/internal/assetserver/assetserver.go +++ b/v3/internal/assetserver/assetserver.go @@ -6,7 +6,6 @@ import ( "net" "net/http" "net/http/httptest" - "net/http/httputil" "net/url" "path" "strings" @@ -29,34 +28,43 @@ type RuntimeHandler interface { type AssetServer struct { options *Options - handler http.Handler - wsHandler *httputil.ReverseProxy + handler http.Handler pluginScripts map[string]string - devServerURL string - assetServerWebView } func NewAssetServer(options *Options) (*AssetServer, error) { - result := &AssetServer{ - options: options, + result := &AssetServer{options: options} + + userHandler := options.Handler + if userHandler == nil { + userHandler = http.NotFoundHandler() } - var err error - result.handler, err = result.setupHandler() - if err != nil { - return nil, err + handler := http.Handler( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + result.serveHTTP(w, r, userHandler) + })) + + if middleware := options.Middleware; middleware != nil { + handler = middleware(handler) } + result.handler = handler + return result, nil } func (a *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { start := time.Now() wrapped := &contentTypeSniffer{rw: rw} - a.serveHTTP(wrapped, req) + + req = req.WithContext(contextWithLogger(req.Context(), a.options.Logger)) + a.handler.ServeHTTP(wrapped, req) + a.options.Logger.Info( "Asset Request:", "windowName", req.Header.Get(webViewRequestHeaderWindowName), @@ -68,17 +76,11 @@ func (a *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { ) } -func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request) { - - if a.wsHandler != nil { - a.wsHandler.ServeHTTP(rw, req) +func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request, userHandler http.Handler) { + if isWebSocket(req) { + // WebSockets are not supported by the AssetServer + rw.WriteHeader(http.StatusNotImplemented) return - } else { - if isWebSocket(req) { - // WebSockets are not supported by the AssetServer - rw.WriteHeader(http.StatusNotImplemented) - return - } } header := rw.Header() @@ -91,7 +93,7 @@ func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request) { switch path { case "", "/", "/index.html": recorder := httptest.NewRecorder() - a.handler.ServeHTTP(recorder, req) + userHandler.ServeHTTP(recorder, req) for k, v := range recorder.Result().Header { header[k] = v } @@ -126,7 +128,7 @@ func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request) { if script, ok := a.pluginScripts[path]; ok { a.writeBlob(rw, path, []byte(script)) } else { - a.handler.ServeHTTP(rw, req) + userHandler.ServeHTTP(rw, req) return } } diff --git a/v3/internal/assetserver/assetserver_dev.go b/v3/internal/assetserver/assetserver_dev.go index 6b5af30a..61f5a6b5 100644 --- a/v3/internal/assetserver/assetserver_dev.go +++ b/v3/internal/assetserver/assetserver_dev.go @@ -4,12 +4,11 @@ package assetserver func (a *AssetServer) LogDetails() { var info = []any{ - "assetsFS", a.options.Assets != nil, "middleware", a.options.Middleware != nil, "handler", a.options.Handler != nil, } - if a.devServerURL != "" { - info = append(info, "devServerURL", a.devServerURL) + if devServerURL := GetDevServerURL(); devServerURL != "" { + info = append(info, "devServerURL", devServerURL) } a.options.Logger.Info("AssetServer Info:", info...) } diff --git a/v3/internal/assetserver/build_dev.go b/v3/internal/assetserver/build_dev.go index cd84afb3..dda03f12 100644 --- a/v3/internal/assetserver/build_dev.go +++ b/v3/internal/assetserver/build_dev.go @@ -4,8 +4,7 @@ package assetserver import ( _ "embed" - "errors" - "fmt" + "io/fs" "net/http" "net/http/httputil" "net/url" @@ -19,83 +18,29 @@ func defaultIndexHTML() []byte { return defaultHTML } -func (a *AssetServer) setupHandler() (http.Handler, error) { - - // Do we have an external dev server URL? - a.devServerURL = GetDevServerURL() - if a.devServerURL == "" { - return NewDefaultAssetHandler(a.options) +func NewAssetFileServer(vfs fs.FS) http.Handler { + devServerURL := GetDevServerURL() + if devServerURL == "" { + return newAssetFileServerFS(vfs) } - // Parse the URL - parsedURL, err := url.Parse(a.devServerURL) + parsedURL, err := url.Parse(devServerURL) if err != nil { - return nil, fmt.Errorf("invalid FRONTEND_DEVSERVER_URL. Should be valid URL: %s", err.Error()) + return http.HandlerFunc( + func(rw http.ResponseWriter, req *http.Request) { + logError(req.Context(), "[ExternalAssetHandler] Invalid FRONTEND_DEVSERVER_URL. Should be valid URL", "error", err.Error()) + http.Error(rw, err.Error(), http.StatusInternalServerError) + }) + } - baseHandler := a.options.Handler - - errSkipProxy := fmt.Errorf("skip proxying") - proxy := httputil.NewSingleHostReverseProxy(parsedURL) - baseDirector := proxy.Director - proxy.Director = func(r *http.Request) { - baseDirector(r) - if a.options.Logger != nil { - a.options.Logger.Debug("ExternalAssetHandler: loading", "url", r.URL) - } - } - - proxy.ModifyResponse = func(res *http.Response) error { - if baseHandler == nil { - return nil - } - - if res.StatusCode == http.StatusSwitchingProtocols { - return nil - } - - if res.StatusCode == http.StatusNotFound || res.StatusCode == http.StatusMethodNotAllowed { - return errSkipProxy - } - - return nil - } - proxy.ErrorHandler = func(rw http.ResponseWriter, r *http.Request, err error) { - if baseHandler != nil && errors.Is(err, errSkipProxy) { - if a.options.Logger != nil { - a.options.Logger.Debug("ExternalAssetHandler: Loading file failed, using original AssetHandler", "url", r.URL) - } - baseHandler.ServeHTTP(rw, r) - } else { - if a.options.Logger != nil { - a.options.Logger.Error("ExternalAssetHandler: Proxy error", "error", err.Error()) - } - rw.WriteHeader(http.StatusBadGateway) - } + logError(r.Context(), "[ExternalAssetHandler] Proxy error", "error", err.Error()) + rw.WriteHeader(http.StatusBadGateway) } - var result http.Handler = http.HandlerFunc( - func(rw http.ResponseWriter, req *http.Request) { - if req.Method == http.MethodGet { - proxy.ServeHTTP(rw, req) - return - } - - if baseHandler != nil { - baseHandler.ServeHTTP(rw, req) - return - } - - rw.WriteHeader(http.StatusMethodNotAllowed) - }) - - if middleware := a.options.Middleware; middleware != nil { - result = middleware(result) - } - - return result, nil + return proxy } func GetDevServerURL() string { diff --git a/v3/internal/assetserver/build_production.go b/v3/internal/assetserver/build_production.go index 0f858faf..b5bba6b1 100644 --- a/v3/internal/assetserver/build_production.go +++ b/v3/internal/assetserver/build_production.go @@ -2,14 +2,17 @@ package assetserver -import "net/http" +import ( + "io/fs" + "net/http" +) func defaultIndexHTML() []byte { return []byte{} } -func (a *AssetServer) setupHandler() (http.Handler, error) { - return NewDefaultAssetHandler(a.options) +func NewAssetFileServer(vfs fs.FS) http.Handler { + return newAssetFileServerFS(vfs) } func GetDevServerURL() string { diff --git a/v3/internal/assetserver/common.go b/v3/internal/assetserver/common.go index 484858cf..109c228b 100644 --- a/v3/internal/assetserver/common.go +++ b/v3/internal/assetserver/common.go @@ -2,8 +2,10 @@ package assetserver import ( "bytes" + "context" "fmt" "io" + "log/slog" "net/http" "strings" ) @@ -20,6 +22,10 @@ const ( WailsUserAgentValue = "wails.io" ) +var ( + assetServerLogger = struct{}{} +) + func serveFile(rw http.ResponseWriter, filename string, blob []byte) error { header := rw.Header() header.Set(HeaderContentLength, fmt.Sprintf("%d", len(blob))) @@ -37,3 +43,19 @@ func isWebSocket(req *http.Request) bool { upgrade := req.Header.Get(HeaderUpgrade) return strings.EqualFold(upgrade, "websocket") } + +func contextWithLogger(ctx context.Context, logger *slog.Logger) context.Context { + return context.WithValue(ctx, assetServerLogger, logger) +} + +func logInfo(ctx context.Context, message string, args ...interface{}) { + if logger, _ := ctx.Value(assetServerLogger).(*slog.Logger); logger != nil { + logger.Info(message, args...) + } +} + +func logError(ctx context.Context, message string, args ...interface{}) { + if logger, _ := ctx.Value(assetServerLogger).(*slog.Logger); logger != nil { + logger.Error(message, args...) + } +} diff --git a/v3/internal/assetserver/fs.go b/v3/internal/assetserver/fs.go index 65f24ed7..4d8aad57 100644 --- a/v3/internal/assetserver/fs.go +++ b/v3/internal/assetserver/fs.go @@ -10,8 +10,8 @@ import ( "strings" ) -// FindEmbedRootPath finds the root path in the embed FS. It's the directory which contains all the files. -func FindEmbedRootPath(fileSystem embed.FS) (string, error) { +// findEmbedRootPath finds the root path in the embed FS. It's the directory which contains all the files. +func findEmbedRootPath(fileSystem embed.FS) (string, error) { stopErr := fmt.Errorf("files or multiple dirs found") fPath := "" @@ -39,7 +39,7 @@ func FindEmbedRootPath(fileSystem embed.FS) (string, error) { return fPath, nil } -func FindPathToFile(fileSystem fs.FS, file string) (string, error) { +func findPathToFile(fileSystem fs.FS, file string) (string, error) { stat, _ := fs.Stat(fileSystem, file) if stat != nil { return ".", nil diff --git a/v3/internal/assetserver/options.go b/v3/internal/assetserver/options.go index f199a9b0..60c43e26 100644 --- a/v3/internal/assetserver/options.go +++ b/v3/internal/assetserver/options.go @@ -2,26 +2,13 @@ package assetserver import ( "fmt" - "io/fs" "log/slog" "net/http" ) // Options defines the configuration of the AssetServer. type Options struct { - // Assets defines the static assets to be used. A GET request is first tried to be served from this Assets. If the Assets returns - // `os.ErrNotExist` for that file, the request handling will fallback to the Handler and tries to serve the GET - // request from it. - // - // If set to nil, all GET requests will be forwarded to Handler. - Assets fs.FS - - // Handler will be called for every GET request that can't be served from Assets, due to `os.ErrNotExist`. Furthermore all - // non GET requests will always be served from this Handler. - // - // If not defined, the result is the following in cases where the Handler would have been called: - // GET request: `http.StatusNotFound` - // Other request: `http.StatusMethodNotAllowed` + // Handler which serves all the content to the WebView. Handler http.Handler // Middleware is a HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default @@ -29,6 +16,8 @@ type Options struct { // The Middleware is called to build a new `http.Handler` used by the AssetSever and it also receives the default // handler used by the AssetServer as an argument. // + // This middleware injects itself before any of Wails internal middlewares. + // // If not defined, the default AssetServer request chain is executed. // // Multiple Middlewares can be chained together with: @@ -50,8 +39,8 @@ type Options struct { // Validate the options func (o Options) Validate() error { - if o.Assets == nil && o.Handler == nil && o.Middleware == nil { - return fmt.Errorf("AssetServer options invalid: either Assets, Handler or Middleware must be set") + if o.Handler == nil && o.Middleware == nil { + return fmt.Errorf("AssetServer options invalid: either Handler or Middleware must be set") } return nil diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go index 319acd6b..60fd64e5 100644 --- a/v3/pkg/application/application.go +++ b/v3/pkg/application/application.go @@ -3,9 +3,6 @@ package application import ( "embed" "encoding/json" - "github.com/pkg/browser" - "github.com/samber/lo" - "github.com/wailsapp/wails/v3/internal/signal" "io" "log" "log/slog" @@ -15,6 +12,10 @@ import ( "strconv" "sync" + "github.com/pkg/browser" + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/internal/signal" + "github.com/wailsapp/wails/v3/internal/assetserver" "github.com/wailsapp/wails/v3/internal/assetserver/webview" "github.com/wailsapp/wails/v3/internal/capabilities" @@ -29,7 +30,7 @@ var globalApplication *App // AlphaAssets is the default assets for the alpha application var AlphaAssets = AssetOptions{ - FS: alphaAssets, + Handler: AssetFileServerFS(alphaAssets), } func init() { @@ -76,7 +77,6 @@ func New(appOptions Options) *App { result.Events = NewWailsEventProcessor(result.dispatchEventToWindows) opts := &assetserver.Options{ - Assets: appOptions.Assets.FS, Handler: appOptions.Assets.Handler, Middleware: assetserver.Middleware(appOptions.Assets.Middleware), Logger: result.Logger, diff --git a/v3/pkg/application/options_application.go b/v3/pkg/application/options_application.go index d005dfb2..10bf7518 100644 --- a/v3/pkg/application/options_application.go +++ b/v3/pkg/application/options_application.go @@ -4,6 +4,8 @@ import ( "io/fs" "log/slog" "net/http" + + "github.com/wailsapp/wails/v3/internal/assetserver" ) type Options struct { @@ -68,26 +70,16 @@ type Options struct { // AssetOptions defines the configuration of the AssetServer. type AssetOptions struct { - // FS defines the static assets to be used. A GET request is first tried to be served from this FS. If the FS returns - // `os.ErrNotExist` for that file, the request handling will fallback to the Handler and tries to serve the GET - // request from it. - // - // If set to nil, all GET requests will be forwarded to Handler. - FS fs.FS - - // Handler will be called for every GET request that can't be served from FS, due to `os.ErrNotExist`. Furthermore all - // non GET requests will always be served from this Handler. - // - // If not defined, the result is the following in cases where the Handler would have been called: - // GET request: `http.StatusNotFound` - // Other request: `http.StatusMethodNotAllowed` + // Handler which serves all the content to the WebView. Handler http.Handler - // Middleware is HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default + // Middleware is a HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default // request handler dynamically, e.g. implement specialized Routing etc. // The Middleware is called to build a new `http.Handler` used by the AssetSever and it also receives the default // handler used by the AssetServer as an argument. // + // This middleware injects itself before any of Wails internal middlewares. + // // If not defined, the default AssetServer request chain is executed. // // Multiple Middlewares can be chained together with: @@ -112,3 +104,10 @@ func ChainMiddleware(middleware ...Middleware) Middleware { return h } } + +// AssetFileServerFS returns a http handler which serves the assets from the fs.FS. +// If an external devserver has been provided 'FRONTEND_DEVSERVER_URL' the files are being served +// from the external server, ignoring the `assets`. +func AssetFileServerFS(assets fs.FS) http.Handler { + return assetserver.NewAssetFileServer(assets) +}