UnityWebRequestについてちょっと調べてみた
この記事はUnity 2 Advent Calendar 2015のエントリです。
UnityWebRequest
UnityWebRequestとはWWWに変わる新しいHTTP通信用のクラスである。
名前空間にExperimental
が含まれている通り、まだ実験的な機能なので今後も仕様が変更されていく可能性がある事をご了承いただきたい。
それでも、WWWと比較して十分使いやすくなってはいるので普通に活用して良さそう。
使い方
例えば、以下のコードはローカルホストにアクセスして、レスポンスをログに吐く
using UnityEngine; using UnityEngine.Experimental.Networking; public class HTTPTest : MonoBehaviour { // Use this for initialization void Start () { StartCoroutine(HttpRequest()); } IEnumerator HttpRequest() { var request = UnityWebRequest.Get("http://localhost:4567"); yield return request.Send(); Debug.Log(request.responseCode.ToString() + ":" + request.downloadHandler.text); } }
コルーチンで、通信終わるまで待つという点は今までと同じ。
WWWクラスの場合コンストラクタ呼び出したタイミングで通信しに行くが、UnityWebRequestの場合はSend
を呼び出したタイミングで通信する。
なので通信する前にSetRequestHeader
で諸々のヘッダ設定ができるようになった。
WWWと比較した時の違いを幾つか挙げるとこんな感じ
- RESTに対応
- データとHTTPのハンドリング部分が分離されている
- DownloadHandlerScriptでロギングや加工が便利
RESTに対応
多分これが一番大きな変更点。
ついにPUT、DELETEメソッドが実装された・・・。
PUTは引数にstringとbyte[]しか渡せない。 POSTはDictionaryが使用できるのでキーバリューペアでサクッといけるがPUTは自分で整形する必要がある。ちなみに文字列をバイト配列に変換する場合下記で行ける。
System.Text.Encoding.UTF8.GetBytes("foo=get")
引数を空文字列にした場合、以下のエラーが出る。
ArgumentException: Cannot create a data handler without payload data
因みにPATCHはないので、必要があればインスタンスのmethod
プロパティに直打ちしよう
request.method = "PATCH";
また、現状POST以外のメソッドはContent-typeが空になっている模様。 なので、そのままパラメータ付きでPUTしてもサーバ側によってはうまく受け取ってくれない可能性が高いので以下の感じでヘッダに組み込むのがいい
request.SetRequestHeader("Content-type", "application/x-www-form-urlencoded");
ここは修正されていくと思う、多分。
データとHTTPのハンドリング部分が分離されている
- 新しいHTTP通信は大まかに三つのクラスで構成されている
- UploadHandler
- DownloadHandler
- UnityWebRequest
UploadHandlerはリクエストを飛ばす際のパラメータデータ、DownloadHandlerはレスポンスのデータを保持する小さなクラスになっている。
その二つをUnityWebRequestが所有している。
UnityWebRequestはURLやヘッダ情報、リダイレクト回数の設定などHTTP通信に関する処理を扱う。
WWWと違い、データそのものと通信がしっかり分離しており扱いやすい。
UploadHandlerとDownloadHandlerは外部から流し込むことができるからだ。
UploadHandler
を外から設定する場合はUploadHandlerRaw
クラスを使う。
var request = UnityWebRequest.Get("http://localhost:4567"); var upload = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes("foo=get")); request.uploadHandler = upload;
コンストラクタの引数がバイト配列なので、変換が必要。 上記で書いたが、PUTはbody部分を空文字列にするとエラーになるので「適当な文字列を入れて初期化」->「uploadHandlerに流し込む」 という流れになった。もっといいやり方ありそう。
DownloadHandler
クラスそのものは流し込むことはできるがあまり意味はない。
むしろ、後述するDownloadHandlerScript
の時に役に立つ。
DownloadHandlerScriptでロギングや加工が便利
DownloadHandlerはダウンロード時の幾つかのタイミングでコールバックされる
ReceiveData
データを読み取った際に呼ばれるコールバックCompleteContent
読み取り完了した時に呼ばれるコールバックReceiveContentLength
ヘッダーからデータの長さを受け取った時に呼ばれるコールバック
データを読み取った各所でメソッドを呼ぶようになっており、Unityではそれをユーザ側でカスタマイズできるようにDownloadHandlerScriptというクラスを提供しています。
サンプルコードから抜粋
using UnityEngine; using System.Collections; using UnityEngine.Experimental.Networking; public class MyDownloadHandler : DownloadHandlerScript { // Standard scripted download handler - will allocate memory on each ReceiveData callback public MyDownloadHandler(): base() { } // Pre-allocated scripted download handler // Will reuse the supplied byte array to deliver data. // Eliminates memory allocation. public MyDownloadHandler(byte[] buffer): base(buffer) { } // Required by DownloadHandler base class. Called when you address the 'bytes' property. protected override byte[] GetData() { return null; } // Called once per frame when data has been received from the network. protected override bool ReceiveData(byte[] data, int dataLength) { if(data == null || data.Length < 1) { Debug.Log("LoggingDownloadHandler :: ReceiveData - received a null/empty buffer"); return false; } Debug.Log(string.Format("LoggingDownloadHandler :: ReceiveData - received {0} bytes", dataLength)); return true; } // Called when all data has been received from the server and delivered via ReceiveData protected override void CompleteContent() { Debug.Log("LoggingDownloadHandler :: CompleteContent - DOWNLOAD COMPLETE!"); } // Called when a Content-Length header is received from the server. protected override void ReceiveContentLength(int contentLength) { Debug.Log(string.Format("LoggingDownloadHandler :: ReceiveContentLength - length {0}", contentLength)); } }
上記のコードは、コールバックの各所でログに吐くような処理をしています。 あとは、これをUnityWebRequestのDownloadHandlerに流し込むだけです。
var request = UnityWebRequest.Get("http://localhost:4567"); var download = new MyDownloadHandler(); request.downloadHandler = download;
これで、データ受信時にログに吐いたり、ゲーム用にレスポンスを加工したりなど痒いところに手が届くようになる。
まとめ
現状、content_typeの問題など、まだまだこちらでカバーしなければならない問題はありますが、WWWクラスと比較するとかなり改善されており、期待が持てるAPIっぽい
明日は @yaegakiさんのmruby on Unityです