【AWS】bitflyer注文詳細取得
【きっかけ】
過去の記事参照
buffalokusojima.hatenablog.com
1. 使用するサービス
(AWS)
- Lambda
- IAM
(外部サービス)
2. 概要
Lambdaを使用し、パラメータとして受け取った注文受付番号からbitflyerの注文詳細を取得します。
3. 実装
基本的にこれまでのコピペみたいなもんです。開発時間も数分もかからない程度になりました。
前回紹介したcloudformationを通してのデプロイをしているので、デプロイ自体もすぐ簡単に出来ます。
const ssm = new (require('aws-sdk/clients/ssm'))(); const request = require('request'); const crypto = require('crypto'); exports.handler = (event, context, callback) => { var body = JSON.parse(event.body); if(!body.id){ console.log("body empty"); callback(null, { statusCode: 400, body: JSON.stringify({message: "body empty"}), headers: {"Content-type": "application/json"} }); return; } getParameterFromSystemManager('bitflyer-keys',callback) .then(function(data){ const apikey = data.split(",")[0]; const sercretKey = data.split(",")[1]; var timestamp = Date.now().toString(); var method = 'GET'; var path = '/v1/me/getparentorder?&parent_order_acceptance_id='+body.id; var text = timestamp + method + path; var sign = crypto.createHmac('sha256', sercretKey).update(text).digest('hex'); var option = { url: 'https://api.bitflyer.jp' + path, method: method, headers: { 'ACCESS-KEY': apikey, 'ACCESS-TIMESTAMP': timestamp, 'ACCESS-SIGN': sign, 'Content-Type': 'application/json' } } return sendRequest(option, callback); }).then(function(data){ if(data.response.statusCode != 200){ console.error("Error:",data.response); callback(null, { statusCode: data.response.statusCode, body: JSON.stringify({message: data.response}), headers: {"Content-type": "application/json"} }); return; } data = JSON.parse(data.body); if(data.length == 0){ console.log('No data Found'); callback(null,{ statusCode: 200, body: JSON.stringify({message: 'No data Found'}), headers: {"Content-type": "application/json"} }); return; } console.log(data) callback(null, { statusCode: 200, body: JSON.stringify({data: data}), headers: {"Content-type": "application/json"} }); return; }); function getParameterFromSystemManager(apikey_name, callback) { return new Promise(function (resolve) { var apikey = process.env[apikey_name]; if(!apikey || typeof apikey == undefined){ // Fetches a parameter called REPO_NAME from SSM parameter store. // Requires a policy for SSM:GetParameter on the parameter being read. var params = { Name: apikey_name, /* required */ WithDecryption:true }; ssm.getParameter(params, function(err, apikey) { if (err){ console.error(err.stack); callback(null,{ statusCode: 500, body: JSON.stringify({message: err.toString()}), headers: {"Content-type": "application/json"} }); resolve(null); return; } process.env[apikey_name] = apikey.Parameter.Value; resolve(apikey.Parameter.Value); }); }else resolve(apikey); }); } function sendRequest(option, callback){ return new Promise(function (resolve) { request(option, function(error, response, body){ if(error){ console.error(error); callback(null,{ statusCode: 500, body: JSON.stringify({message: error.toString()}), headers: {"Content-type": "application/json"} }); resolve(null); } var data = {response, body} resolve(data); }); }); } };
4. おわりに
簡単過ぎて、手抜き感ありますが、次回はこれのフロント実装を説明します。
【AWS】bitflyer注文一覧表示
きっかけ】
過去の記事参照
buffalokusojima.hatenablog.com
1. 使用するサービス
HTML, Javascript, CSS
2. 概要
前回バックエンド側で注文一覧を取得したところまでやりました。今回はそれらのHTML上での表示をやります。
前回の記事は以下になります。
buffalokusojima.hatenablog.com
3.実装
①見た目の部分
読み込み時に注文取得関数を呼び出し、データを受け取った後に描画する仕組みです。
<head> </head> <title>タイトル</title> <style> table { font-family: arial, sans-serif; border-collapse: collapse; width: 100%; } td, th { border: 1px solid #dddddd; text-align: left; padding: 8px; } tr:nth-child(even) { background-color: #dddddd; } #orderDetail{ display: none; } .orderDetailContent{ height: 100px; } span{ padding: 5px; } </style> <body onload="getStatus()"> <div id="positionArea"></div> <div id="openOrderArea"></div>
②読み込み時の呼び出し関数
getStatus内にデータ取得関数や後々作成される関数が入ります。
データ取得関数などAPIを叩く系は共通してsendRequestを呼び出す仕組みになっており、
それぞれの関数で与える引数と、データ取得後に呼び出される関数を渡します。
今回説明するデータ取得関数であるgetOrdersはデータ取得後にコールバック関数である
makeOrdersによって内容が描画される仕組みです。
const URL = "API URL" function getStatus(){ getOrders(); } function getOrders(){ var data = { "method": "GET", "url": URL + "GetOrders" } //引数はそれぞれ、API呼び出しの引数、コールバック関数、コールバック関数への引数 sendRequest(data, makeOrderList, null); } function sendRequest(data, callback, calbackValue){ var request = new XMLHttpRequest(); request.open(data.method, data.url); request.setRequestHeader("Content-Type", "application/json"); request.onload = function(err){ console.log(err) if(request.readyState === 4){ if(request.status === 200){ console.log("success") var res = JSON.parse(request.responseText) console.log(res); callback(res.body, calbackValue); } else{ console.error(request.status, request.response); } } }; request.onerror = function(err){ console.log("err" + request.statusText); }; console.log("sending") var body = null; if(data.body){ body = { "body": JSON.stringify(data.body) } body = JSON.stringify(body); } console.log(body); request.send(body); }
③取得したデータの描画
取得したデータの描画を動的に要素を作成することで実現します。
データが無かった場合はないことを伝える描画をします。
function makeOrderList(data){ //取得したデータ data = JSON.parse(data); //描画対象となるdiv var targetDiv = document.getElementById("openOrderArea"); if(targetDiv.firstElementChild){ targetDiv.removeChild(targetDiv.childNodes[0]); } //データがなければメッセージを表示 if(!data.data){ var div = document.createElement('div'); div.innerText="No Open Order"; targetDiv.appendChild(div); return; } //テーブルのヘッダ作成 var table = document.createElement('table'); table.id = 'orderListTable'; var th = document.createElement('th'); th.innerText = 'date'; table.appendChild(th); th = document.createElement('th'); th.innerText = 'executed' table.appendChild(th); th = document.createElement('th'); th.innerText = 'type'; table.appendChild(th); th = document.createElement('th'); table.appendChild(th); //データの数分、テーブルの中身を作成 for(d of data.data){ var tr = document.createElement('tr'); var td = document.createElement('td'); //注文日付のフォーマット整え var format_str = 'YYYY-MM-DD hh:mm:ss'; var date; if(d.parent_order_date){ date = new Date(d.parent_order_date); }else if(d.child_order_date){ date = new Date(d.child_order_date); } format_str = format_str.replace(/YYYY/g, date.getFullYear()); format_str = format_str.replace(/MM/g, date.getMonth()+1); format_str = format_str.replace(/DD/g, date.getDate()); format_str = format_str.replace(/hh/g, date.getHours()); format_str = format_str.replace(/mm/g, date.getMinutes()); format_str = format_str.replace(/ss/g, date.getSeconds()); td.innerText=format_str; //クリック時の動作関数を宣言(今回は未実装) td.onclick=openOrderDetail; //各種カラムにidを振っておく、後々の関数実装時に役に立つ if(d.child_order_id){ td.id = "child_id"+d.child_order_id; }else{ td.id = "parent_id"+d.parent_order_acceptance_id; } tr.appendChild(td); //サイズの描画 var td = document.createElement('td'); if(d.executed_size && d.executed_size > 0){ td.innerText = d.executed_size; } td.onclick=openOrderDetail; if(d.child_order_id){ td.id = "child_id"+d.child_order_id; }else{ td.id = "parent_id"+d.parent_order_acceptance_id; } tr.appendChild(td); //注文タイプの描画 var td = document.createElement('td'); if(d.parent_order_type){ td.innerText=d.parent_order_type; }else if(d.child_order_type){ td.innerText=d.child_order_type; } td.onclick=openOrderDetail; if(d.child_order_id){ td.id = "child_id"+d.child_order_id; }else{ td.id = "parent_id"+d.parent_order_acceptance_id; } tr.appendChild(td); //キャンセルボタンの実装 var td = document.createElement('td'); var input = document.createElement('input'); input.type="button"; input.value="cancel"; if(d.child_order_id){ input.id = "child_id"+d.child_order_id; }else{ input.id = "parent_id"+d.parent_order_acceptance_id; } //キャンセル関数の呼び出し(今回は未実装) input.addEventListener('click', function(){ console.log("cancel:", this); cancelOrder(d.product_code, this.id); }) td.appendChild(input); tr.appendChild(td) if(d.child_order_id){ tr.id="child_id"+d.child_order_id; }else if(d.parent_order_acceptance_id){ tr.id = "parent_id"+d.parent_order_acceptance_id; } table.appendChild(tr); } targetDiv.appendChild(table) }
4. おわりに
以上で、注文一覧が表示されるようになりました。各種未実装の関数はまた次回以降実装していきます。
画面に表示してみるとこんな感じになります。
【AWS】bitflyer注文一覧取得
【きっかけ】
過去の記事参照
buffalokusojima.hatenablog.com
1. 使用するサービス
(AWS)
- Lambda
- IAM
(外部サービス)
2. 概要
Lambdaを使用して、bitflyerから注文一覧を取得します。通常注文と特殊注文で叩くAPIのパスが異なるので、2回叩き、両方とも取得します。
API Gatewayに紐付けてブラウザ形式でAPIを叩くことが出来ます。詳細は前回記事参照
buffalokusojima.hatenablog.com
3. 実装
以下、Lambda実装
const ssm = new (require('aws-sdk/clients/ssm'))(); const request = require('request'); const crypto = require('crypto'); exports.handler = (event, context, callback) => { var apikey; var sercretKey; var returnData; // bitflyerのAPIKeyを取得(パラメータストアに事前登録必要) getParameterFromSystemManager('bitflyer-keys',callback) .then(function(data){ apikey = data.split(",")[0]; sercretKey = data.split(",")[1]; var timestamp = Date.now().toString(); var method = 'GET'; var path = '/v1/me/getparentorders?product_code=FX_BTC_JPY&parent_order_state=ACTIVE'; var text = timestamp + method + path; var sign = crypto.createHmac('sha256', sercretKey).update(text).digest('hex'); var option = { url: 'https://api.bitflyer.jp' + path, method: method, headers: { 'ACCESS-KEY': apikey, 'ACCESS-TIMESTAMP': timestamp, 'ACCESS-SIGN': sign, 'Content-Type': 'application/json' } } // 特殊注文取得 return sendRequest(option, callback); }).then(function(data){ if(data.response.statusCode != 200){ console.error("Error:",data.response); callback(null, { statusCode: data.response.statusCode, body: JSON.stringify({message: data.response}), headers: {"Content-type": "application/json"} }); return; } data = JSON.parse(data.body); console.log(data) // global変数に特殊注文を保持 returnData = data; var timestamp = Date.now().toString(); var method = 'GET'; var path = '/v1/me/getchildorders?product_code=FX_BTC_JPY&child_order_state=ACTIVE'; var text = timestamp + method + path; var sign = crypto.createHmac('sha256', sercretKey).update(text).digest('hex'); var option = { url: 'https://api.bitflyer.jp' + path, method: method, headers: { 'ACCESS-KEY': apikey, 'ACCESS-TIMESTAMP': timestamp, 'ACCESS-SIGN': sign, 'Content-Type': 'application/json' } } // 通常注文取得 return sendRequest(option, callback); }).then(function(data){ if(data.response.statusCode != 200){ console.error("Error:",data.response); callback(null, { statusCode: data.response.statusCode, body: JSON.stringify({message: data.response}), headers: {"Content-type": "application/json"} }); return; } data = JSON.parse(data.body); console.log(data) // 特殊注文と通常注文を結合 returnData = returnData.concat(data) if(returnData.length == 0){ console.log('No data Found'); callback(null,{ statusCode: 200, body: JSON.stringify({message: 'No data Found'}), headers: {"Content-type": "application/json"} }); return; } callback(null, { statusCode: 200, body: JSON.stringify({data: returnData}), headers: {"Content-type": "application/json"} }); return; }); function getParameterFromSystemManager(apikey_name, callback) { return new Promise(function (resolve) { var apikey = process.env[apikey_name]; if(!apikey || typeof apikey == undefined){ // Fetches a parameter called REPO_NAME from SSM parameter store. // Requires a policy for SSM:GetParameter on the parameter being read. var params = { Name: apikey_name, /* required */ WithDecryption:true }; ssm.getParameter(params, function(err, apikey) { if (err){ console.error(err.stack); callback(null,{ statusCode: 500, body: JSON.stringify({message: err.toString()}), headers: {"Content-type": "application/json"} }); resolve(null); return; } process.env[apikey_name] = apikey.Parameter.Value; resolve(apikey.Parameter.Value); }); }else resolve(apikey); }); } function sendRequest(option, callback){ return new Promise(function (resolve) { request(option, function(error, response, body){ if(error){ console.error(error); callback(null,{ statusCode: 500, body: JSON.stringify({message: error.toString()}), headers: {"Content-type": "application/json"} }); resolve(null); } var data = {response, body} resolve(data); }); }); } };
SSMのパラメータストアへのAPIキー登録方法やその他実装方法の説明については過去記事参照
buffalokusojima.hatenablog.com
4. 以上で簡単にbitflyerから注文一覧を取得出来ます。次回はフロントエンド側の注文表示をやっていきたいと思います。
【AWS】bitflyer注文受付API Gateway実装(フロントエンド編)
【前回の記事】
1. 使用するサービス
前回作成したサービス(過去記事参照)
使用言語はnodeに合わせてjavascriptです。
2. 概要
前回作成したAPI Gatewayに対してHTTP通信を行うクライアント作成をします。ブラウザはChromeで、通信の実装はjavascriptで行います。
3. 実装
通常注文と特殊注文に分けて説明します。
3-1. 通常注文
①見た目の部分
見た目は簡単な部分だけ実装します。
<style> .orderArea{ display: inline-block; position: relative; height: 80px; } #nomalOrder{ display: flex; } .orderArea > .decideOrder{ position: absolute; bottom: 0; } </style> <body> <div class="displayOrder"> <div id="nomalOrder"> <div id="Market" class="orderArea"> <div class="orderTitle">Market</div> <div class="detailArea"> <span> <label for="MarketOrderNumber">Num: </label> <input type="text" id="MarketOrderNumber"/> </span> </div> <div class="decideOrder"> <input type="button" id="marketBuy" onClick="marketBuy()" value="BUY"/> <input type="button" id="marketSell" onClick="marketSell()" value="SELL"/> </div> </div> <div id="Limit" class="orderArea"> <div class="orderTitle">Limit</div> <div class="detailArea"> <span> <label for="LimitOrderNumber">Num: </label> <input type="text" id="LimitOrderNumber"/> </span> <br> <span> <label for="LimitOrderPrice">Price: </label> <input type="text" id="LimitOrderPrice"/> </span> </div> <div class="decideOrder"> <input type="button" id="limitBuy" onClick="limitBuy()" value="BUY"/> <input type="button" id="limitSell" onClick="limitSell()" value="SELL"/> </div> </div> </div> </div> </div>
②リクエスト送信メソッド
const URL = "API GatewayのURL" // ステージのURLを貼り付け // 共通で使用するリクエストメソッド function sendRequest(data){ var request = new XMLHttpRequest(); // 引数にメソッドとURLを持してあるので、それらを入力 request.open(data.method, data.url); request.setRequestHeader("Content-Type", "application/json"); // 送信後の処理。特に画面表示等はない request.onload = function(err){ console.log(err) if(request.readyState === 4){ if(request.status === 200){ console.log("success") var res = JSON.parse(request.responseText) console.log(res); } else{ console.error(request.status, request.response); } } }; // エラーの処理 request.onerror = function(err){ console.log("err" + request.statusText); }; console.log("sending") var body = null; // リクエストに付随するbodyを入力 if(data.body){ body = { "body": JSON.stringify(data.body) } body = JSON.stringify(body); } console.log(body); request.send(body); }
③Market注文メソッド
購入と売却のメソッドを実装します。どちらもタグから枚数を参照し、リクエスト送信メソッドに渡すbodyを
function marketBuy(){ // 枚数参照 var num = document.getElementById("MarketOrderNumber").value; if(!num){ console.log("element empty"); return; } // body作成 var data = { "coin_pair": "FX_BTC_JPY", "type": "MARKET", "size": num, "side": "BUY" } // リクエスト送信メソッド呼び出し sendRequest(data); } // marketBuyと同様 function marketSell(){ var num = document.getElementById("MarketOrderNumber").value; if(!num){ console.log("element empty"); return; } var data = { "coin_pair": "FX_BTC_JPY", "type": "MARKET", "size": num, "side": "SELL" } sendRequest(data); }
④Limit注文
Market注文とほど同様で、違いは参照する値に価格が追加されただけです。
function limitBuy(){ var num = document.getElementById("LimitOrderNumber").value; var price = document.getElementById("LimitOrderPrice").value; if(!num || !price){ console.log("element empty"); return; } var body = { "coin_pair": "FX_BTC_JPY", "type": "LIMIT", "size": num, "side": "BUY", "price": price } var data = { url: URL, method: 'POST', body: body } sendRequest(data); } function limitSell(){ var num = document.getElementById("LimitOrderNumber").value; var price = document.getElementById("LimitOrderPrice").value; if(!num || !price){ console.log("element empty"); return; } var body = { "coin_pair": "FX_BTC_JPY", "type": "LIMIT", "size": num, "side": "SELL", "price": price } var data = { url: URL, method: 'POST', body: body } sendRequest(data); }
3-3. 特殊注文実装
通常注文とほとんど同様です。
①見た目の部分
<style> .orderArea{ display: inline-block; position: relative; height: 200px; } #specialOrder{ display: flex; } .orderArea > .decideOrder{ position: absolute; bottom: 0; } </style> <body> <div class="displayOrder"> <div id="specialOrder"> <div id="ifd-buy" class="orderArea"> <div class="orderTitle">IFD BUY</div> <br> <div class="firstArea"> <div class="orderTitle">STOP BUY</div> <div class="detailArea"> <span> <label for="ifdFbuyNumber">Num: </label> <input type="text" id="ifdFbuyNumber"/> </span> <br> <span> <label for="ifdFbuyPrice">Price: </label> <input type="text" id="ifdFbuyPrice"/> </span> </div> </div> <br> <div class="secondArea"> <div class="orderTitle">STOP SELL</div> <div class="detailArea"> <span> <label for="ifdSsellNumber">Num: </label> <input type="text" id="ifdSsellNumber"/> </span> <br> <span> <label for="ifdSsellPrice">Price: </label> <input type="text" id="ifdSsellPrice"/> </span> </div> </div> <div class="decideOrder"> <input type="button" id="ifdBuy" onClick="ifdBuy()" value="SUBMIT"/> </div> </div> <div id="ifd-sell" class="orderArea"> <div class="orderTitle">IFD SELL</div> <br> <div class="firstArea"> <div class="orderTitle">STOP SELL</div> <div class="detailArea"> <span> <label for="ifdFsellNumber">Num: </label> <input type="text" id="ifdFsellNumber"/> </span> <br> <span> <label for="ifdFsellPrice">Price: </label> <input type="text" id="ifdFsellPrice"/> </span> </div> </div> <br> <div class="secondArea"> <div class="orderTitle">STOP BUY</div> <div class="detailArea"> <span> <label for="ifdSbuyNumber">Num: </label> <input type="text" id="ifdSbuyNumber"/> </span> <br> <span> <label for="ifdSbuyPrice">Price: </label> <input type="text" id="ifdSbuyPrice"/> </span> </div> </div> <div class="decideOrder"> <input type="button" id="ifdSell" onClick="ifdSell()" value="SUBMIT"/> </div> </div> <div id="stop" class="orderArea"> <div class="orderTitle">STOP</div> <br> <div class="detailArea"> <span> <label for="StopOrderNumber">Num: </label> <input type="text" id="StopOrderNumber"/> </span> <br> <span> <label for="StopOrderPrice">Price: </label> <input type="text" id="StopOrderPrice"/> </span> </div> <div class="decideOrder"> <input type="button" id="StopBuy" onClick="stopBuy()" value="BUY"/> <input type="button" id="StopSell" onClick="stopSell()" value="SELL"/> </div> </div> </div> </div>
②リクエスト送信メソッド
通常注文と同様です。
③ifdメソッド
BUYSELLとSELLBUYを記載します。これらもLimit注文とほど同様で、リクエスト送信メソッドに渡すbodyが異なるだけです。
function ifdSell(){ var sell_num = document.getElementById("ifdFsellNumber").value; var sell_price = document.getElementById("ifdFsellPrice").value; if(!sell_num || !sell_price){ console.log("element empty"); return; } var buy_num = document.getElementById("ifdSbuyNumber").value; var buy_price = document.getElementById("ifdSbuyPrice").value; if(!buy_num || !buy_price){ console.log("element empty"); return; } var parameters = [ { 'type': 'STOP', 'side': 'SELL', 'price': sell_price, 'size': sell_num }, { 'type': 'STOP', 'side': 'BUY', 'price': buy_price, 'size': buy_num } ] var body = { 'coin_pair': 'FX_BTC_JPY', 'parameters': parameters, 'order_method': 'IFD' }; var data = { url: URL, method: 'POST', body: body } sendRequest(data); } function ifdBuy(){ var buy_num = document.getElementById("ifdFbuyNumber").value; var buy_price = document.getElementById("ifdFbuyPrice").value; if(!buy_num || !buy_price){ console.log("element empty"); return; } var sell_num = document.getElementById("ifdSsellNumber").value; var sell_price = document.getElementById("ifdSsellPrice").value; if(!sell_num || !sell_price){ console.log("element empty"); return; } var parameters = [ { 'type': 'STOP', 'side': 'BUY', 'price': buy_price, 'size': buy_num }, { 'type': 'STOP', 'side': 'SELL', 'price': sell_price, 'size': sell_num } ] var body ={ 'coin_pair': 'FX_BTC_JPY', 'parameters': parameters, 'order_method': 'IFD' }; var data = { url: URL, method: 'POST', body: body } sendRequest(data); }
④STOP注文
説明は割愛します。
function stopBuy(){ var num = document.getElementById("StopOrderNumber").value; var price = document.getElementById("StopOrderPrice").value; if(!num || !price){ console.log("element empty"); return; } var parameters = [{ "type": "STOP", "size": num, "price": price, "side": "BUY" }]; var body ={ 'coin_pair': 'FX_BTC_JPY', 'parameters': parameters, 'order_method': 'SIMPLE' }; var data = { url: URL, method: 'POST', body: body } sendRequest(data); } function stopSell(){ var num = document.getElementById("StopOrderNumber").value; var price = document.getElementById("StopOrderPrice").value; if(!num || !price){ console.log("element empty"); return; } var parameters = [{ "type": "STOP", "size": num, "price": price, "side": "SELL" }]; var body ={ 'coin_pair': 'FX_BTC_JPY', 'parameters': parameters, 'order_method': 'SIMPLE' }; var data = { url: URL, method: 'POST', body: body } sendRequest(data); }
4. おわりに
これで自作のbitflyer注文サイトが完成しました。見た目としては簡素ですが以下の様になります。
実際に注文が通っていれば、bitflyerのご自身のアカウントの注文欄に追加されているはずです。
これでbitflyerのwebサーバに負荷がかかっている時でもAPIを叩くことで注文が比較的通りやすくなるかと思います。
※筆者は次の暴落時に検証してみようと思います。9月あたりに115万超えなければ60万あたりまで落ちると予想してます。暴落時にナンピンとか勝負する時はIFDでしっかり損切りできる注文を入れましょう。IFDOCOや自作注文も実装してみては面白いでしょう。
次回、注文は入れたけどキャンセル機能がまだなので、そこら辺を説明していきたいと思います。
【AWS】API Gateway cors template
【きっかけ】
過去の記事参照
buffalokusojima.hatenablog.com
1. 使用するサービス
(AWS)
- CloudFormation
2. 概要
前回、作成したAPI GatewayをCloudFormationでテンプレート化するところをやります。
豆知識的な感じなんで一瞬で終わります。API Gatewayの機能でSwaggerファイルをエクスポートするだけです。
AWS公式とかでCORSのtemplate設定は書いてありますが、面倒な時に一発でCORS設定したPAI Gatewayをデプロイするのに便利な技です。
前回の記事は以下参照
buffalokusojima.hatenablog.com
3. 実践
AWSコンソールからAPI Gatewayを選択し、作成したAPI Gatewayのメニューに移ります。メニューから【Stage】を選択します。
タブから【Export】を選択、真ん中の【Export as Swagger】から【YAML】を選択します。(JSONでもいいですが、YAMLで説明します。)
エクスポートされたYAML形式のファイルが表示されるのでpathから以下をコピーします。
paths: /TestMethod: post: responses: 200: description: "200 response" headers: Access-Control-Allow-Origin: type: "string" x-amazon-apigateway-integration: uri: "url" responses: default: statusCode: "200" responseParameters: method.response.header.Access-Control-Allow-Origin: "'*'" passthroughBehavior: "when_no_match" httpMethod: "POST" type: "aws" options: consumes: - "application/json" produces: - "application/json" responses: 200: description: "200 response" headers: Access-Control-Allow-Origin: type: "string" Access-Control-Allow-Methods: type: "string" Access-Control-Allow-Headers: type: "string" x-amazon-apigateway-integration: responses: default: statusCode: "200" responseParameters: method.response.header.Access-Control-Allow-Methods: "'OPTIONS,POST'" method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Origin: "'*'" responseTemplates: application/json: "{}\n" requestTemplates: application/json: "{\n \"statusCode\" : 200\n}\n" passthroughBehavior: "when_no_match" type: "mock"
ここからはtemplateを記述していきます。上で切り出した部分を後で貼りけます。
実際にコードで例にします。
Resources: // Lambdaの呼び出しパーミッション TestPermissiondev: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction Principal: apigateway.amazonaws.com FunctionName: !Ref TestFunction // 呼び出すLambdaの名前 SourceArn: !Sub - >- arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/Test // メソッドPOSTにしてます。GETはここをGETに。 - __Stage__: '*' __ApiId__: !Ref RestApi // API Gatewayのデプロイ ApiGatewayDeployment: Type: AWS::ApiGateway::Deployment Properties: RestApiId: Ref: RestApi // デプロイするAPIを設定 Description: "apigateway deployment" StageName: "prod" // デプロイ名設定 // API Gatewayの設定 RestApi: Type: AWS::ApiGateway::RestApi Properties: Body: info: version: '1.0' title: !Ref 'AWS::StackName' paths: // さっきコピーした部分貼り付け
これでYAMLの形式エラー等がなければCloud Formationでデプロイ出来るはずです。
私がデプロイした時はcloud9からcodecommitにコミットしてcodepipeline上でのcloudformationでの
デプロイですが、上手くデプロイまでいけました。
【AWS】bitflyer注文受付API Gateway実装
【きっかけ】
過去の記事参照
buffalokusojima.hatenablog.com
1. 使用するサービス
(AWS)
- Lambda
(外部サービス)
2. 概要
前回、作成したLambdaをAPI Gatewayに実装するところをやります。前回の記事は以下参照
buffalokusojima.hatenablog.com
3. 実装
3-1. API Gateway作成
AWSのコンソールメニューから【API Gateway】を選択し、【Create API】を選択します。
3-2. RestAPI作成
メニューからRestAPIを選択します。今回はVPCを使用していないので、通常のRestAPIを選択します。【Build】で次へいきます。
3-3. RestAPI設定
デフォルトの設定で大丈夫です。【API name】だけ入力必須です。
3-4. API Gatewayメニュー
これでAPI Gatewayのメニューが開くので、右のメニューから【Resources】を選択し、隣のフィールドの【Action】ボタンを押し、
アクションを選択します。アクションの中から【Create Method】を選択し、メソッドを作成します。
3-5. メソッド選択
【POST】を選択します。これは以前のLambdaでデータ受け取りをPOSTと想定している為です。
3-6. メソッド設定
メソッドの設定を行います。まずは主に呼び出しLambdaの指定です。【Integration Type】が【Lambda】になっていることを確認することと、【Use Lambda Proxy Integration】にチェックが入っていないことを確認します。今回の実装では統合をLambdaではなく、OPTIONSメソッドのMockで行うのでLambda Proxyは不要になります。これは後で記述します。
呼び出したいLambdaのリージョンを選択してからLambdaの名前を入力してもらえれば自動で補完されます。
3-7. CORS設定
次に【アクション】から【Enable CORS】を選択し、このメソッドのCORS設定を有効にします。
画面移動したらデフォルトのまま【Enable CORS and replace existing CORS headers】をクリックします。
次に出るポップアップも【Yes】を押すと自動で色々設定されます。【OPTIONS】メソッドが新たに作成されているのも確認できます。
3-8. API Gatewayのデプロイ
Resourceのデプロイを行います。アクションから【Deploy】を選択してください。
【Deploy Stage】は初回であれば【New Stage】とし、【Stage Name】を入力して【Deploy】を押してください。
すると、ステージメニューに遷移し、【Invoke URL】が表示されるので、curlなどで叩いてレスポンスが返ってくれば成功です。
3-9. 終わりに
簡単にAPI Gatewayの実装を説明しました。CORSはエラーがよく起こる設定なので、CORSエラーが出た時は正しいURLとパスなのか、レスポンスの設定は合っているかを確認してください。手動設定がめんどくさい人様にcloudformationのtemplate作成もいつかやります。
また、今回まともに疎通テストの部分をやっていないので、curlだけ出なく、クライアントも作成した記事を作ろうかと思います。
【AWS】bitflyer注文受付Lambda実装
【きっかけ】
過去の記事参照
buffalokusojima.hatenablog.com
1. 使用するサービス
(AWS)
- Lambda
- IAM
- SSM
- SQS
(外部サービス)
2.概要
HTTP通信で受けた注文をbitflyerAPIへ注文を送信するLambdaを実装します。注文までの流れとしては、クライアントから来たPOSTに対して、
中身を確認し、注文として有効であればSQSにメッセージとして載せて、別Lambdaに飛ばします。SQSをトリガーとして、メッセージを受け取ったLambdaからbitflyerAPIに対して注文を送信します。
※API Gatewayとの接続はまた別でやります。
3. 実装
3-1. 注文受付Lambda作成
以下コード入力
var AWS = require('aws-sdk'); var sqs = new AWS.SQS(); exports.handler = function(event, context, callback) { // 通常注文の場合のSQSURL const NORMAL_QUEUE_URL = 'normal queue url'; // 特殊注文の場合のSQSURL const SPECIAL_QUEUE_URL = 'special queue url'; const body = JSON.parse(event.body); if(!body){ console.log("body empty"); callback(null, { statusCode: 400, body: JSON.stringify({message: "body empty"}), headers: {"Content-type": "application/json"} }); return; } const COIN_PAIR = body.coin_pair; const PRICE = body.price; const SIDE = body.side; const SIZE = body.size; const TYPE = body.type; const PARAMETERS = body.parameters; const ORDER_METHOD = body.order_method; if(!TYPE && !PARAMETERS){ console.log("invalid order:",body); callback(null, { statusCode: 400, body: JSON.stringify({message: "invalid order"}), headers: {"Content-type": "application/json"} }); return; } if(COIN_PAIR != 'FX_BTC_JPY'){ console.log("invalid coin pair"); callback(null, { statusCode: 400, body: JSON.stringify({message: "invalid order"}), headers: {"Content-type": "application/json"} }); return; } var queueBody; var queueUrl; var id=new Date().getTime().toString(); var groupId; // 注文内容によって構成する注文を変更 if(!PARAMETERS){ if(!checkElement(body)){ console.log("Bad body:", body); callback(null, { statusCode: 400, body: JSON.stringify({message: "invalid order"}), headers: {"Content-type": "application/json"} }); return; } queueBody = { product_code: COIN_PAIR, child_order_type: TYPE, side: SIDE, price: PRICE, size: SIZE, } if(TYPE == 'STOP'){ queueBody.trigger_price = queueBody.price; queueBody.price = 0; } queueUrl = NORMAL_QUEUE_URL; groupId = "NORMAL_ORDER"; }else{ if(ORDER_METHOD != "SIMPLE" && ORDER_METHOD != "IFD"){ console.log("bad order method:"); callback(null, { statusCode: 400, body: JSON.stringify({message: "invalid order"}), headers: {"Content-type": "application/json"} }); return; } for(const parameter of PARAMETERS){ if(!checkElement(parameter)){ console.log("Bad body:", parameter); callback(null, { statusCode: 400, body: JSON.stringify({message: "invalid order"}), headers: {"Content-type": "application/json"} }); return; } parameter.product_code = COIN_PAIR parameter.condition_type = parameter.type; if(parameter.condition_type == 'STOP'){ parameter.trigger_price = parameter.price; parameter.price = 0; } } queueBody = { order_method: ORDER_METHOD, parameters: PARAMETERS } queueUrl = SPECIAL_QUEUE_URL; groupId = "SPECIAL_ORDER"; } // SQS message parameters var params = { MessageBody: JSON.stringify(queueBody), MessageGroupId: groupId, MessageDeduplicationId: id, QueueUrl: queueUrl }; console.log(params) // SQS送信 sqs.sendMessage(params, function(err, data) { var responseCode = 200; // response and status of HTTP endpoint var responseBody = { message: '' }; if (err) { console.log('error:', "failed to send message " + err); responseCode = 500; } else { console.log('data:', data.MessageId); responseBody.message = 'Sent to ' + queueUrl; responseBody.messageId = data.MessageId; } callback(null, { statusCode: responseCode, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(responseBody) }); }); function checkElement(element){ if(element.type == 'MARKET' || element.type == 'LIMIT' || element.type == 'STOP'){ if(element.side != 'BUY' && element.side != 'SELL'){ console.log("invalid side"); return false; } element.price = Number(element.price); element.size = Number(element.size); if(isNaN(element.price) && element.type != 'MARKET'){ console.log("number invalid:", element.price); return false; } if(isNaN(element.size)){ console.log("number invalid:", element.size); return false; } return true; } return false; } }
3-3. SQS受付Lambda実装
以下コード入力
const ssm = new (require('aws-sdk/clients/ssm'))(); const request = require('request'); const crypto = require('crypto'); exports.handler = (event, context, callback) => { // SQSからメッセージ取得 var body = JSON.parse(event.Records[0].body); if(!body){ console.log("Queue Empty") callback(null, { statusCode: 400, body: JSON.stringify({message: "Queue Empty"}), headers: {"Content-type": "application/json"} }); return; } console.log(body); if(!body.order_method || !body.parameters){ console.log("element invalid"); callback(null, { statusCode: 400, body: JSON.stringify({message: "element invalid"}), headers: {"Content-type": "application/json"} }); return; } console.log(body.parameters[0].price) // メッセージの内容をbitflyerAPIへ送信します getParameterFromSystemManager('bitflyer-keys', callback) .then(function(data){ const apikey = data.split(",")[0]; const sercretKey = data.split(",")[1]; var timestamp = Date.now().toString(); var method = 'POST'; var path = '/v1/me/sendparentorder'; body = JSON.stringify(body); console.log(body) var text = timestamp + method + path + body; var sign = crypto.createHmac('sha256', sercretKey).update(text).digest('hex'); var option = { url: 'https://api.bitflyer.com' + path, method: method, headers: { 'ACCESS-KEY': apikey, 'ACCESS-TIMESTAMP': timestamp, 'ACCESS-SIGN': sign, 'Content-Type': 'application/json' }, body: body } return sendRequest(option, callback); } ) .then(function(data){ var message; if(data.response.statusCode == 200){ message = data.body; }else{ message = data.response; } console.log(message); callback(null, { statusCode: data.response.statusCode, body: JSON.stringify({message: message}), headers: {"Content-type": "application/json"} }); return; }); function getParameterFromSystemManager(apikey_name, callback) { return new Promise(function (resolve) { var apikey = process.env[apikey_name]; if(!apikey || typeof apikey == undefined){ // Fetches a parameter called REPO_NAME from SSM parameter store. // Requires a policy for SSM:GetParameter on the parameter being read. var params = { Name: apikey_name, /* required */ WithDecryption:true }; ssm.getParameter(params, function(err, apikey) { if (err){ console.error(err.stack); callback(null,{ statusCode: 500, body: JSON.stringify({message: err.toString()}), headers: {"Content-type": "application/json"} }); resolve(null); return; } process.env[apikey_name] = apikey.Parameter.Value; resolve(apikey.Parameter.Value); }); }else resolve(apikey); }); } function sendLine(message, callback){ getParameterFromSystemManager('line-access-key', callback) .then(function(lineKey){ var option = { url: 'https://notify-api.line.me/api/notify', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + lineKey }, method: 'POST', form :{ message: message } }; return sendRequest(option,callback); }) .then(function(data){ callback(null, { statusCode: 200, body: data, headers: {"Content-type": "application/json"} }); }); } function sendRequest(option, callback){ return new Promise(function (resolve) { request(option, function(error, response, body){ if(error){ console.error(error); callback(null,{ statusCode: 500, body: JSON.stringify({message: error.toString()}), headers: {"Content-type": "application/json"} }); resolve(null); } var data = {response, body} resolve(data); }); }); } }
3-4. Lambdaにロール付与
3-2と内容は同じで、このLambdaにもロールを付与します。
3-5. SQSの作成
今回は順序と2重注文を防ぐために、FIFOキューを使用します。コンソールからSQSを検索して移動します。
【Create New Queue】を押して作成画面にいきます。
3-6. 終わりに
以上で、簡単なSQSを使用した注文機能は完成です。といってもこれだけじゃ使えないので、APIゲートウェイの設定もいつか書きます。