mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 22:13:36 +08:00
[v2, windows] Support async request processing on AssetServer (#2926)
This commit is contained in:
parent
3369327ad2
commit
d370f72ede
@ -7,11 +7,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -31,6 +28,7 @@ import (
|
|||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
"github.com/wailsapp/wails/v2/internal/logger"
|
||||||
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
|
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
|
||||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
||||||
)
|
)
|
||||||
@ -612,36 +610,31 @@ func (f *Frontend) processRequest(req *edge.ICoreWebView2WebResourceRequest, arg
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rw := httptest.NewRecorder()
|
webviewRequest, err := webview.NewRequest(
|
||||||
f.assets.ProcessHTTPRequestLegacy(rw, coreWebview2RequestToHttpRequest(req))
|
f.chromium.Environment(),
|
||||||
|
args,
|
||||||
|
func(fn func()) {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
if f.mainWindow.InvokeRequired() {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
f.mainWindow.Invoke(func() {
|
||||||
|
fn()
|
||||||
|
wg.Done()
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
} else {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
headers := []string{}
|
|
||||||
for k, v := range rw.Header() {
|
|
||||||
headers = append(headers, fmt.Sprintf("%s: %s", k, strings.Join(v, ",")))
|
|
||||||
}
|
|
||||||
|
|
||||||
code := rw.Code
|
|
||||||
if code == http.StatusNotModified {
|
|
||||||
// WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other
|
|
||||||
// requests including IPC calls.
|
|
||||||
f.logger.Error("%s: AssetServer returned 304 - StatusNotModified which are going to hang WebView2, changed code to 505 - StatusInternalServerError", uri)
|
|
||||||
code = http.StatusInternalServerError
|
|
||||||
}
|
|
||||||
|
|
||||||
env := f.chromium.Environment()
|
|
||||||
response, err := env.CreateWebResourceResponse(rw.Body.Bytes(), code, http.StatusText(code), strings.Join(headers, "\n"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.logger.Error("CreateWebResourceResponse Error: %s", err)
|
f.logger.Error("%s: NewRequest failed: %s", uri, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer response.Release()
|
|
||||||
|
|
||||||
// Send response back
|
f.assets.ServeWebViewRequest(webviewRequest)
|
||||||
err = args.PutResponse(response)
|
|
||||||
if err != nil {
|
|
||||||
f.logger.Error("PutResponse Error: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var edgeMap = map[string]uintptr{
|
var edgeMap = map[string]uintptr{
|
||||||
@ -832,93 +825,3 @@ func (f *Frontend) ShowWindow() {
|
|||||||
func (f *Frontend) onFocus(arg *winc.Event) {
|
func (f *Frontend) onFocus(arg *winc.Event) {
|
||||||
f.chromium.Focus()
|
f.chromium.Focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
func coreWebview2RequestToHttpRequest(coreReq *edge.ICoreWebView2WebResourceRequest) func() (*http.Request, error) {
|
|
||||||
return func() (r *http.Request, err error) {
|
|
||||||
header := http.Header{}
|
|
||||||
headers, err := coreReq.GetHeaders()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetHeaders Error: %s", err)
|
|
||||||
}
|
|
||||||
defer headers.Release()
|
|
||||||
|
|
||||||
headersIt, err := headers.GetIterator()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetIterator Error: %s", err)
|
|
||||||
}
|
|
||||||
defer headersIt.Release()
|
|
||||||
|
|
||||||
for {
|
|
||||||
has, err := headersIt.HasCurrentHeader()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("HasCurrentHeader Error: %s", err)
|
|
||||||
}
|
|
||||||
if !has {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
name, value, err := headersIt.GetCurrentHeader()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetCurrentHeader Error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
header.Set(name, value)
|
|
||||||
if _, err := headersIt.MoveNext(); err != nil {
|
|
||||||
return nil, fmt.Errorf("MoveNext Error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other
|
|
||||||
// requests including IPC calls.
|
|
||||||
// So prevent 304 status codes by removing the headers that are used in combinationwith caching.
|
|
||||||
header.Del("If-Modified-Since")
|
|
||||||
header.Del("If-None-Match")
|
|
||||||
|
|
||||||
method, err := coreReq.GetMethod()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetMethod Error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
uri, err := coreReq.GetUri()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetUri Error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body io.ReadCloser
|
|
||||||
if content, err := coreReq.GetContent(); err != nil {
|
|
||||||
return nil, fmt.Errorf("GetContent Error: %s", err)
|
|
||||||
} else if content != nil {
|
|
||||||
body = &iStreamReleaseCloser{stream: content}
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest(method, uri, body)
|
|
||||||
if err != nil {
|
|
||||||
if body != nil {
|
|
||||||
body.Close()
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Header = header
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type iStreamReleaseCloser struct {
|
|
||||||
stream *edge.IStream
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *iStreamReleaseCloser) Read(p []byte) (int, error) {
|
|
||||||
if i.closed {
|
|
||||||
return 0, io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
return i.stream.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *iStreamReleaseCloser) Close() error {
|
|
||||||
if i.closed {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
i.closed = true
|
|
||||||
return i.stream.Release()
|
|
||||||
}
|
|
||||||
|
@ -1,78 +0,0 @@
|
|||||||
package assetserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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) ProcessHTTPRequestLegacy(rw http.ResponseWriter, reqGetter func() (*http.Request, error)) {
|
|
||||||
d.processWebViewRequest(&legacyRequest{reqGetter: reqGetter, rw: rw})
|
|
||||||
}
|
|
||||||
|
|
||||||
type legacyRequest struct {
|
|
||||||
req *http.Request
|
|
||||||
rw http.ResponseWriter
|
|
||||||
|
|
||||||
reqGetter func() (*http.Request, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *legacyRequest) URL() (string, error) {
|
|
||||||
req, err := r.request()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return req.URL.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *legacyRequest) Method() (string, error) {
|
|
||||||
req, err := r.request()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return req.Method, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *legacyRequest) Header() (http.Header, error) {
|
|
||||||
req, err := r.request()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return req.Header, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *legacyRequest) Body() (io.ReadCloser, error) {
|
|
||||||
req, err := r.request()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return req.Body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r legacyRequest) Response() webview.ResponseWriter {
|
|
||||||
return &legacyRequestNoOpCloserResponseWriter{r.rw}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r legacyRequest) Close() error { return nil }
|
|
||||||
|
|
||||||
func (r *legacyRequest) request() (*http.Request, error) {
|
|
||||||
if r.req != nil {
|
|
||||||
return r.req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := r.reqGetter()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r.req = req
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type legacyRequestNoOpCloserResponseWriter struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*legacyRequestNoOpCloserResponseWriter) Finish() {}
|
|
@ -26,19 +26,15 @@ type assetServerWebView struct {
|
|||||||
func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
|
func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
|
||||||
d.dispatchInit.Do(func() {
|
d.dispatchInit.Do(func() {
|
||||||
workers := d.dispatchWorkers
|
workers := d.dispatchWorkers
|
||||||
if workers == 0 {
|
if workers <= 0 {
|
||||||
workers = 10
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
workerC := make(chan webview.Request, workers*2)
|
workerC := make(chan webview.Request, workers*2)
|
||||||
for i := 0; i < workers; i++ {
|
for i := 0; i < workers; i++ {
|
||||||
go func() {
|
go func() {
|
||||||
for req := range workerC {
|
for req := range workerC {
|
||||||
uri, _ := req.URL()
|
|
||||||
d.processWebViewRequest(req)
|
d.processWebViewRequest(req)
|
||||||
if err := req.Close(); err != nil {
|
|
||||||
d.logError("Unable to call close for request for uri '%s'", uri)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -49,19 +45,38 @@ func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
|
|||||||
d.dispatchReqC = dispatchC
|
d.dispatchReqC = dispatchC
|
||||||
})
|
})
|
||||||
|
|
||||||
d.dispatchReqC <- req
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// processHTTPRequest processes the HTTP Request by faking a golang HTTP Server.
|
// 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.
|
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
|
||||||
func (d *AssetServer) processWebViewRequest(r webview.Request) {
|
func (d *AssetServer) processWebViewRequestInternal(r webview.Request) {
|
||||||
|
uri := "unknown"
|
||||||
|
var err error
|
||||||
|
|
||||||
wrw := r.Response()
|
wrw := r.Response()
|
||||||
defer wrw.Finish()
|
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
|
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
|
defer rw.WriteHeader(http.StatusNotImplemented) // This is a NOP when a handler has already written and set the status
|
||||||
|
|
||||||
uri, err := r.URL()
|
uri, err = r.URL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.logError("Error processing request, unable to get URL: %s (HttpResponse=500)", err)
|
d.logError("Error processing request, unable to get URL: %s (HttpResponse=500)", err)
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
216
v2/pkg/assetserver/webview/request_windows.go
Normal file
216
v2/pkg/assetserver/webview/request_windows.go
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package webview
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/wailsapp/go-webview2/pkg/edge"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRequest creates as new WebViewRequest for chromium. This Method must be called from the Main-Thread!
|
||||||
|
func NewRequest(env *edge.ICoreWebView2Environment, args *edge.ICoreWebView2WebResourceRequestedEventArgs, invokeSync func(fn func())) (Request, error) {
|
||||||
|
req, err := args.GetRequest()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetRequest failed: %s", err)
|
||||||
|
}
|
||||||
|
defer req.Release()
|
||||||
|
|
||||||
|
r := &request{
|
||||||
|
invokeSync: invokeSync,
|
||||||
|
}
|
||||||
|
|
||||||
|
r.response, err = env.CreateWebResourceResponse(nil, http.StatusInternalServerError, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("CreateWebResourceResponse failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := args.PutResponse(r.response); err != nil {
|
||||||
|
r.finishResponse()
|
||||||
|
return nil, fmt.Errorf("PutResponse failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.deferral, err = args.GetDeferral()
|
||||||
|
if err != nil {
|
||||||
|
r.finishResponse()
|
||||||
|
return nil, fmt.Errorf("GetDeferral failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.url, r.urlErr = req.GetUri()
|
||||||
|
r.method, r.methodErr = req.GetMethod()
|
||||||
|
r.header, r.headerErr = getHeaders(req)
|
||||||
|
|
||||||
|
if content, err := req.GetContent(); err != nil {
|
||||||
|
r.bodyErr = err
|
||||||
|
} else if content != nil {
|
||||||
|
// It is safe to access Content from another Thread: https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/threading-model#thread-safety
|
||||||
|
r.body = &iStreamReleaseCloser{stream: content}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Request = &request{}
|
||||||
|
|
||||||
|
type request struct {
|
||||||
|
response *edge.ICoreWebView2WebResourceResponse
|
||||||
|
deferral *edge.ICoreWebView2Deferral
|
||||||
|
|
||||||
|
url string
|
||||||
|
urlErr error
|
||||||
|
|
||||||
|
method string
|
||||||
|
methodErr error
|
||||||
|
|
||||||
|
header http.Header
|
||||||
|
headerErr error
|
||||||
|
|
||||||
|
body io.ReadCloser
|
||||||
|
bodyErr error
|
||||||
|
rw *responseWriter
|
||||||
|
|
||||||
|
invokeSync func(fn func())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) URL() (string, error) {
|
||||||
|
return r.url, r.urlErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) Method() (string, error) {
|
||||||
|
return r.method, r.methodErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) Header() (http.Header, error) {
|
||||||
|
return r.header, r.headerErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) Body() (io.ReadCloser, error) {
|
||||||
|
return r.body, r.bodyErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) Response() ResponseWriter {
|
||||||
|
if r.rw != nil {
|
||||||
|
return r.rw
|
||||||
|
}
|
||||||
|
|
||||||
|
r.rw = &responseWriter{req: r}
|
||||||
|
return r.rw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *request) Close() error {
|
||||||
|
var errs []error
|
||||||
|
if r.body != nil {
|
||||||
|
if err := r.body.Close(); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
r.body = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.Response().Finish(); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return combineErrs(errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// finishResponse must be called on the main-thread
|
||||||
|
func (r *request) finishResponse() error {
|
||||||
|
var errs []error
|
||||||
|
if r.response != nil {
|
||||||
|
if err := r.response.Release(); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
r.response = nil
|
||||||
|
}
|
||||||
|
if r.deferral != nil {
|
||||||
|
if err := r.deferral.Complete(); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.deferral.Release(); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
r.deferral = nil
|
||||||
|
}
|
||||||
|
return combineErrs(errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
type iStreamReleaseCloser struct {
|
||||||
|
stream *edge.IStream
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *iStreamReleaseCloser) Read(p []byte) (int, error) {
|
||||||
|
if i.closed {
|
||||||
|
return 0, io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
return i.stream.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *iStreamReleaseCloser) Close() error {
|
||||||
|
if i.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
i.closed = true
|
||||||
|
return i.stream.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHeaders(req *edge.ICoreWebView2WebResourceRequest) (http.Header, error) {
|
||||||
|
header := http.Header{}
|
||||||
|
headers, err := req.GetHeaders()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetHeaders Error: %s", err)
|
||||||
|
}
|
||||||
|
defer headers.Release()
|
||||||
|
|
||||||
|
headersIt, err := headers.GetIterator()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetIterator Error: %s", err)
|
||||||
|
}
|
||||||
|
defer headersIt.Release()
|
||||||
|
|
||||||
|
for {
|
||||||
|
has, err := headersIt.HasCurrentHeader()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("HasCurrentHeader Error: %s", err)
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
name, value, err := headersIt.GetCurrentHeader()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("GetCurrentHeader Error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
header.Set(name, value)
|
||||||
|
if _, err := headersIt.MoveNext(); err != nil {
|
||||||
|
return nil, fmt.Errorf("MoveNext Error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other
|
||||||
|
// requests including IPC calls.
|
||||||
|
// So prevent 304 status codes by removing the headers that are used in combinationwith caching.
|
||||||
|
header.Del("If-Modified-Since")
|
||||||
|
header.Del("If-None-Match")
|
||||||
|
return header, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func combineErrs(errs []error) error {
|
||||||
|
// TODO use Go1.20 errors.Join
|
||||||
|
if len(errs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errStrings := make([]string, len(errs))
|
||||||
|
for i, err := range errs {
|
||||||
|
errStrings[i] = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf(strings.Join(errStrings, "\n"))
|
||||||
|
}
|
@ -21,5 +21,5 @@ type ResponseWriter interface {
|
|||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
|
|
||||||
// Finish the response and flush all data. A Finish after the request has already been finished has no effect.
|
// Finish the response and flush all data. A Finish after the request has already been finished has no effect.
|
||||||
Finish()
|
Finish() error
|
||||||
}
|
}
|
||||||
|
@ -133,15 +133,16 @@ func (rw *responseWriter) WriteHeader(code int) {
|
|||||||
C.URLSchemeTaskDidReceiveResponse(rw.r.task, C.int(code), headers, C.int(headersLen))
|
C.URLSchemeTaskDidReceiveResponse(rw.r.task, C.int(code), headers, C.int(headersLen))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *responseWriter) Finish() {
|
func (rw *responseWriter) Finish() error {
|
||||||
if !rw.wroteHeader {
|
if !rw.wroteHeader {
|
||||||
rw.WriteHeader(http.StatusNotImplemented)
|
rw.WriteHeader(http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rw.finished {
|
if rw.finished {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
rw.finished = true
|
rw.finished = true
|
||||||
|
|
||||||
C.URLSchemeTaskDidFinish(rw.r.task)
|
C.URLSchemeTaskDidFinish(rw.r.task)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -84,18 +84,19 @@ func (rw *responseWriter) WriteHeader(code int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *responseWriter) Finish() {
|
func (rw *responseWriter) Finish() error {
|
||||||
if !rw.wroteHeader {
|
if !rw.wroteHeader {
|
||||||
rw.WriteHeader(http.StatusNotImplemented)
|
rw.WriteHeader(http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rw.finished {
|
if rw.finished {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
rw.finished = true
|
rw.finished = true
|
||||||
if rw.w != nil {
|
if rw.w != nil {
|
||||||
rw.w.Close()
|
rw.w.Close()
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *responseWriter) finishWithError(code int, err error) {
|
func (rw *responseWriter) finishWithError(code int, err error) {
|
||||||
|
105
v2/pkg/assetserver/webview/responsewriter_windows.go
Normal file
105
v2/pkg/assetserver/webview/responsewriter_windows.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package webview
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ http.ResponseWriter = &responseWriter{}
|
||||||
|
|
||||||
|
type responseWriter struct {
|
||||||
|
req *request
|
||||||
|
|
||||||
|
header http.Header
|
||||||
|
wroteHeader bool
|
||||||
|
code int
|
||||||
|
body *bytes.Buffer
|
||||||
|
|
||||||
|
finished bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Header() http.Header {
|
||||||
|
if rw.header == nil {
|
||||||
|
rw.header = http.Header{}
|
||||||
|
}
|
||||||
|
return rw.header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Write(buf []byte) (int, error) {
|
||||||
|
if rw.finished {
|
||||||
|
return 0, errResponseFinished
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
return rw.body.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) WriteHeader(code int) {
|
||||||
|
if rw.wroteHeader || rw.finished {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rw.wroteHeader = true
|
||||||
|
|
||||||
|
if rw.body == nil {
|
||||||
|
rw.body = &bytes.Buffer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.code = code
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Finish() error {
|
||||||
|
if !rw.wroteHeader {
|
||||||
|
rw.WriteHeader(http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rw.finished {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rw.finished = true
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
code := rw.code
|
||||||
|
if code == http.StatusNotModified {
|
||||||
|
// WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other
|
||||||
|
// requests including IPC calls.
|
||||||
|
errs = append(errs, fmt.Errorf("AssetServer returned 304 - StatusNotModified which are going to hang WebView2, changed code to 505 - StatusInternalServerError"))
|
||||||
|
code = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.req.invokeSync(func() {
|
||||||
|
resp := rw.req.response
|
||||||
|
|
||||||
|
hdrs, err := resp.GetHeaders()
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Resp.GetHeaders failed: %s", err))
|
||||||
|
} else {
|
||||||
|
for k, v := range rw.header {
|
||||||
|
if err := hdrs.AppendHeader(k, strings.Join(v, ",")); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Resp.AppendHeader failed: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hdrs.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := resp.PutStatusCode(code); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Resp.PutStatusCode failed: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := resp.PutByteContent(rw.body.Bytes()); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Resp.PutByteContent failed: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rw.req.finishResponse(); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("Resp.finishResponse failed: %s", err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return combineErrs(errs)
|
||||||
|
}
|
@ -18,6 +18,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Added support for enabling/disabling swipe gestures for Windows WebView2. Added by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/2878)
|
- Added support for enabling/disabling swipe gestures for Windows WebView2. Added by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/2878)
|
||||||
- When building with `-devtools` flag, CMD/CTRL+SHIFT+F12 can be used to open the devtools. Added by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/2915)
|
- When building with `-devtools` flag, CMD/CTRL+SHIFT+F12 can be used to open the devtools. Added by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/2915)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- AssetServer requests are now processed asynchronously without blocking the main thread on Windows. Changed by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2926)
|
||||||
|
- AssetServer requests are now processed concurrently by spawning a goroutine per request. Changed by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2926)
|
||||||
|
|
||||||
#### Fixed
|
#### Fixed
|
||||||
|
|
||||||
- Fixed typo on docs/reference/options page. Added by [@pylotlight](https://github.com/pylotlight) in [PR](https://github.com/wailsapp/wails/pull/2887)
|
- Fixed typo on docs/reference/options page. Added by [@pylotlight](https://github.com/pylotlight) in [PR](https://github.com/wailsapp/wails/pull/2887)
|
||||||
|
Loading…
Reference in New Issue
Block a user