UnityWebRequestによるHTTP通信

こんにちは。最近はクライアントエンジニアとしてUnityを書いている向井です。

今回は、Unity5.4のリリースで正式リリースされる予定のUnityWebRequestについて紹介したいと思います。
ほぼ公式ドキュメントからの抜粋という形になりますが、日本語による情報も少ないため、ご紹介したいと思います。

UnityWebRequestとは

UnityWebRequestはUnityのWWWの代替クラスで、今日のHTTP通信の需要に対応することを主なゴールとしています。
Unity 5.2から実験的な機能として導入され、5.3ではモバイルプラットフォームに対応し、5.4では正式版としてリリースされる予定です。

UnityWebRequestWWWクラスと比べてPUTやDELETEなどの任意のメソッドを利用できるようになったことが大きな変更点だと思います。
これにより、 いわゆるRESTful APIにUnity標準で対応が可能となります。
サードパーティが提供するHTTP APIが、 RESTfulなAPIとして提供されるようになってきた今日では、 ありがたい対応かと思います。

またLow-Level API(LLAPI)が提供されたことにより、 送受信するデータをより柔軟に制御することができるようになりました。

私が所属するプロジェクトでも開発時機能ではありますが、 署名付きURL経由でのコンテンツのアップロードのために、 UnityWebRequestを導入しています。

UnityWebRequestの構成

UnityWebRequest は大きく分けて3つの要素で構成されます。

  • UploadHandler
    • サーバーへ送信するデータを扱うオブジェクト
  • DownloadHandler
    • サーバーから受信するデータを扱うオブジェクト
  • UnityWebRequest
    • 上記の2つのオブジェクトを管理し、HTTP通信フローの制御を司るオブジェクト

上記のオブジェクトの関係を図示すると以下のとおりです。

UnityWebRequestの構成

基本的な使い方

UnityWebRequestの基本的な使い方をWWWクラスと比較しながら説明します。

GET

まずGET通信について説明します。
WWWクラスで、URLであらわされるリソースを取得するGET通信は以下のように書けます。

using UnityEngine;
using System.Collections;

class MyBehaviour : public MonoBehaviour {
    void Start() {
        StartCoroutine(GetText());
    }

    IEnumerator GetText() {
        WWW request = new WWW("http://example.com");

        yield return request;

        if (!string.IsNullOrEmpty(request.error)) {
            Debug.Log(request.error)
        } else {
            // ステータスコードは, responseHeadersより参照する
            if (request.responseHeaders.ContainsKey("STATUS") &&
                    request.responseHeaders["STATUS"] == 200) {
                // UTF8文字列として取得する
                string text = request.text;

                // バイナリデータとして取得する
                byte[] results = request.bytes;
            }
        }
    }
}

UnityWebRequestでは、以下の様に書くことで同様な通信を行うことが出来ます。

// https://docs.unity3d.com/Manual/UnityWebRequest.html

using UnityEngine;
using System.Collections;
using UnityEngine.Experimental.Networking;
// 5.4以降ではこちら
// using UnityEngine.Networking;

class MyBehaviour : public MonoBehaviour {
    void Start() {
        StartCoroutine(GetText());
    }

    IEnumerator GetText() {
        UnityWebRequest request = UnityWebRequest.Get("http://example.com");
        // 下記でも可
        // UnityWebRequest request = new UnityWebRequest("http://example.com");
        // methodプロパティにメソッドを渡すことで任意のメソッドを利用できるようになった
        // request.method = UnityWebRequest.kHttpVerbGET;

        // リクエスト送信
        yield return request.Send();

        // 通信エラーチェック
        if (request.isError) {
            Debug.Log(request.error);
        } else {
            if (request.responseCode == 200) {
                // UTF8文字列として取得する
                string text = request.downloadHandler.text;

                // バイナリデータとして取得する
                byte[] results = request.downloadHandler.data;
            }
        }
    }
}

WWWクラスと比較すると、エラーの判定やHTTPステータスコードがプロパティから取得できるなど、よりAPIが洗礼されて利用しやすくなった印象です。

また、WWWクラスで存在する、レスポンスをTextureAssetBundleとして取得する機能も同様に用意されています。

// http://docs.unity3d.com/ScriptReference/Experimental.Networking.UnityWebRequest.GetTexture.html

// AssetBundleは下記を参考
// http://docs.unity3d.com/ScriptReference/Experimental.Networking.UnityWebRequest.GetAssetBundle.html
using UnityEngine;
using UnityEngine.Experimental.Networking;
using System.Collections;

class MyBehaviour : public MonoBehaviour {
    void Start() {
        StartCoroutine(GetTexture());
    }

    IEnumerator GetTexture() {
        using(UnityWebRequest www = UnityWebRequest.GetTexture("http://example.com/image.png")) {
            yield return www.Send();

            if (www.isError) {
                Debug.Log(www.error);
            } else {
                // DownloadHandlerを継承したDownloadHandlerTexture経由で取得できる
                Texture myTexture = ((DownloadHandlerTexture)www.downloadHandler).texture;
            }
        }
    }
}

JSONによる通信を行うなどの用途で、リクエストヘッダを設定するには SetRequestHeader メソッドを利用することが出来ます。

UnityWebRequest request = UnityWebRequest.Get("http://example.com/resource.json");
// SetRequestHeaderを介してリクエストヘッダを設定できる
request.SetRequestHeader("Content-Type", "application/json");
request.SetRequestHeader("Accepted", "application/json");

POST

次にPOST通信について説明します。

POST通信はWWWクラスと同様に、POSTのパラメータとしてWWWFormクラスのインスタンスを指定できます。

// https://docs.unity3d.com/ScriptReference/Experimental.Networking.UnityWebRequest.Post.html
using UnityEngine;
using UnityEngine.Experimental.Networking;
using System.Collections;

class MyBehavior : public MonoBehaviour {
    void Start() {
        StartCoroutine(Upload());
    }

    IEnumerator Upload() {
        WWWForm form = new WWWForm();
        form.AddField("myField", "myData");

        using(UnityWebRequest www = UnityWebRequest.Post("http://example.com/myform", form)) {
            yield return www.Send();

            if (www.isError) {
                Debug.Log(www.error);
            } else {
                Debug.Log("Form upload complete!");
            }
        }
    }
}

POST通信の場合は、リクエストヘッダのContent-Typeには自動的にapplication/x-www-form-urlencodedが指定されます。

GET・POST以外のメソッドによる通信

冒頭で述べたとおり、UnityWebRequestではGETやPOSTメソッド以外での通信を行うことができます。

GET・POST通信と同様に、UnityWebRequest配下のPutHeadDeleteメソッドを用いるか、
UnityWebRequestクラスのmethod変数にメソッド名を指定することで通信を行うことが出来ます。

// UnityWebRequestの静的メソッドを用いるか
UnityWebRequest www = UnityWebRequest.Put("http://example.com/myform")

// request.methodにメソッドを指定する
UnityWebRequest request = new UnityWebRequest("http://example.com");
request.method = UnityWebRequest.kHttpVerbPUT;

発展的な使い方 (LLAPI)

ここからは、LLAPIを用いた応用的な使い方について説明します。

上記の通り、UnityWebRequestUploadHandlerDownloadHanderを介してデータの送受信を行います。
このUploadHandlerとDownloadHandlerには、ユーザの用意したハンドラを設定できるため、送受信するデータをより柔軟に制御することが可能になりました。

UploadHandlerには、UploadHandlerRawというクラスが用意されており,
これを用いることでリクエストボディとそのコンテントタイプをユーザが直接指定することができます。

DownloadHandlerには、後述するDownloadHandlerScriptを継承したクラスを指定することで、ダウンロードの各イベントをハンドリングすることが出来ます。

DownloadHandlerScript

DownloadHanderでは、DownloadHandlerScriptを継承したクラスを設定することで、各イベントをハンドリングすることができます。

これにより、データの保存を独自で用意したバッファに書き込むことでメモリの動的な確保を避けたり(こちらの資料ではpreallocatedモードという名前で紹介されています)、ストリーミング再生や、データ受信後の処理(データ復号・伸張など)を独自に差し込むことが可能になります。

以下がDownloadHanderScriptのイベントになります。

  • protected void ReceiveContentLength(long contentLength);
    • 受信するコンテンツ長を受け取った時に呼ばれます
  • protected void OnContentComplete();
    • すべてのデータをサーバーからダウンロードしたときに呼ばれます
  • protected bool ReceiveData(byte[] data, long dataLength);
    • データをダウンロードしている時に毎フレームごとに呼ばれます

Unityのドキュメントでは受信したデータをロギングする例が紹介されています。

// https://docs.unity3d.com/Manual/UnityWebRequest.html
using UnityEngine;
using System.Collections;

public class LoggingDownloadHandler : DownloadHandlerScript {

    // Standard scripted download handler - will allocate memory on each ReceiveData callback
    public LoggingDownloadHandler(): base() {
    }

    // Pre-allocated scripted download handler
    // Will reuse the supplied byte array to deliver data.
    // Eliminates memory allocation.
    public LoggingDownloadHandler(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));
    }
}

まとめ

Unity 5.4のリリースも近づいてきているので、 UnityWebRequestについての構成とWWWとの使い方の比較、DownloadHandlerScriptについて軽く紹介しました。
WWWクラスと比べるとAPIが全体的に使いやすく、またDownloadHandler・UploadHandlerによってかゆいところに手の届くクラスだと感じます。

正式にリリースされたら積極的に利用していこうと思っています。

参考