// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using UnityEngine; using UnityEngine.Networking; using System; using System.Net; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Text; namespace RESTClient { public class RestRequest : IDisposable { public UnityWebRequest Request { get; private set; } private QueryParams queryParams; public RestRequest(UnityWebRequest request) { this.Request = request; } public RestRequest(string url, Method method) { Request = new UnityWebRequest(url, method.ToString()); Request.downloadHandler = new DownloadHandlerBuffer(); } public void AddHeader(string key, string value) { Request.SetRequestHeader(key, value); } public void AddHeaders(Dictionary<string, string> headers) { foreach (KeyValuePair<string, string> header in headers) { AddHeader(header.Key, header.Value); } } public void AddBody(string text, string contentType = "text/plain; charset=UTF-8") { byte[] bytes = Encoding.UTF8.GetBytes(text); this.AddBody(bytes, contentType, false); } public void AddBody(byte[] bytes, string contentType) { this.AddBody(bytes, contentType, false); } public void AddBody(byte[] bytes, string contentType, bool isChunked) { if (Request.uploadHandler != null) { Debug.LogWarning("Request body can only be set once"); return; } Request.chunkedTransfer = isChunked; Request.uploadHandler = new UploadHandlerRaw(bytes); Request.uploadHandler.contentType = contentType; } public virtual void AddBody<T>(T data, string contentType = "application/json; charset=utf-8") { if (typeof(T) == typeof(string)) { this.AddBody(data.ToString(), contentType); return; } string jsonString = JsonUtility.ToJson(data); byte[] bytes = Encoding.UTF8.GetBytes(jsonString); this.AddBody(bytes, contentType, false); } public virtual void AddQueryParam(string key, string value, bool shouldUpdateRequestUrl = false) { if (queryParams == null) { queryParams = new QueryParams(); } queryParams.AddParam(key, value); if (shouldUpdateRequestUrl) { UpdateRequestUrl(); } } public void SetQueryParams(QueryParams queryParams) { if (this.queryParams != null) { Debug.LogWarning("Replacing previous query params"); } this.queryParams = queryParams; } public virtual void UpdateRequestUrl() { if (queryParams == null) { return; } var match = Regex.Match(Request.url, @"^(.+)(\\?)(.+)", RegexOptions.IgnoreCase); if (match.Groups.Count == 4 && match.Groups[0].Value.Length > 0) { string url = match.Groups[0].Value + queryParams.ToString(); Request.url = url; } } #region Response and object parsing private RestResult GetRestResult(bool expectedBodyContent = true) { HttpStatusCode statusCode = (HttpStatusCode)Enum.Parse(typeof(HttpStatusCode), Request.responseCode.ToString()); RestResult result = new RestResult(statusCode); if (result.IsError) { result.ErrorMessage = "Response failed with status: " + statusCode.ToString(); return result; } if (expectedBodyContent && string.IsNullOrEmpty(Request.downloadHandler.text)) { result.IsError = true; result.ErrorMessage = "Response has empty body"; return result; } return result; } private RestResult<T> GetRestResult<T>() { HttpStatusCode statusCode = (HttpStatusCode)Enum.Parse(typeof(HttpStatusCode), Request.responseCode.ToString()); RestResult<T> result = new RestResult<T>(statusCode); if (result.IsError) { result.ErrorMessage = "Response failed with status: " + statusCode.ToString(); return result; } if (string.IsNullOrEmpty(Request.downloadHandler.text)) { result.IsError = true; result.ErrorMessage = "Response has empty body"; return result; } return result; } #endregion #region JSON object parsing response /// <summary> /// Shared method to return response result whether an object or array of objects /// </summary> private RestResult<T> TryParseJsonArray<T>() { RestResult<T> result = GetRestResult<T>(); // try parse an array of objects try { result.AnArrayOfObjects = JsonHelper.FromJsonArray<T>(Request.downloadHandler.text); } catch (Exception e) { result.IsError = true; result.ErrorMessage = "Failed to parse an array of objects of type: " + typeof(T).ToString() + " Exception message: " + e.Message; } return result; } private RestResult<T> TryParseJson<T>() { RestResult<T> result = GetRestResult<T>(); // try parse an object try { result.AnObject = JsonUtility.FromJson<T>(Request.downloadHandler.text); } catch (Exception e) { result.IsError = true; result.ErrorMessage = "Failed to parse object of type: " + typeof(T).ToString() + " Exception message: " + e.Message; } return result; } /// <summary> /// Parses object with T data = JsonUtil.FromJson<T>, then callback RestResponse<T> /// </summary> public IRestResponse<T> ParseJson<T>(Action<IRestResponse<T>> callback = null) { RestResult<T> result = TryParseJson<T>(); RestResponse<T> response; if (result.IsError) { Debug.LogWarning("Response error status:" + result.StatusCode + " code:" + Request.responseCode + " error:" + result.ErrorMessage + " Request url:" + Request.url); response = new RestResponse<T>(result.ErrorMessage, result.StatusCode, Request.url, Request.downloadHandler.text); } else { response = new RestResponse<T>(result.StatusCode, Request.url, Request.downloadHandler.text, result.AnObject); } if (callback != null) { callback(response); } this.Dispose(); return response; } /// <summary> /// Parses array of objects with T[] data = JsonHelper.GetJsonArray<T>, then callback RestResponse<T[]> /// </summary> public IRestResponse<T[]> ParseJsonArray<T>(Action<IRestResponse<T[]>> callback = null) { RestResult<T> result = TryParseJsonArray<T>(); RestResponse<T[]> response; if (result.IsError) { Debug.LogWarning("Response error status:" + result.StatusCode + " code:" + Request.responseCode + " error:" + result.ErrorMessage + " Request url:" + Request.url); response = new RestResponse<T[]>(result.ErrorMessage, result.StatusCode, Request.url, Request.downloadHandler.text); } else { response = new RestResponse<T[]>(result.StatusCode, Request.url, Request.downloadHandler.text, result.AnArrayOfObjects); } if (callback != null) { callback(response); } this.Dispose(); return response; } private RestResult<N> TryParseJsonNestedArray<T, N>(string namedArray) where N : INestedResults<T>, new() { RestResult<N> result = GetRestResult<N>(); // try parse an object try { result.AnObject = JsonHelper.FromJsonNestedArray<T, N>(Request.downloadHandler.text, namedArray); //JsonUtility.FromJson<N>(request.downloadHandler.text); } catch (Exception e) { result.IsError = true; result.ErrorMessage = "Failed to parse object of type: " + typeof(N).ToString() + " Exception message: " + e.Message; } return result; } /// Work-around for nested array public RestResponse<N> ParseJsonNestedArray<T, N>(string namedArray, Action<IRestResponse<N>> callback = null) where N : INestedResults<T>, new() { RestResult<N> result = TryParseJsonNestedArray<T, N>(namedArray); RestResponse<N> response; if (result.IsError) { Debug.LogWarning("Response error status:" + result.StatusCode + " code:" + Request.responseCode + " error:" + result.ErrorMessage + " request url:" + Request.url); response = new RestResponse<N>(result.ErrorMessage, result.StatusCode, Request.url, Request.downloadHandler.text); } else { response = new RestResponse<N>(result.StatusCode, Request.url, Request.downloadHandler.text, result.AnObject); } if (callback != null) { callback(response); } this.Dispose(); return response; } #endregion #region XML object parsing response private RestResult<T> TrySerializeXml<T>() { RestResult<T> result = GetRestResult<T>(); // return early if there was a status / data error other than Forbidden if (result.IsError && result.StatusCode == HttpStatusCode.Forbidden) { Debug.LogWarning("Authentication Failed: " + Request.downloadHandler.text); return result; } else if (result.IsError) { return result; } // otherwise try and serialize XML response text to an object try { result.AnObject = XmlHelper.FromXml<T>(Request.downloadHandler.text); } catch (Exception e) { result.IsError = true; result.ErrorMessage = "Failed to parse object of type: " + typeof(T).ToString() + " Exception message: " + e.Message; } return result; } public RestResponse<T> ParseXML<T>(Action<IRestResponse<T>> callback = null) { RestResult<T> result = TrySerializeXml<T>(); RestResponse<T> response; if (result.IsError) { Debug.LogWarning("Response error status:" + result.StatusCode + " code:" + Request.responseCode + " error:" + result.ErrorMessage + " request url:" + Request.url); response = new RestResponse<T>(result.ErrorMessage, result.StatusCode, Request.url, Request.downloadHandler.text); } else { response = new RestResponse<T>(result.StatusCode, Request.url, Request.downloadHandler.text, result.AnObject); } if (callback != null) { callback(response); } this.Dispose(); return response; } /// <summary> /// To be used with a callback which passes the response with result including status success or error code, request url and any body text. /// </summary> /// <param name="callback">Callback.</param> public RestResponse Result(Action<RestResponse> callback = null) { return GetText(callback, false); } public RestResponse GetText(Action<RestResponse> callback = null, bool expectedBodyContent = true) { RestResult result = GetRestResult(expectedBodyContent); RestResponse response; if (result.IsError) { Debug.LogWarning("Response error status:" + result.StatusCode + " code:" + Request.responseCode + " error:" + result.ErrorMessage + " request url:" + Request.url); response = new RestResponse(result.ErrorMessage, result.StatusCode, Request.url, Request.downloadHandler.text); } else { response = new RestResponse(result.StatusCode, Request.url, Request.downloadHandler.text); } if (callback != null) { callback(response); } this.Dispose(); return response; } #endregion /// <summary> /// Return response body as plain text. /// </summary> /// <param name="callback">Callback with response type: IRestResponse<string></param> public IRestResponse<T> GetText<T>(Action<IRestResponse<T>> callback = null) { RestResult<string> result = GetRestResult<string>(); RestResponse<T> response; if (result.IsError) { response = new RestResponse<T>(result.ErrorMessage, result.StatusCode, Request.url, Request.downloadHandler.text); } else { response = new RestResponse<T>(result.StatusCode, Request.url, Request.downloadHandler.text); } if (callback != null) { callback(response); } this.Dispose(); return response; } /// <summary> /// Return response body as bytes. /// </summary> /// <param name="callback">Callback with response type: IRestResponse<byte[]></param> public IRestResponse<byte[]> GetBytes(Action<IRestResponse<byte[]>> callback = null) { RestResult result = GetRestResult(false); RestResponse<byte[]> response; if (result.IsError) { response = new RestResponse<byte[]>(result.ErrorMessage, result.StatusCode, Request.url, Request.downloadHandler.text); } else { response = new RestResponse<byte[]>(result.StatusCode, Request.url, null, Request.downloadHandler.data); } if (callback != null) { callback(response); } this.Dispose(); return response; } #region Handle native asset UnityWebRequest.Get... requests public IRestResponse<Texture> GetTexture(Action<IRestResponse<Texture>> callback = null) { RestResult result = GetRestResult(false); RestResponse<Texture> response; if (result.IsError) { response = new RestResponse<Texture>(result.ErrorMessage, result.StatusCode, Request.url, Request.downloadHandler.text); } else { Texture texture = ((DownloadHandlerTexture)Request.downloadHandler).texture; response = new RestResponse<Texture>(result.StatusCode, Request.url, null, texture); } if (callback != null) { callback(response); } this.Dispose(); return response; } public IRestResponse<AudioClip> GetAudioClip(Action<IRestResponse<AudioClip>> callback = null) { RestResult result = GetRestResult(false); RestResponse<AudioClip> response; if (result.IsError) { response = new RestResponse<AudioClip>(result.ErrorMessage, result.StatusCode, Request.url, Request.downloadHandler.text); } else { AudioClip audioClip = ((DownloadHandlerAudioClip)Request.downloadHandler).audioClip; response = new RestResponse<AudioClip>(result.StatusCode, Request.url, null, audioClip); } if (callback != null) { callback(response); } this.Dispose(); return response; } public IRestResponse<AssetBundle> GetAssetBundle(Action<IRestResponse<AssetBundle>> callback = null) { RestResult result = GetRestResult(false); RestResponse<AssetBundle> response; if (result.IsError) { response = new RestResponse<AssetBundle>(result.ErrorMessage, result.StatusCode, Request.url, Request.downloadHandler.text); } else { AssetBundle assetBundle = ((DownloadHandlerAssetBundle)Request.downloadHandler).assetBundle; response = new RestResponse<AssetBundle>(result.StatusCode, Request.url, null, assetBundle); } if (callback != null) { callback(response); } this.Dispose(); return response; } #endregion public UnityWebRequestAsyncOperation Send() { #if UNITY_2017_2_OR_NEWER return Request.SendWebRequest(); #else return Request.Send(); #endif } public void Dispose() { Request.Dispose(); // Request completed, clean-up resources } } }