Help us understand the problem. What is going on with this article?

UniRxでUnityWebRequestをラップしてみた

UniRxでUnityWebRequestをラップしてみた

注意:ぶっちゃけUnityちゃんもうasync/await使えるんだからそっちのほうがいいと思います!

食中毒で会社くんなって言われてたときに作ってみました。
Issueにはなってるけどマダカナーってとこですマダカナマダカナー

テストまだ不十分だけど一応公開しときます。
テストしたらgitとかにえいしときます。

未保証で好きに使ってください。

その前に普通にUnityWebRequestつかうのにも問題になりそうなバグなんだか仕様なんだかがあったんで共有。

UnityWebRequestのDownloadHandler.textが空(Empty)になる問題

これやってるときにリクエスト終了後のDownloadHandler.textが空になってどうしたもんかなーとなりました。
dataをみても中身入っているのでどうやらエンコードに失敗している模様。

これ一部のサイト「 http://google.com/ 」とかでだけおきます。
というかUTF8でエンコードできないとおきます。日本語は死んだ・・・
example.comは問題なく動きました。

で、対応方法ですが、自前でエンコードします。どうせ一行ですし。

var text = System.Text.Encoding.UTF8.GetString(request.downloadHandler.data);

なおエンコードはできてない模様・・・え・・・googleさん文字コードなんなの・・・?

使い方

ObservableWWWと一緒です。
クラス名違うだけ、あとheaderの引数からHashは亡きものにしました。
ただ、UnityWebRequestのほうが高機能なのでUnityWebRequestをObservableにする拡張メソッドを定義しています。

ToObservable,ToObservableBytes,ToObservableAssetBundleって感じです。
それ以上の拡張が欲しい場合はすぐできるんで拡張してみてください。

Fetchメソッドを拡張しやすいようにデータの通知部分だけ分離しています。

ソースコード

一切無保証です。参考までに。
あとコレだと多分(ライブラリの特性上)あんまり使いやすくないはずなのでさらに一枚自前の通信クラスか何か作ると良いと思います。

実装はほぼほぼObservableWWWに合わせています。
自分で考えるより偉い人に右ならえするほうが事故らないよね。(つまり大部分パクりました)

ObservableWebRequest
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UniRx;
using UnityEngine;
using UnityEngine.Networking;

#if !UniRxLibrary
using ObservableUnity = UniRx.Observable;
#endif

namespace UniRx.WebRequest
{
    public static class ObservableWebRequest
    {
        public static IObservable<UnityWebRequest> ToRequestObservable(this UnityWebRequest request, IProgress<float> progress = null)
        {
            return ObservableUnity.FromCoroutine<UnityWebRequest>((observer, cancellation) => Fetch(request, null, observer, progress, cancellation));
        }

        public static IObservable<string> ToObservable(this UnityWebRequest request, IProgress<float> progress = null)
        {
            return ObservableUnity.FromCoroutine<string>((observer, cancellation) => FetchText(request, null, observer, progress, cancellation));
        }

        public static IObservable<byte[]> ToBytesObservable(this UnityWebRequest request, IProgress<float> progress = null)
        {
            return ObservableUnity.FromCoroutine<byte[]>((observer, cancellation) => Fetch(request, null, observer, progress, cancellation));
        }

        public static IObservable<string> Get(string url, IDictionary<string,string> headers = null, IProgress<float> progress = null)
        {
            return
                ObservableUnity.FromCoroutine<string>(
                    (observer, cancellation) =>
                        FetchText(UnityWebRequest.Get(url), headers, observer, progress, cancellation));
        }

        public static IObservable<byte[]> GetAndGetBytes(string url, IDictionary<string, string> headers = null, IProgress<float> progress = null)
        {
            return ObservableUnity.FromCoroutine<byte[]>((observer, cancellation) => FetchBytes(UnityWebRequest.Get(url),headers, observer, progress, cancellation));
        }
        public static IObservable<UnityWebRequest> GetRequest(string url, IDictionary<string, string> headers = null, IProgress<float> progress = null)
        {
            return ObservableUnity.FromCoroutine<UnityWebRequest>((observer, cancellation) => Fetch(UnityWebRequest.Get(url), headers, observer, progress, cancellation));
        }

        public static IObservable<string> Post(string url, Dictionary<string, string> postData,
            IDictionary<string, string> headers = null, IProgress<float> progress = null)
        {
            return ObservableUnity.FromCoroutine<string>((observer, cancellation) => FetchText(UnityWebRequest.Post(url, postData), headers, observer, progress, cancellation)); 

        }

        public static IObservable<byte[]> PostAndGetBytes(string url, Dictionary<string, string> postData, IProgress<float> progress = null)
        {
            return ObservableUnity.FromCoroutine<byte[]>((observer, cancellation) => FetchBytes(UnityWebRequest.Post(url, postData),null, observer, progress, cancellation));
        }

        public static IObservable<byte[]> PostAndGetBytes(string url, Dictionary<string, string> postData, IDictionary<string, string> headers, IProgress<float> progress = null)
        {
            return ObservableUnity.FromCoroutine<byte[]>((observer, cancellation) => FetchBytes(UnityWebRequest.Post(url, postData),headers, observer, progress, cancellation));
        }

        public static IObservable<UnityWebRequest> PostRequest(string url, Dictionary<string, string> postData, IProgress<float> progress = null)
        {
            return ObservableUnity.FromCoroutine<UnityWebRequest>((observer, cancellation) => Fetch(UnityWebRequest.Post(url, postData), null, observer, progress, cancellation));
        }

        public static IObservable<UnityWebRequest> PostRequest(string url, Dictionary<string, string> postData, IDictionary<string, string> headers , IProgress<float> progress = null)
        {
            return ObservableUnity.FromCoroutine<UnityWebRequest>((observer, cancellation) => Fetch(UnityWebRequest.Post(url, postData), headers, observer, progress, cancellation));
        }


        public static IObservable<AssetBundle> LoadFromCacheOrDownload(string url, uint version, uint crc, IProgress<float> progress = null)
        {
            return ObservableUnity.FromCoroutine<AssetBundle>((observer, cancellation) => FetchAssetBundle(UnityWebRequest.GetAssetBundle(url, version, crc),null, observer, progress, cancellation));
        }


        static IEnumerator Fetch<T>(UnityWebRequest request, IDictionary<string, string> headers, IObserver<T> observer,
            IProgress<float> reportProgress, CancellationToken cancel)
        {

            if (headers != null)
            {
                foreach (var header in headers)
                {
                    request.SetRequestHeader(header.Key, header.Value);
                }

            }

            if (reportProgress != null)
            {
                var operation = request.Send();
                while (!operation.isDone && !cancel.IsCancellationRequested)
                {
                    try
                    {
                        reportProgress.Report(operation.progress);
                    }
                    catch (Exception ex)
                    {
                        observer.OnError(ex);
                        yield break;
                    }
                    yield return null;
                }
            }
            else
            {
                yield return request.Send();
            }



            if (cancel.IsCancellationRequested)
            {
                yield break;
            }

            if (reportProgress != null)
            {
                try
                {
                    reportProgress.Report(request.downloadProgress);
                }
                catch (Exception ex)
                {
                    observer.OnError(ex);
                    yield break;
                }
            }
        }







        static IEnumerator FetchRequest(UnityWebRequest request, IDictionary<string, string> headers, IObserver<UnityWebRequest> observer,
            IProgress<float> reportProgress, CancellationToken cancel)
        {
            using (request)
            {


                yield return Fetch(request, headers, observer, reportProgress, cancel);

                if (cancel.IsCancellationRequested)
                {
                    yield break;
                }

                if (!string.IsNullOrEmpty(request.error))
                {
                    observer.OnError(new UnityWebRequestErrorException(request));
                }
                else
                {
                    observer.OnNext(request);
                    observer.OnCompleted();
                }
            }
        }

        static IEnumerator FetchText(UnityWebRequest request, IDictionary<string, string> headers, IObserver<string> observer,
    IProgress<float> reportProgress, CancellationToken cancel)
        {
            using (request)
            {
                yield return Fetch(request, headers, observer, reportProgress, cancel);

                if (cancel.IsCancellationRequested)
                {
                    yield break;
                }

                if (!string.IsNullOrEmpty(request.error))
                {
                    observer.OnError(new UnityWebRequestErrorException(request));
                }
                else
                {
                    var text = System.Text.Encoding.UTF8.GetString(request.downloadHandler.data);
                    observer.OnNext(text);
                    observer.OnCompleted();
                }
            }
        }

        static IEnumerator FetchAssetBundle(UnityWebRequest request, IDictionary<string, string> headers, IObserver<AssetBundle> observer,
    IProgress<float> reportProgress, CancellationToken cancel)
        {
            using (request)
            {
                yield return Fetch(request, headers, observer, reportProgress, cancel);

                if (cancel.IsCancellationRequested)
                {
                    yield break;
                }

                if (!string.IsNullOrEmpty(request.error))
                {
                    observer.OnError(new UnityWebRequestErrorException(request));
                }
                else
                {
                    var handler = request.downloadHandler as DownloadHandlerAssetBundle;
                    var assetBundle = (handler != null) ? handler.assetBundle : null;

                    observer.OnNext(assetBundle);
                    observer.OnCompleted();
                }
            }
        }

        static IEnumerator FetchBytes(UnityWebRequest request, IDictionary<string, string> headers, IObserver<byte[]> observer,
    IProgress<float> reportProgress, CancellationToken cancel)
        {
            using (request)
            {
                yield return Fetch(request, headers, observer, reportProgress, cancel);

                if (cancel.IsCancellationRequested)
                {
                    yield break;
                }

                if (!string.IsNullOrEmpty(request.error))
                {
                    observer.OnError(new UnityWebRequestErrorException(request));
                }
                else
                {
                    observer.OnNext(request.downloadHandler.data);
                    observer.OnCompleted();
                }
            }
        }




    }

    public class UnityWebRequestErrorException : Exception
    {
        public string RawErrorMessage { get; private set; }
        public bool HasResponse { get; private set; }
        public string Text { get; private set; }
        public System.Net.HttpStatusCode StatusCode { get; private set; }
        public System.Collections.Generic.Dictionary<string, string> ResponseHeaders { get; private set; }
        public UnityWebRequest Request { get; private set; }

        // cache the text because if www was disposed, can't access it.
        public UnityWebRequestErrorException(UnityWebRequest request)
        {
            this.Request = request;
            this.RawErrorMessage = request.error;
            this.ResponseHeaders = request.GetResponseHeaders();
            this.HasResponse = false;

            StatusCode = (System.Net.HttpStatusCode)request.responseCode;


            if (request.downloadHandler != null)
            {
                Text = request.downloadHandler.text;
            }

            if (request.responseCode != 0)
            {
                this.HasResponse = true;
            }
        }

        public override string ToString()
        {
            var text = this.Text;
            if (string.IsNullOrEmpty(text))
            {
                return RawErrorMessage;
            }
            else
            {
                return RawErrorMessage + " " + text;
            }
        }
    }
}

sample
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UniRx.WebRequest;
using UnityEngine;
using UniRx;
using UnityEngine.Networking;

namespace Assets.Editor
{
    class UnityWebRequestSample: MonoBehaviour
    {

        void Start()
        {
            // Basic: Download from google.
            {
                ObservableWebRequest.Get("http://example.com/")
                    .Subscribe(
                        x => Debug.Log(x.Substring(0, 100)), // onSuccess
                        ex => Debug.LogException(ex)); // onError
            }

            // Linear Pattern with LINQ Query Expressions
            // download after google, start bing download
            {
                var query = from google in ObservableWebRequest.Get("http://google.com/")
                            from bing in ObservableWebRequest.Get("http://bing.com/")
                            select new { google, bing };

                var cancel = query.Subscribe(x => Debug.Log(x.google.Substring(0, 100) + ":" + x.bing.Substring(0, 100)));

                // Call Dispose is cancel downloading.
                cancel.Dispose();
            }

            // Observable.WhenAll is for parallel asynchronous operation
            // (It's like Observable.Zip but specialized for single async operations like Task.WhenAll of .NET 4)
            {
                var parallel = Observable.WhenAll(
                    ObservableWebRequest.Get("http://google.com/"),
                    ObservableWebRequest.Get("http://bing.com/"),
                    ObservableWebRequest.Get("http://unity3d.com/"));

                parallel.Subscribe(xs =>
                {
                    Debug.Log(xs[0].Substring(0, 100)); // google
                    Debug.Log(xs[1].Substring(0, 100)); // bing
                    Debug.Log(xs[2].Substring(0, 100)); // unity
                });
            }

            // with Progress
            {
                // notifier for progress
                var progressNotifier = new ScheduledNotifier<float>();
                progressNotifier.Subscribe(x => Debug.Log(x)); // write www.progress

                // pass notifier to WWW.Get/Post
                ObservableWebRequest.Get("http://google.com/", progress: progressNotifier).Subscribe();
            }

            // with Error
            {
                // If WWW has .error, ObservableWWW throws WWWErrorException to onError pipeline.
                // WWWErrorException has RawErrorMessage, HasResponse, StatusCode, ResponseHeaders
                ObservableWebRequest.Get("http://www.google.com/404")
                    .CatchIgnore((UnityWebRequestErrorException ex) =>
                    {
                        Debug.Log(ex.RawErrorMessage);
                        if (ex.HasResponse)
                        {
                            Debug.Log(ex.StatusCode);
                        }
                        foreach (var item in ex.ResponseHeaders)
                        {
                            Debug.Log(item.Key + ":" + item.Value);
                        }
                    })
                    .Subscribe();
            }

            {
                var uwq = UnityWebRequest.Get("http://google.com/");
                uwq.ToObservable().Subscribe(x =>
                Debug.Log(x.Substring(0, 100))
                );


            }
        }
    }
}


パクってるのでライセンス表記
https://github.com/neuecc/UniRx/blob/master/LICENSE

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした