mixedreality/com.microsoft.mixedreality..../Core/Utilities/WebRequestRest/Rest.cs

398 lines
20 KiB
C#

// 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
{
/// <summary>
/// REST Class for CRUD Transactions.
/// </summary>
public static class Rest
{
#region Authentication
/// <summary>
/// Gets the Basic auth header.
/// </summary>
/// <param name="username">The Username.</param>
/// <param name="password">The password.</param>
/// <returns>The Basic authorization header encoded to base 64.</returns>
public static string GetBasicAuthentication(string username, string password)
{
return $"Basic {Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes($"{username}:{password}"))}";
}
/// <summary>
/// Gets the Bearer auth header.
/// </summary>
/// <param name="authToken">OAuth Token to be used.</param>
/// <returns>The Bearer authorization header.</returns>
public static string GetBearerOAuthToken(string authToken)
{
return $"Bearer {authToken}";
}
#endregion Authentication
#region GET
/// <summary>
/// Rest GET.
/// </summary>
/// <param name="query">Finalized Endpoint Query with parameters.</param>
/// <param name="headers">Optional header information for the request.</param>
/// <param name="timeout">Optional time in seconds before request expires.</param>
/// <param name="downloadHandler">Optional DownloadHandler for the request.</param>
/// <param name="readResponseData">Optional bool. If its true, response data will be read from web request download handler.</param>
/// <param name="certificateHandler">Optional certificate handler for custom certificate verification</param>
/// <param name="disposeCertificateHandlerOnDispose">Optional bool. If true and <paramref name="certificateHandler"/> is not null, <paramref name="certificateHandler"/> will be disposed, when the underlying UnityWebRequest is disposed.</param>
/// <returns>The response data.</returns>
public static async Task<Response> GetAsync(
string query,
Dictionary<string, string> 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
/// <summary>
/// Rest POST.
/// </summary>
/// <param name="query">Finalized Endpoint Query with parameters.</param>
/// <param name="headers">Optional header information for the request.</param>
/// <param name="timeout">Optional time in seconds before request expires.</param>
/// <param name="readResponseData">Optional bool. If its true, response data will be read from web request download handler.</param>
/// <param name="certificateHandler">Optional certificate handler for custom certificate verification</param>
/// <param name="disposeCertificateHandlerOnDispose">Optional bool. If true and <paramref name="certificateHandler"/> is not null, <paramref name="certificateHandler"/> will be disposed, when the underlying UnityWebRequest is disposed.</param>
/// <returns>The response data.</returns>
public static async Task<Response> PostAsync(
string query,
Dictionary<string, string> 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);
}
}
/// <summary>
/// Rest POST.
/// </summary>
/// <param name="query">Finalized Endpoint Query with parameters.</param>
/// <param name="formData">Form Data.</param>
/// <param name="headers">Optional header information for the request.</param>
/// <param name="timeout">Optional time in seconds before request expires.</param>
/// <param name="readResponseData">Optional bool. If its true, response data will be read from web request download handler.</param>
/// <param name="certificateHandler">Optional certificate handler for custom certificate verification</param>
/// <param name="disposeCertificateHandlerOnDispose">Optional bool. If true and <paramref name="certificateHandler"/> is not null, <paramref name="certificateHandler"/> will be disposed, when the underlying UnityWebRequest is disposed.</param>
/// <returns>The response data.</returns>
public static async Task<Response> PostAsync(
string query,
WWWForm formData,
Dictionary<string, string> 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);
}
}
/// <summary>
/// Rest POST.
/// </summary>
/// <param name="query">Finalized Endpoint Query with parameters.</param>
/// <param name="jsonData">JSON data for the request.</param>
/// <param name="headers">Optional header information for the request.</param>
/// <param name="timeout">Optional time in seconds before request expires.</param>
/// <param name="readResponseData">Optional bool. If its true, response data will be read from web request download handler.</param>
/// <param name="certificateHandler">Optional certificate handler for custom certificate verification</param>
/// <param name="disposeCertificateHandlerOnDispose">Optional bool. If true and <paramref name="certificateHandler"/> is not null, <paramref name="certificateHandler"/> will be disposed, when the underlying UnityWebRequest is disposed.</param>
/// <returns>The response data.</returns>
public static async Task<Response> PostAsync(
string query,
string jsonData,
Dictionary<string, string> 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);
}
}
/// <summary>
/// Rest POST.
/// </summary>
/// <param name="query">Finalized Endpoint Query with parameters.</param>
/// <param name="headers">Optional header information for the request.</param>
/// <param name="bodyData">The raw data to post.</param>
/// <param name="timeout">Optional time in seconds before request expires.</param>
/// <param name="readResponseData">Optional bool. If its true, response data will be read from web request download handler.</param>
/// <param name="certificateHandler">Optional certificate handler for custom certificate verification</param>
/// <param name="disposeCertificateHandlerOnDispose">Optional bool. If true and <paramref name="certificateHandler"/> is not null, <paramref name="certificateHandler"/> will be disposed, when the underlying UnityWebRequest is disposed.</param>
/// <returns>The response data.</returns>
public static async Task<Response> PostAsync(
string query,
byte[] bodyData,
Dictionary<string, string> 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
/// <summary>
/// Rest PUT.
/// </summary>
/// <param name="query">Finalized Endpoint Query with parameters.</param>
/// <param name="jsonData">Data to be submitted.</param>
/// <param name="headers">Optional header information for the request.</param>
/// <param name="timeout">Optional time in seconds before request expires.</param>
/// <param name="readResponseData">Optional bool. If its true, response data will be read from web request download handler.</param>
/// <param name="certificateHandler">Optional certificate handler for custom certificate verification</param>
/// <param name="disposeCertificateHandlerOnDispose">Optional bool. If true and <paramref name="certificateHandler"/> is not null, <paramref name="certificateHandler"/> will be disposed, when the underlying UnityWebRequest is disposed.</param>
/// <returns>The response data.</returns>
public static async Task<Response> PutAsync(
string query,
string jsonData,
Dictionary<string, string> 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);
}
}
/// <summary>
/// Rest PUT.
/// </summary>
/// <param name="query">Finalized Endpoint Query with parameters.</param>
/// <param name="bodyData">Data to be submitted.</param>
/// <param name="headers">Optional header information for the request.</param>
/// <param name="timeout">Optional time in seconds before request expires.</param>
/// <param name="readResponseData">Optional bool. If its true, response data will be read from web request download handler.</param>
/// <param name="certificateHandler">Optional certificate handler for custom certificate verification</param>
/// <param name="disposeCertificateHandlerOnDispose">Optional bool. If true and <paramref name="certificateHandler"/> is not null, <paramref name="certificateHandler"/> will be disposed, when the underlying UnityWebRequest is disposed.</param>
/// <returns>The response data.</returns>
public static async Task<Response> PutAsync(
string query,
byte[] bodyData,
Dictionary<string, string> 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
/// <summary>
/// Rest DELETE.
/// </summary>
/// <param name="query">Finalized Endpoint Query with parameters.</param>
/// <param name="headers">Optional header information for the request.</param>
/// <param name="timeout">Optional time in seconds before request expires.</param>
/// <param name="readResponseData">Optional bool. If its true, response data will be read from web request download handler.</param>
/// <param name="certificateHandler">Optional certificate handler for custom certificate verification</param>
/// <param name="disposeCertificateHandlerOnDispose">Optional bool. If true and <paramref name="certificateHandler"/> is not null, <paramref name="certificateHandler"/> will be disposed, when the underlying UnityWebRequest is disposed.</param>
/// <returns>The response data.</returns>
public static async Task<Response> DeleteAsync(
string query,
Dictionary<string, string> 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<Response> ProcessRequestAsync(UnityWebRequest webRequest, int timeout, Dictionary<string, string> 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<byte[]> downloadHandlerDataAction = () => webRequest.downloadHandler?.data;
Func<string> downloadHandlerTextAction = () => webRequest.downloadHandler?.text;
Task<string> 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);
}
}
}
}