Compare commits

...

4 Commits

Author SHA1 Message Date
Lea Anthony 3b764e8ce4 Fix logger for build type 2024-01-16 21:39:23 +11:00
Lea Anthony 26bb58e128 Moved to file config for watcher 2024-01-16 20:54:57 +11:00
Lea Anthony 0e20677116 Refactored handling of external dev server 2024-01-15 21:59:18 +11:00
Lea Anthony 62c47bd089 Fix external server URL 2024-01-14 20:14:24 +11:00
33 changed files with 358 additions and 607 deletions
+2 -2
View File
@@ -25,7 +25,7 @@ func init() {
}
func main() {
app := clir.NewCli("wails", "The Wails CLI", "v3")
app := clir.NewCli("wails", "The Wails3 CLI", "v3")
app.NewSubCommandFunction("init", "Initialise a new project", commands.Init)
app.NewSubCommandFunction("build", "Build the project", commands.Build)
app.NewSubCommandFunction("dev", "Run in Dev mode", commands.Dev)
@@ -37,7 +37,7 @@ func main() {
task.Action(func() error {
return commands.RunTask(&taskFlags, task.OtherArgs())
})
task.LongDescription("\nUsage: wails task [taskname] [flags]\n\nTasks are defined in the `Taskfile.yaml` file. See https://taskfile.dev for more information.")
task.LongDescription("\nUsage: wails3 task [taskname] [flags]\n\nTasks are defined in the `Taskfile.yaml` file. See https://taskfile.dev for more information.")
generate := app.NewSubCommand("generate", "Generation tools")
generate.NewSubCommandFunction("build-assets", "Generate build assets", commands.GenerateBuildAssets)
generate.NewSubCommandFunction("icons", "Generate icons", commands.GenerateIcons)
+1 -1
View File
@@ -5,7 +5,7 @@ go 1.21.1
toolchain go1.21.4
require (
github.com/atterpac/refresh v0.2.4
github.com/atterpac/refresh v0.4.29
github.com/bep/debounce v1.2.1
github.com/davecgh/go-spew v1.1.1
github.com/ebitengine/purego v0.4.0-alpha.4
+2
View File
@@ -67,6 +67,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
github.com/atterpac/refresh v0.2.4 h1:rTD9qchbLa0qPIVr+qpyqkePe6+1Cs14ETXDguEvttI=
github.com/atterpac/refresh v0.2.4/go.mod h1:KiBQUk5h76Z3Yvw/k3Ys2JGDrKmKmflwS9XB6B5N9qY=
github.com/atterpac/refresh v0.4.29 h1:ObGnm1AxV3WWBXHFWlyOTNT/cPyLDtMW8JkexoXKdG4=
github.com/atterpac/refresh v0.4.29/go.mod h1:nwaO4jT1N3pscamqKFExmlYi8K+kfgRFSmzwxFRJRKQ=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+2 -5
View File
@@ -14,9 +14,6 @@ import (
"strings"
)
//go:embed defaultindex.html
var defaultHTML []byte
const (
indexHTML = "index.html"
)
@@ -30,7 +27,7 @@ type assetHandler struct {
retryMissingFiles bool
}
func NewAssetHandler(options *Options, log *slog.Logger) (http.Handler, error) {
func NewDefaultAssetHandler(options *Options) (http.Handler, error) {
vfs := options.Assets
if vfs != nil {
@@ -62,7 +59,7 @@ func NewAssetHandler(options *Options, log *slog.Logger) (http.Handler, error) {
var result http.Handler = &assetHandler{
fs: vfs,
handler: options.Handler,
logger: log,
logger: options.Logger,
}
if middleware := options.Middleware; middleware != nil {
@@ -1,79 +0,0 @@
//go:build dev
// +build dev
package assetserver
import (
"errors"
"fmt"
"log/slog"
"net/http"
"net/http/httputil"
"net/url"
)
func NewExternalAssetsHandler(logger *slog.Logger, options Options, url *url.URL) http.Handler {
baseHandler := options.Handler
errSkipProxy := fmt.Errorf("skip proxying")
proxy := httputil.NewSingleHostReverseProxy(url)
baseDirector := proxy.Director
proxy.Director = func(r *http.Request) {
baseDirector(r)
if logger != nil {
logger.Debug("[ExternalAssetHandler] Loading '%s'", 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 logger != nil {
logger.Debug("[ExternalAssetHandler] Loading '%s' failed, using original AssetHandler", r.URL)
}
baseHandler.ServeHTTP(rw, r)
} else {
if logger != nil {
logger.Error("[ExternalAssetHandler] Proxy error: %v", err)
}
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 := options.Middleware; middleware != nil {
result = middleware(result)
}
return result
}
+59 -181
View File
@@ -1,157 +1,60 @@
package assetserver
import (
"bytes"
"fmt"
"log/slog"
"math/rand"
"net"
"html"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"strings"
"time"
"golang.org/x/net/html"
)
const (
runtimeJSPath = "/wails/runtime.js"
runtimePath = "/wails/runtime"
capabilitiesPath = "/wails/capabilities"
flagsPath = "/wails/flags"
webViewRequestHeaderWindowId = "x-wails-window-id"
webViewRequestHeaderWindowName = "x-wails-window-name"
)
const webViewRequestHeaderWindowId = "x-wails-window-id"
const webViewRequestHeaderWindowName = "x-wails-window-name"
type RuntimeAssets interface {
DesktopIPC() []byte
WebsocketIPC() []byte
RuntimeDesktopJS() []byte
}
type RuntimeHandler interface {
HandleRuntimeCall(w http.ResponseWriter, r *http.Request)
}
type AssetServer struct {
handler http.Handler
runtimeJS []byte
debug bool
ipcJS func(*http.Request) []byte
logger *slog.Logger
runtime RuntimeAssets
options *Options
servingFromDisk bool
// Use http based runtime
runtimeHandler RuntimeHandler
// plugin scripts
pluginScripts map[string]string
// GetCapabilities returns the capabilities of the runtime
GetCapabilities func() []byte
// GetFlags returns the application flags
GetFlags func() []byte
// External dev server proxy
handler http.Handler
wsHandler *httputil.ReverseProxy
// External dev server URL
pluginScripts map[string]string
devServerURL string
assetServerWebView
}
func NewAssetServer(options *Options, servingFromDisk bool, logger *slog.Logger, runtime RuntimeAssets, debug bool, runtimeHandler RuntimeHandler) (*AssetServer, error) {
handler, err := NewAssetHandler(options, logger)
func NewAssetServer(options *Options) (*AssetServer, error) {
result := &AssetServer{
options: options,
}
var err error
result.handler, err = result.setupHandler()
if err != nil {
return nil, err
}
var buffer bytes.Buffer
buffer.Write(runtime.RuntimeDesktopJS())
result := &AssetServer{
handler: handler,
runtimeJS: buffer.Bytes(),
runtimeHandler: runtimeHandler,
options: options,
// 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: servingFromDisk,
logger: logger,
runtime: runtime,
debug: debug,
}
// Check if proxy required
result.devServerURL = GetDevServerURL()
if result.devServerURL != "" {
logger.Info("Using External DevServer", "url", result.devServerURL)
// Parse devServerURL into url.URL
devServerURL, err := url.Parse(result.devServerURL)
if err != nil {
return nil, err
}
err = result.checkDevServerURL(devServerURL)
if err != nil {
return nil, err
}
result.wsHandler = httputil.NewSingleHostReverseProxy(devServerURL)
}
return result, nil
}
func (d *AssetServer) checkDevServerURL(devServerURL *url.URL) error {
// Open a connection to the devserver URL
hostPort := devServerURL.Hostname() + ":" + devServerURL.Port()
_, err := net.DialTimeout("tcp", hostPort, 1*time.Second)
if err != nil {
return fmt.Errorf("unable to connect to dev server: %s. Please check it's running", d.devServerURL)
}
return nil
}
func (d *AssetServer) LogDetails() {
if d.debug {
var info = []any{
"assetsFS", d.options.Assets != nil,
"middleware", d.options.Middleware != nil,
"handler", d.options.Handler != nil,
"devServerURL", d.devServerURL,
}
if d.devServerURL != "" {
info = append(info, "devServerURL", d.devServerURL)
}
d.logger.Info("AssetServer Info:", info...)
}
}
func (d *AssetServer) AddPluginScript(pluginName string, script string) {
if d.pluginScripts == nil {
d.pluginScripts = make(map[string]string)
}
pluginName = strings.ReplaceAll(pluginName, "/", "_")
pluginName = html.EscapeString(pluginName)
pluginScriptName := fmt.Sprintf("/plugin_%s_%d.js", pluginName, rand.Intn(100000))
d.pluginScripts[pluginScriptName] = script
}
func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
func (a *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
start := time.Now()
wrapped := &contentTypeSniffer{rw: rw}
d.serveHTTP(wrapped, req)
d.logger.Info(
a.serveHTTP(wrapped, req)
a.options.Logger.Info(
"Asset Request:",
"windowName", req.Header.Get(webViewRequestHeaderWindowName),
"windowID", req.Header.Get(webViewRequestHeaderWindowId),
@@ -162,10 +65,10 @@ func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
)
}
func (d *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request) {
func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request) {
if d.wsHandler != nil {
d.wsHandler.ServeHTTP(rw, req)
if a.wsHandler != nil {
a.wsHandler.ServeHTTP(rw, req)
return
} else {
if isWebSocket(req) {
@@ -176,30 +79,26 @@ func (d *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request) {
}
header := rw.Header()
if d.servingFromDisk {
header.Add(HeaderCacheControl, "no-cache")
}
// TODO: I don't think this is needed now?
//if a.servingFromDisk {
// header.Add(HeaderCacheControl, "no-cache")
//}
path := req.URL.Path
switch path {
case "", "/", "/index.html":
recorder := httptest.NewRecorder()
d.handler.ServeHTTP(recorder, req)
a.handler.ServeHTTP(recorder, req)
for k, v := range recorder.Result().Header {
header[k] = v
}
switch recorder.Code {
case http.StatusOK:
content, err := d.processIndexHTML(recorder.Body.Bytes())
if err != nil {
d.serveError(rw, err, "Unable to processIndexHTML")
return
}
d.writeBlob(rw, indexHTML, content)
a.writeBlob(rw, indexHTML, recorder.Body.Bytes())
case http.StatusNotFound:
d.writeBlob(rw, indexHTML, defaultHTML)
a.writeBlob(rw, indexHTML, defaultIndexHTML())
default:
rw.WriteHeader(recorder.Code)
@@ -207,83 +106,62 @@ func (d *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request) {
}
return
case runtimeJSPath:
d.writeBlob(rw, path, d.runtimeJS)
case capabilitiesPath:
var data = []byte("{}")
if d.GetCapabilities != nil {
data = d.GetCapabilities()
}
d.writeBlob(rw, path, data)
var data = a.options.GetCapabilities()
a.writeBlob(rw, path, data)
case flagsPath:
var data = []byte("{}")
if d.GetFlags != nil {
data = d.GetFlags()
}
d.writeBlob(rw, path, data)
var data = a.options.GetFlags()
a.writeBlob(rw, path, data)
case runtimePath:
d.runtimeHandler.HandleRuntimeCall(rw, req)
a.options.RuntimeHandler.ServeHTTP(rw, req)
return
default:
// Check if this is a plugin script
if script, ok := d.pluginScripts[path]; ok {
d.writeBlob(rw, path, []byte(script))
if script, ok := a.pluginScripts[path]; ok {
a.writeBlob(rw, path, []byte(script))
} else {
d.handler.ServeHTTP(rw, req)
a.handler.ServeHTTP(rw, req)
return
}
}
}
func (d *AssetServer) processIndexHTML(indexHTML []byte) ([]byte, error) {
htmlNode, err := getHTMLNode(indexHTML)
if err != nil {
return nil, err
}
if d.debug {
err = appendSpinnerToBody(htmlNode)
if err != nil {
return nil, err
}
}
// Inject plugins
for scriptName := range d.pluginScripts {
if err := insertScriptInHead(htmlNode, scriptName); err != nil {
return nil, err
}
}
var buffer bytes.Buffer
err = html.Render(&buffer, htmlNode)
if err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
func (d *AssetServer) writeBlob(rw http.ResponseWriter, filename string, blob []byte) {
func (a *AssetServer) writeBlob(rw http.ResponseWriter, filename string, blob []byte) {
err := serveFile(rw, filename, blob)
if err != nil {
d.serveError(rw, err, "Unable to write content %s", filename)
a.serveError(rw, err, "Unable to write content %s", filename)
}
}
func (d *AssetServer) serveError(rw http.ResponseWriter, err error, msg string, args ...interface{}) {
func (a *AssetServer) serveError(rw http.ResponseWriter, err error, msg string, args ...interface{}) {
args = append(args, err)
d.logError(msg+": %s", args...)
a.options.Logger.Error(msg+":", args...)
rw.WriteHeader(http.StatusInternalServerError)
}
func (d *AssetServer) logInfo(message string, args ...interface{}) {
d.logger.Info("Asset Request: "+message, args...)
func (a *AssetServer) LogDetails() {
if a.options.IsDebug {
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)
}
a.options.Logger.Info("AssetServer Info:", info...)
}
}
func (d *AssetServer) logError(message string, args ...interface{}) {
d.logger.Error("Asset Request: "+message, args...)
func (a *AssetServer) AddPluginScript(pluginName string, script string) {
if a.pluginScripts == nil {
a.pluginScripts = make(map[string]string)
}
pluginName = strings.ReplaceAll(pluginName, "/", "_")
pluginName = html.EscapeString(pluginName)
pluginScriptName := fmt.Sprintf("/wails/plugin/%s.js", pluginName)
a.pluginScripts[pluginScriptName] = script
}
@@ -1,11 +0,0 @@
//go:build !production
package assetserver
import (
"os"
)
func GetDevServerURL() string {
return os.Getenv("WAILS_DEVSERVER_URL")
}
@@ -1,7 +0,0 @@
//go:build production
package assetserver
func GetDevServerURL() string {
return ""
}
+26 -26
View File
@@ -23,9 +23,9 @@ type assetServerWebView struct {
// ServeWebViewRequest processes the HTTP Request asynchronously by faking a golang HTTP Server.
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
// The AssetServer takes ownership of the request and the caller mustn't close it or access it in any other way.
func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
d.dispatchInit.Do(func() {
workers := d.dispatchWorkers
func (a *AssetServer) ServeWebViewRequest(req webview.Request) {
a.dispatchInit.Do(func() {
workers := a.dispatchWorkers
if workers <= 0 {
return
}
@@ -34,7 +34,7 @@ func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
for i := 0; i < workers; i++ {
go func() {
for req := range workerC {
d.processWebViewRequest(req)
a.processWebViewRequest(req)
}
}()
}
@@ -42,34 +42,34 @@ func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
dispatchC := make(chan webview.Request)
go queueingDispatcher(50, dispatchC, workerC)
d.dispatchReqC = dispatchC
a.dispatchReqC = dispatchC
})
if d.dispatchReqC == nil {
go d.processWebViewRequest(req)
if a.dispatchReqC == nil {
go a.processWebViewRequest(req)
} else {
d.dispatchReqC <- req
a.dispatchReqC <- req
}
}
func (d *AssetServer) processWebViewRequest(r webview.Request) {
func (a *AssetServer) processWebViewRequest(r webview.Request) {
uri, _ := r.URL()
d.processWebViewRequestInternal(r)
a.processWebViewRequestInternal(r)
if err := r.Close(); err != nil {
d.logError("Unable to call close for request for uri '%s'", uri)
a.options.Logger.Error("Unable to call close for request for uri '%s'", uri)
}
}
// processHTTPRequest processes the HTTP Request by faking a golang HTTP Server.
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
func (d *AssetServer) processWebViewRequestInternal(r webview.Request) {
func (a *AssetServer) processWebViewRequestInternal(r webview.Request) {
uri := "unknown"
var err error
wrw := r.Response()
defer func() {
if err := wrw.Finish(); err != nil {
d.logError("Error finishing request '%s': %s", uri, err)
a.options.Logger.Error("Error finishing request '%s': %s", uri, err)
}
}()
@@ -78,26 +78,26 @@ func (d *AssetServer) processWebViewRequestInternal(r webview.Request) {
uri, err = r.URL()
if err != nil {
d.logError("Error processing request, unable to get URL: %s (HttpResponse=500)", err)
a.options.Logger.Error("Error processing request, unable to get URL: %s (HttpResponse=500)", err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
method, err := r.Method()
if err != nil {
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Method: %w", err))
a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Method: %w", err))
return
}
header, err := r.Header()
if err != nil {
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Header: %w", err))
a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Header: %w", err))
return
}
body, err := r.Body()
if err != nil {
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Body: %w", err))
a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Body: %w", err))
return
}
@@ -108,7 +108,7 @@ func (d *AssetServer) processWebViewRequestInternal(r webview.Request) {
req, err := http.NewRequest(method, uri, body)
if err != nil {
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Request: %w", err))
a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Request: %w", err))
return
}
@@ -119,8 +119,8 @@ func (d *AssetServer) processWebViewRequestInternal(r webview.Request) {
req.URL.Fragment = ""
req.URL.RawFragment = ""
if url := req.URL; req.RequestURI == "" && url != nil {
req.RequestURI = url.String()
if requestURL := req.URL; req.RequestURI == "" && requestURL != nil {
req.RequestURI = requestURL.String()
}
req.Header = header
@@ -137,28 +137,28 @@ func (d *AssetServer) processWebViewRequestInternal(r webview.Request) {
if req.ContentLength == 0 {
req.ContentLength, _ = strconv.ParseInt(req.Header.Get(HeaderContentLength), 10, 64)
} else {
req.Header.Set(HeaderContentLength, fmt.Sprintf("%d", req.ContentLength))
req.Header.Set(HeaderContentLength, fmt.Sprintf("%a", req.ContentLength))
}
if host := req.Header.Get(HeaderHost); host != "" {
req.Host = host
}
if expectedHost := d.ExpectedWebViewHost; expectedHost != "" && expectedHost != req.Host {
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("expected host '%s' in request, but was '%s'", expectedHost, req.Host))
if expectedHost := a.ExpectedWebViewHost; expectedHost != "" && expectedHost != req.Host {
a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("expected host '%s' in request, but was '%s'", expectedHost, req.Host))
return
}
d.ServeHTTP(rw, req)
a.ServeHTTP(rw, req)
}
func (d *AssetServer) webviewRequestErrorHandler(uri string, rw http.ResponseWriter, err error) {
func (a *AssetServer) webviewRequestErrorHandler(uri string, rw http.ResponseWriter, err error) {
logInfo := uri
if uri, err := url.ParseRequestURI(uri); err == nil {
logInfo = strings.Replace(logInfo, fmt.Sprintf("%s://%s", uri.Scheme, uri.Host), "", 1)
}
d.logError("Error processing request (HttpResponse=500)", "details", logInfo, "error", err.Error())
a.options.Logger.Error("Error processing request (HttpResponse=500)", "details", logInfo, "error", err.Error())
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
+103
View File
@@ -0,0 +1,103 @@
//go:build !production
package assetserver
import (
_ "embed"
"errors"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"os"
)
//go:embed defaultindex.html
var defaultHTML []byte
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)
}
// Parse the URL
parsedURL, err := url.Parse(a.devServerURL)
if err != nil {
return nil, fmt.Errorf("invalid FRONTEND_DEVSERVER_URL. Should be valid URL: %s", err.Error())
}
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)
}
}
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
}
func GetDevServerURL() string {
return os.Getenv("FRONTEND_DEVSERVER_URL")
}
@@ -0,0 +1,17 @@
//go:build production
package assetserver
import "net/http"
func defaultIndexHTML() []byte {
return []byte{}
}
func (a *AssetServer) setupHandler() (http.Handler, error) {
return NewDefaultAssetHandler(a.options)
}
func GetDevServerURL() string {
return ""
}
+3 -79
View File
@@ -2,13 +2,10 @@ package assetserver
import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
"strings"
"golang.org/x/net/html"
)
const (
@@ -16,8 +13,9 @@ const (
HeaderContentType = "Content-Type"
HeaderContentLength = "Content-Length"
HeaderUserAgent = "User-Agent"
HeaderCacheControl = "Cache-Control"
HeaderUpgrade = "Upgrade"
// TODO: Is this needed?
HeaderCacheControl = "Cache-Control"
HeaderUpgrade = "Upgrade"
WailsUserAgentValue = "wails.io"
)
@@ -35,80 +33,6 @@ func serveFile(rw http.ResponseWriter, filename string, blob []byte) error {
return err
}
func createScriptNode(scriptName string) *html.Node {
return &html.Node{
Type: html.ElementNode,
Data: "script",
Attr: []html.Attribute{
{
Key: "src",
Val: scriptName,
},
},
}
}
func createDivNode(id string) *html.Node {
return &html.Node{
Type: html.ElementNode,
Data: "div",
Attr: []html.Attribute{
{
Namespace: "",
Key: "id",
Val: id,
},
},
}
}
func insertScriptInHead(htmlNode *html.Node, scriptName string) error {
headNode := findFirstTag(htmlNode, "head")
if headNode == nil {
return errors.New("cannot find head in HTML")
}
scriptNode := createScriptNode(scriptName)
if headNode.FirstChild != nil {
headNode.InsertBefore(scriptNode, headNode.FirstChild)
} else {
headNode.AppendChild(scriptNode)
}
return nil
}
func appendSpinnerToBody(htmlNode *html.Node) error {
bodyNode := findFirstTag(htmlNode, "body")
if bodyNode == nil {
return errors.New("cannot find body in HTML")
}
scriptNode := createDivNode("wails-spinner")
bodyNode.AppendChild(scriptNode)
return nil
}
func getHTMLNode(htmldata []byte) (*html.Node, error) {
return html.Parse(bytes.NewReader(htmldata))
}
func findFirstTag(htmlnode *html.Node, tagName string) *html.Node {
var extractor func(*html.Node) *html.Node
var result *html.Node
extractor = func(node *html.Node) *html.Node {
if node.Type == html.ElementNode && node.Data == tagName {
return node
}
for child := node.FirstChild; child != nil; child = child.NextSibling {
result := extractor(child)
if result != nil {
return result
}
}
return nil
}
result = extractor(htmlnode)
return result
}
func isWebSocket(req *http.Request) bool {
upgrade := req.Header.Get(HeaderUpgrade)
return strings.EqualFold(upgrade, "websocket")
+8 -7
View File
@@ -2,6 +2,7 @@ package assetserver
import (
"embed"
"errors"
"fmt"
"io/fs"
"os"
@@ -10,18 +11,18 @@ import (
)
// FindEmbedRootPath finds the root path in the embed FS. It's the directory which contains all the files.
func FindEmbedRootPath(fsys embed.FS) (string, error) {
func FindEmbedRootPath(fileSystem embed.FS) (string, error) {
stopErr := fmt.Errorf("files or multiple dirs found")
fPath := ""
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
fPath = path
if entries, dErr := fs.ReadDir(fsys, path); dErr != nil {
if entries, dErr := fs.ReadDir(fileSystem, path); dErr != nil {
return dErr
} else if len(entries) <= 1 {
return nil
@@ -31,20 +32,20 @@ func FindEmbedRootPath(fsys embed.FS) (string, error) {
return stopErr
})
if err != nil && err != stopErr {
if err != nil && !errors.Is(err, stopErr) {
return "", err
}
return fPath, nil
}
func FindPathToFile(fsys fs.FS, file string) (string, error) {
stat, _ := fs.Stat(fsys, file)
func FindPathToFile(fileSystem fs.FS, file string) (string, error) {
stat, _ := fs.Stat(fileSystem, file)
if stat != nil {
return ".", nil
}
var indexFiles []string
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
+2 -2
View File
@@ -15,8 +15,8 @@ var (
// The list of builtin mime-types by extension as defined by
// the golang standard lib package "mime"
// The standard lib also takes into account mime type definitions from
// etc files like '/etc/apache2/mime.types' but we want to have the
// same behavivour on all platforms and not depend on some external file.
// /etc files like '/etc/apache2/mime.types' but we want to have the
// same behaviour on all platforms and not depend on some external file.
mimeTypesByExt = map[string]string{
".avif": "image/avif",
".css": "text/css; charset=utf-8",
+16
View File
@@ -3,6 +3,7 @@ package assetserver
import (
"fmt"
"io/fs"
"log/slog"
"net/http"
)
@@ -33,6 +34,21 @@ type Options struct {
// Multiple Middlewares can be chained together with:
// ChainMiddleware(middleware ...Middleware) Middleware
Middleware Middleware
// Logger is the logger used by the AssetServer. If not defined, no logging will be done.
Logger *slog.Logger
// RuntimeHandler is the handler used for the runtime calls.
RuntimeHandler http.Handler
// GetCapabilities returns the capabilities of the runtime
GetCapabilities func() []byte
// GetFlags returns the application flags
GetFlags func() []byte
// IsDebug is true if the server is running in debug mode
IsDebug bool
}
// Validate the options
-8
View File
@@ -1,8 +0,0 @@
<html>
<head>
<link href="/main.css" rel="stylesheet">
</head>
<body data-wails-drag>
<div id="logo"></div>
</body>
</html>
File diff suppressed because one or more lines are too long
-20
View File
@@ -1,20 +0,0 @@
import {ready} from '@wails/runtime';
ready(() => {
// Get input + focus
let nameElement = document.getElementById("name");
nameElement.focus();
// Setup the greet function
window.greet = function () {
// Get name
let name = nameElement.value;
// Call App.Greet(name)
window.backend.main.App.Greet(name).then((result) => {
// Update result with data back from App.Greet()
document.getElementById("result").innerText = result;
});
};
});
-8
View File
@@ -1,8 +0,0 @@
<html>
<head>
<link href="/main.css" rel="stylesheet">
</head>
<body data-wails-drag>
<div id="logo"></div>
</body>
</html>
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More