NodeJsでBitpointの価格を取得してレスポンスを返す

#環境
Unity2013.3.0p4
Windows10

#概要
UnityからBitpointの価格情報のwebsocketのapiにアクセスしようとしたが、うまくいきませんでした。
仕方ないのでNodeJsからSockJs-Clientを使用して情報を取得、HttpServerを立て、HttpリクエストしてきたUnityに情報を返しています。

#GoogleComputeEngineの設定、WinScpの設定
以下を参照して設定する
サーバのIPアドレス（外部IP）をメモしておく
[GoogleComputeEngineのVMインスタンスにWinScpでログイン](https://qiita.com/fukaken5050/items/5a7b5be893276ba5a1b4)

#プログラムのアップロード
WinScpを用いて後述の「bitpoint.js」と「serverBitpoint.sh」をアップロードし、パーミッションで実行権限を付与する
　・WinScp上でファイルを選択し、プロパティから行う

#NodeJsのインストール
SSHでサーバにログインし、「Putty」のコマンドラインを開く
「sudo yum install -y nodejs」と入力

#opensslの更新
「sudo yum update openssl」と入力

#モジュールのダウンロード
js内で参照しているモジュールをダウンロードする

・「npm init」と入力
　色々聞かれるがそのままリターン
　package.jsonができる
・「npm install -save XXX」と入力
　-saveでpackage.jsonにモジュール情報が保存される
　XXXはモジュール名
　　Bitpoint.js内でrequireしているもの
　　「node Bitpoint.js」と入力してエラーが出るものが足りないもの
　　　request
　　　sockjs-client
　　　date-utils
　　　※他にもエラーと言われるものがあるかもしれない

#プログラムの実行
「nohup ./serverBitpoint.sh &」と入力

#プログラム
アクセストークンを取得しているがこれはダミー。
　USERNAMEとPASSWORDもダミー
　なぜか実行しないとwebsocketに通信できなかった（気のせいかもしれない）
bitpoint.js

~~~js
const Crypto = require("crypto");
const Request = require('request');
const SockJS = require('sockjs-client');
const Http = require('http');
const Url = require('url');
const Qs = require('querystring');
require('date-utils');

const USERNAME = "hogehoge@hoge.com";
const PASSWORD = "fugafuga";
var _server;

Main();
function Main()
{
	GetAccessToken();
	CreateHttpServer();
}

function GetAccessToken()
{
	process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

	var sha256 = Crypto.createHash('sha256');
	sha256.update(PASSWORD);
	var hash = sha256.digest('hex')

	Request.get(`https://public.bitpoint.co.jp/bpj-api/login?username=${USERNAME}&password=${hash}`, (error, response, body) =>
	{
		var accessToken = JSON.parse(body)['access_token'];
		console.log( 'access_token', accessToken );
	});
}

function CreateHttpServer()
{
	_server = Http.createServer();
	_server.on('request', function( req, res )
	{
		console.log("Method = ", req.method);
		console.log("STATUS: ", res.statusCode);
		console.log("Content-Type: ", res.getHeader("Content-Type") );

		var getParams = Url.parse( req.url, true );
		console.log( "Get:", getParams.query );

		if( req.method == 'POST' )
		{
			var body='';
			req.on( 'data', function( data )
			{
				body += data;
			});
			req.on( 'end', function()
			{
				var postParams = Qs.parse( body );
				console.log( "Post:", postParams );
				HttpResponse( req, res, getParams, postParams );
			});
		}
		else
			HttpResponse( req, res, getParams, null );
	});
	_server.listen(8080)
	console.log('Start Server');
}

function HttpResponse( req, res, getParams, postParams )
{
	var Responses =
	{
		"Default": function ()
		{
			res.writeHead(200, {'Content-Type': 'application/json'});
			var now = new Date().toFormat("YYYYMMDDHH24MISS");
			res.end( `{ "time":"${now}" }` );
		},
		"leverageTwoWay": function ()
		{
			var accessToken = ( getParams.query[ "access_token" ] ) ? getParams.query[ "access_token" ] : "";
			RequestLeverageTwoWay( accessToken, postParams, function( response )
			{
				res.writeHead(200, {'Content-Type': 'application/json'});
				res.end( response );
			});
		}
	}
	var uri = Url.parse(req.url).pathname;
	if (uri === "/")
	{
		Responses["Default"](); 
		return;
	}
	else if (uri === "/leverageTwoWay")
	{
		Responses["leverageTwoWay"]();
		return;
	}
}

function RequestLeverageTwoWay( accessToken, postParams, onResponse )
{
	var response = '{ "error":"" }';
	if( accessToken == "" )
	{
		response = '{ "error":"accessToken" }';
		console.log( response );
		onResponse( response );
		return;
	}
	if( postParams == null )
	{
		response = '{ "error":"postParams" }';
		console.log( response );
		onResponse( response );
		return;
	}
	
	var options = postParams;//{ 'server': '{"currencyCd1":"BTC","currencyCd2":"JPY"}' };
	const sock = new SockJS(`https://public.bitpoint.co.jp/bpj-api/leverageTwoWay?access_token=${accessToken}`, null, options );
	sock.onopen = function()
	{
		console.log( "Open:", sock.url );
	};

	sock.onmessage = function(e)
	{
		console.log( "Message:", e.data );
	
		var now = new Date().toFormat("YYYYMMDDHH24MISS");
		response = `{ "updatetime":"${now}", "data":${e.data} }`;
		sock.close();
	};

	sock.onclose = function()
	{
		console.log( "Close:", sock.url );
		onResponse( response );
	};
}

~~~

serverBitpoint.sh

~~~sh
#!/bin/bash
node bitpoint.js
~~~
　
#クライアントプログラム
ApiLogin.Requestを呼び出し、AccessTokenを取得
ApiLeverageTwoWay.Requestを呼び出し、建てたサーバから価格情報を取得
 返ってきたjsonは何らかのパーサーでクラスとかにする

・アクセストークンの取得
　「XXXX,YYYY」はBitpointのLogin時のメールアドレスとパスワード
Assets/ApiLogin.cs

~~~c#
	public class ApiLogin
	{
		public static Coroutine Request( ExchangeBitpoint exchange, UnityAction<string> onResponse )
		{
			var sha256 = SHA256.Create();
			var hash = sha256.ComputeHash( Encoding.ASCII.GetBytes( YYYY ) );
			var password = BitConverter.ToString( hash ).Replace("-", "").ToLower();

			var param = new Dictionary<string, string>();
			param[ "username" ] = XXXX;
			param[ "password" ] = password;

			var query = exchange.GenerateFormParameter( param );

			var api = "/login";
			return exchange.Request( RequestType.Get, api + '?' + query, null, onResponse );
		}

		public class Response
		{
			public string access_token { get; set; }
		}
	}

~~~

Assets/ApiLeverageTwoWay.cs

~~~c#
	public class ApiLeverageTwoWay
	{
		public static Coroutine Request( ExchangeBitpoint exchange, string accessToken, UnityAction<string> onResponse )
		{
			var query = "{ \"currencyCd1\":\"BTC\", \"currencyCd2\":\"JPY\" }";

			var api = "/leverageTwoWay";
			return exchange.Request( RequestType.RelayPost, api + "?access_token=" + accessToken, query, onResponse );
		}

		public class Response
		{
			public class Datum
			{
				public string currencyCd1 { get; set; }
				public string currencyCd2 { get; set; }
				public decimal seq { get; set; }
				public decimal buySellCls { get; set; }
				public string indicator { get; set; }
				public decimal price { get; set; }
				public decimal openPrice { get; set; }
				public decimal highPrice { get; set; }
				public decimal lowPrice { get; set; }
				public decimal preClosePrice { get; set; }
				public string priceDate { get; set; }
				public string openPriceDate { get; set; }
				public string highPriceDate { get; set; }
				public string lowPriceDate { get; set; }
				public string preClosePriceDate { get; set; }
				public decimal change { get; set; }
				public decimal changeRate { get; set; }
			}
			public string updatetime { get; set; }
			public string error { get; set; }
			public List<Datum> data { get; set; }
		}
	}
~~~

Assets/ExchangeBitpoint.cs
・「RequestType.RelayPost」でリクエストが呼ばれたときは自分が建てたサーバが呼ばれるようにしている
　XXXXにサーバのIpアドレスを入れる

~~~c#
using System;
using System.Collections;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;

public enum RequestType
{
	Get,
	Post,
	Delete,
	RelayPost,	//Bitpoint
}

public class ExchangeBitpoint : MonoBehaviour
{
	private RemoteCertificateValidationCallback _remoteCertificateValidationCallback = new RemoteCertificateValidationCallback( ( sender, certificate, chain, sslPolicyErrors ) => { return true; } );

	public string GenerateFormParameter( Dictionary<string, string > dictForm )
	{
		string formParam = "";
		var count = dictForm.Count;
		var index = 0;
		foreach( var pair in dictForm )
		{
			formParam += pair.Key + "=" + pair.Value;
			index++;
			if( index < count )
				formParam += "&";
		}
		return formParam;
	}

	public Coroutine Request( RequestType type, string path, object query, UnityAction<string> onResponse, int timeout = 0 )
	{
		return StartCoroutine( OpRequest( type, path, query as string, onResponse, timeout ) );
	}

	protected IEnumerator OpRequest( RequestType type, string path, string query, UnityAction<string> onResponse, int timeout )
	{
		var url = "";
		if( type != RequestType.RelayPost )
			url = "https://public.bitpoint.co.jp/bpj-api" + path;
		else
			url = "http://XXXX:8080" + path;

		var prevCertCallback = ServicePointManager.ServerCertificateValidationCallback;
		ServicePointManager.ServerCertificateValidationCallback = _remoteCertificateValidationCallback;
		var httpRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create( url );
		switch( type )
		{
			case RequestType.Get:
				httpRequest.Method = "GET";
				break;
			default:
				httpRequest.Method = "POST";
				httpRequest.ContentType = "application/json";
				byte[] bodyRaw = ( query != null ) ? Encoding.UTF8.GetBytes( query ) : null;
				httpRequest.ContentLength = bodyRaw.Length;
				var isRequest = false;
				httpRequest.BeginGetRequestStream( ( ar ) =>
				{
					System.IO.Stream reqStream = httpRequest.EndGetRequestStream( ar );
					//送信するデータを書き込む
					reqStream.Write( bodyRaw, 0, bodyRaw.Length );
					reqStream.Close();
					isRequest = true;
				}, null );
				while( isRequest == false )
					yield return null;
				break;
		}

		string text = null;
		bool isResponse = false;
		httpRequest.BeginGetResponse( ( ar ) =>
		{
			var response = (System.Net.HttpWebResponse)httpRequest.EndGetResponse( ar );
			var statusCode = (int)response.StatusCode;

			System.IO.Stream resStream = response.GetResponseStream();
			//受信して表示
			System.IO.StreamReader sr = new System.IO.StreamReader( resStream, System.Text.Encoding.UTF8 );
			text = sr.ReadToEnd();
			sr.Close();
		//	response.Close();
			isResponse = true;
		}, null );

		while( isResponse == false )
			yield return null;

		ServicePointManager.ServerCertificateValidationCallback = prevCertCallback;
		if( onResponse != null )
			onResponse( text );
		yield break;
	}
}
~~~

#参考
[DEVGRU](http://team-6.hatenablog.jp/entry/2017/08/22/231929)