UnityでHTTP通信してみる

# はじめに
**「UnityでHTTP通信してみる」**というテーマでTechTrainアドベントカレンダーを書いていきたいと思います！

こういった記事はすでにいくつも存在していますが、コードが掲載されていても「サーバに実際にリクエストを送ってレスポンスを受け取る」という処理を自分のローカルでも試してみるためにはサーバが要りますよね。

やっぱりプログラミングは手を動かしてみることも大事だと思うので、簡単なAPIサーバをGoで書いたプロジェクトを作ったので、もしよければ下記のリンクからcloneして、**ローカルで動かしながら実際にHTTP通信を実装していただければより深い学びにつながる**と思います！
[sample-serverのgithubリンク](https://github.com/shun-shun123/sample-server)

# HTTP/GET
まずは、HTTP/GETから実装していきます。GETリクエストの場合は、クライアントから特にデータを含めたりはせず、特定のエンドポイントに対してリクエストを送信して、返ってきた結果を処理する形が主だと思います。なので、今回も特にBodyにデータを含めたりはせず、JSON形式でレスポンスを受け取るという実装をしていきます。

```csharp
    private IEnumerator GET()
    {
        using (var req = UnityWebRequest.Get("http://localhost:80/user"))
        {
            yield return req.SendWebRequest();
            if (req.isNetworkError)
            {
                resultText.text = req.error;
            }
            else if (req.isHttpError)
            {
                resultText.text = req.error;
            }
            else
            {
                resultText.text = req.downloadHandler.text;
            }
        }
    }
```

コードの解説をしていきます。

でHTTPリクエストを作成します。

```csharp
UnityWebRequest.Get(string uri)
```

は、UnityWebRequestクラスで定義された静的メソッドで、URIで指定されたエンドポイントからデータを取得します。このメソッドを呼び出せば、**標準のDownloadHandlerBufferを返り値のUnityWebRequestに設定するため、明示的に設定する必要はありません。**
また、このメソッドは**UploadHandlerをアタッチしません。**これは、HTTP/GETの場合カスタムのヘッダーやデータを含めることがないからです。

UnityWebRequestを作成したら、次にリクエストを実際に送信します。送信している部分が下記のコードです。

```csharp
yield return req.SendWebRequest();
```

非同期処理になるので、コルーチンを使って、処理が完了するまで待機するようになっています。

次にエラー処理ですが、エラーの種類は大きく下記の二種類に分けられます。

- isNetworkError
- isHttpError

前者のisNetworkErrorはUntyWebRequestがシステムエラーを起こした際にtrueになるもので、
後者のisHttpErrorはレスポンスのステータスコードがエラーコードだった場合にtrueになります。

なお、isErrorという変数もありますが、こちらは**非推奨となっているため使用を避ける方がいい**と思います。

HTTP/GETリクエストを送信するフローとして、
1. UnityWebRequestを作成
2. SendWebRequest()を使ってリクエストを送信
3. isNetworkErrorとisHttpErrorでエラーハンドリング

の3ステップで実装できます。sample-serverの/userに対してGETリクエストを送信するとダミーのUserデータが返されるのでぜひ実行して試してみてください！

# HTTP/POST
次はbodyにデータを含めてHTTP/POSTリクエストを送信するところまで実装してみます。今回はJSON形式でデータを送信し、レスポンスを受け取る処理を解説します。

まず、HTTP通信の際のbodyにデータを含めるためにデータをシリアライズしてbyte[]に変換する必要があります。最初にそこの実装を解説していきます。

送信するデータや、レスポンスとして返ってくるデータの形式をモデルクラスとして定義する必要があります。この辺は、自分で書いてもいいのですが、JSONデータから自動的にモデルクラスを生成してくれるツールもあるので、面倒な場合や工数削減に[こう言ったツール](https://app.quicktype.io/)を使うのも良いかと思います。あと、モデルクラスの静的関数にSerialize/Deserialize用のメソッドを定義しておくとコードが書きやすいと思います。

```csharp
using System;
using UnityEngine;

[Serializable]
public class User
{
    public string id;
    public string name;
    public int age;

    public static User Deserialize(string json)
    {
        User user = JsonUtility.FromJson<User>(json);
        return user;
    }

    public static string Serialize(User model)
    {
        string json = JsonUtility.ToJson(model);
        return json;
    }
}

```

まずは、SerializableAttributeについて。オブジェクトをシリアル化する際にこの属性を付与していないと例外が発生します。シリアル化とは、オブジェクトをバイナリ形式に変換することだと思ってもらえば大丈夫です。モデルクラスにはまずこの属性をつける必要があります。

次に、モデルクラスを文字列のJSONに変換するにはUnityAPIに存在するJsonUtilityを使用します。
ただ、このJsonUtilityはジェネリックに対応していなかったり、エラー時にnullを返してきたりといろいろ扱いづらい部分もあります。もちろんサクッと使うには便利だったりするのですが、必要であればJSON.NETなどを利用することも考えてみるといいと思います。

ただ、以前JSON.NETを使ってiOSアプリを開発している際に、ジェネリクスのパースの際にリフレクションを使っているため、IL2CPPを使ってiOS向けなどにビルドする場合にビルドエラーが発生しました。その時の解決策としてジェネリクスを使わないという方法で解決したのですが、完成してからビルドしたときに使えないとならないように事前に確認しておくと良いかと思います。

文字列のJSONにパースできたら、それをbyte[]にシリアライズする必要があります。これはC#のSystem.Text空間に定義されているメソッドを利用してエンコードしていきます。

```csharp
Encoding.UTF8.GetBytes(json);
```

ここまでで、モデルクラスをbyte[]にシリアライズすることができます。送信したいデータをシリアライズできたら、UnityWebRequestを作成し、メソッドにPOSTを指定した上でヘッダーを登録するなどの処理を行なってリクエストを送信します。この辺りの実装は下記の通りになります。

```csharp
using (var req = new UnityWebRequest("http://localhost:80/user", UnityWebRequest.kHttpVerbPOST))
{
    req.uploadHandler = (UploadHandler)new UploadHandlerRaw(body);
    req.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
    req.SetRequestHeader("Content-Type", "application/json");
    yield return req.SendWebRequest();
}
```

# レスポンスのJSONをデシリアライズする
ここまでどうやってHTTPリクエストを送信するかについて解説してきましたが、最後にレスポンスのJSONをどうやってモデルクラスにデシリアライズするかについて解説していきます。

まず、レスポンスボディのデータを取得する方法ですが、downloadHandlerのtextプロパティにアクセスすることで、UTF8エンコーディングの文字列が取得できます。

この文字列をUnityのJsonUtilityで定義されたFromJsonメソッドを使用してデシリアライズしていきます。

```csharp
var response = User.Deserialize(req.downloadHandler.text);
Debug.Log($"ID:{response.id}\nName:{response.name}\nAge:{response.age}");
```

# おまけ
HTTPメソッドの文字列は全てUnityWebRequestのStatic変数として定義されています。ハードコーディングしてタイポしてるってことがないようにこちらを使うことをお勧めします。
[UnityWebRequestのスクリプトリファレンス](https://docs.unity3d.com/ja/2017.4/ScriptReference/Networking.UnityWebRequest.html)

レスポンスのステータスコードは、
UnityWebRequestのresponseCode変数にアクセスすることで確認できます。
ステータスコードに合わせて処理を変更したい場合などは、こちらを使用してください。

```csharp
Debug.Log($"StatusCode: {req.responseCode}");
```

# まとめ
今回は「UnityでHTTP通信する」方法についてまとめました！何よりもさらっと読んだ方は実際にサーバのコードも[こちら](https://github.com/shun-shun123/sample-server)用意しているので自分で実装してみて試してみてください！