Microsoft Azure PlayFab logo
    • Multiplayer
    • LiveOps
    • Data & Analytics
    • Add-ons
    • For Any Role

      • Engineer
      • Designer
      • Executive
      • Marketer
    • For Any Stage

      • Build
      • Improve
      • Grow
    • For Any Size

      • Solo
      • Indie
      • AAA
  • Runs on PlayFab
  • Pricing
    • Blog
    • Forums
    • Contact us
  • Sign up
  • Sign in
  • Ask a question
  • Spaces
    • PlayStream
    • Feature Requests
    • Add-on Marketplace
    • Bugs
    • API and SDK Questions
    • General Discussion
    • LiveOps
    • Topics
    • Questions
    • Articles
    • Ideas
    • Users
    • Badges
  • Home /
  • Bugs /
avatar image
Question by jpgordon00 · Feb 17, 2021 at 01:41 AM · sdks

UnityWebRequest does not use timeout

In the Unity SDK for PlayFab, I noticed that the PlayFabUnityHttp class does not use a timeout.To fix this, I used the timeout property of UnityWebRequest and assigned it to PlayFabSettings.RequestTimeout. Please note that the timeout is in seconds.

Here is the modified code. If there is another solution or a better solution, I am open to hearing improvements.

#if UNITY_2017_2_OR_NEWER

using PlayFab.Json;
using PlayFab.SharedModels;
using System;
using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;

namespace PlayFab.Internal
{
    public class PlayFabUnityHttp : ITransportPlugin
    {
        public int Timeout {
            get {
                return PlayFabSettings.RequestTimeout;
            }
        }

        private bool _isInitialized = false;
        private readonly int _pendingWwwMessages = 0;

        public bool IsInitialized { get { return _isInitialized; } }

        public void Initialize() { _isInitialized = true; }

        public void Update() { }

        public void OnDestroy() { }

        public void SimpleGetCall(string fullUrl, Action<byte[]> successCallback, Action<string> errorCallback)
        {
            PlayFabHttp.instance.StartCoroutine(SimpleCallCoroutine("get", fullUrl, null, successCallback, errorCallback));
        }

        public void SimplePutCall(string fullUrl, byte[] payload, Action<byte[]> successCallback, Action<string> errorCallback)
        {
            PlayFabHttp.instance.StartCoroutine(SimpleCallCoroutine("put", fullUrl, payload, successCallback, errorCallback));
        }

        public void SimplePostCall(string fullUrl, byte[] payload, Action<byte[]> successCallback, Action<string> errorCallback)
        {
            PlayFabHttp.instance.StartCoroutine(SimpleCallCoroutine("post", fullUrl, payload, successCallback, errorCallback));
        }

        private static IEnumerator SimpleCallCoroutine(string method, string fullUrl, byte[] payload, Action<byte[]> successCallback, Action<string> errorCallback)
        {
            if (payload == null)
            {
                using (UnityWebRequest www = UnityWebRequest.Get(fullUrl))
                {
                    www.timeout = Timeout;
#if UNITY_2017_2_OR_NEWER
                    yield return www.SendWebRequest();
#else
                    yield return www.Send();
#endif

                    if (!string.IsNullOrEmpty(www.error))
                        errorCallback(www.error);
                    else
                        successCallback(www.downloadHandler.data);
                };
            }
            else
            {

                UnityWebRequest request;
                request.timeout = Timeout;
                if (method == "put")
                {
                    request = UnityWebRequest.Put(fullUrl, payload);
                }
                else
                {
                    request = new UnityWebRequest(fullUrl, "POST");
                    request.uploadHandler = (UploadHandler)new UploadHandlerRaw(payload);
                    request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
                    request.SetRequestHeader("Content-Type", "application/json");
                }


#if UNITY_2017_2_OR_NEWER
                request.chunkedTransfer = false; // can be removed after Unity's PUT will be more stable
                yield return request.SendWebRequest();
#else
                yield return request.Send();
#endif

                if (request.isNetworkError || request.isHttpError)
                {
                    errorCallback(request.error);
                }
                else
                {
                    successCallback(request.downloadHandler.data);
                }
            }
        }

        public void MakeApiCall(object reqContainerObj)
        {
            CallRequestContainer reqContainer = (CallRequestContainer)reqContainerObj;
            reqContainer.RequestHeaders["Content-Type"] = "application/json";

#if !UNITY_WSA && !UNITY_WP8 && !UNITY_WEBGL
            if (PlayFabSettings.CompressApiData)
            {
                reqContainer.RequestHeaders["Content-Encoding"] = "GZIP";
                reqContainer.RequestHeaders["Accept-Encoding"] = "GZIP";

                using (var stream = new MemoryStream())
                {
                    using (var zipstream = new Ionic.Zlib.GZipStream(stream, Ionic.Zlib.CompressionMode.Compress,
                        Ionic.Zlib.CompressionLevel.BestCompression))
                    {
                        zipstream.Write(reqContainer.Payload, 0, reqContainer.Payload.Length);
                    }
                    reqContainer.Payload = stream.ToArray();
                }
            }
#endif

            // Start the www corouting to Post, and get a response or error which is then passed to the callbacks.
            PlayFabHttp.instance.StartCoroutine(Post(reqContainer));
        }

        private IEnumerator Post(CallRequestContainer reqContainer)
        {
#if PLAYFAB_REQUEST_TIMING
            var stopwatch = System.Diagnostics.Stopwatch.StartNew();
            var startTime = DateTime.UtcNow;
#endif

            var www = new UnityWebRequest(reqContainer.FullUrl)
            {
                uploadHandler = new UploadHandlerRaw(reqContainer.Payload),
                downloadHandler = new DownloadHandlerBuffer(),
                method = "POST"
            };
            www.timeout = Timeout;

            foreach (var headerPair in reqContainer.RequestHeaders)
            {
                if (!string.IsNullOrEmpty(headerPair.Key) && !string.IsNullOrEmpty(headerPair.Value))
                    www.SetRequestHeader(headerPair.Key, headerPair.Value);
                else
                    Debug.LogWarning("Null header: " + headerPair.Key + " = " + headerPair.Value);
            }

#if UNITY_2017_2_OR_NEWER
            yield return www.SendWebRequest();
#else
            yield return www.Send();
#endif

#if PLAYFAB_REQUEST_TIMING
            stopwatch.Stop();
            var timing = new PlayFabHttp.RequestTiming {
                StartTimeUtc = startTime,
                ApiEndpoint = reqContainer.ApiEndpoint,
                WorkerRequestMs = (int)stopwatch.ElapsedMilliseconds,
                MainThreadRequestMs = (int)stopwatch.ElapsedMilliseconds
            };
            PlayFabHttp.SendRequestTiming(timing);
#endif

            if (!string.IsNullOrEmpty(www.error))
            {
                OnError(www.error, reqContainer);
            }
            else
            {
                try
                {
                    byte[] responseBytes = www.downloadHandler.data;
                    bool isGzipCompressed = responseBytes != null && responseBytes[0] == 31 && responseBytes[1] == 139;
                    string responseText = "Unexpected error: cannot decompress GZIP stream.";
                    if (!isGzipCompressed && responseBytes != null)
                        responseText = System.Text.Encoding.UTF8.GetString(responseBytes, 0, responseBytes.Length);
#if !UNITY_WSA && !UNITY_WP8 && !UNITY_WEBGL
                    if (isGzipCompressed)
                    {
                        var stream = new MemoryStream(responseBytes);
                        using (var gZipStream = new Ionic.Zlib.GZipStream(stream, Ionic.Zlib.CompressionMode.Decompress, false))
                        {
                            var buffer = new byte[4096];
                            using (var output = new MemoryStream())
                            {
                                int read;
                                while ((read = gZipStream.Read(buffer, 0, buffer.Length)) > 0)
                                    output.Write(buffer, 0, read);
                                output.Seek(0, SeekOrigin.Begin);
                                var streamReader = new StreamReader(output);
                                var jsonResponse = streamReader.ReadToEnd();
                                //Debug.Log(jsonResponse);
                                OnResponse(jsonResponse, reqContainer);
                                //Debug.Log("Successful UnityHttp decompress for: " + www.url);
                            }
                        }
                    }
                    else
#endif
                    {
                        OnResponse(responseText, reqContainer);
                    }
                }
                catch (Exception e)
                {
                    OnError("Unhandled error in PlayFabUnityHttp: " + e, reqContainer);
                }
            }
            www.Dispose();
        }

        public int GetPendingMessages()
        {
            return _pendingWwwMessages;
        }

        public void OnResponse(string response, CallRequestContainer reqContainer)
        {
            try
            {
#if PLAYFAB_REQUEST_TIMING
                var startTime = DateTime.UtcNow;
#endif
                var serializer = PluginManager.GetPlugin<ISerializerPlugin>(PluginContract.PlayFab_Serializer);
                var httpResult = serializer.DeserializeObject<HttpResponseObject>(response);

                if (httpResult.code == 200)
                {
                    // We have a good response from the server
                    reqContainer.JsonResponse = serializer.SerializeObject(httpResult.data);
                    reqContainer.DeserializeResultJson();
                    reqContainer.ApiResult.Request = reqContainer.ApiRequest;
                    reqContainer.ApiResult.CustomData = reqContainer.CustomData;

                    PlayFabHttp.instance.OnPlayFabApiResult(reqContainer);
#if !DISABLE_PLAYFABCLIENT_API
                    PlayFabDeviceUtil.OnPlayFabLogin(reqContainer.ApiResult, reqContainer.settings, reqContainer.instanceApi);
#endif
                    try
                    {
                        PlayFabHttp.SendEvent(reqContainer.ApiEndpoint, reqContainer.ApiRequest, reqContainer.ApiResult, ApiProcessingEventType.Post);
                    }
                    catch (Exception e)
                    {
                        Debug.LogException(e);
                    }

                    try
                    {
                        reqContainer.InvokeSuccessCallback();
                    }
                    catch (Exception e)
                    {
                        Debug.LogException(e);
                    }
                }
                else
                {
                    if (reqContainer.ErrorCallback != null)
                    {
                        reqContainer.Error = PlayFabHttp.GeneratePlayFabError(reqContainer.ApiEndpoint, response, reqContainer.CustomData);
                        PlayFabHttp.SendErrorEvent(reqContainer.ApiRequest, reqContainer.Error);
                        reqContainer.ErrorCallback(reqContainer.Error);
                    }
                }
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }

        public void OnError(string error, CallRequestContainer reqContainer)
        {
            reqContainer.JsonResponse = error;
            if (reqContainer.ErrorCallback != null)
            {
                reqContainer.Error = PlayFabHttp.GeneratePlayFabError(reqContainer.ApiEndpoint, reqContainer.JsonResponse, reqContainer.CustomData);
                PlayFabHttp.SendErrorEvent(reqContainer.ApiRequest, reqContainer.Error);
                reqContainer.ErrorCallback(reqContainer.Error);
            }
        }
    }
}

#endif


Comment
mihigh

People who like this

1 Show 0
10 |1200 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

1 Reply

· Add your reply
  • Sort: 
avatar image
Best Answer

Answer by Citrus Yan · Feb 17, 2021 at 05:42 AM

Thanks for sharing this with the community:)

Comment
mihigh

People who like this

1 Show 0 · Share
10 |1200 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Navigation

Spaces
  • General Discussion
  • API and SDK Questions
  • Feature Requests
  • PlayStream
  • Bugs
  • Add-on Marketplace
  • LiveOps
  • Follow this Question

    Answers Answers and Comments

    3 People are following this question.

    avatar image avatar image avatar image

    Related Questions

    UE 4.18 Playfab crash when connecting fails 1 Answer

    Weird iOS Stripping issue with Unity 2020: Null Reference ,Weird iOS Stripping Bug: Unity 2020. Null Reference 1 Answer

    UE4 continually crashing 1 Answer

    Bug in LocalMultiplayerAgent 2 Answers

    Unity3D Editor Extensions NullReferenceException on install, can't start 1 Answer

    PlayFab

    • Multiplayer
    • LiveOps
    • Data & Analytics
    • Runs on PlayFab
    • Pricing

    Solutions

    • For Any Role

      • Engineer
      • Designer
      • Executive
      • Marketer
    • For Any Stage

      • Build
      • Improve
      • Grow
    • For Any Size

      • Solo
      • Indie
      • AAA

    Engineers

    • Documentation
    • Quickstarts
    • API Reference
    • SDKs
    • Usage Limits

    Resources

    • Forums
    • Contact us
    • Blog
    • Service Health
    • Terms of Service
    • Attribution

    Follow us

    • Facebook
    • Twitter
    • LinkedIn
    • YouTube
    • Sitemap
    • Contact Microsoft
    • Privacy & cookies
    • Terms of use
    • Trademarks
    • Safety & eco
    • About our ads
    • © Microsoft 2020
    • Anonymous
    • Sign in
    • Create
    • Ask a question
    • Create an article
    • Post an idea
    • Spaces
    • PlayStream
    • Feature Requests
    • Add-on Marketplace
    • Bugs
    • API and SDK Questions
    • General Discussion
    • LiveOps
    • Explore
    • Topics
    • Questions
    • Articles
    • Ideas
    • Users
    • Badges