5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 08:41:41 +08:00
wails/v2/pkg/assetserver/assetserver_webview.go
lvyaoting aad811391b
chore: fix function names in comment (#3383)
Signed-off-by: lvyaoting <lvyaoting@outlook.com>
2024-04-09 17:19:06 -05:00

186 lines
4.8 KiB
Go

package assetserver
import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
)
type assetServerWebView struct {
// ExpectedWebViewHost is checked against the Request Host of every WebViewRequest, other hosts won't be processed.
ExpectedWebViewHost string
dispatchInit sync.Once
dispatchReqC chan<- webview.Request
dispatchWorkers int
}
// 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
if workers <= 0 {
return
}
workerC := make(chan webview.Request, workers*2)
for i := 0; i < workers; i++ {
go func() {
for req := range workerC {
d.processWebViewRequest(req)
}
}()
}
dispatchC := make(chan webview.Request)
go queueingDispatcher(50, dispatchC, workerC)
d.dispatchReqC = dispatchC
})
if d.dispatchReqC == nil {
go d.processWebViewRequest(req)
} else {
d.dispatchReqC <- req
}
}
func (d *AssetServer) processWebViewRequest(r webview.Request) {
uri, _ := r.URL()
d.processWebViewRequestInternal(r)
if err := r.Close(); err != nil {
d.logError("Unable to call close for request for uri '%s'", uri)
}
}
// processWebViewRequestInternal 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) {
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)
}
}()
var rw http.ResponseWriter = &contentTypeSniffer{rw: wrw} // Make sure we have a Content-Type sniffer
defer rw.WriteHeader(http.StatusNotImplemented) // This is a NOP when a handler has already written and set the status
uri, err = r.URL()
if err != nil {
d.logError("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))
return
}
header, err := r.Header()
if err != nil {
d.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))
return
}
if body == nil {
body = http.NoBody
}
defer body.Close()
req, err := http.NewRequest(method, uri, body)
if err != nil {
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Request: %w", err))
return
}
// For server requests, the URL is parsed from the URI supplied on the Request-Line as stored in RequestURI. For
// most requests, fields other than Path and RawQuery will be empty. (See RFC 7230, Section 5.3)
req.URL.Scheme = ""
req.URL.Host = ""
req.URL.Fragment = ""
req.URL.RawFragment = ""
if url := req.URL; req.RequestURI == "" && url != nil {
req.RequestURI = url.String()
}
req.Header = header
if req.RemoteAddr == "" {
// 192.0.2.0/24 is "TEST-NET" in RFC 5737
req.RemoteAddr = "192.0.2.1:1234"
}
if req.ContentLength == 0 {
req.ContentLength, _ = strconv.ParseInt(req.Header.Get(HeaderContentLength), 10, 64)
} else {
size := strconv.FormatInt(req.ContentLength, 10)
req.Header.Set(HeaderContentLength, size)
}
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))
return
}
d.ServeHTTP(rw, req)
}
func (d *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 '%s': %s (HttpResponse=500)", logInfo, err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
func queueingDispatcher[T any](minQueueSize uint, inC <-chan T, outC chan<- T) {
q := newRingqueue[T](minQueueSize)
for {
in, ok := <-inC
if !ok {
return
}
q.Add(in)
for q.Len() != 0 {
out, _ := q.Peek()
select {
case outC <- out:
q.Remove()
case in, ok := <-inC:
if !ok {
return
}
q.Add(in)
}
}
}
}