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

この記事は2018年03月04日にqiitaに投稿した内容です。

環境

Unity2017.3.0p4 Windows10

概要

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

GoogleComputeEngineの設定、WinScpの設定

以下を参照して設定する サーバのIPアドレス(外部IP)をメモしておく GoogleComputeEngineのVMインスタンスにWinScpでログイン

プログラムのアップロード

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

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

#!/bin/bash
node bitpoint.js

 

クライアントプログラム

ApiLogin.Requestを呼び出し、AccessTokenを取得 ApiLeverageTwoWay.Requestを呼び出し、建てたサーバから価格情報を取得 返ってきたjsonは何らかのパーサーでクラスとかにする

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

    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

    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アドレスを入れる

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