github.com/AlpineAIO/wails/v2@v2.0.0-beta.32.0.20240505041856-1047a8fa5fef/pkg/assetserver/webview/request_windows.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  package webview
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"strings"
    11  
    12  	"github.com/wailsapp/go-webview2/pkg/edge"
    13  )
    14  
    15  // NewRequest creates as new WebViewRequest for chromium. This Method must be called from the Main-Thread!
    16  func NewRequest(env *edge.ICoreWebView2Environment, args *edge.ICoreWebView2WebResourceRequestedEventArgs, invokeSync func(fn func())) (Request, error) {
    17  	req, err := args.GetRequest()
    18  	if err != nil {
    19  		return nil, fmt.Errorf("GetRequest failed: %s", err)
    20  	}
    21  	defer req.Release()
    22  
    23  	r := &request{
    24  		invokeSync: invokeSync,
    25  	}
    26  
    27  	code := http.StatusInternalServerError
    28  	r.response, err = env.CreateWebResourceResponse(nil, code, http.StatusText(code), "")
    29  	if err != nil {
    30  		return nil, fmt.Errorf("CreateWebResourceResponse failed: %s", err)
    31  	}
    32  
    33  	if err := args.PutResponse(r.response); err != nil {
    34  		r.finishResponse()
    35  		return nil, fmt.Errorf("PutResponse failed: %s", err)
    36  	}
    37  
    38  	r.deferral, err = args.GetDeferral()
    39  	if err != nil {
    40  		r.finishResponse()
    41  		return nil, fmt.Errorf("GetDeferral failed: %s", err)
    42  	}
    43  
    44  	r.url, r.urlErr = req.GetUri()
    45  	r.method, r.methodErr = req.GetMethod()
    46  	r.header, r.headerErr = getHeaders(req)
    47  
    48  	if content, err := req.GetContent(); err != nil {
    49  		r.bodyErr = err
    50  	} else if content != nil {
    51  		// It is safe to access Content from another Thread: https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/threading-model#thread-safety
    52  		r.body = &iStreamReleaseCloser{stream: content}
    53  	}
    54  
    55  	return r, nil
    56  }
    57  
    58  var _ Request = &request{}
    59  
    60  type request struct {
    61  	response *edge.ICoreWebView2WebResourceResponse
    62  	deferral *edge.ICoreWebView2Deferral
    63  
    64  	url    string
    65  	urlErr error
    66  
    67  	method    string
    68  	methodErr error
    69  
    70  	header    http.Header
    71  	headerErr error
    72  
    73  	body    io.ReadCloser
    74  	bodyErr error
    75  	rw      *responseWriter
    76  
    77  	invokeSync func(fn func())
    78  }
    79  
    80  func (r *request) URL() (string, error) {
    81  	return r.url, r.urlErr
    82  }
    83  
    84  func (r *request) Method() (string, error) {
    85  	return r.method, r.methodErr
    86  }
    87  
    88  func (r *request) Header() (http.Header, error) {
    89  	return r.header, r.headerErr
    90  }
    91  
    92  func (r *request) Body() (io.ReadCloser, error) {
    93  	return r.body, r.bodyErr
    94  }
    95  
    96  func (r *request) Response() ResponseWriter {
    97  	if r.rw != nil {
    98  		return r.rw
    99  	}
   100  
   101  	r.rw = &responseWriter{req: r}
   102  	return r.rw
   103  }
   104  
   105  func (r *request) Close() error {
   106  	var errs []error
   107  	if r.body != nil {
   108  		if err := r.body.Close(); err != nil {
   109  			errs = append(errs, err)
   110  		}
   111  		r.body = nil
   112  	}
   113  
   114  	if err := r.Response().Finish(); err != nil {
   115  		errs = append(errs, err)
   116  	}
   117  
   118  	return combineErrs(errs)
   119  }
   120  
   121  // finishResponse must be called on the main-thread
   122  func (r *request) finishResponse() error {
   123  	var errs []error
   124  	if r.response != nil {
   125  		if err := r.response.Release(); err != nil {
   126  			errs = append(errs, err)
   127  		}
   128  		r.response = nil
   129  	}
   130  	if r.deferral != nil {
   131  		if err := r.deferral.Complete(); err != nil {
   132  			errs = append(errs, err)
   133  		}
   134  
   135  		if err := r.deferral.Release(); err != nil {
   136  			errs = append(errs, err)
   137  		}
   138  		r.deferral = nil
   139  	}
   140  	return combineErrs(errs)
   141  }
   142  
   143  type iStreamReleaseCloser struct {
   144  	stream *edge.IStream
   145  	closed bool
   146  }
   147  
   148  func (i *iStreamReleaseCloser) Read(p []byte) (int, error) {
   149  	if i.closed {
   150  		return 0, io.ErrClosedPipe
   151  	}
   152  	return i.stream.Read(p)
   153  }
   154  
   155  func (i *iStreamReleaseCloser) Close() error {
   156  	if i.closed {
   157  		return nil
   158  	}
   159  	i.closed = true
   160  	return i.stream.Release()
   161  }
   162  
   163  func getHeaders(req *edge.ICoreWebView2WebResourceRequest) (http.Header, error) {
   164  	header := http.Header{}
   165  	headers, err := req.GetHeaders()
   166  	if err != nil {
   167  		return nil, fmt.Errorf("GetHeaders Error: %s", err)
   168  	}
   169  	defer headers.Release()
   170  
   171  	headersIt, err := headers.GetIterator()
   172  	if err != nil {
   173  		return nil, fmt.Errorf("GetIterator Error: %s", err)
   174  	}
   175  	defer headersIt.Release()
   176  
   177  	for {
   178  		has, err := headersIt.HasCurrentHeader()
   179  		if err != nil {
   180  			return nil, fmt.Errorf("HasCurrentHeader Error: %s", err)
   181  		}
   182  		if !has {
   183  			break
   184  		}
   185  
   186  		name, value, err := headersIt.GetCurrentHeader()
   187  		if err != nil {
   188  			return nil, fmt.Errorf("GetCurrentHeader Error: %s", err)
   189  		}
   190  
   191  		header.Set(name, value)
   192  		if _, err := headersIt.MoveNext(); err != nil {
   193  			return nil, fmt.Errorf("MoveNext Error: %s", err)
   194  		}
   195  	}
   196  
   197  	// WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other
   198  	// requests including IPC calls.
   199  	// So prevent 304 status codes by removing the headers that are used in combinationwith caching.
   200  	header.Del("If-Modified-Since")
   201  	header.Del("If-None-Match")
   202  	return header, nil
   203  }
   204  
   205  func combineErrs(errs []error) error {
   206  	// TODO use Go1.20 errors.Join
   207  	if len(errs) == 0 {
   208  		return nil
   209  	}
   210  
   211  	errStrings := make([]string, len(errs))
   212  	for i, err := range errs {
   213  		errStrings[i] = err.Error()
   214  	}
   215  
   216  	return fmt.Errorf(strings.Join(errStrings, "\n"))
   217  }