diff --git a/helpers/JsonHelper.cs b/helpers/JsonHelper.cs
index 3f97288..1556f76 100644
--- a/helpers/JsonHelper.cs
+++ b/helpers/JsonHelper.cs
@@ -12,7 +12,8 @@
namespace RESTClient {
///
- /// Wrapper work-around for json array described on https://forum.unity3d.com/threads/how-to-load-an-array-with-jsonutility.375735/
+ /// Wrapper work-around for json array
+ /// Issue reference: https://forum.unity3d.com/threads/how-to-load-an-array-with-jsonutility.375735/
///
#pragma warning disable 0649 // suppresses warning: array "is never assigned to, and will always have its default value 'null'"
[Serializable]
@@ -52,7 +53,7 @@ public static T[] FromJsonArray(string json) {
if (JsonObject.TryParse(json, out jsonObject)) {
JsonArray jsonArray = jsonObject.GetNamedArray(namedArray);
T[] array = GetArray(jsonArray);
- N nestedResults = new N(); //NestedResults nestedResults = new NestedResults(array);
+ N nestedResults = new N();
nestedResults.SetArray(array);
string namedCount = nestedResults.GetCountField();
@@ -65,7 +66,7 @@ public static T[] FromJsonArray(string json) {
return default(N);
}
#endif
- N results = JsonUtility.FromJson(json); // NestedResults nestedResults = JsonUtility.FromJson(json);
+ N results = JsonUtility.FromJson(json);
return results;
}
diff --git a/helpers/SignatureHelper.cs b/helpers/SignatureHelper.cs
new file mode 100644
index 0000000..c65a216
--- /dev/null
+++ b/helpers/SignatureHelper.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Security.Cryptography;
+
+#if NETFX_CORE
+using Windows.Security.Cryptography.Core;
+using Windows.Security.Cryptography;
+using System.Runtime.InteropServices.WindowsRuntime;
+#endif
+
+namespace RESTClient {
+ public static class SignatureHelper {
+ public static string Sign(byte[] key, string stringToSign) {
+#if NETFX_CORE
+ MacAlgorithmProvider provider = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256);
+ CryptographicHash hash = provider.CreateHash(key.AsBuffer());
+ hash.Append(CryptographicBuffer.ConvertStringToBinary(stringToSign, BinaryStringEncoding.Utf8));
+ return CryptographicBuffer.EncodeToBase64String( hash.GetValueAndReset() );
+#else
+ var hmac = new HMACSHA256();
+ hmac.Key = key;
+ byte[] sig = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
+ return Convert.ToBase64String(sig);
+#endif
+ }
+ }
+}
diff --git a/helpers/UrlHelper.cs b/helpers/UrlHelper.cs
new file mode 100644
index 0000000..eddf630
--- /dev/null
+++ b/helpers/UrlHelper.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Text;
+using System.Collections.Generic;
+
+namespace RESTClient {
+ public static class UrlHelper {
+
+ ///
+ /// The returned url format will be: baseUrl + path(s) + query string
+ ///
+ /// The URL.
+ /// Base URL.
+ /// Query parameters.
+ /// Paths.
+ public static string BuildQuery(string baseUrl, Dictionary queryParams = null, params string[] paths) {
+ StringBuilder q = new StringBuilder();
+ if (queryParams == null) {
+ return BuildQuery(baseUrl, "", paths);
+ }
+
+ foreach (KeyValuePair param in queryParams) {
+ if (q.Length == 0) {
+ q.Append("?");
+ } else {
+ q.Append("&");
+ }
+ q.Append(param.Key + "=" + param.Value);
+ }
+
+ return BuildQuery(baseUrl, q.ToString(), paths);
+ }
+
+ public static string BuildQuery(string baseUrl, string queryString, params string[] paths) {
+ StringBuilder sb = new StringBuilder();
+ sb.Append(baseUrl);
+ if (!baseUrl.EndsWith("/")) {
+ sb.Append("/");
+ }
+
+ foreach (string path in paths) {
+ if (!path.EndsWith("/")) {
+ sb.Append(path);
+ } else {
+ sb.Append(path + "/");
+ }
+ }
+
+ sb.Append(queryString);
+ return sb.ToString();
+ }
+ }
+}
+
diff --git a/helpers/XmlHelper.cs b/helpers/XmlHelper.cs
new file mode 100644
index 0000000..004705e
--- /dev/null
+++ b/helpers/XmlHelper.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using UnityEngine;
+using System.Xml;
+using System.Xml.Serialization;
+using System.IO;
+using System.Text;
+
+namespace RESTClient {
+ public static class XmlHelper {
+ public static XmlDocument LoadResourceDocument(string filename) {
+ string xml = LoadResourceText(filename);
+ XmlDocument doc = new XmlDocument();
+ doc.LoadXml(xml);
+ return doc;
+ }
+
+ public static string LoadResourceText(string filename) {
+ TextAsset contents = (TextAsset)Resources.Load(filename);
+ return contents.text;
+ }
+
+ public static string LoadAsset(string filepath, string extension = ".xml") {
+ string path = Path.Combine(Application.dataPath, filepath + extension);
+ return File.ReadAllText(path);
+ }
+
+ public static T FromXml(string xml) where T : class {
+ XmlSerializer serializer = new XmlSerializer(typeof(T));
+ using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xml))) {
+ return (T)serializer.Deserialize(stream);
+ }
+ }
+ }
+}
diff --git a/http/Method.cs b/http/Method.cs
index 8ebb5da..7d161b3 100644
--- a/http/Method.cs
+++ b/http/Method.cs
@@ -9,6 +9,7 @@ public enum Method {
GET,
POST,
PATCH,
- DELETE
+ DELETE,
+ PUT
}
}
diff --git a/http/RestRequest.cs b/http/RestRequest.cs
index 169bfb2..f5f4662 100644
--- a/http/RestRequest.cs
+++ b/http/RestRequest.cs
@@ -11,14 +11,14 @@
namespace RESTClient {
public class RestRequest : IDisposable {
- public RestRequest(UnityWebRequest request) {
- this.Request = request;
- }
-
public UnityWebRequest Request { get; private set; }
private QueryParams queryParams;
+ public RestRequest(UnityWebRequest request) {
+ this.Request = request;
+ }
+
public RestRequest(string url, Method method) {
Request = new UnityWebRequest(url, method.ToString());
Request.downloadHandler = new DownloadHandlerBuffer();
@@ -28,6 +28,17 @@ public void AddHeader(string key, string value) {
Request.SetRequestHeader(key, value);
}
+ public void AddHeaders(Dictionary headers) {
+ foreach (KeyValuePair header in headers) {
+ AddHeader(header.Key, header.Value);
+ }
+ }
+
+ public void AddBody(string text, string contentType = "text/plain; charset=UTF-8") {
+ byte[] bytes = Encoding.UTF8.GetBytes(text);
+ this.AddBody(bytes, contentType);
+ }
+
public void AddBody(byte[] bytes, string contentType) {
if (Request.uploadHandler != null) {
Debug.LogWarning("Request body can only be set once");
@@ -64,7 +75,24 @@ public virtual void UpdateRequestUrl() {
}
}
- #region Response and json object parsing
+ #region Response and object parsing
+
+ private RestResult GetRestResult(bool expectedBodyContent = true) {
+ HttpStatusCode statusCode = (HttpStatusCode)Enum.Parse(typeof(HttpStatusCode), Request.responseCode.ToString());
+ RestResult result = new RestResult(statusCode);
+
+ if (result.IsError) {
+ result.ErrorMessage = "Response failed with status: " + statusCode.ToString();
+ return result;
+ }
+
+ if (expectedBodyContent && string.IsNullOrEmpty(Request.downloadHandler.text)) {
+ result.IsError = true;
+ result.ErrorMessage = "Response has empty body";
+ return result;
+ }
+ return result;
+ }
private RestResult GetRestResult() {
HttpStatusCode statusCode = (HttpStatusCode)Enum.Parse(typeof(HttpStatusCode), Request.responseCode.ToString());
@@ -84,6 +112,8 @@ private RestResult GetRestResult() {
return result;
}
+ #region JSON object parsing response
+
///
/// Shared method to return response result whether an object or array of objects
///
@@ -174,6 +204,57 @@ public IRestResponse ParseJsonArray(Action> callback
this.Dispose();
}
+ #endregion
+
+ #region XML object parsing response
+
+ private RestResult TrySerializeXml() where T : class {
+ RestResult result = GetRestResult();
+ // return early if there was a status / data error other than Forbidden
+ if (result.IsError && result.StatusCode == HttpStatusCode.Forbidden) {
+ Debug.LogWarning("Authentication Failed: " + Request.downloadHandler.text);
+ return result;
+ } else if (result.IsError) {
+ return result;
+ }
+ // otherwise try and serialize XML response text to an object
+ try {
+ result.AnObject = XmlHelper.FromXml(Request.downloadHandler.text);
+ } catch (Exception e) {
+ result.IsError = true;
+ result.ErrorMessage = "Failed to parse object of type: " + typeof(T).ToString() + " Exception message: " + e.Message;
+ }
+ return result;
+ }
+
+ public void ParseXML(Action> callback = null) where T : class {
+ RestResult result = TrySerializeXml();
+
+ if (result.IsError) {
+ Debug.LogWarning("Response error status:" + result.StatusCode + " code:" + Request.responseCode + " error:" + result.ErrorMessage + " request url:" + Request.url);
+ callback(new RestResponse(result.ErrorMessage, result.StatusCode, Request.url, Request.downloadHandler.text));
+ } else {
+ callback(new RestResponse(result.StatusCode, Request.url, Request.downloadHandler.text, result.AnObject));
+ }
+ this.Dispose();
+ }
+
+ ///
+ /// To be used with a callback which passes the response with result including status success or error code, request url and any body text.
+ ///
+ /// Callback.
+ public void Result(Action callback = null) {
+ RestResult result = GetRestResult(false);
+ if (result.IsError) {
+ Debug.LogWarning("Response error status:" + result.StatusCode + " code:" + Request.responseCode + " error:" + result.ErrorMessage + " request url:" + Request.url);
+ callback(new RestResponse(result.ErrorMessage, result.StatusCode, Request.url, Request.downloadHandler.text));
+ } else {
+ callback(new RestResponse(result.StatusCode, Request.url, Request.downloadHandler.text));
+ }
+ }
+
+ #endregion
+
/// Just return as plain text
public IRestResponse GetText(Action> callback = null) {
RestResult result = GetRestResult();
diff --git a/http/RestResponse.cs b/http/RestResponse.cs
index aeb6c00..9d26118 100644
--- a/http/RestResponse.cs
+++ b/http/RestResponse.cs
@@ -76,5 +76,10 @@ public RestResult(HttpStatusCode statusCode) : base(statusCode) {
}
}
+ internal sealed class RestResult : Response {
+ public RestResult(HttpStatusCode statusCode) : base(statusCode) {
+ }
+ }
+
}