關於 web service, unity, blogger 等軟體工程筆記

Unity WebRequest

Edit icon 沒有留言
Unity

在 Unity 建立 Web Request,向指定網址資源送出 HTTP 要求 (Request),等待遠端伺服器回應 (Response),抓取回應得資料內容 (Content) 進行處理。

關於 HTTP 的資料傳輸結構,可以參考的「HTTP 淺談,使用 PostMan 操作」。

HTTP Request

向遠端伺服器建立 HTTP 連線要求存取資源,可以使用之前就存在 UnityEngine.WWW 來達成,但 WWW 僅能支援 GET/ POST 這兩種方法 (Methods),對於存取 REST API 所需要的 GET/ POST/ PUT/ DELETE 的四個常用方法來說,根本是沒有辦法使用的。

在 Mono Net framework 中,有實做 System.Net.HttpRequest,可以透過該類別進行 HTTP 要求處理。但該類別實做方式,並沒有辦法支援 Unity 上的任意的平台 (Platform) 上運作,例如在 WebGL。

WebRequest 使用 GET 取得指定網址資源的回應資料範例:

using System.Net;
using System.IO;

var request = WebRequest.Create("http://twsiyuan.com");
using (var response = request.GetResponse())
{
using (var stream = response.GetResponseStream())
{
using (var reader = new StreamReader(stream))
{
var html = reader.ReadToEnd();
Debug.Log(html);
}
}
}

在 Unity 5.2x 以後,Unity 釋出更強大處理 HTTP 資源存取的類別,UnityEngine.Experimental.Networking.UnityWebRequest,當時還是屬於實驗性質階段,但是在已經支援的平台上 (Editor, Standalone players and WebGL),功能已經很完整了。

在最近 Unity 5.4.1 發佈後,UnityWebRequest 已正式移至到 UnityEngine.Networking.UnityWebRequest,也支援更多平台 (iOS, Android, Windows Phone 8, Windows Store Apps, PS3 and Xbox 360)。除了存取二進位資料 (Binary data) 外,Unity 也有封裝讀取貼圖 (Textures),音源 (AudioClips),Prefabs 等等的處理函數,甚至可以自定義資源取得方式 (DownloadHandler)。

實在是相當方便啊,UnityWebRequest,不用嗎?

UnityWebRequest

UnityWebRequest 類別設計方式分成三塊:

  • UnityWebRequest:負責處理 HTTP 協定的傳輸處理
  • UploadHandler:負責將資源匯整 (Marshal) 成二進位資料傳給遠端伺服器
  • DownloadHandler:處理下載的二進位資料,以及最後將資料處理成應用層可用的資源。

使用 UnityWebRequest 的流程如下:

  • 建立 UnityWebRequest
  • 設定 UploadHandler 傳給遠端伺服器的內容(可以不傳)
  • 設定 DownloadHandler 處理遠端伺服器回傳的資料(可以不收,例如心跳封包 Acknowledgement, ACK)
  • 呼叫 Send() 等待回應
  • 檢查是否有錯誤 isError
  • 最後從 DownloadHandler 取得回應資料處理

更多細節請參考 Unity Manual

Examples

整理一些之後可能會使用到的範例,待之後可以直接複製修改使用。

使用 GET 取得回應的文字資料 (HTML):

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

IEnumerator DoRequest()
{
var request = UnityWebRequest.Get("http://twsiyuan.com");

yield return request.Send();

if (request.isError)
{
Debug.Log(request.error);
yield break;
}

var html = request.downloadHandler.text;
Debug.Log(html);
}

其中 request.Send() 可以改成以下,將目前進度 (Progress) 丟出給應用層處理,例如進度條功能的製作:

var asyncOp = request.Send();

while (!asyncOp.isDone)
{
// TODO: ProgressCallback(asyncOp.progress);
yield return null;
}

讀取貼圖 (Textures),使用 DownloadHandlerTexture:

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

IEnumerator DoRequest()
{
var request = UnityWebRequest.GetTexture("http://twsiyuan.com/images/profile.jpg");

yield return request.Send();

if (!request.isError)
{
var tex = (request.downloadHandler as DownloadHandlerTexture).texture;
var rect = new Rect(0, 0, tex.width, tex.height);
var pivot = new Vector2(0.5f, 0.5f);
var sprite = Sprite.Create(tex, rect, pivot);

// TODO: Sprite Rendering
image.sprite = sprite;
}
}

若不喜歡 as 的處理方式,可以改用 Unity Helper Wrapper:

var tex = DownloadHandlerTexture.GetContent(request);

除了讀取貼圖外,也可以讀取 AudioClip 以及 AssetBundle:

// Audio
var request = UnityWebRequest.GetAudioClip("http://example.com", AudioType.MPEG);
yield return request.Send();
var audioClip = DownloadHandlerAudioClip.GetContent(request);

// AssetBundle
var request = UnityWebRequest.GetAssetBundle("http://example.com");
yield return request.Send();
var assetBundle = DownloadHandlerAssetBundle.GetContent(request);

處理 JSON 資料,PUT 方法,使用 Raw Buffer:

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

IEnumerator DoRequest(object data)
{
var json = JsonUtility.ToJson(data);
var jsonRaw = System.Text.Encoding.UTF8.GetBytes(json);

var uploader = new UploadHandlerRaw(jsonRaw);
uploader.contentType = "application/json; charset=utf-8";

var downloader = new DownloadHandlerBuffer();

var request = new UnityWebRequest("http://example.com", UnityWebRequest.kHttpVerbPUT);
request.uploadHandler = uploader;
request.downloadHandler = downloader;
request.SetRequestHeader("Accept", "application/json");
request.SetRequestHeader("Accept-Charset", "utf-8");

yield return request.Send();

var rjson = System.Text.Encoding.UTF8.GetString(downloader.data);
// TODO: Response Json Unmarshal
}

抑或是整理一下以上的程式碼,繼承實作 DownloadHandler,來自訂自己的 Json Handler:

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

UploadHandlerRaw CreateJsonUploadHandler(object data)
{
if (data == null)
{
return null;
}

var json = JsonUtility.ToJson(data);
var jsonRaw = System.Text.Encoding.UTF8.GetBytes(json);

var uploader = new UploadHandlerRaw(jsonRaw);
uploader.contentType = "application/json; charset=utf-8";

return uploader;
}

IEnumerator DoRequest<TResult>(object putData)
{
var uploader = this.CreateJsonUploadHandler(putData);
var downloader = new DownloadHandlerJson<TResult>();

var request = new UnityWebRequest("http://example.com", UnityWebRequest.kHttpVerbPUT);
request.uploadHandler = uploader;
request.downloadHandler = downloader;
request.SetRequestHeader("Accept", "application/json");
request.SetRequestHeader("Accept-Charset", "utf-8");

yield return request.Send();

var jsonObject = DownloadHandlerJson<TResult>.GetContent(request);
// TODO: Json Object
}

其中 DownloadHandlerJson 的實作:

class DownloadHandlerJson<T> : DownloadHandlerScript
{
MemoryStream buffer = null;
string json;
T unmarhal = default(T);

public DownloadHandlerJson() : base()
{
}

public DownloadHandlerJson(byte[] preallocateBuffer) : base(preallocateBuffer)
{
}

public T jsonObject
{
get
{
return this.unmarhal;
}
}

protected override string GetText()
{
return this.json;
}

protected override byte[] GetData()
{
throw new System.NotSupportedException("Raw data access is not supported for json object");
}

protected override void ReceiveContentLength(int contentLength)
{
base.ReceiveContentLength(contentLength);
this.buffer = new MemoryStream(contentLength);
}

protected override bool ReceiveData(byte[] data, int dataLength)
{
this.buffer.Write(data, 0, dataLength);
return true;
}

protected override void CompleteContent()
{
base.CompleteContent();
this.json = System.Text.Encoding.UTF8.GetString(this.buffer.ToArray());
this.unmarhal = JsonUtility.FromJson<T>(this.json);
this.buffer.Dispose();
}

public static T GetContent(UnityWebRequest request)
{
return DownloadHandler.GetCheckedDownloader<DownloadHandlerJson<T>>(request).jsonObject;
}
}
  • Line3: 使用 System.IO.MemoryStream 來處理伺服器回傳資料的 Buffer
  • Line5: 沒有限定 Generic type T 是 strcut 或是 class,因此使用 default(T) 給與初始值
  • Line28-31: 這範例中不允許存取 Raw bytes
  • Line36: 當收到伺服器回應大小 (Content-Length) 後,建立對應大小的 Buffer (Note: 應該加入大小限制,避免記憶體要求失敗而炸掉)
  • Line39-43: 根據 preallocateBuffer 每次一小段資料讀取,收到資料後,寫到準備好的 Buffer 中
  • Line45-51: 伺服器資料已經全部收到,轉成 Json 文字,讀取成指定物件,釋放 Buffer

Reference

沒有留言: