How to use UnityWebRequest for your REST API?
#unity#restapi#webgl
When I started working with Unity, my goal was to create a cross-platform game that could be deployed across multiple platforms: App Store, Google Play, Amazon, Facebook, Web, and Chrome Extension. While this approach sounds great in theory, it requires careful consideration of the libraries you plan to use.
Initially, I decided to use HttpClient based on the System.Net
namespace. However, when compiling to WebGL, I discovered that System.Net
and HttpClient
were excluded from the build, breaking the connection between my game and API.
The solution? Use UnityWebRequest
.
Below are examples of how to build GET and POST requests with JSON using UnityWebRequest.
using System;
using System.Collections.Generic;
using UnityEngine.Networking;
using System.Collections;
using Newtonsoft.Json;
using UnityEngine;
using System.Text;
namespace Jumper.Network
{
public abstract class API
{
// TODO: add https certificate verification
#if UNITY_WEBGL
// WebGL version is loaded by browser under https protocol.
public const string HOST = "https://api.example.com/client/";
#else
public const string HOST = "http://api.example.com/client/";
#endif
public static readonly string TOKEN_QUERY_PARAM = "?token=";
public static readonly string REGISTER_PATH = "users/register";
private const string USER_ID_KEY = ":user_id";
#region internal methods for http client
internal static string path(string path, Dictionary<string, string> properties)
{
// In case the user has no user_id (possibly due to missing internet connection on boot)
// we should use the /games namespace for sending requests.
if (properties.ContainsKey(USER_ID_KEY) && !string.IsNullOrEmpty(properties[USER_ID_KEY]))
{
return resolve(
GAME_PATH + "/:game_id/sessions/:user_id/" + path + TOKEN_QUERY_PARAM + ":token",
properties);
}
return resolve(
GAME_PATH + "/:game_id/" + path + TOKEN_QUERY_PARAM + ":token",
properties);
}
internal static string resolve(string path, Dictionary<string, string> properties)
{
foreach(var kv in properties)
{
path = path.Replace(kv.Key, kv.Value);
}
return path;
}
internal static string requestError(UnityWebRequest request)
{
string responseBody = string.Empty;
if (request.downloadHandler != null)
{
responseBody = request.downloadHandler.text;
}
return string.Format(
"[api#error] request status code: {0}, data: ======= response: {1}, error: {2} =======",
request.responseCode, responseBody, request.error);
}
internal static T requestResponse<T>(UnityWebRequest request)
{
try
{
var responseData = request.downloadHandler.text;
return JsonConvert.DeserializeObject<T>(responseData);
}
catch (Exception ex)
{
Debug.Log(ex.Message);
return default(T);
}
}
public const string CONTENT_TYPE_JSON = "application/json";
/// <summary>
/// Create the instance of authenticated http client.
/// </summary>
/// <returns>The client.</returns>
internal static IEnumerator Request(string url, string method, string data = null, Action<UnityWebRequest> done = null)
{
UnityWebRequest request;
switch(method)
{
case UnityWebRequest.kHttpVerbGET:
request = UnityWebRequest.Get(url);
yield return request.SendWebRequest();
done?.Invoke(request);
break;
case UnityWebRequest.kHttpVerbPOST:
request = UnityWebRequest.Post(url, data);
request.method = UnityWebRequest.kHttpVerbPOST;
request.downloadHandler = new DownloadHandlerBuffer();
request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(data));
request.SetRequestHeader("Content-Type", CONTENT_TYPE_JSON);
request.SetRequestHeader("Accept", CONTENT_TYPE_JSON);
yield return request.SendWebRequest();
done?.Invoke(request);
break;
}
}
internal static IEnumerator Post(string url, object o, Action<UnityWebRequest> done = null) =>
Request(url, UnityWebRequest.kHttpVerbPOST, JsonConvert.SerializeObject(o), done);
internal static IEnumerator Get(string url, Action<UnityWebRequest> done = null) =>
Request(url, UnityWebRequest.kHttpVerbGET, null, done);
internal static Action<T1, T2> wrapCallback<T1, T2>(Action<T1, T2> doneCallback)
{
// In case of having a missing done callback, use an empty function
// to avoid null checks when invoking the callback.
return doneCallback ?? ((_arg1, _arg2) => { });
}
}
#endregion
}
```C#
// Example of using Api.cs
using Newtonsoft.Json;
using UnityEngine;
using System.Collections.Generic;
using System;
using System.Collections;
namespace Jumper.Network
{
public class Users : API
{
public static IEnumerator Create(
Dictionary<string, string> properties, User user,
Action<User, string> doneCallback = null)
{
var done = wrapCallback(doneCallback);
try
{
return Post(path(REGISTER_PATH, properties), user,
(request) =>
{
if (request.isNetworkError || request.responseCode != 201)
done(null, requestError(request));
else
done(requestResponse<User>(request), null);
});
}
catch (Exception ex)
{
// Catch all exceptions to ensure the application never crashes
Debug.Log(ex.Message);
done(null, ex.Message);
}
return null;
}
}
}
```C#
// Example from MonoBehaviour
StartCoroutine(Users.Create(
Api.GameProperties, Api.CurrentUser, (user, err) =>
{
if (err != null)
{
Debug.LogError(err);
}
else
{
// good to go
}
}));