// 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);
}
}
}
}