// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; namespace Microsoft.MixedReality.Toolkit.Utilities { /// /// REST Class for CRUD Transactions. /// public static class Rest { #region Authentication /// /// Gets the Basic auth header. /// /// The Username. /// The password. /// The Basic authorization header encoded to base 64. public static string GetBasicAuthentication(string username, string password) { return $"Basic {Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes($"{username}:{password}"))}"; } /// /// Gets the Bearer auth header. /// /// OAuth Token to be used. /// The Bearer authorization header. public static string GetBearerOAuthToken(string authToken) { return $"Bearer {authToken}"; } #endregion Authentication #region GET /// /// Rest GET. /// /// Finalized Endpoint Query with parameters. /// Optional header information for the request. /// Optional time in seconds before request expires. /// Optional DownloadHandler for the request. /// Optional bool. If its true, response data will be read from web request download handler. /// Optional certificate handler for custom certificate verification /// Optional bool. If true and is not null, will be disposed, when the underlying UnityWebRequest is disposed. /// The response data. public static async Task GetAsync( string query, Dictionary headers = null, int timeout = -1, DownloadHandler downloadHandler = null, bool readResponseData = false, CertificateHandler certificateHandler = null, bool disposeCertificateHandlerOnDispose = true, CancellationToken cancellationToken = default(CancellationToken)) { using (var webRequest = UnityWebRequest.Get(query)) { if (downloadHandler != null) { webRequest.downloadHandler = downloadHandler; } cancellationToken.Register(() => { webRequest.Abort(); }); return await ProcessRequestAsync(webRequest, timeout, headers, readResponseData, certificateHandler, disposeCertificateHandlerOnDispose); } } #endregion GET #region POST /// /// Rest POST. /// /// Finalized Endpoint Query with parameters. /// Optional header information for the request. /// Optional time in seconds before request expires. /// Optional bool. If its true, response data will be read from web request download handler. /// Optional certificate handler for custom certificate verification /// Optional bool. If true and is not null, will be disposed, when the underlying UnityWebRequest is disposed. /// The response data. public static async Task PostAsync( string query, Dictionary headers = null, int timeout = -1, bool readResponseData = false, CertificateHandler certificateHandler = null, bool disposeCertificateHandlerOnDispose = true, CancellationToken cancellationToken = default(CancellationToken)) { #if UNITY_2022_2_OR_NEWER using (var webRequest = UnityWebRequest.PostWwwForm(query, null as string)) #else using (var webRequest = UnityWebRequest.Post(query, null as string)) #endif { cancellationToken.Register(() => { webRequest.Abort(); }); return await ProcessRequestAsync(webRequest, timeout, headers, readResponseData, certificateHandler, disposeCertificateHandlerOnDispose); } } /// /// Rest POST. /// /// Finalized Endpoint Query with parameters. /// Form Data. /// Optional header information for the request. /// Optional time in seconds before request expires. /// Optional bool. If its true, response data will be read from web request download handler. /// Optional certificate handler for custom certificate verification /// Optional bool. If true and is not null, will be disposed, when the underlying UnityWebRequest is disposed. /// The response data. public static async Task PostAsync( string query, WWWForm formData, Dictionary headers = null, int timeout = -1, bool readResponseData = false, CertificateHandler certificateHandler = null, bool disposeCertificateHandlerOnDispose = true, CancellationToken cancellationToken = default(CancellationToken)) { using (var webRequest = UnityWebRequest.Post(query, formData)) { cancellationToken.Register(() => { webRequest.Abort(); }); return await ProcessRequestAsync(webRequest, timeout, headers, readResponseData, certificateHandler, disposeCertificateHandlerOnDispose); } } /// /// Rest POST. /// /// Finalized Endpoint Query with parameters. /// JSON data for the request. /// Optional header information for the request. /// Optional time in seconds before request expires. /// Optional bool. If its true, response data will be read from web request download handler. /// Optional certificate handler for custom certificate verification /// Optional bool. If true and is not null, will be disposed, when the underlying UnityWebRequest is disposed. /// The response data. public static async Task PostAsync( string query, string jsonData, Dictionary headers = null, int timeout = -1, bool readResponseData = false, CertificateHandler certificateHandler = null, bool disposeCertificateHandlerOnDispose = true, CancellationToken cancellationToken = default(CancellationToken)) { #if UNITY_2022_2_OR_NEWER using (var webRequest = UnityWebRequest.PostWwwForm(query, "POST")) #else using (var webRequest = UnityWebRequest.Post(query, "POST")) #endif { cancellationToken.Register(() => { webRequest.Abort(); }); var data = new UTF8Encoding().GetBytes(jsonData); webRequest.uploadHandler = new UploadHandlerRaw(data); webRequest.downloadHandler = new DownloadHandlerBuffer(); webRequest.SetRequestHeader("Content-Type", "application/json"); webRequest.SetRequestHeader("Accept", "application/json"); return await ProcessRequestAsync(webRequest, timeout, headers, readResponseData, certificateHandler, disposeCertificateHandlerOnDispose); } } /// /// Rest POST. /// /// Finalized Endpoint Query with parameters. /// Optional header information for the request. /// The raw data to post. /// Optional time in seconds before request expires. /// Optional bool. If its true, response data will be read from web request download handler. /// Optional certificate handler for custom certificate verification /// Optional bool. If true and is not null, will be disposed, when the underlying UnityWebRequest is disposed. /// The response data. public static async Task PostAsync( string query, byte[] bodyData, Dictionary headers = null, int timeout = -1, bool readResponseData = false, CertificateHandler certificateHandler = null, bool disposeCertificateHandlerOnDispose = true, CancellationToken cancellationToken = default(CancellationToken)) { #if UNITY_2022_2_OR_NEWER using (var webRequest = UnityWebRequest.PostWwwForm(query, "POST")) #else using (var webRequest = UnityWebRequest.Post(query, "POST")) #endif { cancellationToken.Register(() => { webRequest.Abort(); }); webRequest.uploadHandler = new UploadHandlerRaw(bodyData); webRequest.downloadHandler = new DownloadHandlerBuffer(); webRequest.SetRequestHeader("Content-Type", "application/octet-stream"); return await ProcessRequestAsync(webRequest, timeout, headers, readResponseData, certificateHandler, disposeCertificateHandlerOnDispose); } } #endregion POST #region PUT /// /// Rest PUT. /// /// Finalized Endpoint Query with parameters. /// Data to be submitted. /// Optional header information for the request. /// Optional time in seconds before request expires. /// Optional bool. If its true, response data will be read from web request download handler. /// Optional certificate handler for custom certificate verification /// Optional bool. If true and is not null, will be disposed, when the underlying UnityWebRequest is disposed. /// The response data. public static async Task PutAsync( string query, string jsonData, Dictionary headers = null, int timeout = -1, bool readResponseData = false, CertificateHandler certificateHandler = null, bool disposeCertificateHandlerOnDispose = true, CancellationToken cancellationToken = default(CancellationToken)) { using (var webRequest = UnityWebRequest.Put(query, jsonData)) { cancellationToken.Register(() => { webRequest.Abort(); }); webRequest.SetRequestHeader("Content-Type", "application/json"); return await ProcessRequestAsync(webRequest, timeout, headers, readResponseData, certificateHandler, disposeCertificateHandlerOnDispose); } } /// /// Rest PUT. /// /// Finalized Endpoint Query with parameters. /// Data to be submitted. /// Optional header information for the request. /// Optional time in seconds before request expires. /// Optional bool. If its true, response data will be read from web request download handler. /// Optional certificate handler for custom certificate verification /// Optional bool. If true and is not null, will be disposed, when the underlying UnityWebRequest is disposed. /// The response data. public static async Task PutAsync( string query, byte[] bodyData, Dictionary headers = null, int timeout = -1, bool readResponseData = false, CertificateHandler certificateHandler = null, bool disposeCertificateHandlerOnDispose = true, CancellationToken cancellationToken = default(CancellationToken)) { using (var webRequest = UnityWebRequest.Put(query, bodyData)) { cancellationToken.Register(() => { webRequest.Abort(); }); webRequest.SetRequestHeader("Content-Type", "application/octet-stream"); return await ProcessRequestAsync(webRequest, timeout, headers, readResponseData, certificateHandler, disposeCertificateHandlerOnDispose); } } #endregion PUT #region DELETE /// /// Rest DELETE. /// /// Finalized Endpoint Query with parameters. /// Optional header information for the request. /// Optional time in seconds before request expires. /// Optional bool. If its true, response data will be read from web request download handler. /// Optional certificate handler for custom certificate verification /// Optional bool. If true and is not null, will be disposed, when the underlying UnityWebRequest is disposed. /// The response data. public static async Task DeleteAsync( string query, Dictionary headers = null, int timeout = -1, bool readResponseData = false, CertificateHandler certificateHandler = null, bool disposeCertificateHandlerOnDispose = true, CancellationToken cancellationToken = default(CancellationToken)) { using (var webRequest = UnityWebRequest.Delete(query)) { cancellationToken.Register(() => { webRequest.Abort(); }); return await ProcessRequestAsync(webRequest, timeout, headers, readResponseData, certificateHandler, disposeCertificateHandlerOnDispose); } } #endregion DELETE private static async Task ProcessRequestAsync(UnityWebRequest webRequest, int timeout, Dictionary headers = null, bool readResponseData = false, CertificateHandler certificateHandler = null, bool disposeCertificateHandlerOnDispose = true) { if (timeout > 0) { webRequest.timeout = timeout; } if (headers != null) { foreach (var header in headers) { webRequest.SetRequestHeader(header.Key, header.Value); } } // HACK: Workaround for extra quotes around boundary. if (webRequest.method == UnityWebRequest.kHttpVerbPOST || webRequest.method == UnityWebRequest.kHttpVerbPUT) { string contentType = webRequest.GetRequestHeader("Content-Type"); if (contentType != null) { contentType = contentType.Replace("\"", ""); webRequest.SetRequestHeader("Content-Type", contentType); } } webRequest.certificateHandler = certificateHandler; webRequest.disposeCertificateHandlerOnDispose = disposeCertificateHandlerOnDispose; await webRequest.SendWebRequest(); long responseCode = webRequest.responseCode; Func downloadHandlerDataAction = () => webRequest.downloadHandler?.data; Func downloadHandlerTextAction = () => webRequest.downloadHandler?.text; Task downloadHandlerTextTask = ResponseUtils.BytesToString(downloadHandlerDataAction.Invoke()); #if UNITY_2020_1_OR_NEWER if (webRequest.result == UnityWebRequest.Result.ConnectionError || webRequest.result == UnityWebRequest.Result.ProtocolError) #else if (webRequest.isNetworkError || webRequest.isHttpError) #endif // UNITY_2020_1_OR_NEWER { if (responseCode == 401) { return new Response(false, "Invalid Credentials", null, responseCode); } if (webRequest.GetResponseHeaders() == null) { return new Response(false, "Device Unavailable", null, responseCode); } string responseHeaders = webRequest.GetResponseHeaders().Aggregate(string.Empty, (current, header) => $"\n{header.Key}: {header.Value}"); string downloadHandlerText = await downloadHandlerTextTask; Debug.LogError($"REST Error: {responseCode}\n{downloadHandlerText}{responseHeaders}"); return new Response(false, $"{responseHeaders}\n{downloadHandlerText}", downloadHandlerDataAction.Invoke(), responseCode); } if (readResponseData) { return new Response(true, await downloadHandlerTextTask, downloadHandlerDataAction.Invoke(), responseCode); } else // This option can be used only if action will be triggered in the same scope as the webrequest { return new Response(true, downloadHandlerTextAction, downloadHandlerDataAction, responseCode); } } } }