20191209のNode.jsに関する記事は9件です。

zone.jsを使用してexpressでリクエストIDを出力するミドルウェアの作成

zone.jsを使用してアクセス毎にユニークな識別子をログへ出力する

expressではアクセス毎にリクエストIDを発行する機能はなく、自前で用意する必要があります。
そこで、Nginx or UUIDv4を使用してリクエストID毎にユニークな識別子を用意します。
リクエスト毎に値を保持する必要があるので、zone.jsを使用し持ち回れるようにしておきます。
nginxでrequest_id
uuid
zone.jsについて

logger.js
'use strict';

require('zone.js');
const uuidv4 = require('uuid/v4');
const moment = require('moment');

exports.middleware = function(req, res, next) {
    const prop = {
        name: 'requestId',
        properties: {
            requestId: req.header('x-request-id') || uuidv4()
        }
    };
    Zone
        .current
        .fork(prop)
        .run(next);
};

exports.out = function(text) {
    const reqId  = Zone.current.get('requestId');
    console.log(`[${moment().format("YYYY-MM-DD HH:mm:ss.SS")}(${reqId})] ${text}`);
};

app.js
const logger = require('./middleware/logger');
app.use(logger.middleware);
index.js
var express = require('express');
var router = express.Router();

const logger = require('../middleware/logger');

router.get('/', function(req, res, next) {
  logger.out(`リクエスト受信 params => 【${req.query.example}】`);

  // 確認用に遅れてレスポンスを返す
  setTimeout(function () {
    logger.out(`レスポンス送信 params => 【${req.query.example}】`);
    res.json({test: 'Hello'});
  }, Math.floor(Math.random() * 1000));
});

module.exports = router;

確認

UUIDをリクエストパラメータにつけて複数アクセスをしてログを確認してみる。

[2019-12-09 23:34:05.76(8355c676546235af1cac5d31989d5e5a)] リクエスト受信 params => 【d951a794-11cd-45c8-966f-07f20594de2f】
[2019-12-09 23:34:05.79(30cd941018ec1a704c68177a49f746d2)] リクエスト送信 params => 【ab255a13-83fb-4bf2-9264-525524a5080b】
~~~~~~~~~~~~~~~~~省略~~~~~~~~~~~~~~~~
[2019-12-09 23:34:06.35(4de3ed283bbab693587359c7a53166b7)] リクエスト受信 params => 【e923b5b4-c07b-42e4-a050-56cc3526ddd3】
[2019-12-09 23:34:06.35(63cfc0e59444a18e5858e6b8cb53b6ff)] リクエスト受信 params => 【ea76cd1f-ca91-4d2d-88ad-158fdd31fc37】
[2019-12-09 23:34:06.40(8355c676546235af1cac5d31989d5e5a)] リクエスト送信 params => 【d951a794-11cd-45c8-966f-07f20594de2f】

8355c676546235af1cac5d31989d5e5aのリクエストがパラメータのUUID一致で表示されています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Cloud Functions で obniz を 1分おきに動かしてみた

この記事は obniz Advent Calendar 2019 の11日目の記事です。

obniz で高頻度なセンシングをラクに実現してみたい!

今年の首都圏を襲った台風をきっかけに、 obniz で自宅の気温や気圧などのログを取って可視化してみたいと考え、obniz Cloud の12分おきのセンシングではもの足りず、 Google Cloud Functions で obniz を 1分おきに動かしてみました。

obniz とは

obniz(オブナイズ)は日本の CambrianRobotics 社が開発したマイコンボードで、Wi-Fi に接続してインターネット経由で操作します。簡単に Wi-Fi に繋がり、ファームウェアの書き込み不要で API 経由で操作するので、インターネットと連携したハードウェアをサクッと作れるのが特徴です。

obniz の動かし方

obniz の動かし方の種類

obniz の動かし方は以下のように分類することができます。

obniz を1分おきに動かしてセンサの値を記録したい

obniz サーバーレスイベントは?

obniz サーバーレスイベントを使えば、定期的にセンサの値を記録するようなアプリケーションを作成することができます。ただ、 各イベントの実行は1日に120回まで という制限があります。一定間隔で実行しても12分に1回実行できるわけで、IoTセンシング用途としては十分だと思いますが、 もっと高頻度なセンシングをラクに実現してみたい! ということで、別の方法を調べてみました。

専用のPCやサーバーを用意する?

Raspberry Piなど常時起動できるPCを1台用意して、定期的に走るように設定する方法もありますが、常に起動しておく必要があり、万が一エラーが起きてシャットダウンされた場合、復旧するのが面倒そうです。

FaaS

そこでクラウドサービスを利用することを検討しました。Node.js で数十秒間の関数を定期的に実行したいとなると、FaaSと呼ばれる、イベント駆動型コード実行サービスを使用するのが、容易で適していそうでした。
例えば Google Cloud Functions では、既にNode.js を動かせる環境が入っているため、環境設定が記載されたファイルや、実行する関数が記載されたファイルをアップロードするだけで、クラウド上で実行できるのです。

obniz 公式サイトにも、AWS Lambda で obniz を動かすレッスンがありますが、今回は無料制限回数が多かった(上無料体験期間が残っていた) Google Cloud Functions を使用してみました。
料金 - Google Cloud Functions によると月200万回、100万秒、5GBまで無料で実行できる?ようです。
毎分実行しても1ヶ月に5万回未満なので、無料の範囲に収まりそう…?
(その他の部分で料金がかかるかもしれません。この記事を参考にして生じた損失に関して一切の責任を負いません。)

作るもの

Google Cloud Functions を使用し、 obniz を1分おきに動かしてセンサの値を読み取り、Google Spreadsheet に記録します。測定する量は、温度、湿度、気圧、照度とします。
https://qiita.com/y-hira/items/b8fe1268a12492bd865c
の記事のように、obniz のセンサの値を Google Spreadsheet に記録することができます。

手順

手元のPCで Node.js で obniz を動かすプロジェクトを作成し、 ZIP ファイルに固めて Google Cloud Functions にアップロードする流れになります。macOS 10.13 にて行いました。

obniz に配線

今回は BME280 とアナログ照度センサを配線しました。
image.png

ついでにテレビやエアコンの操作用に赤外線LEDも配線してありますが(笑)

データ記録用のスプレッドシートを作成

https://qiita.com/y-hira/items/b8fe1268a12492bd865c
の手順に従って、スプレッドシートと、GAS のプロジェクトを作成しました。GAS のプロジェクトの「現在のウェブアプリケーションのURL」をメモしておきます。

Node.js と npm をインストール

まず、手元の PC で Node.js と npm を使えるようにしておきます。

Node ファイルの作成

適当なフォルダ(ここでは obniz_gcf) を作成し、 obniz のライブラリをインストールします。

mkdir obniz_gcf
cd obniz_gcf
npm init

npm init では簡単な質問に答えていくと package.json が生成されます。デフォルトのままで良ければ Enter を押していくことで進めます。

npm install obniz

obniz のコードを書くファイル index.js を作成します。

touch index.js

コードを作成

POST するために、慣れているjQuery を使いたかったので、NodeJSでjQueryを使う を参考にコードを作成しました。

index.js
var Obniz = require("obniz");
const jsdom = require('jsdom');
const { JSDOM } = jsdom;
const dom = new JSDOM(`<html><body><div id="aaa">AAA<div></body></html>`);
const { document } = dom.window;
const jquery = require('jquery');
const $ = jquery(dom.window);

exports.handler = function (event, context, callback) {
    var obniz = new Obniz(process.env.OBNIZ_ID);
    const url = process.env.POST_URL;
    var sensor;

    obniz.onconnect = async function () {
        obniz.io8.output(true);
        sensor = obniz.wired("BME280", { gnd: 9, sck: 10, sdi: 11, address: 0x77 });
        await sensor.applyCalibration();
        const illum = await obniz.ad7.getWait();

        const obj = await sensor.getAllWait();
        var param = { sheet: "log", obniz_id: obniz.id, temperature: obj.temperature, humidity: obj.humidity, pressure: obj.pressure, illuminance: illum };
        console.log(param);
        $.post(url, param)
            .done(function (data) {
                console.dir(data);
                obniz.close();
                callback(null, "success");
            });
    }
};

追加で使用するライブラリもインストールします。

npm install jquery
npm install jsdom

Google Cloud Functions にアップロード

ZIP に圧縮

解凍したときに index.js がトップの階層に来るように ZIP に圧縮します。

zip -r obniz_gcf_codes.zip index.js node_modules/ package-lock.json package.json

Google Cloud Platform のコンソール で新たにプロジェクトを作成し、 Cloud Functions のメニューから新たに関数を作成します。

image.png

定期実行の設定

1分ごとに実行するため、トリガーには Cloud Pub/Sub を選択します。
「新しいトピックを作成」し、適当な名前を付けます。
Cloud Scheduler を開き、「ジョブを作成」します。トピックには先程作成したトピック名を指定してください。毎分実行したい場合は、頻度として * * * * * を指定します。その他の時間間隔もこの文字列次第で設定することができます。

image.png

image.png

Google Cloud Functions の設定

ソースコードは ZIPアップロード とし、さきほど圧縮してできた ZIP ファイルを選択します。
実行する関数は handler です。

image.png

また追加の設定項目を開き、環境変数として、 OBNIZ_ID に obniz の ID, POST_URL には GAS アプリケーションのURLを指定します。

image.png

最後に デプロイ !
アップロードに少々時間がかかるかもしれません。
うまくいけば、1分おきに、関数の実行が開始されます!
スプレッドシートを確認して、センサの値が正しく記録されているか確認してみましょう!

約2ヶ月間動かしてみた結果

既に2ヶ月近く動かしているのですが、安定して実行できています。 obniz はコンセントに挿した USB - AC アダプタに挿しっぱなしです。万が一停電が起きても、復旧時にも特に何もする必要がないのが楽でいいですね。

Google Cloud Functions の記録

obniz を接続している Wi-Fi ルーターがもともと不安定でときどき再起動してしまうこともあり、ときどきタイムアウトしていますが、おおむね安定して実行できているようです。Google Cloud Functions の設定では、50秒でタイムアウトとなっています。

image.png

どうやらタイムアウト時間の50秒近くかかっている場合もあるようです。
image.png

GAS の記録

GAS の方では1回あたり大体10秒ちょっとかかっているようですね。エラーもほぼないようです。

image.png

image.png

スプレッドシートの記録

2ヶ月もデータを溜めると、スプレッドシートを開くのにも時間がかかるようになってしまいました(笑)
GAS で毎日 Google スプレッドシート上のグラフを画像にして Google ドライブに保存するようにもしており、またデータにアクセスしたい場合は Google Colab を使って Python で処理すれば、 Google スプレッドシートを直接開く必要がないので、端末のスペックに依存しなくて済むかと思いますが、これ以上データを蓄積したい場合は、 IoT 向けのストレージを使うべきでしょう。

image.png

試しに11月1日0時0分〜11月30日23時59分59秒までにスプレッドシートに記録された行数を数えてみると、42951行ありました。
60*24*30=43200 なので、249回だけ記録できなかったことがあるようですね。でも、 99.4% 以上の割合で記録できています!

また台風通過時の気圧のグラフです。肝心の台風の目通過時は、 obniz Cloud で12分おきに取得していたのですが、それでも台風通過の様子がよく分かりますね。
たまに値が飛んでいますが、自宅の環境センシングとしては十分です。
image.png

気になる請求金額は

image.png

ちゃんと無料枠に収まっていました!
ただし他にもプロジェクトがある場合や設定次第で課金される可能性も十分あるので、無料枠で使いたい方は十分お気をつけください!

最後に

Google Cloud Functions で obniz を1分おきに動かすことができました!
台風通過時の気圧の変化、季節や時間による温度や湿度の変化を可視化できて満足です。
エアコンをつけたときの温度と湿度の変化など、グラフを見て気付かされることもありますね。逆にグラフを見ればエアコンをいつつけたかも分かります。もっとセンサの種類を増やしたくなりました。
ただ、蓄積したデータへのアクセスが問題なので、より良いデータの蓄積方法も考えてみたいと思います。

Let's IoT!
Let's obniz!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitHub ActionsでNode.jsのテストカバレッジをCoverallsに登録する

はじめに

GitHub Actionsを触りたくてActionを作ってみました。Node.js(というかTypeScript)で書いているのですが、テストカバレッジをREADMEにバッジ表示したくて調べました。

前提

公式テンプレート1を利用してリポジトリを作成します。
https://github.com/actions/typescript-action

Coveralls側でAdd repoしてください。TOKENは使わないので放っておいてよいです。

サンプル

https://github.com/oke-py/npm-audit-action
https://coveralls.io/github/oke-py/npm-audit-action

設定ファイル修正

package.json

テストにはJestを使用しています。npm testの引数に-- --coverageを追加するだけでカバレッジを取得できます。

diff --git a/package.json b/package.json
index ec291cb..2e0a658 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
     "lint": "eslint src/**/*.ts",
     "pack": "ncc build",
     "test": "jest",
-    "all": "npm run build && npm run format && npm run lint && npm run pack && npm test"
+    "all": "npm run build && npm run format && npm run lint && npm run pack && npm test -- --coverage"
   },
   "repository": {
     "type": "git",

.github/workflows/test.yml

GitHub Actionsで実行されるテストの設定を変更します。Coveralls公式のAction1が提供されているので利用します。

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index d62684f..fade74a 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -14,10 +14,13 @@ jobs:
     - run: |
         npm install
         npm run all
+    - uses: coverallsapp/github-action@master
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}
   test: # make sure the action works on a clean machine without building
     runs-on: ubuntu-latest
     steps:

以上です。あとはREADMEにバッジを表示するだけです(割愛)。

おわりに

Coveralls連携は簡単でした。カバレッジ上げよう(本記事が公開される頃にはマシになっているだろうか・・・)。
GitHub Actionsよいですね。楽しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[メモ]サウンドプログラミング

Max know-how

アルゴリズム

C know-how

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

fs.stat を Promise 化して複数のファイルの stat を一気に取る。

書いてから思ったのですが、下から読んだ方がいいかも。

Promise 以前の状況

例えば node.js で 'x' という名前のファイルの stat を取得したい場合

sample.js
const fs = require( 'fs' )

fs.stat(
    'x'
,   ( er, stat ) => {
        if ( er )   console.error( er )
        else        console.log( stat )
    }
)

という風にやってたと思います。

ファイルが複数の場合

複数のファイル、例えば 'x', 'y' という名前のファイルの stat を取得してから何かやりたいような場合、時間のかかる逐次処理でよければ

sample.js
const fs = require( 'fs' )

const
stats = []
fs.stat(
    'x'
,   ( er, stat ) => {
        if ( er )   console.error( er )
        else {
            stats.push( stat )
            fs.stat(
                'y'
            ,   ( er, stat ) => {
                    if ( er )   console.error( er )
                    else {
                        stats.push( stat )
                        console.log( stats )
                    }
                }
            )
        }
    }
)

並行にしたい場合はちょっと苦しいテクニックを使って

sample.js
const fs = require( 'fs' )

const
stats = [ null, null ]
fs.stat(
    'x'
,   ( er, stat ) => {
        if ( er )   console.error( er )
        else {
            stats[ 0 ] = stat
            if ( stats[ 1 ] ) console.log( stats )
        }
    }
)
fs.stat(
    'y'
,   ( er, stat ) => {
        if ( er )   console.error( er )
        else {
            stats[ 1 ] = stat
            if ( stats[ 0 ] ) console.log( stats )
        }
    }
)

のようにする必要がありました。ちょっと大変です。特にファイルの数が増えたりしたら。

Promise 以降

fs.stat を Promise 化すると上のような大変さがなくなります。

Promise 化

util.promisify を使う
sample.js
const fs = require( 'fs' )
const { promisify } = require( 'util' )

promisify( fs.stat )( 'x' ).then(
    _ => console.log( _ )
).catch(
    _ => console.error( _ )
)
素でやる

util.promisify がある今となってはもうやることはないと思いますが、参考までに。

sample.js
const fs = require( 'fs' )

new Promise(
    ( rs, rj ) => fs.stat(
        'x'
    ,   ( er, stat ) => er ? rj( er ) : rs( stat )
    )
).then(
    _ => console.log( _ )
).catch(
    _ => console.error( _ )
)

ファイルが複数の場合

Promise.all を使う

Promise の配列を作って Promise.all に渡してやります。配列の中のすべての Promise が解決されるか、どれかがリジェクトされるまで待ちます。

sample.js
const fs = require( 'fs' )
const { promisify } = require( 'util' )

Promise.all(
    [ 'x', 'y' ].map(
        _ => promisify( fs.stat )( _ )
    )
).then(
    _ => console.log( _ )
).catch(
    _ => console.error( _ )
)
関数化してみる
sample.js
const fs = require( 'fs' )
const { promisify } = require( 'util' )

const
Stats = files => Promise.all( files.map( _ => promisify( fs.stat )( _ ) ) )

Stats( [ 'x', 'y', 'z' ] ).then(
    _ => console.log( _ )
).catch(
    _ => console.error( _ )
)

最後に

Promise.all を使わない手はありませんね!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Translation API v3 を Node で使ってみた

はじめに

案件で使う機会があったので忘備録的な感じで記載していこうと思います。
諸々間違い、認識違いがあるかもしれませんが生暖かく見守っていただければと思います。

実装イメージ

  • XServer X10プランを使用します。
  • NodeでWebサーバ起動して云々はXServer上で実装するのは難しいのでphpで受けてコマンド呼び出しで動かします。
  • 翻訳結果はjson形式で返却します。
    • 本来であれば翻訳結果をjavascriptが受け取りうまくゴニョゴニョしてhtml上で表現するが正しいと思いますが、残念ながら自分はPHPerでjavascriptが得意じゃないのでこの部分は割愛させていただきたく。

環境

  • XServer X10プランで契約できるレンタルサーバ
  • php
    • 7.2.17
  • perl
    • 5.16
  • nodebrew
    • 1.0.1
  • Node
    • v12.10.0
  • npm
    • 6.10.3

※php, perlのバージョンはXServerのデフォルト設定(2019/12/04時点)
※nodebrewは自分が試した時点での最新。
※Nodeはnodebrewにて指定する感じ、npmはnodeインストール時に一緒に入るもの。

ディレクトリ構成

  • XServerの基本構造をそのまま使用します。
  • 翻訳機能についてはドキュメントルート直下には作りませんでした。直URLでアクセスされたときのことを考えたくなかったからです。
  • 各ディレクトリ・ファイルについては後述で説明しますが、最終的には下記のような構成になります。
/xxxxx.xsrv.jp/
    public_html/
        index.html
        translation.php
    node/
        json/
        node_modules/
        package-lock.json
        translation.js
        gcpprj-example-xxxxxx-yyyyyyyyyyyy.json

nodebrew, node.js, npmのインストール

$ cd ~/
$ wget git.io/nodebrew
$ perl nodebrew setup
 :
 :
$ vi .bashrc
export PATH=$HOME/.nodebrew/current/bin:$PATH   ※この行を.bashrcの末尾に追加
$ source ~/.bashrc
$ nodebrew help    ※このコマンドを叩くと諸々出てくれば成功
nodebrew 1.0.1
 :
 :
$ nodebrew install v12.10.0  ※これでnode.js v12.10.0がインストールされる
$ nodebrew use v12.10.0      ※これでnode.js v12.10.0を使うよと宣言する感じ
$ node -v
v12.10.0
$ npm -v
6.10.3

Google Cloud Platformの設定及びGoogleTranslateAPIを使用するための準備

  • GCP側でプロジェクトを作成します。
  • 「請求アカウント」を作成し上記で作ったプロジェクトに紐付けてください。
  • 使用APIに「Cloud Translation API」を追加してください。
  • このクイックスタートのページの「プロジェクトをセットアップする」ボタンを押下しプロジェクトを指定すると「JSONとしての秘密鍵」がDownloadされます。 上記の「gcpprj-example-xxxxxx-yyyyyyyyyyyy.json」というのが「JSONとしての秘密鍵」になります。
  • クイックスタートにはその後「環境変数なんちゃら」という記載がありますが、とりあえず今のところはスルー。
  • クイックスタートの次の手順「クライアント ライブラリのインストール」では「NODE.JS」を選択するとnpmのインストールコマンドが出てくるのでそれを上記のnodeディレクトリ配下で実行します。
    • nodeディレクトリはデフォルトでは無いのでmkdirで作っていきながら作業する感じです。
$ cd ~/
$ cd xxxxx.xsrv.jp
$ mkdir node
$ cd node
$ mkdir json
$ npm install --save @google-cloud/translate   <== これがクイックスタート上に出てきたインストールコマンド
$ ls -1F
json/
node_modules/
package-lock.json
  • このnodeディレクトリ配下に先程Downloadされた「JSONとしての秘密鍵」をFTP等でアップロードします。
$ cd ~/xxxxx.xsrv.jp/node
$ ls -1F
json/
node_modules/
package-lock.json
gcpprj-example-xxxxxx-yyyyyyyyyyyy.json
  • クイックスタートの次の手順「テキストの翻訳」で、NODE.JSのサンプルコードをそのまんまコピーして上記で作ったnodeディレクトリにtranslation.jsとして保存。
  • サンプルコードのままだと、翻訳対象文字列がコードに直書き状態なので引数で受け取って可変できるようにします。その時、翻訳対象文字列を直接引数で指定してしまうと「コマンドインジェクション」が発生する可能性が高く怖いので翻訳対象文字列を引数のjsonファイルから取得するような仕様に変更します。

  • translation.jsの中身は下記の通り

const projectId = 'XXXXX-yyyyy-zzzzz';  // GCP上のプロジェクトIDを記載
const location = 'global';

// jsonから読み込むように修正
const jsonPath = process.argv[2];
const json = require(jsonPath);
const text = json.text;

// Imports the Google Cloud Translation library
const {TranslationServiceClient} = require('@google-cloud/translate').v3beta1;

// Instantiates a client
const translationClient = new TranslationServiceClient();
async function translateText() {
  // Construct request
  const request = {
    parent: translationClient.locationPath(projectId, location),
    contents: [text],
    mimeType: 'text/plain', // mime types: text/plain, text/html
    sourceLanguageCode: 'ja',   // 日本語から。
    targetLanguageCode: 'en',   // 英語に。
  };

  // Run request
  const [response] = await translationClient.translateText(request);

  for (const translation of response.translations) {
    console.log(`${translation.translatedText}`);
  }
}

translateText();
  • ディレクトリ内はこんな感じ
$ cd ~/xxxxx.xsrv.jp/node
$ ls -1F
json/  ※翻訳対象の文字列をjson形式にして保存する場所
node_modules/
package-lock.json
gcpprj-example-xxxxxx-yyyyyyyyyyyy.json
translation.js
  • 試しにこの状態で下記のコマンドを叩いて強引に実行してみます。
  • jsonで設定している日本語は「私は本を持っている」です。
$ cd ~/xxxxx.xsrv.jp/node
$ echo "{\"text\":\"\u79c1\u306f\u672c\u3092\u6301\u3063\u3066\u3044\u308b\"}" > json/example.json  ※ダミーjson作成
$ node translation.js /home/xxx/xxxxx.xsrv.jp/node/json/example.json
(node:395938) UnhandledPromiseRejectionWarning: Error: Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.
    at GoogleAuth.getApplicationDefaultAsync ...
     :
     :
(node:398472) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:398472) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

$
  • うまく動きません。「環境変数なんちゃら」をスルーしたためです。
  • 「環境変数を設定する」、「翻訳対象の文字列を取得し、jsonファイルを作る」という処理はこの後に記載するphpロジックにて実装することを想定しています。

php, htmlで残りの処理を作成する。

  • ドキュメントルート直下にindex.htmlを設置、翻訳対象文字列を入力するフォームを作ります。
<html>
  <head>
    <meta charset="UTF-8" />
    <title>日本語を英語に翻訳する</title>
  </head>
  <boby>
    <form action="translation.php" method="post">
      日→英、翻訳テキスト:<input type="text" name="target"> <input type="submit" value="翻訳">
    </form>
  </body>
</html>
  • translation.phpで下記機能を実装します。
    • 翻訳対象文字列の取得
    • jsonファイルの生成
    • 環境変数のセット
    • translation.jsの実行、結果取得
    • json形式にして結果を出力
define('HOME_DIR',       '/home/xxx/');  // 各自環境のものを入れる
define('NODE',           HOME_DIR   . '.nodebrew/current/bin/node');  // nodeのインストール状況では違うものになるはず
define('DOMAIN_DIR',     HOME_DIR   . 'xxxxx.xsrv.jp/');
define('NODE_DIR',       DOMAIN_DIR . 'node/');
define('JSON_DIR',       NODE_DIR   . 'json/');
define('GCP_JSON',       NODE_DIR   . 'gcpprj-example-xxxxxx-yyyyyyyyyyyy.json');
define('TRANSLATION_JS', NODE_DIR   . 'translation.js');


// 引数が無い場合は何もしない
if (!isset($_POST['target']) || $_POST['target'] === '') {
    header("Location: https://xxxxx.xsrv.jp/index.html");
    exit();
}

// jsonファイルを生成
define('TEXT_RANGE', implode(array_merge(range(0, 9), range('a', 'z'), range('A', 'Z'))));
$rndtxt   = substr(str_shuffle(TEXT_RANGE), 0, 16);
$jsonfile = sprintf("%s%s.json",JSON_DIR, $rndtxt);
$json_ary['text'] = $_POST['target'];
$jsondata         = json_encode($json_ary);
file_put_contents($jsonfile, $jsondata);

// JSONの秘密鍵を環境変数にセット
putenv('GOOGLE_APPLICATION_CREDENTIALS='.GCP_JSON);

// コマンドの作成・実行
$command = sprintf("%s %s %s", NODE, TRANSLATION_JS, $jsonfile);
$out = $res = '';
exec($command, $out, $res);

// 結果解析
if (!empty($out[0])) {
    header('content-type: application/json; charset=utf-8');
    echo json_encode(['text' => $out[0]]);
} else {
    header('content-type: application/json; charset=utf-8');
    echo json_encode(['error' => 'error']);
}
exit();

やってみる

  • こんな感じで入れてみて…

コメント 2019-12-06 205149.png

  • こんな感じで出る。
  • firefoxだとjson形式を開くと色々解析してくれる(が、今回はシンプル極まりないので特に意味なしですが)

コメント 2019-12-06 205244_2.png

補足

  • Google Translation API v3 はまだβ版なので注意しよう。
    • 早くphp版ライブラリでないかぁと思ったり。
  • Google Translation API v3 には無料枠がある。
    • FAQの「Cloud Translation API へのアクセス」の「無料の割り当てはありますか?」に記載あり。
    • 自分が見た価格表はv3の特別枠の記載があったが消えている(2019/12/04更新したみたいでその時消えたっぽい)
    • こうやって記載しているそばから更新されていく可能性大(なぜならまだβ版だから)
    • 案件で使った経緯はこの「無料枠」があるところが大きかった。(が、今は消えているのでちょっとドキドキしています)

まとめ

  • 自分の担当案件ではよく「日英のHPをCMSで作る」という要件がよくあるのでこれからも多用していくかなと思っています。
  • 正直、ロジックを作るところよりもGCPの設定のほうが難しかった。

:christmas_tree: FORK Advent Calendar 2019
:arrow_left: 9日目 Vuetifyのdatepickerを使って【和暦】+【年度/月】pickerを作ってみた @BigFly
:arrow_right: 11日目 @talow1 さんよろしくおねがいします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.nodebrewからnへの移行をやってみた

かれこれnodeを触っておらず、nodebrewの存在を忘れていた状態でした。
アップデートしてもバージョンが変わらず、頭空っぽにして作業していたので笑、『Node.jsとnpmをアップデートする方法』という記事を参考にnを入れてアップデートを行うも(当然)上手くいかずwhichしてnodeのパスを見ると.nodebrewが経由されたことを知り、そこで思い出しましたw

あまりnodeを使わないので、このまま$ n --stable$ n --latestで管理しようと思いnodebrewを消し、nに移行することにした際のまとめです。

注意事項

参考にされる際は、自己責任でお願いします。

この移行作業を行うことでnpmインストールしたライブラリまで消えてしまうため、削除する際に何をインストールしていたのか確認し、移行した後に再度入れなおしてください。
その際におそらく前のバージョンが新しいnodeでサポート外になり、サポートされている新しめなバージョンがインストールされることになると考えられます。

環境

  • MacBookPro Mojave 10.14
    • nodebrewnが入っており環境変数で.nodebrewを見るように固定している
$ sudo n --stable
12.13.1

$ node -v
v8.9.4

$ which node
/Users/gremito/.nodebrew/current/bin/node

$ less ~/.bash_profile
...
export PATH="${HOME}/.nodebrew/current/bin:$PATH"
...

移行作業

作業は簡単でnodebrewを消すだけ

$ rm -fr .nodebrew

$ vi ~/.bash_profile
# .nodebrewの設定を削除
$ source ~/.bash_profile

$ node -v
v12.13.1
$ which node
/usr/local/bin/node
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【解決済】node.js v12.13.1で "expo start"コマンドで起動するとエラーを吐いてしまうバグ

"Unterminated character class. Run CLI with --verbose flag for more details."

ゼロ環境でExpoを導入しようとしたのでnode入れろとかC++入れろとか挙句の果てにはexpoの最新版だとインストール出来ないって怒られやっとインストール出来て起動出来ると思ったらよくわからないエラーに遭遇したので書き留めて置きます。

環境

expo 3.5.0
node.js LTS版(12.13.1)

原因と解決策

nodeの12.10前後のバージョンが悪さをしてるみたいなのでダウングレードもしくは修正済のバージョンにアップグレードするのが安定しそうでしたが自分は入れ直したりアップグレードする手間が惜しかったでエラー箇所を修正しました。エラーと修復手順を見る感じだと文法の記述ミスなのかな。

エラー箇所の修復手順

手順1 原因のファイルを開きます

"Expoのプロジェクトを作成したフォルダ"\node_modules\metro-config\src\defaults\blacklist.js

手順2 エラー原因箇所を修正して保存します

image.png

var sharedBlacklist = [
/node_modules[\/\]react[\/\]dist[\/\]./,
/website\/node_modules\/.
/,
/heapCapture\/bundle.js/,
/.\/tests\/./

手順3 Power Shellでプロジェクト起動"expo start"

image.png

参考資料

https://github.com/facebook/metro/issues/453#issue-comment-box

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

express-graphql + TypeScript で始めるGraphQL API Server

はじめに

この記事は express-graphqlNode.js + TypeScript で簡単にGraphQL APIサーバを実装する
ハンズオンちっくな記事です。
実際に手を動かしてみてください?

ディレクトリ構造は下記のようになります。

.
├── src
│   ├── data
│   │   └── index.ts
│   ├── fields
│   │   ├── index.ts
│   │   └── member
│   │       ├── index.ts
│   │       ├── query.ts
│   │       ├── mutation.ts
│   │       ├── resolvers.ts
│   │       └── types.ts
│   └── index.ts
├── package.json
└── tsconfig.json

準備

パッケージのインストール

実行は ts-node で行います。

yarn add @types/express cors express express-graphql graphql typescript
yarn add -D ts-node tsconfig-paths

tsconfig.json

alias の登録をします。

tsconfig.json
{
  "compilerOptions": {
    "sourceMap": false,
    "noImplicitAny": true,
    "module": "commonjs",
    "target": "es5",
    "lib": ["es2018", "dom"],
    "moduleResolution": "node",
    "removeComments": true,
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "strictFunctionTypes": false,
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"],
    }
  },
  "include": [
    "./src/**/*.ts"
  ]
}

GraphQL Query

メンバー一覧を取得するAPI を実装します。

実装

Data

実際にこのDataは SQL などからDBの値を取得しますが、今回は DB との接続はなしで JS で用意します。

src/data/index.ts
export const memberList = [
  {
    id: 1,
    name: 'Rachel',
    age: 29
  },
  {
    id: 2,
    name: 'Ross',
    age: 29
  },
  {
    id: 3,
    name: 'Joey',
    age: 29
  }
];  

Types

型定義を types.ts として作成します。

src/fields/member/types.ts
import { GraphQLObjectType, GraphQLNonNull, GraphQLString, GraphQLInt } from 'graphql';

export const memberType = new GraphQLObjectType({
  name: 'member',
  description: 'member',
  fields: {
    id: {
      type: new GraphQLNonNull(GraphQLInt),
      description: 'The Member ID.'
    },
    name: {
      type: new GraphQLNonNull(GraphQLString),
      description: 'The Member name.'
    },
    age: {
      type: new GraphQLNonNull(GraphQLInt),
      description: 'The Member age.'
    }
  }
});

Resolvers

Resolver では何をレスポンスするかの処理を書きます。
この例ではメンバーリストを取得したいだけなので、そのまま memberList を返します。

src/fields/member/resolvers.ts
import { memberList } from '@/data';

export const getMemberList = () => Promise.resolve(memberList);

Query

Query は REST APIの GET に相当します。

src/fields/member/query
import { GraphQLList } from 'graphql';
import { getMemberList } from '@/fields/member/resolvers';
import { memberType } from '@/fields/member/types';

export const memberQuery = {
  memberList: {
    type: new GraphQLList(memberType),
    description: 'Get list of members data.',
    resolve: getMemberList
  }
};

src/fieles/member/index.ts

実装した member モジュールの query をまとめてエクスポートします。

src/fields/member/index.ts
import { memberQuery as query } from '@/fields/member/query';

export const memberField = {
  query
};

src/fields/index

実装したすべてのモジュールを Root Query としてまとめてエクスポートします。

src/fields/index.ts
import { GraphQLObjectType } from 'graphql';
import { memberField } from '@/fields/member/';

export const queryType = new GraphQLObjectType({
  name: 'Query',
  description: 'The root query type.',
  fields: {
    ...memberField.query
  }
});

Express

最後に Express でサーバを実装します。

src/index.ts
import * as express from 'express';
import * as graphqlHTTP from 'express-graphql';
import { GraphQLSchema } from 'graphql';
import { queryType } from '@/fields/';

const PORT = 4000;
const app = express();

const schema = new GraphQLSchema({
  query: queryType
});

app.use(
  '/graphql',
  express.json(),
  graphqlHTTP({
    schema,
    graphiql: true
  })
);

app.listen(PORT, () => console.log('Listening on :4000'));

実行

サーバ起動

下記コマンドでAPIサーバを起動します。

yarn ts-node -r tsconfig-paths/register src/index.ts

動作チェック

localhost:4000/graphql にアクセスすると、GraphiQL エディタが起動します。

下記クエリを入力して実行。memberList が取得できれば成功?

query getMemberList {
  memberList {
    id
    name
    age
  }
}

スクリーンショット 2019-12-09 02.43.18.png

GraphQL Mutation

続いて Mutation を実装します。
この例では新メンバーを追加する Mutation を実装します。

実装

Types

入力側のパラメータの型を追加します。

src/fields/member/types.ts
export const memberCreateInput = new GraphQLInputObjectType({
  name: 'memberCreateInput',
  fields: {
    name: {
      type: new GraphQLNonNull(GraphQLString),
      description: 'The Member name.'
    },
    age: {
      type: new GraphQLNonNull(GraphQLInt),
      description: 'The Member age.'
    }
  }
});

Resolvers

新メンバーを追加する処理を追加します。

src/fields/member/resolvers.ts
export const createMember = ({ name, age }: { name: string; age: number }) => {
  const member = {
    id: memberList.length + 1,
    name,
    age
  };
  memberList.push(member);
  return memberList;
};

Mutation

新たに Mutation を作成します。

src/fields/member/mutation.ts
import { GraphQLNonNull, GraphQLList } from 'graphql';
import { createMember } from '@/fields/member/resolvers';
import { memberType, memberCreateInput } from '@/fields/member/types';

export const memberMutation = {
  createMember: {
    type: new GraphQLList(memberType),
    args: {
      member: {
        type: new GraphQLNonNull(memberCreateInput)
      }
    },
    resolve: (_: any, args: any) => {
      return createMember(args.member);
    }
  }
};

src/fieles/member/index.ts

実装した member の Mutation をエクスポートします。

src/fields/member/index.ts
import { memberQuery as query } from '@/fields/member/query';
import { memberMutation as mutation } from '@/fields/member/mutation';

export const memberField = {
  query,
  mutation
};

src/fields/index

実装したすべてのモジュールを Root Mutation としてまとめてエクスポートします。

src/fields/index.ts
import { GraphQLObjectType } from 'graphql';
import { memberField } from '@/fields/member/';

export const queryType = new GraphQLObjectType({
  name: 'Query',
  description: 'The root query type.',
  fields: {
    ...memberField.query
  }
});

export const mutationType = new GraphQLObjectType({
  name: 'Mutation',
  description: 'The root Mutation type.',
  fields: {
    ...memberField.mutation
  }
});

Express

最後に SchemaMutation を追加します。

src/index.ts
import { queryType, mutationType } from '@/fields/';

const schema = new GraphQLSchema({
  query: queryType,
  mutation: mutationType
});

実行

サーバ起動

下記コマンドでAPIサーバを起動します。

yarn ts-node -r tsconfig-paths/register src/index.ts

動作チェック

下記クエリを入力して実行。memberList に入力したメンバーが追加できれば成功?

mutation createMember {
  createMember(member: {
    name: "Monica"
    age: 29
  }) {
    id
    name
    age
  }
}

スクリーンショット 2019-12-10 17.28.15.png

さいごに

GraphQL のメリットとして、書いたコードがそのままドキュメントになることが挙げられます。
予め description を書くルールなどを定めておけばAPI ドキュメントを用意する必要がなくなります。

実際に手を動かして、GraphQL を体験してみてください!!!

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む