From 4b0cad9bdf46725ff44970c32f0ebb235aa2390e Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Thu, 20 Jun 2024 19:24:05 -0400 Subject: [PATCH] com.utilities.rest 3.2.0 (#82) - Refactor `serverSentEventHandler` from `Action` to `Func` - Fix `Validate(debug)` not capturing request body for json payloads --- README.md | 8 +- .../Documentation~/README.md | 8 +- .../com.utilities.rest/Runtime/Rest.cs | 127 +++++++++++++----- .../Packages/com.utilities.rest/package.json | 2 +- 4 files changed, 107 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 46ccd37..d9b5079 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Advanced features includes progress notifications, authentication and native mul - [Rest Parameters](#rest-parameters) - [Get](#get) - [Post](#post) - - [Server Sent Events](#server-sent-events) :new: + - [Server Sent Events](#server-sent-events) :warning: - [Data Received Callbacks](#data-received-callbacks) - [Put](#put) - [Patch](#patch) @@ -115,10 +115,14 @@ response.Validate(debug: true); #### Server Sent Events +> [!WARNING] +> This callback was recently refactored from `Action` to `Func`. To update this callback without asynchronous calls, just add `await Task.CompletedTask;` at the end of your callback function. + ```csharp var jsonData = "{\"data\":\"content\"}"; -var response = await Rest.PostAsync("www.your.api/endpoint", jsonData, (sseResponse, ssEvent) => { +var response = await Rest.PostAsync("www.your.api/endpoint", jsonData, async (sseResponse, ssEvent) => { Debug.Log(ssEvent); + await Task.CompletedTask; }); // Validates the response for you and will throw a RestException if the response is unsuccessful. response.Validate(debug: true); diff --git a/Utilities.Rest/Packages/com.utilities.rest/Documentation~/README.md b/Utilities.Rest/Packages/com.utilities.rest/Documentation~/README.md index f00fa26..d9352db 100644 --- a/Utilities.Rest/Packages/com.utilities.rest/Documentation~/README.md +++ b/Utilities.Rest/Packages/com.utilities.rest/Documentation~/README.md @@ -46,7 +46,7 @@ Advanced features includes progress notifications, authentication and native mul - [Rest Parameters](#rest-parameters) - [Get](#get) - [Post](#post) - - [Server Sent Events](#server-sent-events) :new: + - [Server Sent Events](#server-sent-events) :warning: - [Data Received Callbacks](#data-received-callbacks) - [Put](#put) - [Patch](#patch) @@ -115,10 +115,14 @@ response.Validate(debug: true); #### Server Sent Events +> [!WARNING] +> This callback was recently refactored from `Action` to `Func`. To update this callback without asynchronous calls, just add `await Task.CompletedTask;` at the end of your callback function. + ```csharp var jsonData = "{\"data\":\"content\"}"; -var response = await Rest.PostAsync("www.your.api/endpoint", jsonData, (sseResponse, ssEvent) => { +var response = await Rest.PostAsync("www.your.api/endpoint", jsonData, async (sseResponse, ssEvent) => { Debug.Log(ssEvent); + await Task.CompletedTask; }); // Validates the response for you and will throw a RestException if the response is unsuccessful. response.Validate(debug: true); diff --git a/Utilities.Rest/Packages/com.utilities.rest/Runtime/Rest.cs b/Utilities.Rest/Packages/com.utilities.rest/Runtime/Rest.cs index e107f07..7d72924 100644 --- a/Utilities.Rest/Packages/com.utilities.rest/Runtime/Rest.cs +++ b/Utilities.Rest/Packages/com.utilities.rest/Runtime/Rest.cs @@ -29,6 +29,7 @@ public static class Rest private const string content_type = "Content-Type"; private const string content_length = "Content-Length"; private const string application_json = "application/json"; + private const string multipart_form_data = "multipart/form-data"; private const string application_octet_stream = "application/octet-stream"; private const string ssePattern = @"(?:(?:(?[^:\n]*):)(?(?:(?!\n\n|\ndata:).)*)(?:\ndata:(?(?:(?!\n\n).)*))?\n\n)"; @@ -74,6 +75,18 @@ public static async Task GetAsync( return await webRequest.SendAsync(parameters, cancellationToken); } + [Obsolete("use new overload with serverSentEventHandler: Func")] + public static async Task GetAsync( + string query, + Action serverSentEventHandler, + RestParameters parameters = null, + CancellationToken cancellationToken = default) + { + await Awaiters.UnityMainThread; + using var webRequest = UnityWebRequest.Get(query); + return await webRequest.SendAsync(parameters, serverSentEventHandler, cancellationToken); + } + /// /// Rest GET. /// @@ -84,7 +97,7 @@ public static async Task GetAsync( /// The response data. public static async Task GetAsync( string query, - Action serverSentEventHandler, + Func serverSentEventHandler, RestParameters parameters = null, CancellationToken cancellationToken = default) { @@ -203,19 +216,38 @@ public static async Task PostAsync( return await webRequest.SendAsync(parameters, cancellationToken); } + [Obsolete("Use new overload with serverSentEventHandler: Func")] + public static async Task PostAsync( + string query, + string jsonData, + Action serverSentEventHandler, + RestParameters parameters = null, + CancellationToken cancellationToken = default) + { + await Awaiters.UnityMainThread; + using var webRequest = new UnityWebRequest(query, UnityWebRequest.kHttpVerbPOST); + var data = new UTF8Encoding().GetBytes(jsonData); + using var uploadHandler = new UploadHandlerRaw(data); + webRequest.uploadHandler = uploadHandler; + using var downloadHandler = new DownloadHandlerBuffer(); + webRequest.downloadHandler = downloadHandler; + webRequest.SetRequestHeader(content_type, application_json); + return await webRequest.SendAsync(parameters, serverSentEventHandler, cancellationToken); + } + /// /// Rest POST. /// /// Finalized Endpoint Query with parameters. /// JSON data for the request. - /// server sent event callback handler. + /// server sent event callback handler. /// Optional, . /// Optional, . /// The response data. public static async Task PostAsync( string query, string jsonData, - Action serverSentEventHandler, + Func serverSentEventHandler, RestParameters parameters = null, CancellationToken cancellationToken = default) { @@ -1082,18 +1114,32 @@ public static async Task SendAsync( return await SendAsync(webRequest, parameters, serverSentEventHandler, cancellationToken); } + [Obsolete("use new .ctr with new serverSentEventHandler: Func")] + public static async Task SendAsync( + this UnityWebRequest webRequest, + RestParameters parameters = null, + Action serverSentEventHandler = null, + CancellationToken cancellationToken = default) + { + return await SendAsync(webRequest, parameters, (response, @event) => + { + serverSentEventHandler?.Invoke(response, @event); + return Task.CompletedTask; + }, cancellationToken); + } + /// /// Process a asynchronously. /// /// The . /// Optional, . - /// Optional, server sent event callback handler. + /// Optional, server sent event callback handler. /// Optional . /// public static async Task SendAsync( this UnityWebRequest webRequest, RestParameters parameters = null, - Action serverSentEventHandler = null, + Func serverSentEventHandler = null, CancellationToken cancellationToken = default) { await Awaiters.UnityMainThread; @@ -1138,41 +1184,47 @@ UnityWebRequest.kHttpVerbPUT or if (hasUpload && webRequest.uploadHandler != null) { var contentType = webRequest.GetRequestHeader(content_type); - if (webRequest.uploadHandler.data is { Length: > 0 } && - contentType.Contains("multipart/form-data")) + + if (webRequest.uploadHandler.data is { Length: > 0 }) { - var boundary = contentType.Split(';')[1].Split('=')[1]; var encodedData = Encoding.UTF8.GetString(webRequest.uploadHandler.data); - var formData = encodedData.Split(new[] { $"\r\n--{boundary}\r\n", $"\r\n--{boundary}--\r\n" }, StringSplitOptions.RemoveEmptyEntries); - var formParts = new Dictionary(); - foreach (var form in formData) + if (contentType.Contains(multipart_form_data)) { - var formFields = form.Split(new[] { "\r\n\r\n" }, StringSplitOptions.RemoveEmptyEntries); - var fieldHeader = formFields[0]; - var key = fieldHeader.Split(new[] { "name=\"" }, StringSplitOptions.RemoveEmptyEntries)[1].Split("\"")[0]; + var boundary = contentType.Split(';')[1].Split('=')[1]; + var formData = encodedData.Split(new[] { $"\r\n--{boundary}\r\n", $"\r\n--{boundary}--\r\n" }, StringSplitOptions.RemoveEmptyEntries); + var formParts = new Dictionary(); - if (fieldHeader.Contains("application/octet-stream")) - { - var fileName = fieldHeader.Split(new[] { "filename=\"" }, StringSplitOptions.RemoveEmptyEntries)[1].Split("\"")[0]; - formParts.Add(key, fileName); - } - else + foreach (var form in formData) { - var value = formFields[1]; - formParts.Add(key, value); + const string eol = "\r\n\r\n"; + var formFields = form.Split(new[] { eol }, StringSplitOptions.RemoveEmptyEntries); + var fieldHeader = formFields[0]; + const string fieldName = "name=\""; + var key = fieldHeader.Split(new[] { fieldName }, StringSplitOptions.RemoveEmptyEntries)[1].Split('"')[0]; + + if (fieldHeader.Contains(application_octet_stream)) + { + const string filename = "filename=\""; + var fileName = fieldHeader.Split(new[] { filename }, StringSplitOptions.RemoveEmptyEntries)[1].Split('"')[0]; + formParts.Add(key, fileName); + } + else + { + var value = formFields[1]; + formParts.Add(key, value); + } } - } - requestBody = JsonConvert.SerializeObject(new { contentType, formParts }); - } - else - { - requestBody = string.Empty; + requestBody = JsonConvert.SerializeObject(new { contentType, formParts }); + } + else + { + requestBody = encodedData; + } } } - if (parameters is { Progress: not null } || serverSentEventHandler != null) { @@ -1194,7 +1246,7 @@ async void CallbackThread() { if (serverSentEventHandler != null) { - SendServerEventCallback(false, requestBody); + await SendServerEventCallback(false, requestBody).ConfigureAwait(true); } if (parameters is { Progress: not null }) @@ -1276,12 +1328,12 @@ UnityWebRequest.Result.ConnectionError or if (serverSentEventHandler != null) { - SendServerEventCallback(true, requestBody); + await SendServerEventCallback(true, requestBody).ConfigureAwait(true); } return new Response(webRequest, requestBody, true, parameters); - void SendServerEventCallback(bool isEnd, string body) + async Task SendServerEventCallback(bool isEnd, string body) { var allEventMessages = webRequest.downloadHandler?.text; if (string.IsNullOrWhiteSpace(allEventMessages)) { return; } @@ -1337,7 +1389,16 @@ void SendServerEventCallback(bool isEnd, string body) } var sseResponse = new Response(webRequest, body, true, parameters, (@event.Data ?? @event.Value).ToString(Formatting.None)); - serverSentEventHandler.Invoke(sseResponse, @event); + + try + { + await serverSentEventHandler.Invoke(sseResponse, @event).ConfigureAwait(true); + } + catch (Exception e) + { + Debug.LogError(e); + } + parameters.ServerSentEventCount++; parameters.ServerSentEvents.Add(@event); } diff --git a/Utilities.Rest/Packages/com.utilities.rest/package.json b/Utilities.Rest/Packages/com.utilities.rest/package.json index f7d1041..b7a41ce 100644 --- a/Utilities.Rest/Packages/com.utilities.rest/package.json +++ b/Utilities.Rest/Packages/com.utilities.rest/package.json @@ -3,7 +3,7 @@ "displayName": "Utilities.Rest", "description": "This package contains useful RESTful utilities for the Unity Game Engine.", "keywords": [], - "version": "3.1.2", + "version": "3.2.0", "unity": "2021.3", "documentationUrl": "https://github.com/RageAgainstThePixel/com.utilities.rest#documentation", "changelogUrl": "https://github.com/RageAgainstThePixel/com.utilities.rest/releases",