private async Task doFetch(TaskCompletionSource <HttpResponseMessage> tcs, HttpRequestMessage request, CancellationToken cancellationToken) { try { var requestObject = new JSObject(); requestObject.SetObjectProperty("method", request.Method.Method); // See https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials for // standard values and meanings requestObject.SetObjectProperty("credentials", DefaultCredentials); // See https://developer.mozilla.org/en-US/docs/Web/API/Request/cache for // standard values and meanings requestObject.SetObjectProperty("cache", Cache); // See https://developer.mozilla.org/en-US/docs/Web/API/Request/mode for // standard values and meanings requestObject.SetObjectProperty("mode", Mode); // We need to check for body content if (request.Content != null) { if (request.Content is StringContent) { requestObject.SetObjectProperty("body", await request.Content.ReadAsStringAsync()); } else { using (var uint8Buffer = Uint8Array.From(await request.Content.ReadAsByteArrayAsync())) { requestObject.SetObjectProperty("body", uint8Buffer); } } } // Process headers // Cors has it's own restrictions on headers. // https://developer.mozilla.org/en-US/docs/Web/API/Headers using (var jsHeaders = new HostObject("Headers")) { if (request.Headers != null) { foreach (var header in request.Headers) { foreach (var value in header.Value) { jsHeaders.Invoke("append", header.Key, value); } } } if (request.Content?.Headers != null) { foreach (var header in request.Content.Headers) { foreach (var value in header.Value) { jsHeaders.Invoke("append", header.Key, value); } } } requestObject.SetObjectProperty("headers", jsHeaders); } JSObject abortController = null; JSObject signal = null; WasmHttpReadStream wasmHttpReadStream = null; CancellationTokenRegistration abortRegistration = default(CancellationTokenRegistration); if (cancellationToken.CanBeCanceled) { abortController = new HostObject("AbortController"); signal = (JSObject)abortController.GetObjectProperty("signal"); requestObject.SetObjectProperty("signal", signal); abortRegistration = cancellationToken.Register((Action)(() => { if (abortController.JSHandle != -1) { abortController.Invoke((string)"abort"); abortController?.Dispose(); } wasmHttpReadStream?.Dispose(); })); } var args = new Core.Array(); args.Push(request.RequestUri.ToString()); args.Push(requestObject); requestObject.Dispose(); var response = (Task <object>)fetch.Invoke("apply", window, args); args.Dispose(); var t = await response; var status = new WasmFetchResponse((JSObject)t, abortController, abortRegistration); //Console.WriteLine($"bodyUsed: {status.IsBodyUsed}"); //Console.WriteLine($"ok: {status.IsOK}"); //Console.WriteLine($"redirected: {status.IsRedirected}"); //Console.WriteLine($"status: {status.Status}"); //Console.WriteLine($"statusText: {status.StatusText}"); //Console.WriteLine($"type: {status.ResponseType}"); //Console.WriteLine($"url: {status.Url}"); HttpResponseMessage httpresponse = new HttpResponseMessage((HttpStatusCode)Enum.Parse(typeof(HttpStatusCode), status.Status.ToString())); httpresponse.Content = StreamingSupported && StreamingEnabled ? new StreamContent(wasmHttpReadStream = new WasmHttpReadStream(status)) : (HttpContent) new WasmHttpContent(status); // Fill the response headers // CORS will only allow access to certain headers. // If a request is made for a resource on another origin which returns the CORs headers, then the type is cors. // cors and basic responses are almost identical except that a cors response restricts the headers you can view to // `Cache-Control`, `Content-Language`, `Content-Type`, `Expires`, `Last-Modified`, and `Pragma`. // View more information https://developers.google.com/web/updates/2015/03/introduction-to-fetch#response_types // // Note: Some of the headers may not even be valid header types in .NET thus we use TryAddWithoutValidation using (var respHeaders = (JSObject)status.Headers) { if (respHeaders != null) { using (var entriesIterator = (JSObject)respHeaders.Invoke("entries")) { JSObject nextResult = null; try { nextResult = (JSObject)entriesIterator.Invoke("next"); while (!(bool)nextResult.GetObjectProperty("done")) { using (var resultValue = (WebAssembly.Core.Array)nextResult.GetObjectProperty("value")) { var name = (string)resultValue [0]; var value = (string)resultValue [1]; if (!httpresponse.Headers.TryAddWithoutValidation(name, value)) { if (httpresponse.Content != null) { if (!httpresponse.Content.Headers.TryAddWithoutValidation(name, value)) { Console.WriteLine($"Warning: Can not add response header for name: {name} value: {value}"); } } } } nextResult?.Dispose(); nextResult = (JSObject)entriesIterator.Invoke("next"); } } finally { nextResult?.Dispose(); } } } } tcs.SetResult(httpresponse); signal?.Dispose(); } catch (Exception exception) { tcs.SetException(exception); } }
private async Task doFetch(TaskCompletionSource <HttpResponseMessage> tcs, HttpRequestMessage request, CancellationToken cancellationToken) { try { var requestObject = Runtime.NewJSObject(); requestObject.SetObjectProperty("method", request.Method.Method); // See https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials for // standard values and meanings requestObject.SetObjectProperty("credentials", DefaultCredentials); // See https://developer.mozilla.org/en-US/docs/Web/API/Request/cache for // standard values and meanings requestObject.SetObjectProperty("cache", Cache); // See https://developer.mozilla.org/en-US/docs/Web/API/Request/mode for // standard values and meanings requestObject.SetObjectProperty("mode", Mode); // We need to check for body content if (request.Content != null) { if (request.Content is StringContent) { requestObject.SetObjectProperty("body", await request.Content.ReadAsStringAsync()); } else { requestObject.SetObjectProperty("body", await request.Content.ReadAsByteArrayAsync()); } } // Process headers // Cors has it's own restrictions on headers. // https://developer.mozilla.org/en-US/docs/Web/API/Headers var requestHeaders = GetHeadersAsStringArray(request); if (requestHeaders != null && requestHeaders.Length > 0) { using (var headersObj = (JSObject)WebAssembly.Runtime.GetGlobalObject("Headers")) using (var jsHeaders = Runtime.NewJSObject(headersObj)) { for (int i = 0; i < requestHeaders.Length; i++) { //Console.WriteLine($"append: {requestHeaders[i][0]} / {requestHeaders[i][1]}"); jsHeaders.Invoke("append", requestHeaders [i] [0], requestHeaders [i] [1]); } requestObject.SetObjectProperty("headers", jsHeaders); } } JSObject abortController = null; JSObject signal = null; WasmHttpReadStream wasmHttpReadStream = null; CancellationTokenRegistration abortRegistration = default(CancellationTokenRegistration); if (cancellationToken.CanBeCanceled) { using (var abortObj = (JSObject)WebAssembly.Runtime.GetGlobalObject("AbortController")) abortController = Runtime.NewJSObject(abortObj); signal = (JSObject)abortController.GetObjectProperty("signal"); requestObject.SetObjectProperty("signal", signal); abortRegistration = cancellationToken.Register(() => { if (abortController.JSHandle != -1) { abortController.Invoke("abort"); abortController?.Dispose(); } wasmHttpReadStream?.Dispose(); }); } var args = Runtime.NewJSArray(); args.Invoke("push", request.RequestUri.ToString()); args.Invoke("push", requestObject); requestObject.Dispose(); var response = (Task <object>)fetch.Invoke("apply", window, args); args.Dispose(); var t = await response; var status = new WasmFetchResponse((JSObject)t, abortController, abortRegistration); //Console.WriteLine($"bodyUsed: {status.IsBodyUsed}"); //Console.WriteLine($"ok: {status.IsOK}"); //Console.WriteLine($"redirected: {status.IsRedirected}"); //Console.WriteLine($"status: {status.Status}"); //Console.WriteLine($"statusText: {status.StatusText}"); //Console.WriteLine($"type: {status.ResponseType}"); //Console.WriteLine($"url: {status.Url}"); HttpResponseMessage httpresponse = new HttpResponseMessage((HttpStatusCode)Enum.Parse(typeof(HttpStatusCode), status.Status.ToString())); httpresponse.Content = StreamingSupported && StreamingEnabled ? new StreamContent(wasmHttpReadStream = new WasmHttpReadStream(status)) : (HttpContent) new WasmHttpContent(status); // Fill the response headers // CORS will only allow access to certain headers. // If a request is made for a resource on another origin which returns the CORs headers, then the type is cors. // cors and basic responses are almost identical except that a cors response restricts the headers you can view to // `Cache-Control`, `Content-Language`, `Content-Type`, `Expires`, `Last-Modified`, and `Pragma`. // View more information https://developers.google.com/web/updates/2015/03/introduction-to-fetch#response_types // // Note: Some of the headers may not even be valid header types in .NET thus we use TryAddWithoutValidation using (var respHeaders = status.Headers) { // Here we invoke the forEach on the headers object // Note: the Action takes 3 objects and not two. The other seems to be the Header object. var foreachAction = new Action <object, object, object> ((value, name, other) => { if (!httpresponse.Headers.TryAddWithoutValidation((string)name, (string)value)) { if (httpresponse.Content != null) { if (!httpresponse.Content.Headers.TryAddWithoutValidation((string)name, (string)value)) { Console.WriteLine($"Warning: Can not add response header for name: {name} value: {value}"); } } } ((JSObject)other).Dispose(); }); try { respHeaders.Invoke("forEach", foreachAction); } finally { // Do not remove the following line of code. The httpresponse is used in the lambda above when parsing the Headers. // if a local is captured (used) by a lambda it becomes heap memory as we translate them into fields on an object. // The foreachAction is allocated when marshalled to JavaScript. Since we do not know when JS is finished with the // Action we need to tell the Runtime to de-allocate the object and remove the instance from JS as well. WebAssembly.Runtime.FreeObject(foreachAction); } } tcs.SetResult(httpresponse); signal?.Dispose(); } catch (Exception exception) { tcs.SetException(exception); } }
private async Task doFetch(TaskCompletionSource <HttpResponseMessage> tcs, HttpRequestMessage request, CancellationToken cancellationToken) { try { var requestObject = new JSObject(); if (request.Properties.TryGetValue("WebAssemblyFetchOptions", out var fetchOoptionsValue) && fetchOoptionsValue is IDictionary <string, object> fetchOptions) { foreach (var item in fetchOptions) { requestObject.SetObjectProperty(item.Key, item.Value); } } requestObject.SetObjectProperty("method", request.Method.Method); // We need to check for body content if (request.Content != null) { if (request.Content is StringContent) { requestObject.SetObjectProperty("body", await request.Content.ReadAsStringAsync()); } else { // 2.1.801 seems to have a problem with the line // using (var uint8Buffer = Uint8Array.From(await request.Content.ReadAsByteArrayAsync ())) // so we split it up into two lines. var byteAsync = await request.Content.ReadAsByteArrayAsync(); using (var uint8Buffer = Uint8Array.From(byteAsync)) { requestObject.SetObjectProperty("body", uint8Buffer); } } } // Process headers // Cors has it's own restrictions on headers. // https://developer.mozilla.org/en-US/docs/Web/API/Headers using (var jsHeaders = new HostObject("Headers")) { if (request.Headers != null) { foreach (var header in request.Headers) { foreach (var value in header.Value) { jsHeaders.Invoke("append", header.Key, value); } } } if (request.Content?.Headers != null) { foreach (var header in request.Content.Headers) { foreach (var value in header.Value) { jsHeaders.Invoke("append", header.Key, value); } } } requestObject.SetObjectProperty("headers", jsHeaders); } WasmHttpReadStream wasmHttpReadStream = null; JSObject abortController = new HostObject("AbortController"); JSObject signal = (JSObject)abortController.GetObjectProperty("signal"); requestObject.SetObjectProperty("signal", signal); signal.Dispose(); CancellationTokenSource abortCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); CancellationTokenRegistration abortRegistration = abortCts.Token.Register((Action)(() => { if (abortController.JSHandle != -1) { abortController.Invoke("abort"); abortController?.Dispose(); } wasmHttpReadStream?.Dispose(); })); var args = new Runtime.InteropServices.JavaScript.Array(); args.Push(request.RequestUri.ToString()); args.Push(requestObject); requestObject.Dispose(); var response = fetch.Invoke("apply", window, args) as Task <object>; args.Dispose(); if (response == null) { throw new Exception("Internal error marshalling the response Promise from `fetch`."); } var t = await response; var status = new WasmFetchResponse((JSObject)t, abortController, abortCts, abortRegistration); //Console.WriteLine($"bodyUsed: {status.IsBodyUsed}"); //Console.WriteLine($"ok: {status.IsOK}"); //Console.WriteLine($"redirected: {status.IsRedirected}"); //Console.WriteLine($"status: {status.Status}"); //Console.WriteLine($"statusText: {status.StatusText}"); //Console.WriteLine($"type: {status.ResponseType}"); //Console.WriteLine($"url: {status.Url}"); HttpResponseMessage httpresponse = new HttpResponseMessage((HttpStatusCode)Enum.Parse(typeof(HttpStatusCode), status.Status.ToString())); var streamingEnabled = request.Properties.TryGetValue("WebAssemblyEnableStreamingResponse", out var streamingEnabledValue) && (bool)streamingEnabledValue; httpresponse.Content = StreamingSupported && streamingEnabled ? new StreamContent(wasmHttpReadStream = new WasmHttpReadStream(status)) : (HttpContent) new WasmHttpContent(status); // Fill the response headers // CORS will only allow access to certain headers. // If a request is made for a resource on another origin which returns the CORs headers, then the type is cors. // cors and basic responses are almost identical except that a cors response restricts the headers you can view to // `Cache-Control`, `Content-Language`, `Content-Type`, `Expires`, `Last-Modified`, and `Pragma`. // View more information https://developers.google.com/web/updates/2015/03/introduction-to-fetch#response_types // // Note: Some of the headers may not even be valid header types in .NET thus we use TryAddWithoutValidation using (var respHeaders = (JSObject)status.Headers) { if (respHeaders != null) { using (var entriesIterator = (JSObject)respHeaders.Invoke("entries")) { JSObject nextResult = null; try { nextResult = (JSObject)entriesIterator.Invoke("next"); while (!(bool)nextResult.GetObjectProperty("done")) { using (var resultValue = (Runtime.InteropServices.JavaScript.Array)nextResult.GetObjectProperty("value")) { var name = (string)resultValue [0]; var value = (string)resultValue [1]; if (!httpresponse.Headers.TryAddWithoutValidation(name, value)) { if (httpresponse.Content != null) { if (!httpresponse.Content.Headers.TryAddWithoutValidation(name, value)) { Console.WriteLine($"Warning: Can not add response header for name: {name} value: {value}"); } } } } nextResult?.Dispose(); nextResult = (JSObject)entriesIterator.Invoke("next"); } } finally { nextResult?.Dispose(); } } } } tcs.SetResult(httpresponse); } catch (JSException jsExc) { var httpExc = new System.Net.Http.HttpRequestException(jsExc.Message); tcs.SetException(httpExc); } catch (Exception exception) { tcs.SetException(exception); } }