nirasan's tech blog

趣味や仕事の覚え書きです。Linux, Perl, PHP, Ruby, Javascript, Android, Cocos2d-x, Unity などに興味があります。

Rails で作った API サーバーに Unity でリクエストをする

はじめに

サーバー側の変更点

  • Doorkeeper でアクセストークンの更新に対応するため config/initializers/doorkeeper.rb に以下を追記する。
  use_refresh_token

スクリプト

  • 任意のオブジェクトに以下のスクリプトをアタッチして使用する
  • AuthorizedRequest メソッドに URL とリクエスト成功時のコールバック関数などを指定すると、自動で認証を行った上でリクエストを送信してくれる。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using MiniJSON;
using System;

public class APIManager : MonoBehaviour {

	public string TokenUrl = "http://127.0.0.1:3000/oauth/token";
	public string UsersUrl = "http://127.0.0.1:3000/users/100";

	public string client_id = "07461c1f68870ea090ed20af49d0680782950119213e2a1af9e1f653c688dca3";
	public string client_secret = "20dea83f022c9e2a0e6fbec1da0e9c0e20c40e1bf09d2e8db029b461653a88bd";

	public string username = "user1@example.com";
	public string password = "password";

	public string AccessToken;
	public long ExpiresIn;
	public long CreatedAt;
	public string RefreshToken;

	private static readonly DateTime UNIX_EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);	
	public DateTime ExpiresAt;

	void Start () {
		StartCoroutine(GetUsers());
	}

	/// <summary>
	/// 使用例としてユーザー情報を取得する
	/// </summary>
	public IEnumerator GetUsers () {

		yield return StartCoroutine(AuthorizedRequest(
			url: UsersUrl, 
			onSuccess: (WWW www) => {
				Debug.Log(www.text);
				Debug.Log(www.error);
				foreach (var kv in www.responseHeaders) {
					Debug.Log(string.Format("{0} : {1}", kv.Key, kv.Value));
				}
			}
		));
	}

	/// <summary>
	/// 認証が必要なページにHTTPリクエストを送信する
	/// アクセストークンが未取得であれば取得し
	/// アクセストークンが期限切れであれば更新する
	/// </summary>
	/// <param name="url">URL.</param>
	/// <param name="form">Form.</param>
	/// <param name="headers">Headers.</param>
	/// <param name="onSuccess">On success.</param>
	/// <param name="onError">On error.</param>
	public IEnumerator AuthorizedRequest (string url, WWWForm form = null, Dictionary<string, string> headers = null, System.Action<WWW> onSuccess = null, System.Action onError = null) {

		if (string.IsNullOrEmpty(AccessToken)) {

			var tokenGet = TokenGet();
			yield return StartCoroutine(tokenGet);

			if (Request.RequestIsError(tokenGet)) {
				yield break;
			}
		}

		if (ExpiresAt < DateTime.Now) {

			var tokenRefresh = TokenRefresh();
			yield return StartCoroutine(tokenRefresh);

			if (Request.RequestIsError(tokenRefresh)) {
				yield break;
			}
		}

		if (headers == null) {
			headers = new Dictionary<string, string>();
			headers.Add("Authorization", string.Format("Bearer {0}", AccessToken));
		}

		var request = new Request(url, form, headers, onSuccess, onError);
		yield return StartCoroutine(request.Send());
		yield return request;
	}

	/// <summary>
	/// アクセストークンを取得する
	/// </summary>
	IEnumerator TokenGet () {

		var form = new WWWForm();
		form.AddField("grant_type", "password");
		form.AddField("username", username);
		form.AddField("password", password);
		form.AddField("client_id", client_id);
		form.AddField("client_secret", client_secret);

		var request = new Request(url: TokenUrl, form: form, onSuccess: TokenUpdate);
		yield return StartCoroutine(request.Send());
		yield return request;
	}

	/// <summary>
	/// アクセストークンを更新する
	/// </summary>
	IEnumerator TokenRefresh () {

		var form = new WWWForm();
		form.AddField("grant_type", "refresh_token");
		form.AddField("refresh_token", RefreshToken);
		form.AddField("client_id", client_id);
		form.AddField("client_secret", client_secret);

		var request = new Request(url: TokenUrl, form: form, onSuccess: TokenUpdate);
		yield return StartCoroutine(request.Send());
		yield return request;
	}

	/// <summary>
	/// アクセストークン取得リクエストのレスポンスからアクセストークン情報を取得しインスタンス変数に格納する
	/// </summary>
	/// <param name="www">Www.</param>
	private void TokenUpdate (WWW www) {
		var jsonDict = Request.WWWToJson(www);

		AccessToken = (string)jsonDict["access_token"];
		ExpiresIn = (long)jsonDict["expires_in"];
		CreatedAt = (long)jsonDict["created_at"];
		RefreshToken = (string)jsonDict["refresh_token"];

		ExpiresAt = UNIX_EPOCH.AddSeconds(CreatedAt + ExpiresIn).ToLocalTime();
	}

	private class Request {

		public bool IsError { private set; get; }

		private float TIMEOUT = 5f;

		private string url;
		private byte[] formdata;
		private Dictionary<string, string> headers;
		private System.Action<WWW> onSuccess;
		private System.Action onError;

		public Request (string url, WWWForm form = null, Dictionary<string, string> headers = null, System.Action<WWW> onSuccess = null, System.Action onError = null) {
			this.url = url;
			this.formdata = form == null ? null : form.data;
			this.headers = headers;
			this.onSuccess = onSuccess == null ? (WWW www) => {} : onSuccess;
			this.onError = onError == null ? () => {} : onError;
		}

		public IEnumerator Send () {

			/*
			Debug.Log(url);
			if (headers != null) {
				foreach (var kv in headers) {
					Debug.Log(string.Format("{0} : {1}", kv.Key, kv.Value));
				}
			}
			*/

			var www = new WWW(url, formdata, headers);
			var endtime = Time.realtimeSinceStartup + TIMEOUT;

			while (!www.isDone || Time.realtimeSinceStartup < endtime) {
				yield return null;
			}

			if (WWWIsError(www)) {
				this.IsError = true;
				onError();
			} else {
				this.IsError = false;
				onSuccess(www);
			}

			/*
			Debug.Log(www.text);
			Debug.Log(www.error);
			foreach (var kv in www.responseHeaders) {
				Debug.Log(string.Format("{0} : {1}", kv.Key, kv.Value));
			}
			*/

			yield return this;
		}

		private bool WWWIsError (WWW www) {
			return !string.IsNullOrEmpty(www.error);
		}

		public static Dictionary<string, object> WWWToJson (WWW www) {
			return Json.Deserialize(www.text) as Dictionary<string, object>;
		}

		public static bool RequestIsError (IEnumerator request) {
			return ((Request)request.Current).IsError;
		}
	}
}