[assetserver] Introduce middleware and extract options (#2016)

* [assetserver] Add support for HTTP Middlewares

* [dev] Disable frontend DevServer if no Assets has been defined and inform user

* [dev] Consistent WebSocket behaviour in dev and prod mode for assets handler and middleware

In prod mode we can't support WebSockets so make sure the
assets handler and middleware never see WebSockets in dev mode.

* [templates] Migrate to new AssetServer option

* [docs] Add assetserver.Options to the reference
This commit is contained in:
stffabi 2022-10-29 23:15:15 +02:00 committed by GitHub
commit 638caf72f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 397 additions and 150 deletions

View file

@ -7,6 +7,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v2/pkg/options/mac"
"github.com/wailsapp/wails/v2/pkg/options/windows"
)
@ -36,7 +37,9 @@ func main() {
StartHidden: false,
HideWindowOnClose: false,
BackgroundColour: &options.RGBA{R: 255, G: 255, B: 255, A: 255},
Assets: assets,
AssetServer: &assetserver.Options{
Assets: assets,
},
Menu: nil,
Logger: nil,
LogLevel: logger.DEBUG,

View file

@ -9,6 +9,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v2/pkg/options/windows"
)
@ -37,7 +38,9 @@ func main() {
StartHidden: false,
HideWindowOnClose: false,
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
Assets: assets,
AssetServer: &assetserver.Options{
Assets: assets,
},
Menu: nil,
Logger: nil,
LogLevel: logger.DEBUG,

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -13,6 +13,7 @@ import (
"path/filepath"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
"github.com/wailsapp/wails/v2/internal/frontend/desktop"
"github.com/wailsapp/wails/v2/internal/frontend/devserver"
"github.com/wailsapp/wails/v2/internal/frontend/dispatcher"
@ -90,6 +91,14 @@ func CreateApp(appoptions *options.App) (*App, error) {
}
}
assetConfig := assetserver.BuildAssetServerConfig(appoptions)
if assetConfig.Assets == nil && frontendDevServerURL != "" {
myLogger.Warning("No AssetServer.Assets has been defined but a frontend DevServer, the frontend DevServer will not be used.")
frontendDevServerURL = ""
assetdir = ""
}
if frontendDevServerURL != "" {
if devServer == "" {
return nil, fmt.Errorf("Unable to use FrontendDevServerUrl without a DevServer address")
@ -107,7 +116,7 @@ func CreateApp(appoptions *options.App) (*App, error) {
} else {
if assetdir == "" {
// If no assetdir has been defined, let's try to infer it from the project root and the asset FS.
assetdir, err = tryInferAssetDirFromFS(appoptions.Assets)
assetdir, err = tryInferAssetDirFromFS(assetConfig.Assets)
if err != nil {
return nil, err
}
@ -121,12 +130,17 @@ func CreateApp(appoptions *options.App) (*App, error) {
}
myLogger.Info("Serving assets from disk: %s", absdir)
appoptions.Assets = os.DirFS(absdir)
assetConfig.Assets = os.DirFS(absdir)
ctx = context.WithValue(ctx, "assetdir", assetdir)
}
}
// Migrate deprecated options to the new AssetServer option
appoptions.Assets = nil
appoptions.AssetsHandler = nil
appoptions.AssetServer = &assetConfig
if devServer != "" {
ctx = context.WithValue(ctx, "devserver", devServer)
}

View file

@ -14,6 +14,7 @@ import (
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed defaultindex.html
@ -32,7 +33,13 @@ type assetHandler struct {
retryMissingFiles bool
}
func NewAssetHandler(ctx context.Context, vfs iofs.FS, assetsHandler http.Handler) (http.Handler, error) {
func NewAssetHandler(ctx context.Context, options assetserver.Options) (http.Handler, error) {
var log *logger.Logger
if _logger := ctx.Value("logger"); _logger != nil {
log = _logger.(*logger.Logger)
}
vfs := options.Assets
if vfs != nil {
if _, err := vfs.Open("."); err != nil {
return nil, err
@ -49,13 +56,14 @@ func NewAssetHandler(ctx context.Context, vfs iofs.FS, assetsHandler http.Handle
}
}
result := &assetHandler{
var result http.Handler = &assetHandler{
fs: vfs,
handler: assetsHandler,
handler: options.Handler,
logger: log,
}
if _logger := ctx.Value("logger"); _logger != nil {
result.logger = _logger.(*logger.Logger)
if middleware := options.Middleware; middleware != nil {
result = middleware(result)
}
return result, nil

View file

@ -4,15 +4,16 @@ import (
"bytes"
"context"
"fmt"
iofs "io/fs"
"net/http"
"net/http/httptest"
"strconv"
"golang.org/x/net/html"
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/wailsapp/wails/v2/internal/logger"
"golang.org/x/net/html"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
const (
@ -22,6 +23,7 @@ const (
type AssetServer struct {
handler http.Handler
wsHandler http.Handler
runtimeJS []byte
ipcJS func(*http.Request) []byte
@ -31,8 +33,12 @@ type AssetServer struct {
appendSpinnerToBody bool
}
func NewAssetServer(ctx context.Context, vfs iofs.FS, assetsHandler http.Handler, bindingsJSON string) (*AssetServer, error) {
handler, err := NewAssetHandler(ctx, vfs, assetsHandler)
func NewAssetServerMainPage(ctx context.Context, bindingsJSON string, options *options.App) (*AssetServer, error) {
return NewAssetServer(ctx, bindingsJSON, BuildAssetServerConfig(options))
}
func NewAssetServer(ctx context.Context, bindingsJSON string, options assetserver.Options) (*AssetServer, error) {
handler, err := NewAssetHandler(ctx, options)
if err != nil {
return nil, err
}
@ -66,17 +72,21 @@ func NewAssetServerWithHandler(ctx context.Context, handler http.Handler, bindin
}
func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if isWebSocket(req) {
// Forward WebSockets to the distinct websocket handler if it exists
if wsHandler := d.wsHandler; wsHandler != nil {
wsHandler.ServeHTTP(rw, req)
} else {
rw.WriteHeader(http.StatusNotImplemented)
}
return
}
header := rw.Header()
if d.servingFromDisk {
header.Add(HeaderCacheControl, "no-cache")
}
if isWebSocket(req) {
// WebSockets can always directly be forwarded to the handler
d.handler.ServeHTTP(rw, req)
return
}
path := req.URL.Path
switch path {
case "", "/", "/index.html":

View file

@ -12,15 +12,17 @@ import (
)
/*
The assetserver for dev serves assets from disk.
It injects a websocket based IPC script into `index.html`.
The assetserver for the dev mode.
Depending on the UserAgent it injects a websocket based IPC script into `index.html` or the default desktop IPC. The
default desktop IPC is injected when the webview accesses the devserver.
*/
func NewBrowserAssetServer(ctx context.Context, handler http.Handler, bindingsJSON string) (*AssetServer, error) {
func NewDevAssetServer(ctx context.Context, handler http.Handler, wsHandler http.Handler, bindingsJSON string) (*AssetServer, error) {
result, err := NewAssetServerWithHandler(ctx, handler, bindingsJSON)
if err != nil {
return nil, err
}
result.wsHandler = wsHandler
result.appendSpinnerToBody = true
result.ipcJS = func(req *http.Request) []byte {
if strings.Contains(req.UserAgent(), WailsUserAgentValue) {

View file

@ -8,9 +8,26 @@ import (
"net/http"
"strings"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"golang.org/x/net/html"
)
func BuildAssetServerConfig(options *options.App) assetserver.Options {
if opts := options.AssetServer; opts != nil {
if options.Assets != nil || options.AssetsHandler != nil {
panic("It's not possible to use the deprecated Assets and AssetsHandler options and the new AssetServer option at the same time. Please migrate all your Assets options to the AssetServer option.")
}
return *opts
}
return assetserver.Options{
Assets: options.Assets,
Handler: options.AssetsHandler,
}
}
const (
HeaderHost = "Host"
HeaderContentType = "Content-Type"

View file

@ -90,7 +90,7 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
} else {
appBindings.DB().UpdateObfuscatedCallMap()
}
assets, err := assetserver.NewAssetServer(ctx, appoptions.Assets, appoptions.AssetsHandler, bindings)
assets, err := assetserver.NewAssetServerMainPage(ctx, bindings, appoptions)
if err != nil {
log.Fatal(err)
}

View file

@ -94,7 +94,7 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
} else {
appBindings.DB().UpdateObfuscatedCallMap()
}
assets, err := assetserver.NewAssetServer(ctx, appoptions.Assets, appoptions.AssetsHandler, bindings)
assets, err := assetserver.NewAssetServerMainPage(ctx, bindings, appoptions)
if err != nil {
log.Fatal(err)
}

View file

@ -102,7 +102,7 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
appBindings.DB().UpdateObfuscatedCallMap()
}
assets, err := assetserver.NewAssetServer(ctx, appoptions.Assets, appoptions.AssetsHandler, bindings)
assets, err := assetserver.NewAssetServerMainPage(ctx, bindings, appoptions)
if err != nil {
log.Fatal(err)
}
@ -435,9 +435,8 @@ func (f *Frontend) setupChromium() {
log.Fatal(err)
}
if opts := f.frontendOptions.Windows; opts != nil {
if opts.ZoomFactor > 0.0 {
if opts := f.frontendOptions.Windows; opts != nil {
if opts.ZoomFactor > 0.0 {
chromium.PutZoomFactor(opts.ZoomFactor)
}
err = settings.PutIsZoomControlEnabled(opts.IsZoomControlEnabled)

View file

@ -12,6 +12,7 @@ import (
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"sync"
@ -53,7 +54,10 @@ func (d *DevWebServer) Run(ctx context.Context) error {
d.server.GET("/wails/reload", d.handleReload)
d.server.GET("/wails/ipc", d.handleIPCWebSocket)
assetServerConfig := assetserver.BuildAssetServerConfig(d.appoptions)
var assetHandler http.Handler
var wsHandler http.Handler
_fronendDevServerURL, _ := ctx.Value("frontenddevserverurl").(string)
if _fronendDevServerURL == "" {
assetdir, _ := ctx.Value("assetdir").(string)
@ -62,7 +66,7 @@ func (d *DevWebServer) Run(ctx context.Context) error {
})
var err error
assetHandler, err = assetserver.NewAssetHandler(ctx, d.appoptions.Assets, d.appoptions.AssetsHandler)
assetHandler, err = assetserver.NewAssetHandler(ctx, assetServerConfig)
if err != nil {
log.Fatal(err)
}
@ -81,7 +85,11 @@ func (d *DevWebServer) Run(ctx context.Context) error {
d.logger.Error("Timeout waiting for frontend DevServer")
}
assetHandler = newExternalDevServerAssetHandler(d.logger, externalURL, d.appoptions.AssetsHandler)
assetHandler = newExternalDevServerAssetHandler(d.logger, externalURL, assetServerConfig)
// WebSockets aren't currently supported in prod mode, so a WebSocket connection is the result of the
// FrontendDevServer e.g. Vite to support auto reloads.
// Therefore we direct WebSockets directly to the FrontendDevServer instead of returning a NotImplementedStatus.
wsHandler = httputil.NewSingleHostReverseProxy(externalURL)
}
// Setup internal dev server
@ -90,7 +98,7 @@ func (d *DevWebServer) Run(ctx context.Context) error {
log.Fatal(err)
}
assetServer, err := assetserver.NewBrowserAssetServer(ctx, assetHandler, bindingsJSON)
assetServer, err := assetserver.NewDevAssetServer(ctx, assetHandler, wsHandler, bindingsJSON)
if err != nil {
log.Fatal(err)
}

View file

@ -10,11 +10,21 @@ import (
"net/http/httputil"
"net/url"
"github.com/labstack/echo/v4"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
func newExternalDevServerAssetHandler(logger *logger.Logger, url *url.URL, handler http.Handler) http.Handler {
func newExternalDevServerAssetHandler(logger *logger.Logger, url *url.URL, options assetserver.Options) http.Handler {
handler := newExternalAssetsHandler(logger, url, options.Handler)
if middleware := options.Middleware; middleware != nil {
handler = middleware(handler)
}
return handler
}
func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.Handler) http.Handler {
errSkipProxy := fmt.Errorf("skip proxying")
proxy := httputil.NewSingleHostReverseProxy(url)
@ -56,23 +66,18 @@ func newExternalDevServerAssetHandler(logger *logger.Logger, url *url.URL, handl
}
}
e := echo.New()
e.Any("/*",
func(c echo.Context) error {
req := c.Request()
rw := c.Response()
if c.IsWebSocket() || req.Method == http.MethodGet {
return http.HandlerFunc(
func(rw http.ResponseWriter, req *http.Request) {
if req.Method == http.MethodGet {
proxy.ServeHTTP(rw, req)
return nil
return
}
if handler != nil {
handler.ServeHTTP(rw, req)
return nil
return
}
return c.NoContent(http.StatusMethodNotAllowed)
rw.WriteHeader(http.StatusMethodNotAllowed)
})
return e
}

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "staticanalysis",
Width: 1024,
Height: 768,
Assets: assets,
Title: "staticanalysis",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -0,0 +1,20 @@
package assetserver
import (
"net/http"
)
// Middleware defines a HTTP middleware that can be applied to the AssetServer.
// The handler passed as next is the next handler in the chain. One can decide to call the next handler
// or implement a specialized handling.
type Middleware func(next http.Handler) http.Handler
// ChainMiddleware allows chaining multiple middlewares to one middleware.
func ChainMiddleware(middleware ...Middleware) Middleware {
return func(h http.Handler) http.Handler {
for i := len(middleware) - 1; i >= 0; i-- {
h = middleware[i](h)
}
return h
}
}

View file

@ -0,0 +1,35 @@
package assetserver
import (
"io/fs"
"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 http.Handler
// 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.
//
// If not defined, the default AssetServer request chain is executed.
//
// Multiple Middlewares can be chained together with:
// ChainMiddleware(middleware ...Middleware) Middleware
Middleware Middleware
}

View file

@ -8,6 +8,7 @@ import (
"net/http"
"runtime"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v2/pkg/options/linux"
"github.com/wailsapp/wails/v2/pkg/options/mac"
"github.com/wailsapp/wails/v2/pkg/options/windows"
@ -47,9 +48,13 @@ type App struct {
AlwaysOnTop bool
// BackgroundColour is the background colour of the window
// You can use the options.NewRGB and options.NewRGBA functions to create a new colour
BackgroundColour *RGBA
Assets fs.FS
AssetsHandler http.Handler
BackgroundColour *RGBA
// Deprecated: Use AssetServer.Assets instead.
Assets fs.FS
// Deprecated: Use AssetServer.Handler instead.
AssetsHandler http.Handler
// AssetServer configures the Assets for the application
AssetServer *assetserver.Options
Menu *menu.Menu
Logger logger.Logger `json:"-"`
LogLevel logger.LogLevel

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed frontend/dist
@ -16,10 +17,12 @@ app := NewApp()
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -9,6 +9,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v2/pkg/options/windows"
)
@ -37,7 +38,9 @@ Frameless: false,
StartHidden: false,
HideWindowOnClose: false,
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
Assets: assets,
AssetServer: &assetserver.Options{
Assets: assets,
},
Menu: nil,
Logger: nil,
LogLevel: logger.DEBUG,

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/src
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -16,10 +17,12 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
Assets: assets,
Title: "{{.ProjectName}}",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
Bind: []interface{}{

View file

@ -19,6 +19,7 @@ import (
"fmt"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"net/http"
"os"
"strings"
@ -54,13 +55,15 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "helloworld",
Width: 1024,
Height: 768,
Assets: assets,
Title: "helloworld",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
Handler: NewFileLoader(),
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 255},
OnStartup: app.startup,
AssetsHandler: NewFileLoader(),
Bind: []interface{}{
app,
},

View file

@ -38,6 +38,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -49,15 +50,17 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "alwaysontop",
Width: 1024,
Height: 768,
Assets: assets,
Title: "alwaysontop",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
Frameless: true,
CSSDragProperty: "widows",
CSSDragValue: "1",
Bind: []interface{}{
app,
app,
},
})

View file

@ -31,7 +31,9 @@ In v2, there is just a single method, `wails.Run()`, that accepts [application o
Title: "MyApp",
Width: 800,
Height: 600,
Assets: assets,
AssetServer: &assetserver.Options{
Assets: assets,
},
Bind: []interface{}{
basic,
},
@ -165,7 +167,9 @@ var assets embed.FS
func main() {
err := wails.Run(&options.App{
/* Other Options */
Assets: assets,
AssetServer: &assetserver.Options{
Assets: assets,
},
})
}
```

View file

@ -33,6 +33,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -43,12 +44,14 @@ func main() {
app := &App{}
err := wails.Run(&options.App{
Title: "Basic Demo",
Width: 1024,
Height: 768,
Assets: &assets,
OnStartup: app.startup,
OnShutdown: app.shutdown,
Title: "Basic Demo",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
OnStartup: app.startup,
OnShutdown: app.shutdown,
Bind: []interface{}{
app,
},
@ -147,6 +150,7 @@ import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
//go:embed all:frontend/dist
@ -157,10 +161,12 @@ func main() {
app := &App{}
err := wails.Run(&options.App{
Title: "Basic Demo",
Width: 1024,
Height: 768,
Assets: &assets,
Title: "Basic Demo",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
Bind: []interface{}{
app,
},
@ -185,10 +191,12 @@ You may bind as many structs as you like. Just make sure you create an instance
```go {8-10}
//...
err := wails.Run(&options.App{
Title: "Basic Demo",
Width: 1024,
Height: 768,
Assets: &assets,
Title: "Basic Demo",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
Bind: []interface{}{
app,
&mystruct1{},

View file

@ -10,7 +10,13 @@ The `Options.App` struct contains the application configuration.
It is passed to the `wails.Run()` method:
```go title="Example"
import "github.com/wailsapp/wails/v2/pkg/options"
import (
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v2/pkg/options/linux"
"github.com/wailsapp/wails/v2/pkg/options/mac"
"github.com/wailsapp/wails/v2/pkg/options/windows"
)
func main() {
@ -29,8 +35,11 @@ func main() {
HideWindowOnClose: false,
BackgroundColour: &options.RGBA{R: 0, G: 0, B: 0, A: 255},
AlwaysOnTop: false,
Assets: assets,
AssetsHandler: assetsHandler,
AssetServer: &assetserver.Options{
Assets: assets,
Handler: assetsHandler,
Middleware: assetsMidldeware,
},
Menu: app.applicationMenu(),
Logger: nil,
LogLevel: logger.DEBUG,
@ -213,19 +222,20 @@ Type: `bool`
### Assets
The frontend assets to be used by the application. Requires an `index.html` file.
Name: Assets<br/>
Type: `embed.FS`
Deprecated: Please use Assets on [AssetServer specific options](#assetserver).
### AssetsHandler
<img src="http://badges.github.io/stability-badges/dist/experimental.svg" />
Deprecated: Please use AssetsHandler on [AssetServer specific options](#assetserver).
The assets handler is a generic `http.Handler` which will be called for any non GET request on the assets server
and for GET requests which can not be served from the `assets` because the file is not found.
### AssetServer
| Value | Win | Mac | Lin |
This defines AssetServer specific options. It allows to customize the AssetServer with static assets, serving assets
dynamically with an `http.Handler` or hook into the request chain with an `assetserver.Middleware`.
Not all features of an `http.Request` are currently supported, please see the following feature matrix:
| Feature | Win | Mac | Lin |
| ----------------------- | --- | --- | --- |
| GET | ✅ | ✅ | ✅ |
| POST | ✅ | ✅ | ❌ |
@ -239,16 +249,55 @@ and for GET requests which can not be served from the `assets` because the file
| Response Headers | ✅ | ✅ | ❌ |
| Response Body | ✅ | ✅ | ✅ |
| Response Body Streaming | ❌ | ❌ | ✅ |
| WebSockets | ❌ | ❌ | ❌ |
NOTE: Linux is currently very limited due to targeting a WebKit2GTK Version < 2.36.0. In the future some features will be
supported by the introduction of WebKit2GTK 2.36.0+ support.
Name: AssetServer<br/>
Type: `*assetserver.Options`
#### Assets
The static frontend assets to be used by the application.
A GET request is first tried to be served from this `fs.FS`. If the `fs.FS` returns `os.ErrNotExist` for that file,
the request handling will fallback to the [Handler](#handler) and tries to serve the GET request from it.
If set to nil, all GET requests will be forwarded to [Handler](#handler).
Name: Assets<br/>
Type: `fs.FS`
#### Handler
The assets handler is a generic `http.Handler` for fallback handling of assets that can't be found.
The handler will be called for every GET request that can't be served from [Assets](#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`
NOTE: When used in combination with a Frontend DevServer there might be limitations, eg. Vite serves the index.html
on every path, that does not contain a file extension.
Name: AssetsHandler<br/>
Type: `http.Handler`
#### Middleware
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.
If not defined, the default AssetServer request chain is executed.
Name: Middleware<br/>
Type: `assetserver.Middleware`
### Menu
The menu to be used by the application. More details about Menus in the [Menu Reference](../reference/runtime/menu.mdx).