5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-07 12:11:50 +08:00
wails/v3/internal/assetserver/assetserver.go
Lea Anthony a0b2ab7c0a
Fix default index.html serving.
Support multi-language default.
Remove default page from production builds.
Improve defaultindex.html.
2024-12-14 11:10:07 +11:00

186 lines
4.4 KiB
Go

package assetserver
import (
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"time"
)
const (
webViewRequestHeaderWindowId = "x-wails-window-id"
webViewRequestHeaderWindowName = "x-wails-window-name"
servicePrefix = "wails/services"
HeaderAcceptLanguage = "accept-language"
)
type RuntimeHandler interface {
HandleRuntimeCall(w http.ResponseWriter, r *http.Request)
}
type AssetServer struct {
options *Options
handler http.Handler
services map[string]http.Handler
assetServerWebView
}
func NewAssetServer(options *Options) (*AssetServer, error) {
result := &AssetServer{
options: options,
}
userHandler := options.Handler
if userHandler == nil {
userHandler = http.NotFoundHandler()
}
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}
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),
"windowID", req.Header.Get(webViewRequestHeaderWindowId),
"code", wrapped.status,
"method", req.Method,
"path", req.URL.EscapedPath(),
"duration", time.Since(start),
)
}
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
}
header := rw.Header()
// TODO: I don't think this is needed now?
//if a.servingFromDisk {
// header.Add(HeaderCacheControl, "no-cache")
//}
reqPath := req.URL.Path
switch reqPath {
case "", "/", "/index.html":
recorder := httptest.NewRecorder()
userHandler.ServeHTTP(recorder, req)
for k, v := range recorder.Result().Header {
header[k] = v
}
switch recorder.Code {
case http.StatusOK:
a.writeBlob(rw, indexHTML, recorder.Body.Bytes())
case http.StatusNotFound:
// Read the accept-language header
acceptLanguage := req.Header.Get(HeaderAcceptLanguage)
if acceptLanguage == "" {
acceptLanguage = "en"
}
// Set content type for default index.html
header.Set(HeaderContentType, "text/html; charset=utf-8")
a.writeBlob(rw, indexHTML, defaultIndexHTML(acceptLanguage))
default:
rw.WriteHeader(recorder.Code)
}
default:
// Check if the path matches the keys in the services map
for route, handler := range a.services {
if strings.HasPrefix(reqPath, route) {
req.URL.Path = strings.TrimPrefix(reqPath, route)
handler.ServeHTTP(rw, req)
return
}
}
// Check if it can be served by the user-provided handler
if !strings.HasPrefix(reqPath, servicePrefix) {
userHandler.ServeHTTP(rw, req)
return
}
rw.WriteHeader(http.StatusNotFound)
return
}
}
func (a *AssetServer) AttachServiceHandler(prefix string, handler http.Handler) {
if a.services == nil {
a.services = make(map[string]http.Handler)
}
a.services[prefix] = handler
}
func (a *AssetServer) writeBlob(rw http.ResponseWriter, filename string, blob []byte) {
err := ServeFile(rw, filename, blob)
if err != nil {
a.serveError(rw, err, "Unable to write content %s", filename)
}
}
func (a *AssetServer) serveError(rw http.ResponseWriter, err error, msg string, args ...interface{}) {
args = append(args, err)
a.options.Logger.Error(msg+":", args...)
rw.WriteHeader(http.StatusInternalServerError)
}
func GetStartURL(userURL string) (string, error) {
devServerURL := GetDevServerURL()
startURL := baseURL.String()
if devServerURL != "" {
// Parse the port
parsedURL, err := url.Parse(devServerURL)
if err != nil {
return "", fmt.Errorf("Error parsing environment variable 'FRONTEND_DEVSERVER_URL`: " + err.Error() + ". Please check your `Taskfile.yml` file")
}
port := parsedURL.Port()
if port != "" {
baseURL.Host = net.JoinHostPort(baseURL.Hostname(), port)
startURL = baseURL.String()
}
}
if userURL != "" {
parsedURL, err := baseURL.Parse(userURL)
if err != nil {
return "", fmt.Errorf("Error parsing URL: " + err.Error())
}
startURL = parsedURL.String()
}
return startURL, nil
}