mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 23:51:44 +08:00
[darwin] Add support for Request/Response streaming (#2219)
This commit is contained in:
parent
e2f1429c67
commit
a312c0ffcb
@ -48,7 +48,10 @@ const bool IsFullScreen(void *ctx);
|
||||
const bool IsMinimised(void *ctx);
|
||||
const bool IsMaximised(void *ctx);
|
||||
|
||||
void ProcessURLResponse(void *inctx, unsigned long long requestId, int statusCode, void *headersString, int headersStringLength, void* data, int datalength);
|
||||
void ProcessURLDidReceiveResponse(void *inctx, unsigned long long requestId, int statusCode, void *headersString, int headersStringLength);
|
||||
bool ProcessURLDidReceiveData(void *inctx, unsigned long long requestId, void* data, int datalength);
|
||||
void ProcessURLDidFinish(void *inctx, unsigned long long requestId);
|
||||
int ProcessURLRequestReadBodyStream(void *inctx, unsigned long long requestId, void *buf, int bufLen);
|
||||
|
||||
/* Dialogs */
|
||||
|
||||
|
@ -52,12 +52,33 @@ WailsContext* Create(const char* title, int width, int height, int frameless, in
|
||||
return result;
|
||||
}
|
||||
|
||||
void ProcessURLResponse(void *inctx, unsigned long long requestId, int statusCode, void *headersString, int headersStringLength, void* data, int datalength) {
|
||||
void ProcessURLDidReceiveResponse(void *inctx, unsigned long long requestId, int statusCode, void *headersString, int headersStringLength) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
@autoreleasepool {
|
||||
NSData *nsHeadersJSON = [NSData dataWithBytes:headersString length:headersStringLength];
|
||||
[ctx processURLDidReceiveResponse:requestId :statusCode :nsHeadersJSON];
|
||||
}
|
||||
}
|
||||
|
||||
bool ProcessURLDidReceiveData(void *inctx, unsigned long long requestId, void* data, int datalength) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
@autoreleasepool {
|
||||
NSData *nsdata = [NSData dataWithBytes:data length:datalength];
|
||||
[ctx processURLResponse:requestId :statusCode :nsHeadersJSON :nsdata];
|
||||
return [ctx processURLDidReceiveData:requestId :nsdata];
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessURLDidFinish(void *inctx, unsigned long long requestId) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
@autoreleasepool {
|
||||
[ctx processURLDidFinish:requestId];
|
||||
}
|
||||
}
|
||||
|
||||
int ProcessURLRequestReadBodyStream(void *inctx, unsigned long long requestId, void *buf, int bufLen) {
|
||||
WailsContext *ctx = (__bridge WailsContext*) inctx;
|
||||
@autoreleasepool {
|
||||
return [ctx processURLRequestReadBodyStream:requestId :buf :bufLen];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,10 @@
|
||||
- (void) SaveFileDialog :(NSString*)title :(NSString*)defaultFilename :(NSString*)defaultDirectory :(bool)canCreateDirectories :(bool)treatPackagesAsDirectories :(bool)showHiddenFiles :(NSString*)filters;
|
||||
|
||||
- (void) loadRequest:(NSString*)url;
|
||||
- (void) processURLResponse:(unsigned long long)requestId :(int)statusCode :(NSData *)headersString :(NSData*)data;
|
||||
- (void) processURLDidReceiveResponse:(unsigned long long)requestId :(int)statusCode :(NSData *)headersJSON;
|
||||
- (bool) processURLDidReceiveData:(unsigned long long)requestId :(NSData *)data;
|
||||
- (void) processURLDidFinish:(unsigned long long)requestId;
|
||||
- (int) processURLRequestReadBodyStream:(unsigned long long)requestId :(void *)buf :(int)bufLen;
|
||||
- (void) ExecJS:(NSString*)script;
|
||||
- (NSScreen*) getCurrentScreen;
|
||||
|
||||
|
@ -15,6 +15,8 @@
|
||||
#import "message.h"
|
||||
#import "Role.h"
|
||||
|
||||
typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);
|
||||
|
||||
@implementation WailsWindow
|
||||
|
||||
- (BOOL)canBecomeKeyWindow
|
||||
@ -107,7 +109,6 @@
|
||||
[self.mouseEvent release];
|
||||
[self.userContentController release];
|
||||
[self.urlRequests release];
|
||||
[self.urlRequestsLock release];
|
||||
[self.applicationMenu release];
|
||||
[super dealloc];
|
||||
}
|
||||
@ -138,7 +139,6 @@
|
||||
|
||||
- (void) CreateWindow:(int)width :(int)height :(bool)frameless :(bool)resizable :(bool)fullscreen :(bool)fullSizeContent :(bool)hideTitleBar :(bool)titlebarAppearsTransparent :(bool)hideTitle :(bool)useToolbar :(bool)hideToolbarSeparator :(bool)webviewIsTransparent :(bool)hideWindowOnClose :(NSString*)appearance :(bool)windowIsTranslucent :(int)minWidth :(int)minHeight :(int)maxWidth :(int)maxHeight {
|
||||
self.urlRequestsId = 0;
|
||||
self.urlRequestsLock = [NSLock new];
|
||||
self.urlRequests = [NSMutableDictionary new];
|
||||
|
||||
NSWindowStyleMask styleMask = 0;
|
||||
@ -427,37 +427,57 @@
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) processURLResponse:(unsigned long long)requestId :(int)statusCode :(NSData *)headersJSON :(NSData *)data {
|
||||
NSNumber *key = [NSNumber numberWithUnsignedLongLong:requestId];
|
||||
|
||||
[self.urlRequestsLock lock];
|
||||
id<WKURLSchemeTask> urlSchemeTask = self.urlRequests[key];
|
||||
[self.urlRequestsLock unlock];
|
||||
|
||||
@try {
|
||||
if (urlSchemeTask == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
- (void) processURLDidReceiveResponse:(unsigned long long)requestId :(int)statusCode :(NSData *)headersJSON {
|
||||
[self processURLSchemeTaskCall:requestId :^(id<WKURLSchemeTask> urlSchemeTask) {
|
||||
NSDictionary *headerFields = [NSJSONSerialization JSONObjectWithData: headersJSON options: NSJSONReadingMutableContainers error: nil];
|
||||
NSHTTPURLResponse *response = [[[NSHTTPURLResponse alloc] initWithURL:urlSchemeTask.request.URL statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:headerFields] autorelease];
|
||||
|
||||
@try {
|
||||
[urlSchemeTask didReceiveResponse:response];
|
||||
}];
|
||||
}
|
||||
|
||||
- (bool) processURLDidReceiveData:(unsigned long long)requestId :(NSData *)data {
|
||||
return [self processURLSchemeTaskCall:requestId :^(id<WKURLSchemeTask> urlSchemeTask) {
|
||||
[urlSchemeTask didReceiveData:data];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) processURLDidFinish:(unsigned long long)requestId {
|
||||
[self processURLSchemeTaskCall:requestId :^(id<WKURLSchemeTask> urlSchemeTask) {
|
||||
[urlSchemeTask didFinish];
|
||||
} @catch (NSException *exception) {
|
||||
// This is very bad to detect a stopped schemeTask this should be implemented in a better way
|
||||
// See todo in stopURLSchemeTask...
|
||||
if (![exception.reason isEqualToString: @"This task has already been stopped"]) {
|
||||
@throw exception;
|
||||
}];
|
||||
|
||||
NSNumber *key = [NSNumber numberWithUnsignedLongLong:requestId];
|
||||
[self removeURLSchemeTask:key];
|
||||
}
|
||||
|
||||
- (int) processURLRequestReadBodyStream:(unsigned long long)requestId :(void *)buf :(int)bufLen {
|
||||
int res = 0;
|
||||
int *pRes = &res;
|
||||
|
||||
bool hasRequest = [self processURLSchemeTaskCall:requestId :^(id<WKURLSchemeTask> urlSchemeTask) {
|
||||
NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream;
|
||||
if (!stream) {
|
||||
*pRes = -3;
|
||||
} else {
|
||||
NSStreamStatus status = stream.streamStatus;
|
||||
if (status == NSStreamStatusAtEnd) {
|
||||
*pRes = 0;
|
||||
} else if (status != NSStreamStatusOpen) {
|
||||
*pRes = -4;
|
||||
} else if (!stream.hasBytesAvailable) {
|
||||
*pRes = 0;
|
||||
} else {
|
||||
*pRes = [stream read:buf maxLength:bufLen];
|
||||
}
|
||||
}
|
||||
} @finally {
|
||||
[self.urlRequestsLock lock];
|
||||
[self.urlRequests removeObjectForKey:key]; // This will release the urlSchemeTask which was retained from the dictionary
|
||||
[self.urlRequestsLock unlock];
|
||||
}];
|
||||
|
||||
if (!hasRequest) {
|
||||
res = -2;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
- (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask {
|
||||
@ -467,6 +487,7 @@
|
||||
const char *headerJSON = "";
|
||||
const void *body = nil;
|
||||
int bodyLen = 0;
|
||||
int hasBodyStream = 0;
|
||||
|
||||
NSData *headers = [NSJSONSerialization dataWithJSONObject: urlSchemeTask.request.allHTTPHeaderFields options:0 error: nil];
|
||||
if (headers) {
|
||||
@ -477,23 +498,85 @@
|
||||
if (urlSchemeTask.request.HTTPBody) {
|
||||
body = urlSchemeTask.request.HTTPBody.bytes;
|
||||
bodyLen = urlSchemeTask.request.HTTPBody.length;
|
||||
} else {
|
||||
// TODO handle HTTPBodyStream
|
||||
} else if (urlSchemeTask.request.HTTPBodyStream) {
|
||||
hasBodyStream = 1;
|
||||
[urlSchemeTask.request.HTTPBodyStream open];
|
||||
}
|
||||
|
||||
[self.urlRequestsLock lock];
|
||||
unsigned long long requestId;
|
||||
@synchronized(self.urlRequests) {
|
||||
self.urlRequestsId++;
|
||||
unsigned long long requestId = self.urlRequestsId;
|
||||
requestId = self.urlRequestsId;
|
||||
NSNumber *key = [NSNumber numberWithUnsignedLongLong:requestId];
|
||||
self.urlRequests[key] = urlSchemeTask;
|
||||
[self.urlRequestsLock unlock];
|
||||
}
|
||||
|
||||
processURLRequest(self, requestId, url, method, headerJSON, body, bodyLen);
|
||||
processURLRequest(self, requestId, url, method, headerJSON, body, bodyLen, hasBodyStream);
|
||||
}
|
||||
|
||||
- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask {
|
||||
// TODO implement the stopping process here in a better way...
|
||||
// As soon as we introduce response body streaming we need to rewrite this nevertheless.
|
||||
NSArray<NSNumber*> *keys;
|
||||
@synchronized(self.urlRequests) {
|
||||
keys = [self.urlRequests allKeys];
|
||||
}
|
||||
|
||||
for (NSNumber *key in keys) {
|
||||
if (self.urlRequests[key] == urlSchemeTask) {
|
||||
[self removeURLSchemeTask:key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) removeURLSchemeTask:(NSNumber *)urlSchemeTaskKey {
|
||||
id<WKURLSchemeTask> urlSchemeTask = nil;
|
||||
@synchronized(self.urlRequests) {
|
||||
urlSchemeTask = self.urlRequests[urlSchemeTaskKey];
|
||||
}
|
||||
|
||||
if (!urlSchemeTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream;
|
||||
if (stream) {
|
||||
[stream close];
|
||||
}
|
||||
|
||||
@synchronized(self.urlRequests) {
|
||||
[self.urlRequests removeObjectForKey:urlSchemeTaskKey];
|
||||
}
|
||||
}
|
||||
|
||||
- (bool)processURLSchemeTaskCall:(unsigned long long)requestId :(schemeTaskCaller)fn {
|
||||
NSNumber *key = [NSNumber numberWithUnsignedLongLong:requestId];
|
||||
|
||||
id<WKURLSchemeTask> urlSchemeTask;
|
||||
@synchronized(self.urlRequests) {
|
||||
urlSchemeTask = self.urlRequests[key];
|
||||
}
|
||||
|
||||
if (urlSchemeTask == nil) {
|
||||
// Stopped task, drop content...
|
||||
return false;
|
||||
}
|
||||
|
||||
@try {
|
||||
fn(urlSchemeTask);
|
||||
} @catch (NSException *exception) {
|
||||
[self removeURLSchemeTask:key];
|
||||
|
||||
// This is very bad to detect a stopped schemeTask this should be implemented in a better way
|
||||
// But it seems to be very tricky to not deadlock when keeping a lock curing executing fn()
|
||||
// It seems like those call switch the thread back to the main thread and then deadlocks when they reentrant want
|
||||
// to get the lock again to start another request or stop it.
|
||||
if ([exception.reason isEqualToString: @"This task has already been stopped"]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@throw exception;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
|
||||
|
@ -14,18 +14,14 @@ package darwin
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
@ -37,7 +33,7 @@ import (
|
||||
const startURL = "wails://wails/"
|
||||
|
||||
var messageBuffer = make(chan string, 100)
|
||||
var requestBuffer = make(chan *request, 100)
|
||||
var requestBuffer = make(chan *wkWebViewRequest, 100)
|
||||
var callbackBuffer = make(chan uint, 10)
|
||||
|
||||
type Frontend struct {
|
||||
@ -96,8 +92,11 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
|
||||
}
|
||||
result.assets = assets
|
||||
|
||||
// Start 10 processors to handle requests in parallel
|
||||
for i := 0; i < 10; i++ {
|
||||
go result.startRequestProcessor()
|
||||
}
|
||||
}
|
||||
|
||||
go result.startMessageProcessor()
|
||||
go result.startCallbackProcessor()
|
||||
@ -342,8 +341,10 @@ func (f *Frontend) ExecJS(js string) {
|
||||
f.mainWindow.ExecJS(js)
|
||||
}
|
||||
|
||||
func (f *Frontend) processRequest(r *request) {
|
||||
rw := httptest.NewRecorder()
|
||||
func (f *Frontend) processRequest(r *wkWebViewRequest) {
|
||||
rw := &wkWebViewResponseWriter{r: r}
|
||||
defer rw.Close()
|
||||
|
||||
f.assets.ProcessHTTPRequest(
|
||||
r.url,
|
||||
rw,
|
||||
@ -363,28 +364,6 @@ func (f *Frontend) processRequest(r *request) {
|
||||
return req, nil
|
||||
},
|
||||
)
|
||||
|
||||
header := map[string]string{}
|
||||
for k := range rw.Header() {
|
||||
header[k] = rw.Header().Get(k)
|
||||
}
|
||||
headerData, _ := json.Marshal(header)
|
||||
|
||||
var content unsafe.Pointer
|
||||
var contentLen int
|
||||
if _contents := rw.Body.Bytes(); _contents != nil {
|
||||
content = unsafe.Pointer(&_contents[0])
|
||||
contentLen = len(_contents)
|
||||
}
|
||||
|
||||
var headers unsafe.Pointer
|
||||
var headersLen int
|
||||
if len(headerData) != 0 {
|
||||
headers = unsafe.Pointer(&headerData[0])
|
||||
headersLen = len(headerData)
|
||||
}
|
||||
|
||||
C.ProcessURLResponse(r.ctx, r.id, C.int(rw.Code), headers, C.int(headersLen), content, C.int(contentLen))
|
||||
}
|
||||
|
||||
//func (f *Frontend) processSystemEvent(message string) {
|
||||
@ -403,64 +382,12 @@ func (f *Frontend) processRequest(r *request) {
|
||||
// }
|
||||
//}
|
||||
|
||||
type request struct {
|
||||
id C.ulonglong
|
||||
url string
|
||||
method string
|
||||
headers string
|
||||
body []byte
|
||||
|
||||
ctx unsafe.Pointer
|
||||
}
|
||||
|
||||
func (r *request) GetHttpRequest() (*http.Request, error) {
|
||||
var body io.Reader
|
||||
if len(r.body) != 0 {
|
||||
body = bytes.NewReader(r.body)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(r.method, r.url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.headers != "" {
|
||||
var h map[string]string
|
||||
if err := json.Unmarshal([]byte(r.headers), &h); err != nil {
|
||||
return nil, fmt.Errorf("Unable to unmarshal request headers: %s", err)
|
||||
}
|
||||
|
||||
for k, v := range h {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
//export processMessage
|
||||
func processMessage(message *C.char) {
|
||||
goMessage := C.GoString(message)
|
||||
messageBuffer <- goMessage
|
||||
}
|
||||
|
||||
//export processURLRequest
|
||||
func processURLRequest(ctx unsafe.Pointer, requestId C.ulonglong, url *C.char, method *C.char, headers *C.char, body unsafe.Pointer, bodyLen C.int) {
|
||||
var goBody []byte
|
||||
if body != nil && bodyLen != 0 {
|
||||
goBody = C.GoBytes(body, bodyLen)
|
||||
}
|
||||
|
||||
requestBuffer <- &request{
|
||||
id: requestId,
|
||||
url: C.GoString(url),
|
||||
method: C.GoString(method),
|
||||
headers: C.GoString(headers),
|
||||
body: goBody,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
//export processCallback
|
||||
func processCallback(callbackID uint) {
|
||||
callbackBuffer <- callbackID
|
||||
|
@ -15,7 +15,7 @@ extern "C"
|
||||
#endif
|
||||
|
||||
void processMessage(const char *);
|
||||
void processURLRequest(void*, unsigned long long, const char *, const char *, const char *, const void *, int);
|
||||
void processURLRequest(void*, unsigned long long, const char *, const char *, const char *, const void *, int, int);
|
||||
void processMessageDialogResponse(int);
|
||||
void processOpenFileDialogResponse(const char*);
|
||||
void processSaveFileDialogResponse(const char*);
|
||||
|
106
v2/internal/frontend/desktop/darwin/wkwebview_request.go
Normal file
106
v2/internal/frontend/desktop/darwin/wkwebview_request.go
Normal file
@ -0,0 +1,106 @@
|
||||
//go:build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
|
||||
#import "Application.h"
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//export processURLRequest
|
||||
func processURLRequest(ctx unsafe.Pointer, requestId C.ulonglong, url *C.char, method *C.char, headers *C.char, body unsafe.Pointer, bodyLen C.int, hasBodyStream C.int) {
|
||||
var bodyReader io.Reader
|
||||
if body != nil && bodyLen != 0 {
|
||||
bodyReader = bytes.NewReader(C.GoBytes(body, bodyLen))
|
||||
} else if hasBodyStream != 0 {
|
||||
bodyReader = &bodyStreamReader{id: requestId, ctx: ctx}
|
||||
}
|
||||
|
||||
requestBuffer <- &wkWebViewRequest{
|
||||
id: requestId,
|
||||
url: C.GoString(url),
|
||||
method: C.GoString(method),
|
||||
headers: C.GoString(headers),
|
||||
body: bodyReader,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
type wkWebViewRequest struct {
|
||||
id C.ulonglong
|
||||
url string
|
||||
method string
|
||||
headers string
|
||||
body io.Reader
|
||||
|
||||
ctx unsafe.Pointer
|
||||
}
|
||||
|
||||
func (r *wkWebViewRequest) GetHttpRequest() (*http.Request, error) {
|
||||
req, err := http.NewRequest(r.method, r.url, r.body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.headers != "" {
|
||||
var h map[string]string
|
||||
if err := json.Unmarshal([]byte(r.headers), &h); err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshal request headers: %s", err)
|
||||
}
|
||||
|
||||
for k, v := range h {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
var _ io.Reader = &bodyStreamReader{}
|
||||
|
||||
type bodyStreamReader struct {
|
||||
id C.ulonglong
|
||||
ctx unsafe.Pointer
|
||||
}
|
||||
|
||||
// Read implements io.Reader
|
||||
func (r *bodyStreamReader) Read(p []byte) (n int, err error) {
|
||||
var content unsafe.Pointer
|
||||
var contentLen int
|
||||
if p != nil {
|
||||
content = unsafe.Pointer(&p[0])
|
||||
contentLen = len(p)
|
||||
}
|
||||
|
||||
res := C.ProcessURLRequestReadBodyStream(r.ctx, r.id, content, C.int(contentLen))
|
||||
if res > 0 {
|
||||
return int(res), nil
|
||||
}
|
||||
|
||||
switch res {
|
||||
case 0:
|
||||
return 0, io.EOF
|
||||
case -1:
|
||||
return 0, fmt.Errorf("body: stream error")
|
||||
case -2:
|
||||
return 0, errRequestStopped
|
||||
case -3:
|
||||
return 0, fmt.Errorf("body: no stream defined")
|
||||
case -4:
|
||||
return 0, io.ErrClosedPipe
|
||||
default:
|
||||
return 0, fmt.Errorf("body: unknown error %d", res)
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
//go:build darwin
|
||||
|
||||
package darwin
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework Cocoa -framework WebKit
|
||||
|
||||
#import "Application.h"
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
errRequestStopped = errors.New("request has been stopped")
|
||||
)
|
||||
|
||||
type wkWebViewResponseWriter struct {
|
||||
r *wkWebViewRequest
|
||||
|
||||
header http.Header
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
func (rw *wkWebViewResponseWriter) Header() http.Header {
|
||||
if rw.header == nil {
|
||||
rw.header = http.Header{}
|
||||
}
|
||||
return rw.header
|
||||
}
|
||||
|
||||
func (rw *wkWebViewResponseWriter) Write(buf []byte) (int, error) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
var content unsafe.Pointer
|
||||
var contentLen int
|
||||
if buf != nil {
|
||||
content = unsafe.Pointer(&buf[0])
|
||||
contentLen = len(buf)
|
||||
}
|
||||
|
||||
if !C.ProcessURLDidReceiveData(rw.r.ctx, rw.r.id, content, C.int(contentLen)) {
|
||||
return 0, errRequestStopped
|
||||
}
|
||||
return contentLen, nil
|
||||
}
|
||||
|
||||
func (rw *wkWebViewResponseWriter) WriteHeader(code int) {
|
||||
if rw.wroteHeader {
|
||||
return
|
||||
}
|
||||
rw.wroteHeader = true
|
||||
|
||||
header := map[string]string{}
|
||||
for k := range rw.Header() {
|
||||
header[k] = rw.Header().Get(k)
|
||||
}
|
||||
headerData, _ := json.Marshal(header)
|
||||
|
||||
var headers unsafe.Pointer
|
||||
var headersLen int
|
||||
if len(headerData) != 0 {
|
||||
headers = unsafe.Pointer(&headerData[0])
|
||||
headersLen = len(headerData)
|
||||
}
|
||||
|
||||
C.ProcessURLDidReceiveResponse(rw.r.ctx, rw.r.id, C.int(code), headers, C.int(headersLen))
|
||||
}
|
||||
|
||||
func (rw *wkWebViewResponseWriter) Close() {
|
||||
if !rw.wroteHeader {
|
||||
rw.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
C.ProcessURLDidFinish(rw.r.ctx, rw.r.id)
|
||||
}
|
@ -258,11 +258,11 @@ Not all features of an `http.Request` are currently supported, please see the fo
|
||||
| DELETE | ✅ | ✅ | ✅ [^1] |
|
||||
| Request Headers | ✅ | ✅ | ✅ [^1] |
|
||||
| Request Body | ✅ | ✅ | ❌ |
|
||||
| Request Body Streaming | ❌ | ❌ | ❌ |
|
||||
| Request Body Streaming | ✅ | ✅ | ❌ |
|
||||
| Response StatusCodes | ✅ | ✅ | ✅ [^1] |
|
||||
| Response Headers | ✅ | ✅ | ✅ [^1] |
|
||||
| Response Body | ✅ | ✅ | ✅ |
|
||||
| Response Body Streaming | ❌ | ❌ | ✅ |
|
||||
| Response Body Streaming | ❌ | ✅ | ✅ |
|
||||
| WebSockets | ❌ | ❌ | ❌ |
|
||||
| HTTP Redirects 30x | ✅ | ❌ | ❌ |
|
||||
|
||||
|
@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- The [AssetServer](/docs/reference/options#assetserver) now supports serving the index.html file when requesting a directory. Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2110)
|
||||
- Added support for WebKit2GTK 2.36+ on Linux. This brings additional features for the [AssetServer](/docs/reference/options#assetserver), like support for HTTP methods and Headers. The app must be compiled with the Go build tag `webkit2_36` to activate support for this features. This also bumps the minimum requirement of WebKit2GTK to 2.36 for your app. Fixed by @stffabi in this [PR](https://github.com/wailsapp/wails/pull/2151)
|
||||
- Added support for file input selection on macOS. Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2209)
|
||||
- Added support Request/Response streaming of the AssetServer on macOS. Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2219)
|
||||
|
||||
### Fixed
|
||||
- The `noreload` flag in wails dev wasn't applied. Fixed by @stffabi in this [PR](https://github.com/wailsapp/wails/pull/2081)
|
||||
|
Loading…
Reference in New Issue
Block a user