- 投稿日:2019-12-09T23:44:14+09:00
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.jsconst logger = require('./middleware/logger'); app.use(logger.middleware);index.jsvar 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一致で表示されています。
- 投稿日:2019-12-09T21:26:25+09:00
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 開発者コンソールから JavaScript やブロックプログラムで記述
- ブラウザ上で実行
- サーバーレスイベントで実行
- Webhook
- 決められたある時間(11:11など)
- 決められた時間の間隔(1時間毎など)
- obnizがオンラインになったら
- obnizのボタンが押されたら
- Python で動かす
- Node.js で動かす
- 手元のPCで Node.js を走らせておく
- サーバーを用意する
- サーバーレスアーキテクチャを使用する
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 に配線
ついでにテレビやエアコンの操作用に赤外線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 obnizobniz のコードを書くファイル
index.js
を作成します。touch index.jsコードを作成
POST するために、慣れているjQuery を使いたかったので、NodeJSでjQueryを使う を参考にコードを作成しました。
index.jsvar 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 jsdomGoogle Cloud Functions にアップロード
ZIP に圧縮
解凍したときに
index.js
がトップの階層に来るように ZIP に圧縮します。zip -r obniz_gcf_codes.zip index.js node_modules/ package-lock.json package.jsonGoogle Cloud Platform のコンソール で新たにプロジェクトを作成し、
Cloud Functions
のメニューから新たに関数を作成します。定期実行の設定
1分ごとに実行するため、トリガーには
Cloud Pub/Sub
を選択します。
「新しいトピックを作成」し、適当な名前を付けます。
Cloud Scheduler を開き、「ジョブを作成」します。トピックには先程作成したトピック名を指定してください。毎分実行したい場合は、頻度として* * * * *
を指定します。その他の時間間隔もこの文字列次第で設定することができます。Google Cloud Functions の設定
ソースコードは
ZIPアップロード
とし、さきほど圧縮してできた ZIP ファイルを選択します。
実行する関数はhandler
です。また追加の設定項目を開き、環境変数として、
OBNIZ_ID
に obniz の ID,POST_URL
には GAS アプリケーションのURLを指定します。最後に デプロイ !
アップロードに少々時間がかかるかもしれません。
うまくいけば、1分おきに、関数の実行が開始されます!
スプレッドシートを確認して、センサの値が正しく記録されているか確認してみましょう!約2ヶ月間動かしてみた結果
既に2ヶ月近く動かしているのですが、安定して実行できています。 obniz はコンセントに挿した USB - AC アダプタに挿しっぱなしです。万が一停電が起きても、復旧時にも特に何もする必要がないのが楽でいいですね。
Google Cloud Functions の記録
obniz を接続している Wi-Fi ルーターがもともと不安定でときどき再起動してしまうこともあり、ときどきタイムアウトしていますが、おおむね安定して実行できているようです。Google Cloud Functions の設定では、50秒でタイムアウトとなっています。
どうやらタイムアウト時間の50秒近くかかっている場合もあるようです。
GAS の記録
GAS の方では1回あたり大体10秒ちょっとかかっているようですね。エラーもほぼないようです。
スプレッドシートの記録
2ヶ月もデータを溜めると、スプレッドシートを開くのにも時間がかかるようになってしまいました(笑)
GAS で毎日 Google スプレッドシート上のグラフを画像にして Google ドライブに保存するようにもしており、またデータにアクセスしたい場合は Google Colab を使って Python で処理すれば、 Google スプレッドシートを直接開く必要がないので、端末のスペックに依存しなくて済むかと思いますが、これ以上データを蓄積したい場合は、 IoT 向けのストレージを使うべきでしょう。試しに11月1日0時0分〜11月30日23時59分59秒までにスプレッドシートに記録された行数を数えてみると、42951行ありました。
60*24*30=43200 なので、249回だけ記録できなかったことがあるようですね。でも、 99.4% 以上の割合で記録できています!また台風通過時の気圧のグラフです。肝心の台風の目通過時は、 obniz Cloud で12分おきに取得していたのですが、それでも台風通過の様子がよく分かりますね。
たまに値が飛んでいますが、自宅の環境センシングとしては十分です。
気になる請求金額は
ちゃんと無料枠に収まっていました!
ただし他にもプロジェクトがある場合や設定次第で課金される可能性も十分あるので、無料枠で使いたい方は十分お気をつけください!最後に
Google Cloud Functions で obniz を1分おきに動かすことができました!
台風通過時の気圧の変化、季節や時間による温度や湿度の変化を可視化できて満足です。
エアコンをつけたときの温度と湿度の変化など、グラフを見て気付かされることもありますね。逆にグラフを見ればエアコンをいつつけたかも分かります。もっとセンサの種類を増やしたくなりました。
ただ、蓄積したデータへのアクセスが問題なので、より良いデータの蓄積方法も考えてみたいと思います。Let's IoT!
Let's obniz!
- 投稿日:2019-12-09T18:15:51+09:00
GitHub ActionsでNode.jsのテストカバレッジをCoverallsに登録する
はじめに
GitHub Actionsを触りたくてActionを作ってみました。Node.js(というかTypeScript)で書いているのですが、テストカバレッジをREADMEにバッジ表示したくて調べました。
前提
公式テンプレート1を利用してリポジトリを作成します。
https://github.com/actions/typescript-actionCoveralls側で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よいですね。楽しいです。
- 投稿日:2019-12-09T16:06:52+09:00
fs.stat を Promise 化して複数のファイルの stat を一気に取る。
書いてから思ったのですが、下から読んだ方がいいかも。
Promise 以前の状況
例えば node.js で 'x' という名前のファイルの stat を取得したい場合
sample.jsconst fs = require( 'fs' ) fs.stat( 'x' , ( er, stat ) => { if ( er ) console.error( er ) else console.log( stat ) } )という風にやってたと思います。
ファイルが複数の場合
複数のファイル、例えば 'x', 'y' という名前のファイルの stat を取得してから何かやりたいような場合、時間のかかる逐次処理でよければ
sample.jsconst 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.jsconst 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.jsconst fs = require( 'fs' ) const { promisify } = require( 'util' ) promisify( fs.stat )( 'x' ).then( _ => console.log( _ ) ).catch( _ => console.error( _ ) )素でやる
util.promisify がある今となってはもうやることはないと思いますが、参考までに。
sample.jsconst 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.jsconst fs = require( 'fs' ) const { promisify } = require( 'util' ) Promise.all( [ 'x', 'y' ].map( _ => promisify( fs.stat )( _ ) ) ).then( _ => console.log( _ ) ).catch( _ => console.error( _ ) )関数化してみる
sample.jsconst 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 を使わない手はありませんね!
- 投稿日:2019-12-09T14:58:58+09:00
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.jsonnodebrew, node.js, npmのインストール
- hokaccha/nodebrewで記載があるコマンドをそのまま実行します。
$ 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.3Google 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();
- ここまでやるとディレクトリ構成のような形になっていると思います。
やってみる
- こんな感じで入れてみて…
- こんな感じで出る。
- firefoxだとjson形式を開くと色々解析してくれる(が、今回はシンプル極まりないので特に意味なしですが)
補足
- Google Translation API v3 はまだβ版なので注意しよう。
- 早くphp版ライブラリでないかぁと思ったり。
- Google Translation API v3 には無料枠がある。
まとめ
- 自分の担当案件ではよく「日英のHPをCMSで作る」という要件がよくあるのでこれからも多用していくかなと思っています。
- 正直、ロジックを作るところよりもGCPの設定のほうが難しかった。
FORK Advent Calendar 2019
9日目 Vuetifyのdatepickerを使って【和暦】+【年度/月】pickerを作ってみた @BigFly
11日目 @talow1 さんよろしくおねがいします。
- 投稿日:2019-12-09T12:58:26+09:00
.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
nodebrew
とn
が入っており環境変数で.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
- 投稿日:2019-12-09T08:09:42+09:00
【解決済】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 エラー原因箇所を修正して保存します
var sharedBlacklist = [
/node_modules[\/\]react[\/\]dist[\/\]./,
/website\/node_modules\/./,
/heapCapture\/bundle.js/,
/.\/tests\/./手順3 Power Shellでプロジェクト起動"expo start"
参考資料
https://github.com/facebook/metro/issues/453#issue-comment-box
- 投稿日:2019-12-09T03:24:24+09:00
express-graphql + TypeScript で始めるGraphQL API Server
はじめに
この記事は
express-graphql
でNode.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 typescriptyarn add -D ts-node tsconfig-pathstsconfig.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.tsexport 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.tsimport { 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.tsimport { memberList } from '@/data'; export const getMemberList = () => Promise.resolve(memberList);Query
Query
は REST APIのGET
に相当します。src/fields/member/queryimport { 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.tsimport { memberQuery as query } from '@/fields/member/query'; export const memberField = { query };src/fields/index
実装したすべてのモジュールを
Root Query
としてまとめてエクスポートします。src/fields/index.tsimport { 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.tsimport * 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 } }GraphQL Mutation
続いて
Mutation
を実装します。
この例では新メンバーを追加するMutation
を実装します。実装
Types
入力側のパラメータの型を追加します。
src/fields/member/types.tsexport 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.tsexport 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.tsimport { 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.tsimport { 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.tsimport { 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
最後に
Schema
にMutation
を追加します。src/index.tsimport { 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 } }さいごに
GraphQL
のメリットとして、書いたコードがそのままドキュメントになることが挙げられます。
予めdescription
を書くルールなどを定めておけばAPI ドキュメントを用意する必要がなくなります。実際に手を動かして、
GraphQL
を体験してみてください!!!以上