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
                    }
                }));
← Back to all posts

© Copyright 2023 Bitscorp