ngtokuの日記

主に雑記帳です。SNSではngtokuのID取れなかったんで、別のIDでやってます。

備忘メモ (font-family)

スタイルシートで以下のようにしてたらWindows10のEdge, IEともにSegoe UIでなくてArialが使われているのに気がついて、css修正して確認してみたらブラウザの方でHelveticaをArialに読み替えてるようだ。
font-family: (略),"Helvetica Neue",Helvetica,"Segoe UI",SegoeUI,Arial,(略),sans-serif;
そういえばArialってそういう位置付けだったよね、と。


以下のように修正するとデザイナーの意図通りになったけど、「イントラ用途だしそのままでも良いかもね」とコメントして報告。
font-family: (略),"Helvetica Neue","Segoe UI",SegoeUI,Helvetica,Arial,(略),sans-serif;


それぞれバージョンは以下。
Microsoft Edge 44.17763.1.0
Internet Explorer 11.437.17763.0

Lambda経由でEC2にJSONを飛ばしてみる

LambdaからEC2で動いているServletJSON飛ばしたつもりが、受け取れてないなーと、ちょっと手こずったので備忘メモ。
原因はContent-Lengthセットしてなかった事でござる。

const http = require('http');

exports.handler = function(event, context) {
    const postData = JSON.stringify(event);
    const options = {
        protocol: 'http:',
        host: process.env.EC2_API_HOST,
        port: process.env.EC2_API_PORT,
        path: process.env.EC2_API_PATH,
        method: 'POST',
        headers: {
          "Content-type": "application/json",
          'Content-Length': encodeURIComponent(postData).replace(/%../g,"x").length
        }
    };
    const req = http.request(options, (res) => {
        var responseBody = '';
        res.setEncoding('utf8');
        res.on('data', (chunk) => {
            responseBody += chunk;
        });
        res.on('end', () => {
            context.succeed(JSON.parse(responseBody));
        });
    })

    req.setTimeout(1000);
    req.on('timeout', function() {
      console.log('request timed out');
      var responseBody = JSON.parse(postData);
      var index = 0;
      for (var cov in responseBody["coverage"]) {
          responseBody["coverage"][cov]["prem"] = 1000 * ++index;
      }
      responseBody["resultCode"] = "999";
      var errors=[];
      errors.push("EC2落としたんで、Lambdaでタイムアウトしました。テスト用に保険料は設定しておきました。");
      responseBody["errorCode"] = errors;
      context.succeed(responseBody);
    });
  
    req.on('error', (e) => {
        console.error(`problem with request: ${e.message}`);
    });

    req.write(postData);
    req.end();
}

EC2で動いているServletAPI Gatewayにリクエストを投げるhtmlはgithubに登録しておいたので、必要に応じて参照のこと。
GitHub - eternalvirtue/JacksonTest

Lambda経由でYOLPを呼んでみる(GET版)

そう言えばGETで直接呼ぶやり方やってないやんって気がついたので補足。
基本的にはここをなぞっているだけなので、ここ読めば良いよ。
API Gateway で AWS Lambda 関数の REST API を作成する - Amazon API Gateway


API Gatewayからリソースの作成を選択。
f:id:ngtoku:20190126232058p:plain


リソース名とリソースパスを以下のように設定。
今回は エリアコード/ジャンルコード というURLパスにするので acが先で、
f:id:ngtoku:20190126232247p:plain


次にgc
f:id:ngtoku:20190126232312p:plain


メソッドの作成でGETを選択。
f:id:ngtoku:20190126232428p:plain


Lambda関数は例によって同じやつ。
f:id:ngtoku:20190126232504p:plain


統合リクエストからマッピングテンプレートを選択し、content-typeにapplication/jsonを記載して
f:id:ngtoku:20190126232705p:plain


テンプレートエディタに以下のように記載。これでPOSTの時と同じようにLambdaにJSONが飛んでいく。
f:id:ngtoku:20190126232816p:plain


念のためテストで確認。
f:id:ngtoku:20190126233100p:plain


うまく動いたらAPIをデプロイして完成。
あとはこんな感じでデプロイしたAPIにリクエスト投げてみて、JSON返ってきてるなーと確認。
https://xxxxx.xxxxx-xx.xx-xxxx-x.amazonaws.com/xxxxxxx/13103/0110/

以下みたいにエリアコードだけでもレスポンスを得たければ、{ac}の直下にメソッドを足す必要があるよ。
https://xxxxx.xxxxx-xx.xx-xxxx-x.amazonaws.com/xxxxxxx/13103/

メソッド足さない状態でアクセスしたら「{"message":"Missing Authentication Token"}」だって。

めでたしめでたし。

Lambda経由でYOLPを呼んでみる(ちょっとだけ Nuxt.js static HTML版)

一個前のエントリ(↓)はNode.js上で動かしているが、静的HTMLにしてどこかのホストで動かす時の手順。
Lambda経由でYOLPを呼んでみる(ちょっとだけ Nuxt.js 版) - ngtokuの日記

まずnuxt_config.jsにrouterを足す。
本当はenvプロパティ(↓)を使ったり、
API: env プロパティ - Nuxt.js

doenv(↓)を使ったりするのが良いが、単純化のため今回はベタ打ちである。
GitHub - nuxt-community/dotenv-module: Loads your .env file into your application context

  router: {
    base: '/test/nuxt/', // https://xxxx.xxxx/test/nuxt/で動かしたい時
  },


これをしないと、静的出力されたhtmlでjavascriptのパスがこうなってしまう。

<script type="text/javascript" src="/_nuxt/xxxx.js">

試しにhtmlを一括置換で変更してみたり、htmlのDocumentRootに_nuxtディレクトリを作ってjavascriptを置いてみたりしたが、処理のどこかでエラーが起きるらしく、最終的に「This page could not be found」となってしまった。


それからgenerateすると、distの下に作成される。

$ yarn run generate
 INFO  Generating pages

✔ success Generated /
✨  Done in 16.92s.


最後に、dist以下にあるものをホストにアップロードすると動く。
今回の設定だと以下の様になる。xxxx.xxxxは自ホストに置き換える事。
https://xxxx.xxxx/test/nuxt/


Node.js上で動かす時は、nuxt_config.jsを変更前の状態に戻すか、 base: '/', とするか(envやdoenvで切り替えるのが良い)、いっそのことhttp://localhost:3000/test/nuxt/ で検証するか、お好みで選択。

Lambda経由でYOLPを呼んでみる(ちょっとだけ Nuxt.js 版)

先ほどのエントリに引き続き、リクエスト発信HTMLだけお試しでNuxt.js使ってみた。
React.jsベースのNext.jsでなくて、Vue.jsベースのNuxt.jsね。

二つ前のエントリ(↓)の "6.リクエスト発信ページ作成" だけ置き換える感じ。
Lambda経由でYOLPを呼んでみる - ngtokuの日記


Node.jsは入っている前提で。
inputやbuttonはelement-ui使ってるので、そのまま使うならカスタムUIはelement-uiで、
その他はお好みで。

$yarn create nuxt-app nuxt-aws-gateway
yarn create v1.6.0
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
warning Your current version of Yarn is out of date. The latest version is "1.12.3", while you're on "1.6.0".
info To upgrade, run the following command:
$ curl --compressed -o- -L https://yarnpkg.com/install.sh | bash
success Installed "create-nuxt-app@2.1.1" with binaries:
      - create-nuxt-app
[#####################################################################] 306/306> Generating Nuxt.js project in /Users/xxx/xxx/nuxt-aws-gateway
? Project name nuxt-aws-gateway
? Project description AWS API Gateway Test
? Use a custom server framework none
? Use a custom UI framework element-ui
? Choose rendering mode Single Page App
? Use axios module yes
? Use eslint no
? Use prettier no
? Author name ngtoku
? Choose a package manager yarn


まずは nuxt-aws-gateway/pages/index.vue を編集。
実作業はデフォルトで作成されるレイアウトの下にロジックを足したけど、以下は必要な部分のみ抜粋してある。

前のエントリと同じく、APIのURLは各自で作成したAPIの物を入力。

<template>
  <section class="container">
    <div>
      <h4>データ件数は最大10件に絞ってあります。</h4><br />
      <form id="form" method="post" accept-charset="utf-8" return false>
        <p>
            <label >住所コード(JIS 0402)。先頭5桁</label><br>
            <el-input  id="ac" name="ac"  placeholder="住所コード(JIS 0402)" v-model="input_ac" size="small" class="el-input-style"></el-input>
            <a href="http://www.soumu.go.jp/denshijiti/code.html" target="_blank">コード値参照先</a>
        </p>
        <p>
            <label>ジャンルコード</label><br>
            <el-input  id="gc" name="gc"  placeholder="ジャンルコード" v-model="input_gc" size="small" class="el-input-style"></el-input>
            <a href="https://developer.yahoo.co.jp/webapi/map/openlocalplatform/genre.html" target="_blank">コード値参照先</a>
        </p>
      <br />
      <el-button size="medium" d="searchButton" @click="getYolpData" :disabled="sendingRequest">検索</el-button>
      </form>
      <br/>
      <div v-if="resData !== null" >
        <div id="dataCount"> {{ resData.ResultInfo.Count }} 件ヒットしました。</div>
        <ul id="result" class="yolp-results">
           <li v-for="item in resData.Feature">
            {{ item.Name }}
          </li>
        </ul>
      </div>
      <div id="responseData"></div>
    </div>
  </section>
</template>

<script>
import axios from 'axios'

export default {
  components: {
  },
  data() {
    return {
      input_ac: '13103',
      input_gc: '0110',
      sendingRequest: false
    }
  },
  computed: {
    resData () { return this.$store.state.resData },
    sendingRequest: function () {
      	return this.sendingRequest;          
      }
  },
  methods: {
    async getYolpData() {
        this.sendingRequest = true;
        this.$store.commit('set', null) // データエリアをクリア
        const response = await axios.post('APIのURLは各自で作成したAPIの物を入力', {
            ac: this.input_ac,
            gc: this.input_gc
        }).then((res) => {
          //console.log(res);
          const resData = res.data;
          //console.log(resData);
          this.$store.commit('set', resData); // レスポンスをセット
        })
        .catch((e) => {
            callback({ statusCode: 500, message: 'サーバーエラーです' })
        })
        .finally(() => {
             this.sendingRequest = false;
        });
    },
  }
}
</script>

<style>

.container {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  // align-items: center;
  text-align: center;
}

.el-input-style {
    width: 180px;   
}

.yolp-results {
    text-align: left;   
}
</style>


YOLPから取得したデータはstoreに格納しているので、store/index.js を作成して以下記載。

export const state = () => ({
  resData: null
})

export const mutations = {
  set (state, resData) {
    state.resData = resData;
  }
}


あとは起動して、動作確認

$yarn run dev
yarn run v1.6.0
$ nuxt
 INFO  Building project
✔ success Builder initialized
✔ success Nuxt files generated
 READY  Listening on http://localhost:3000

Nuxt.jsの理解がまだまだ足りてないなーと反省中。

Lambda経由でYOLPを呼んでみる(ちょっとだけ React.js CDN版)

リクエスト発信HTMLだけお試しでReact.js使ってみた。
一個前のエントリ(↓)の "6.リクエスト発信ページ作成" だけ置き換える感じ。
Lambda経由でYOLPを呼んでみる - ngtokuの日記

前のエントリと同じく、APIのURLは各自で作成したAPIの物を入力。

<!DOCTYPE html>
<html lang="ja">
<head>
    <script src="https://unpkg.com/react@0.13.3/dist/react.js"></script>
    <script src="https://unpkg.com/react@0.13.3/dist/JSXTransformer.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <meta charset="UTF-8">
    <title>YOLP via Lambda on API Gateway </title>
</head>
<body>
    <div id="app"></div>
    <script type="text/jsx">
     var InitialLayout = React.createClass({
        getInitialState: function() {
            // 初期値
            return {
                ac: "13103",
                gc: "0110",
                waitingResponse: false
            }
        },
        // リクエスト送信
        _create: function () {
            $('#result').empty();
            $('#dataCount').empty();
            // 各フィールドから値を取得してJSONデータを作成
            var data = {
                ac: $("#ac").val(),
                gc: $("#gc").val()
            };
            return  $.ajax({
                            url:'APIのURLは各自で作成したAPIの物を入力',
                            type:'POST',
                            data:JSON.stringify(data),
                            contentType: 'application/json', 
                            dataType: "json", 
                            beforeSend: function () {$('#responseData').html("処理中...");}.bind(this)
            })
        },
        // SUBMIT
        _onSubmit: function (e) {
            this.setState({waitingResponse: true});
            e.preventDefault();
            var xhr = this._create();
            xhr.done(this._onSuccess).fail(this._onError).always(); 
            // 本当はalwaysでthis.setState({waitingResponse: false}); したかったが、レスポンスを待たずに実行されてしまうので断念。
        },
        // リクエストOK
        _onSuccess: function (data) {
            $('#dataCount').html(data.ResultInfo.Count + "件ヒットしました。");
            $('#responseData').empty();
            // レスポンスデータ全体を見たい場合はコメントを外す
            // $('#responseData').html(JSON.stringify(data));
            $('#result').empty();
            $(data.Feature).each(function() {
                // console.log("succedss; " +  this.Name);
                $('#result').append($('<li></li>').append($('<div></div>').text(this.Name)));
            });
            this.setState({waitingResponse: false}); // _onSubmitのコメント参照
        },
        // リクエストNG
        _onError: function (data) {
            $('#dataCount').empty();
            $('#responseData').empty();
            $('responseData').html(data);
            this.setState({waitingResponse: false}); // _onSubmitのコメント参照
        },
        //  inputの値変更時の処理。実際に動く奴ならここでバリデーションや必須項目チェックが入る
        _onChange: function (e) {
            switch (event.target.name) {
                case 'ac':
                    // バリデーションや必須チェック
                    break;
                case 'gc':
                    // バリデーションや必須チェック
                    break;
            }
            var state = {};
            state[e.target.name] =  $.trim(e.target.value);
            this.setState(state);
        },
       render: function() {
         return (
            <div>
            <h4>データ件数は最大10件に絞ってあります。</h4>
            <form id="form" method="post" acceptCharset="utf-8" onSubmit={this._onSubmit}>
                <p>
                    <label htmlFor="ac">住所コード(JIS 0402)。先頭5桁</label><br />
                    <input className="input is-large" type="text" id="ac" name="ac" value={this.state.ac} onChange={this._onChange} />
                    <a href="http://www.soumu.go.jp/denshijiti/code.html" target="_blank">コード値参照先</a>
                </p>
                <p>
                    <label htmlFor="gc">ジャンルコード</label><br />
                     <input className="input is-large" type="text" id="gc" name="gc" value={this.state.gc} onChange={this._onChange} />
                    <a href="https://developer.yahoo.co.jp/webapi/map/openlocalplatform/genre.html" target="_blank">コード値参照先</a>
                </p>
                <button id="searchButton" type="submit" disabled={this.state.waitingResponse}>検索</button>
            </form>
            <div id="dataCount"></div>
            <ul id="result"></ul>
            <div id="responseData"></div>
            </div>
         );
       }
     });
     var m = React.render(<InitialLayout />, document.getElementById('app'));
    </script>
  </body>
</html>

ん〜、動くけどなんちゃってな感じに。

Lambda経由でYOLPを呼んでみる

来週Lambda関係のミーティングがあるので、お試しで。
長い関数になるとnpmでアップロードする事になるが、お試しの短い関数なのでGUIで済ませる。
厳密にはステージングやバージョニングを考慮する必要があるが、お試しなので気にしない。


イメージはこんな感じ
f:id:ngtoku:20181112093324p:plain


2.Lambda関数作成

AWS Lambdaのダッシュボードから「関数の作成」ボタンを押下。
f:id:ngtoku:20181110195510p:plain



"一から作成"のまま、名前は適当に、ロールは既存のものが有るならそれを、無ければ画像を参考にロール名も適当に入れる。
f:id:ngtoku:20181110195652p:plain
その後「関数の作成」ボタンを押下して作成。



関数の中身を実装。内容は以下。

const https = require('https');

exports.handler = function(event, context) {
    var ac = event.ac;
    var gc = event.gc;
    const options = {
      protocol: 'https:',
      host: 'map.yahooapis.jp',
      path: '/search/local/V1/localSearch?detail=full&results=10&start=1&output=json&appid=' + process.env.YOLP_APPID + '&ac=' + ac + '&gc=' + gc,
      method: 'GET',
    };
    const req = https.request(options, (res) => {
        var body = '';
        res.setEncoding('utf8');
        res.on('data', (chunk) => {
            body += chunk;
        });
        res.on('end', () => {
            context.succeed(JSON.parse(body));
        });
    })

    req.on('error', (e) => {
        console.error(`problem with request: ${e.message}`);
    });

    req.end();
}

特に説明は不要だと思うけど、process.env.YOLP_APPIDは画面下部にある環境変数で、YOLPのパラメータは以下参照。
YOLP(地図):Yahoo!ローカルサーチAPI - Yahoo!デベロッパーネットワーク


f:id:ngtoku:20181110203236p:plain

作成したら画面右上の「保存」ボタンを押下。



3.テストデータ作成

「保存」ボタンの左二つ目に有る「テストイベントの選択」から「テストイベントの設定」を選択して、パラメータacとgcに適当な値を入れる。
acは以下参照。
総務省|電子自治体|全国地方公共団体コード
gcは以下参照。
YOLP(地図):YOLP業種コード - Yahoo!デベロッパーネットワーク
以下サンプル。
f:id:ngtoku:20181110201109p:plain



4.テスト実行

「保存」ボタンの左に有る「テスト」を実行し、Execution Resultに店舗情報があることを確認する。
以下サンプル。勝手に店舗情報載せるのもアレなので、スクロールはしていない。
f:id:ngtoku:20181110201324p:plain



5.API作成

Amazon API Gatewayから「+APIの作成」ボタンを押下。
f:id:ngtoku:20181110201450p:plain



新しいAPI のままで、適当にAPI名を入れて「APIの作成」ボタンを押下。
f:id:ngtoku:20181110201631p:plain



APIができたら"アクション"から"メソッドの作成"を選択し、POSTメソッドを作成する。
f:id:ngtoku:20181110201747p:plain



わかりづらいかもしれないが、上画像のチェックマークをクリックすると以下画面になる。
Lambda関数に先ほど作成した関数の名称(入力中に補完してくれる)を入れて「保存」ボタンを押下する。
確認ダイアログが出るが了承する。
f:id:ngtoku:20181110201810p:plain



それから"アクション"から"CORSの有効化"を選択し、デフォルトのまま"CORSを有効にして既存のCORSヘッダーを置換"ボタンを押下。
確認ダイアログが出るが了承する。
f:id:ngtoku:20181110202446p:plain



また"アクション"から"APIのデプロイ"を選択し、画面を参考にステージ名に任意の名称を入れて「デプロイ」ボタンを押下する。
f:id:ngtoku:20181110202604p:plain



これでAPIが解放された、画像で隠してある分がリクエスト先のURLである。
f:id:ngtoku:20181110202654p:plain



6.リクエスト発信ページ作成

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>YOLP via Lambda on API Gateway </title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
</head>
<body>
    <h4>データ件数は最大10件に絞ってあります。</h4>
    <form id="form" method="post" accept-charset="utf-8" return false>
        <p>
            <label>住所コード(JIS 0402)。先頭5桁</label><br>
            <input class="input is-large" type="text" id="ac" name="ac" value="13103">
            <a href="http://www.soumu.go.jp/denshijiti/code.html" target="_blank">コード値参照先</a>
        </p>
        <p>
            <label>ジャンルコード</label><br>
            <input class="input is-large" type="text" id="gc" name="gc" value="0110">
            <a href="https://developer.yahoo.co.jp/webapi/map/openlocalplatform/genre.html" target="_blank">コード値参照先</a>
        </p>
    </form>
    <button id="searchButton">検索</button>
    <div id="dataCount"></div>
    <ul id="result"></ul>
    <div id="responseData"></div>
    <script type="text/javascript">
        $(function(){
            // Ajax button click
            $('#searchButton').on('click',function(){
                $('#result').empty();
                $('#dataCount').empty();
                // 多重送信を防ぐため通信完了までボタンをdisableにする
                var button = $(this);
                button.attr("disabled", true);
                $('#responseData').html("処理中...");
                // 各フィールドから値を取得してJSONデータを作成
                var data = {
                    ac: $("#ac").val(),
                    gc: $("#gc").val()
                };
                $.ajax({
                    url:'APIのURLは各自で作成したAPIの物を入力',
                    type:'POST',
                    data:JSON.stringify(data),
                    contentType: 'application/json', 
                    dataType: "json", 
                })
                .done( (data) => { // 成功時
                    $('#dataCount').html(data.ResultInfo.Count + "件ヒットしました。");
                    $('#responseData').empty();
                    // レスポンスデータ全体を見たい場合はコメントを外す
                    // $('#responseData').html(JSON.stringify(data));
                    $('#result').empty();
                    $(data.Feature).each(function() {
                        console.log("succedss; " +  this.Name);
                        $('#result').append($('<li></li>').append($('<div></div>').text(this.Name)));});
                })
                .fail( (data) => { // 失敗時
                    $('#dataCount').empty();
                    $('#responseData').empty();
                    $('responseData').html(data);
                })
                .always( (data) => { // 常時
                    button.attr("disabled", false);  // ボタンを再び enableにする
                });
            });
        });
    </script>
</body>
</html>

特に難しいことはしていないので、参照のこと。あえて言うならPOSTデータの中身がJSONになっている事くらい。
APIのURLは各自で作成したAPIの物を入力。
問題なければ以下のようにレスポンスが戻ってくる。
f:id:ngtoku:20181110203046p:plain
めでたしめでたし。