5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-04 17:42:24 +08:00
wails/v2/pkg/assetserver/webview/request_windows.go
2023-09-21 06:43:28 +10:00

218 lines
4.7 KiB
Go

//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,
}
code := http.StatusInternalServerError
r.response, err = env.CreateWebResourceResponse(nil, code, http.StatusText(code), "")
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"))
}