Skip to content

IWebServiceRequest interface

wvdvegt edited this page Jan 14, 2019 · 7 revisions

IWebServiceRequest interface

The IWebServiceRequest is a complex interface with a single purpose, create and perform a http request based on request components supplied. It is designed to be synchronous so a service health check or login can be performed before making further calls. The idea is that if asynchronous invocation is needed (i.e. by calling a sequence of API methods of an asset), one has to wrap this sequence into a dedicated method and call this dedicated method asynchronously.

For both implementation has the code to take care of some headers that are defined as properties and thus are not allowed in the header collection (i.e. content-type and accept).

Unity implementation:

    #region IWebServiceRequest implementation

    // http://docs.unity3d.com/ScriptReference/Experimental.Networking.UnityWebRequest.html
    //
    // Note: We use a synchronous way to invoke a 'Coroutine'. 
    //
    // The idea is that the Asset Method is invoked using a Coroutine and not these methodes 
    // themselves that are invoked by the asset. The reason is that inside the asset we need 
    // the answers of this method (like the Auth Token, or be sure the GameStorage structure has 
    // been restored before restoring data etc).
    //
    // Unity3D differ a lot from Xamarin in this (async) matter.
    //
    // The WWW class is not used as UCM used PUT and DElETE als http methods (WWW only supports GET and POST).
    //
    public void WebServiceRequest(RequestSetttings requestSettings, out RequestResponse requestResponse)
    {
        requestResponse = new RequestResponse(requestSettings);

        //Coroutine cr = behaviour.StartCoroutine(
        InternalWebServiceRequest(requestSettings, requestResponse);

        Debug.Log(String.Format("{0} - {1}", requestResponse.responseCode, requestResponse.uri));
    }

    // http://answers.unity3d.com/questions/1028197/best-practise-to-switch-between-coroutine-and-sync.html
    // http://twistedoakstudios.com/blog/Post83_coroutines-more-than-you-want-to-know
    //
    // 'Fake' Coroutine.
    private Coroutine InternalWebServiceRequest(RequestSetttings requestSettings, RequestResponse requestResponse)
    {
        UnityWebRequest request = new UnityWebRequest();

        Debug.Log(requestSettings.uri);

        request.method = requestSettings.method;
        request.url = requestSettings.uri.ToString();

        foreach (KeyValuePair<String, String> kvp in requestSettings.requestHeaders)
        {
            request.SetRequestHeader(kvp.Key, kvp.Value);
        }

        if (!String.IsNullOrEmpty(requestSettings.body))
        {
            byte[] data = Encoding.UTF8.GetBytes(requestSettings.body);
            UploadHandlerRaw upHandler = new UploadHandlerRaw(data);
            upHandler.contentType = requestSettings.requestHeaders["Content-Type"];
            request.uploadHandler = upHandler;
        }

        DownloadHandler dnHandler = new DownloadHandlerBuffer();
        request.downloadHandler = dnHandler;
        request.Send();

        while (!request.isDone)
        {
            // Nothing
        }

        if (request.isNetworkError)
        {
            Debug.Log(request.error);
            requestResponse.responsMessage = request.error;
        }
        else
        {
            requestResponse.responseCode = (int)request.responseCode;
            requestResponse.responseHeaders = request.GetResponseHeaders();
            requestResponse.body = request.downloadHandler.text;

            // Unity does not have a responseMessage available.
            requestResponse.responsMessage = String.Empty;
        }

        return null;
    }

    #endregion

Xamarin implementation:

Note: This interface is updated to support returning either a (binary) response stream or the body.

    #region IWebServiceRequest Members
    
    #if ASYNC
    
    public void WebServiceRequest(RequestSetttings requestSettings, out RequestResponse requestReponse)
    {
        // Wrap the actual method in a Task. Neccesary because we cannot:
        // 1) Make this method async (out is not allowed)
        // 2) Return a Task<RequestResponse> as it breaks the interface (only void does not break it).
        //
        Task<RequestResponse> taskName = Task.Factory.StartNew<RequestResponse>(() =>
        {
            return WebServiceRequestAsync(requestSettings).Result;
        });

        requestReponse = taskName.Result;
    }

    /// <summary>
    /// Web service request.
    /// </summary>
    ///
    /// <param name="requestSettings"> Options for controlling the operation. </param>
    ///
    /// <returns>
    /// A RequestResponse.
    /// </returns>
    private async Task<RequestResponse> WebServiceRequestAsync(RequestSetttings requestSettings)
    #else
    /// <summary>
    /// Web service request.
    /// </summary>
    ///
    /// <param name="requestSettings">  Options for controlling the operation. </param>
    /// <param name="requestResponse"> The request response. </param>
    public void WebServiceRequest(RequestSetttings requestSettings, out RequestResponse requestResponse)
    {
        requestResponse = WebServiceRequest(requestSettings);
    }

    /// <summary>
    /// Web service request.
    /// </summary>
    ///
    /// <param name="requestSettings">  Options for controlling the operation. </param>
    ///
    /// <returns>
    /// A RequestResponse.
    /// </returns>
    private RequestResponse WebServiceRequest(RequestSetttings requestSettings)
    
    #endif
    
    {
        RequestResponse result = new RequestResponse(requestSettings);

        try
        {
            //! Might throw a silent System.IOException on .NET 3.5 (sync).
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestSettings.uri);

            request.Method = requestSettings.method;

            // Both Accept and Content-Type are not allowed as Headers in a HttpWebRequest.
            // They need to be assigned to a matching property.

            if (requestSettings.requestHeaders.ContainsKey("Accept"))
            {
                request.Accept = requestSettings.requestHeaders["Accept"];
            }

            if (!String.IsNullOrEmpty(requestSettings.body))
            {
                byte[] data = Encoding.UTF8.GetBytes(requestSettings.body);

                if (requestSettings.requestHeaders.ContainsKey("Content-Type"))
                {
                    request.ContentType = requestSettings.requestHeaders["Content-Type"];
                }

                foreach (KeyValuePair<string, string> kvp in requestSettings.requestHeaders)
                {
                    if (kvp.Key.Equals("Accept") || kvp.Key.Equals("Content-Type"))
                    {
                        continue;
                    }
                    request.Headers.Add(kvp.Key, kvp.Value);
                }

                request.ContentLength = data.Length;

                // See https://msdn.microsoft.com/en-us/library/system.net.servicepoint.expect100continue(v=vs.110).aspx
                // A2 currently does not support this 100-Continue response for POST requets.
                request.ServicePoint.Expect100Continue = false;

    #if ASYNC
    
                Stream stream = await request.GetRequestStreamAsync();
                await stream.WriteAsync(data, 0, data.Length);
                stream.Close();
    
    #else
    
                Stream stream = request.GetRequestStream();
                stream.Write(data, 0, data.Length);
                stream.Close();
    
    #endif
    
            }
            else
            {
                foreach (KeyValuePair<string, string> kvp in requestSettings.requestHeaders)
                {
                    if (kvp.Key.Equals("Accept") || kvp.Key.Equals("Content-Type"))
                    {
                        continue;
                    }
                    request.Headers.Add(kvp.Key, kvp.Value);
                }
            }
    
    #if ASYNC
    
            WebResponse response = await request.GetResponseAsync();
    
    #else
    
            WebResponse response = request.GetResponse();
    
    #endif
    
            if (response.Headers.HasKeys())
            {
                foreach (string key in response.Headers.AllKeys)
                {
                    result.responseHeaders.Add(key, response.Headers.Get(key));
                }
            }

            result.responseCode = (int)(response as HttpWebResponse).StatusCode;

            using (StreamReader reader = new StreamReader(response.GetResponseStream()))
            {
    
    #if ASYNC
    
                if (result.hasBinaryResponse)
                {
                    result.binaryResponse = await StreamToByteArrayAsync(reader.BaseStream);
                }
                else
                {
                    result.body = await reader.ReadToEndAsync();
                }
    
    #else
    
                if (result.hasBinaryResponse)
                {
                    result.binaryResponse = StreamToByteArray(reader.BaseStream);
                }
                else
                {
                    result.body = reader.ReadToEnd();
                }
    
    #endif
    
            }
        }
        catch (Exception e)
        {
            result.responsMessage = e.Message;

            Log(Severity.Error, String.Format("{0} - {1}", e.GetType().Name, e.Message));
        }

        return result;
    }

    #if ASYNC
    /// <summary>
    /// Stream to byte array asynchronous.
    /// </summary>
    ///
    /// <param name="inputStream">  Stream to read data from. </param>
    ///
    /// <returns>
    /// A byte[].
    /// </returns>
    private async Task<byte[]> StreamToByteArrayAsync(Stream inputStream)
    {
        using (MemoryStream memStream = new MemoryStream())
        {
            await inputStream.CopyToAsync(memStream);

            return memStream.ToArray();
        }
    }
    #else 
    /// <summary>
    /// Stream to byte array.
    /// </summary>
    ///
    /// <param name="inputStream">  Stream to read data from. </param>
    ///
    /// <returns>
    /// A byte[].
    /// </returns>
    private byte[] StreamToByteArray(Stream inputStream)
    {
        byte[] bytes = new byte[4069];

        using (MemoryStream memoryStream = new MemoryStream())
        {
            int count;

            while ((count = inputStream.Read(bytes, 0, bytes.Length)) > 0)
            {
                memoryStream.Write(bytes, 0, count);
            }

            return memoryStream.ToArray();
        }
    }
    #endif

    #endregion IWebServiceRequest Members
  • In order to use this implementation in portable libraries, the ASYNC symbol has to be defined.
  • The symbol is necessary as the actual http request can only be invoked from a method marked async, but the interface cannot be defined using async as it would break compatibility with .Net 3.5 as used in Unity.

Clone this wiki locally