Skip to content

Commit e3ef7be

Browse files
author
David Douglas
committed
📦 RESTClient for Unity
1 parent 0b8641b commit e3ef7be

File tree

12 files changed

+674
-0
lines changed

12 files changed

+674
-0
lines changed

.gitignore

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/[Ll]ibrary/
2+
/[Tt]emp/
3+
/[Oo]bj/
4+
/[Bb]uild/
5+
/[Bb]uilds/
6+
/Assets/AssetStoreTools*
7+
8+
# Visual Studio 2015 cache directory
9+
/.vs/
10+
11+
# Autogenerated VS/MD/Consulo solution and project files
12+
ExportedObj/
13+
.consulo/
14+
*.csproj
15+
*.unityproj
16+
*.sln
17+
*.suo
18+
*.tmp
19+
*.user
20+
*.userprefs
21+
*.pidb
22+
*.booproj
23+
*.svd
24+
*.pdb
25+
26+
# Unity3D generated meta files
27+
*.pidb.meta
28+
*.meta
29+
30+
# Unity3D Generated File On Crash Reports
31+
sysinfo.txt
32+
33+
# Builds
34+
*.apk
35+
*.unitypackage
36+
37+
# IDEs
38+
/.vscode
39+
omnisharp.json
40+
.editorconfig

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# REST Client for Unity
2+
For Unity developers looking to use REST Services in their Unity game / app.
3+
4+
**RESTClient** for Unity is built on top of [UnityWebRequest](https://docs.unity3d.com/Manual/UnityWebRequest.html) and Unity's [JsonUtility](https://docs.unity3d.com/ScriptReference/JsonUtility.html) to make it easier to compose REST requests and return the results serialized as native C# data model objects.
5+
6+
## Features
7+
- Methods to add request body, headers and query strings to REST requests.
8+
- Ability to return result as native object or as plain text.
9+
- Work around for nested arrays.
10+
- Work around for parsing abstract types on UWP.
11+
12+
## How do I use this with cloud services?
13+
Checkout the following projects for Unity which were built using this REST Client library as examples.
14+
- [Azure App Services](https://github.com/Unity3dAzure/AppServicesDemo)
15+
- [Azure Blob Storage](https://github.com/Unity3dAzure/StorageServicesDemo)
16+
- [Azure Functions](https://github.com/Unity3dAzure/AzureFunctions)
17+
- [Nether (serverless)](https://github.com/MicrosoftDX/nether/tree/serverless/src/Client/Unity)
18+
19+
## Requirements
20+
Requires Unity v5.3 or greater as [UnityWebRequest](https://docs.unity3d.com/Manual/UnityWebRequest.html) and [JsonUtility](https://docs.unity3d.com/ScriptReference/JsonUtility.html) features are used. Unity will be extending platform support for UnityWebRequest so keep Unity up to date if you need to support these additional platforms.
21+
22+
## Supported platforms
23+
Intended to work on all the platforms [UnityWebRequest](https://docs.unity3d.com/Manual/UnityWebRequest.html) supports including:
24+
* Unity Editor (Mac/PC) and Standalone players
25+
* iOS
26+
* Android
27+
* Windows 10 (UWP)
28+
29+
## Troubleshooting
30+
- Remember to wrap async calls with [`StartCoroutine()`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html)
31+
- Before building for a target platform remember to check the **Internet Client capability** is enabled in the Unity player settings, otherwise all REST calls will fail with a '-1' status error.
32+
33+
Questions or tweet [@deadlyfingers](https://twitter.com/deadlyfingers)

RestClient.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using UnityEngine;
5+
using System.Collections;
6+
using System.Collections.Generic;
7+
using System;
8+
using System.Text.RegularExpressions;
9+
10+
11+
#if !NETFX_CORE || UNITY_ANDROID
12+
using System.Net;
13+
using System.Security.Cryptography.X509Certificates;
14+
using System.Net.Security;
15+
#endif
16+
17+
namespace RESTClient {
18+
public class RestClient {
19+
public string Url { get; private set; }
20+
21+
/// <summary>
22+
/// Creates a new REST Client
23+
/// </summary>
24+
public RestClient(string url, bool forceHttps = true) {
25+
if (forceHttps) {
26+
Url = HttpsUri(url);
27+
}
28+
// required for running in Windows and Android
29+
#if !NETFX_CORE || UNITY_ANDROID
30+
ServicePointManager.ServerCertificateValidationCallback = RemoteCertificateValidationCallback;
31+
#endif
32+
}
33+
34+
public override string ToString() {
35+
return this.Url;
36+
}
37+
38+
/// <summary>
39+
/// Changes 'http' to be 'https' instead
40+
/// </summary>
41+
private static string HttpsUri(string appUrl) {
42+
return Regex.Replace(appUrl, "(?si)^http://", "https://").TrimEnd('/');
43+
}
44+
45+
private static string DomainName(string url) {
46+
var match = Regex.Match(url, @"^(https:\/\/|http:\/\/)(www\.)?([a-z0-9-_]+\.[a-z]+)", RegexOptions.IgnoreCase);
47+
if (match.Groups.Count == 4 && match.Groups[3].Value.Length > 0) {
48+
return match.Groups[3].Value;
49+
}
50+
return url;
51+
}
52+
53+
#if !NETFX_CORE || UNITY_ANDROID
54+
private bool RemoteCertificateValidationCallback(System.Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
55+
// Check the certificate to see if it was issued from Azure
56+
if (certificate.Subject.Contains(DomainName(Url))) {
57+
return true;
58+
} else {
59+
return false;
60+
}
61+
}
62+
#endif
63+
64+
}
65+
}

helpers/JsonHelper.cs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using UnityEngine;
5+
using System;
6+
using System.Text.RegularExpressions;
7+
#if NETFX_CORE
8+
using Windows.Data.Json;
9+
using System.Collections.Generic;
10+
using System.Linq;
11+
#endif
12+
13+
namespace RESTClient {
14+
/// <summary>
15+
/// Wrapper work-around for json array described on https://forum.unity3d.com/threads/how-to-load-an-array-with-jsonutility.375735/
16+
/// </summary>
17+
#pragma warning disable 0649 // suppresses warning: array "is never assigned to, and will always have its default value 'null'"
18+
[Serializable]
19+
internal class Wrapper<T> {
20+
21+
public T[] array;
22+
}
23+
24+
public static class JsonHelper {
25+
/// <summary>
26+
/// Work-around to parse json array
27+
/// </summary>
28+
public static T[] FromJsonArray<T>(string json) {
29+
// Work-around for JsonUtility array serialization issues in Windows Store Apps.
30+
#if NETFX_CORE
31+
JsonArray jsonArray = new JsonArray();
32+
if (JsonArray.TryParse(json, out jsonArray)) {
33+
return GetArray<T>(jsonArray);
34+
}
35+
Debug.LogWarning("Failed to parse json array of type:" + typeof(T).ToString() );
36+
return default(T[]);
37+
#endif
38+
string newJson = "{\"array\":" + json + "}";
39+
Wrapper<T> wrapper = new Wrapper<T>();
40+
try {
41+
wrapper = JsonUtility.FromJson<Wrapper<T>>(newJson);
42+
} catch (Exception e) {
43+
Debug.LogWarning("Failed to parse json array of type:" + typeof(T).ToString() + " Exception message: " + e.Message);
44+
return default(T[]);
45+
}
46+
return wrapper.array;
47+
}
48+
49+
public static N FromJsonNestedArray<T, N>(string json, string namedArray) where N : INestedResults<T>, new() {
50+
#if NETFX_CORE
51+
JsonObject jsonObject = new JsonObject();
52+
if (JsonObject.TryParse(json, out jsonObject)) {
53+
JsonArray jsonArray = jsonObject.GetNamedArray(namedArray);
54+
T[] array = GetArray<T>(jsonArray);
55+
N nestedResults = new N(); //NestedResults<T> nestedResults = new NestedResults<T>(array);
56+
nestedResults.SetArray(array);
57+
58+
string namedCount = nestedResults.GetCountField();
59+
uint count = Convert.ToUInt32( jsonObject.GetNamedNumber(namedCount) );
60+
nestedResults.SetCount(count);
61+
62+
return nestedResults;
63+
} else {
64+
Debug.LogWarning("Failed to parse json nested array of type:" + typeof(T).ToString());
65+
return default(N);
66+
}
67+
#endif
68+
N results = JsonUtility.FromJson<N>(json); // NestedResults<T> nestedResults = JsonUtility.FromJson<NestedResults<T>(json);
69+
return results;
70+
}
71+
72+
#if NETFX_CORE
73+
private static T[] GetArray<T>(JsonArray array)
74+
{
75+
List<T> list = new List<T>();
76+
foreach (var x in array) {
77+
try {
78+
T item = JsonUtility.FromJson<T>(x.ToString());
79+
list.Add(item);
80+
} catch (Exception e) {
81+
Debug.LogWarning("Failed to parse json of type:" + typeof(T).ToString() + " Exception message: " + e.Message + " json:'" + x.ToString() + "'");
82+
}
83+
}
84+
return list.ToArray();
85+
}
86+
#endif
87+
88+
/// <summary>
89+
/// Workaround to only exclude Data Model's read only system properties being returned as json object. Unfortunately there is no JsonUtil attribute to do this as [NonSerialized] will just ignore the properties completely (both in and out).
90+
/// </summary>
91+
public static string ToJsonExcludingSystemProperties(object obj) {
92+
string jsonString = JsonUtility.ToJson(obj);
93+
return Regex.Replace(jsonString, "(?i)(\\\"id\\\":\\\"\\\",)?(\\\"createdAt\\\":\\\"[0-9TZ:.-]*\\\",)?(\\\"updatedAt\\\":\\\"[0-9TZ:.-]*\\\",)?(\\\"version\\\":\\\"[A-Z0-9=]*\\\",)?(\\\"deleted\\\":(true|false),)?(\\\"ROW_NUMBER\\\":\\\"[0-9]*\\\",)?", "");
94+
}
95+
}
96+
}

helpers/ReflectionHelper.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Reflection;
5+
6+
namespace RESTClient {
7+
/// <summary>
8+
/// Helper methods to check and get object properties
9+
/// </summary>
10+
public static class ReflectionHelper {
11+
public static bool HasProperty(object obj, string propertyName) {
12+
return GetProperty(obj, propertyName) != null;
13+
}
14+
15+
public static PropertyInfo GetProperty(object obj, string propertyName) {
16+
#if NETFX_CORE
17+
return obj.GetType().GetTypeInfo().GetDeclaredProperty(propertyName); // GetProperty for UWP
18+
#else
19+
return obj.GetType().GetProperty(propertyName);
20+
#endif
21+
}
22+
23+
public static bool HasField(object obj, string fieldName) {
24+
return GetField(obj, fieldName) != null;
25+
}
26+
27+
public static FieldInfo GetField(object obj, string fieldName) {
28+
#if NETFX_CORE
29+
return obj.GetType().GetTypeInfo().GetDeclaredField(fieldName); // GetField for UWP
30+
#else
31+
return obj.GetType().GetField(fieldName);
32+
#endif
33+
}
34+
}
35+
}

http/IRestResponse.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Net;
6+
7+
namespace RESTClient {
8+
public interface IRestResponse<T> {
9+
bool IsError { get; }
10+
11+
string ErrorMessage { get; }
12+
13+
string Url { get; }
14+
15+
HttpStatusCode StatusCode { get; }
16+
17+
string Content { get; }
18+
19+
T Data { get; }
20+
}
21+
}
22+

http/Method.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using UnityEngine;
5+
using System.Collections;
6+
7+
namespace RESTClient {
8+
public enum Method {
9+
GET,
10+
POST,
11+
PATCH,
12+
DELETE
13+
}
14+
}

http/QueryParams.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Collections;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Text;
9+
using UnityEngine;
10+
11+
namespace RESTClient {
12+
public class QueryParams {
13+
private Dictionary<string, string> parameters;
14+
15+
public QueryParams() {
16+
parameters = new Dictionary<string, string>();
17+
}
18+
19+
public void AddParam(string key, string value) {
20+
parameters.Add(key, value);
21+
}
22+
23+
public override string ToString() {
24+
if (parameters.Count == 0) {
25+
return "";
26+
}
27+
StringBuilder sb = new StringBuilder("?");
28+
foreach (KeyValuePair<string, string> param in parameters) {
29+
string key = WWW.EscapeURL(param.Key);
30+
string value = WWW.EscapeURL(param.Value);
31+
sb.Append(key + "=" + value + "&");
32+
}
33+
sb.Remove(sb.Length - 1, 1);
34+
return sb.ToString();
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)