DEV Community

loading...
Balconygames

Unity Web Request How to use UnityWebRequest for your Rest API?

oivoodoo profile image Alexandr K Updated on ・3 min read

When I started to work with unity the goal was to have the cross platform game and deploy the game to App Store, Google Play, Amazon, Facebook, Web, Chrome Extension to cover as much as possible platforms. The idea is good but it would require to focus on libraries that you are planning to use.

At the first time I've decided to use HttpClient based on System.Net namespace but as result of compiling to WebGL System.Net and HttpClient was excluded from the build and no connection between game and API.

The solution is to use UnityWebRequest.

My examples how to build GET, POST requests with JSON.

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 çase if user has no user_id because of missing internet connection on boot
            // we should use /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 missing done callback use empty function to skip checks
            // on null or not callback instance.
            return doneCallback ?? ((_arg1, _arg2) => { });
        }
    }
#endregion
}

Enter fullscreen mode Exit fullscreen mode
// Example of usage 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 here all the exceptions ensure never die
                Debug.Log(ex.Message);
                done(null, ex.Message);
            }

            return null;
        }
    }
}

Enter fullscreen mode Exit fullscreen mode
// Example from MonoBehaviour

StartCoroutine(Users.Create(
                Api.GameProperties, Api.CurrentUser, (user, err) =>
                {
                    if (err != null)
                    {
                        Debug.LogError(err);
                    }
                    else
                    {
                        // good to go
                    }
                }));
Enter fullscreen mode Exit fullscreen mode

Discussion (1)

pic
Editor guide
Collapse
jdnichollsc profile image
Juan David Nicholls Cardona

If you want to improve your code, try using promises instead of callbacks, check this library: github.com/proyecto26/RestClient