2019年10月4日金曜日

Actions on Googleで使うFirebase Functionsの開発用ローカルサーバーを作る

Actions on Google のドキュメントやチュートリアルだとあまり詳しく書かれていない、Firebase Functionsの代わりにローカルサーバーを使用する環境を構築する方法です。

コードを1行変えて動作を確認するにも毎回Firebaseにデプロイして…という手間から解放されます…!

【参考記事】
Speed up Actions on Google development workflow with local fulfillment
https://medium.com/voiceano/speed-up-actions-on-google-development-workflow-with-local-fulfilment-7de17d5f166f

実行コマンドやjsonの設定内容も上記記事から引用させていただいています。大変参考になりました。ありがとうございます!

環境

Windows 10 Home (バージョン1903)
Node v10.16.3
firebase-functions @3.2.0
firebase-admin @8.6.0
ngrok @3.2.5
nodemon @1.19.2

設定手順

躓いたポイントは後でまとめるとして、まず全部うまくいった場合の手順を。

すでに、Firebase FunctionにデプロイしてDialogflowと連動して動作しているプロジェクトがある前提で進めます。

ngrok をインストール

ローカルで実行するサーバーにDialogflowがアクセスできるようにするためのものです。

下記を実行してインストール。

npm install ngrok --save-dev

functions ディレクトリ内の package.json の "scripts""tunnel" を追加。
(おそらく scripts の項目自体はすでに存在するので、そこに足す感じで大丈夫です。)

"scripts": {
    "tunnel": "ngrok http 8000"
}

これで、node.jsコンソールで functions ディレクトリに移動して npm run tunnel を実行すると、http://localhost:8000 にアクセスできるオープンなURLが得られるようになります。

fulfillment のコードを編集して、本番用とローカル用を分ける

Actions on Googleのチュートリアル等に従って作成した感じだと、fulfillmentはindex.jsのファイルが一つあるだけだと思いますが、これを

  • app.js(intent handlers. リクエストを受けてActionがどんな答えを返すかを決めるコード)
  • index.js()
  • local.js(ローカル実行する際に使用するExpressサーバーの設定)

の3つに分けます。

元のindex.jsがこんな感じだったら

// 元の index.js

const functions = require('firebase-functions'); // 変更前

const {
  dialogflow
} = require('actions-on-google');

const app = dialogflow();

app.intent('Default Welcome Intent', (conv) => {
  conv.ask("こんにちは。ご注文をどうぞ。");
});

app.intent('menu', (conv) => {
  conv.ask("こちらのメニューがお選びいただけます。");
});

exports.fulfillment = functions.https.onRequest(app); // 変更前

こんな感じ。 module.exports = app でexportします。

// app.js

// const functions = require('firebase-functions'); は index.js に

const {
  dialogflow
} = require('actions-on-google');

const app = dialogflow();

app.intent('Default Welcome Intent', (conv) => {
  conv.ask("こんにちは。ご注文をどうぞ。");
});

app.intent('menu', (conv) => {
  conv.ask("こちらのメニューがお選びいただけます。");
});

module.exports = app; // 変更後

Firebase Functionsの環境で使う用の設定を index.js ファイルに書きます。

// index.js

const functions = require("firebase-functions");
const app = require("./app"); // app.js から export したモジュールを使う

exports.fulfillment = functions.https.onRequest(app);

const functions = require("firebase-functions");exports.fulfillment = functions.https.onRequest(app); を index.jsに残して、あとはごっそり app.js に分離する感じですね。

そしてローカルの環境で使う用の設定を local.js ファイルに書きます。

// local.js

// テスト用にlocalで動かすExpressサーバー用のコード

const express = require('express');
const bodyParser = require('body-parser');

const app = require('./app'); // app.js から export したモジュールを使う
const myApp = express().use(bodyParser.json());

// local endpoint にアクセスできる状態かどうかの確認用
myApp.get('/', (req, res) => {
  res.send('The connection was successful')
});

// Dialogflow agent からの POST request を app モジュールで扱う
myApp.post('/', app);

myApp.listen(process.env.PORT || 8000);

コードの準備はこれで完了です。

nodemon をインストール

Expressサーバーの実行、およびコードの変更を検知して、自動的にサーバーを再起動して変更を反映してくれるものです。

下記を実行してインストール。

npm install nodemon --save-dev

functions ディレクトリ内の package.json の "scripts""dev" を追加。

"scripts": {
    "tunnel": "ngrok http 8000",
    "dev": "nodemon local.js"
}

環境変数の設定

これは元記事にはないのですが、必要でした。

  • GCLOUD_PROJECT にFirebaseのプロジェクトID
  • FIREBASE_CONFIG にservice-account.json へのパス
    を設定します。

コントロールパネルから設定しました。
ダブルクォートとかで囲む必要はないです。ユーザー環境変数で大丈夫でした。

npm run dev を実行するコンソールから

set GCLOUD_PROJECT=my-app
set FIREBASE_CONFIG=C:\Path\To\my-app\functions\service-account.json

とやっても設定できますが、これだとコンソールを開きなおすと再設定が必要です。
(.bashrc的なものに書くやり方もあると思うんですが調べてません…すみません)

【参考】環境の構成 | Firebase
https://firebase.google.com/docs/functions/config-env?hl=ja#automatically_populated_environment_variables

ローカルWebサーバーの実行

ローカルでExpressサーバーを実行して、それを公開URLでアクセスできるようにします。

node.js コンソールを2つ開いて、それぞれで

  • npm run dev (変更を監視する)
  • npm run tunnel (ローカルサーバーを公開する)

を実行します。

npm run tunnel を実行すると以下のような画面が表示されるので、 Forwarding に表示されている https で始まるURLを、DialogflowのWebhook URLに設定します。

Dialogflow > Fulfillment > 「URL」欄

これで、Dialogflowはローカルサーバーと通信するようになります。

Firebase Functionにデプロイしたときと同じく、Actions on Googleのテスト画面で動作を確認できます。

コードに変更を加えて保存すると nodemon が自動的にリスタートして、変更が反映されます。(Firebase Functionにデプロイしなおすよりずっと速いです!)

※ngrokの無料プランでは、 ngrokを立ち上げ直すとURLが変わる ので、起動のたびにDialogflowに再設定が必要です。
接続数や連続起動しておける時間の制限もあります。でも8時間とかなので、無料プランで十分使えると思います。

※console.log も使えます。( npm run dev したコンソールに流れてくる。)

本番(Firabase Functions)へのデプロイ

また本番につなげたいときは、下記コマンドでデプロイして、DialogflowのWebhook URLを差し替えれば、今度はFirebase Functionsと通信するようになります。

firebase deploy --only functions
設定は以上です!

躓いたポイント

npm ~ のコマンドがコンパイルエラーになる

npm 系のコマンドはnode.jsコンソールから実行するとうまくいきました。

私の環境設定が何か漏れているのかもしれませんが、Powershell やコマンドプロンプトでは、下記エラーが起きてうまくいきませんでした。

エラー: 文字が正しくありません。
コード: 800A03F6
ソース: Microsoft JScript コンパイル エラー

環境変数の設定で迷走した件

(2019/9月始め頃の情報)

当初、下記のようなエラーが出てうまくいかなかったのですが、

Microsoft Windows [Version 10.0.18362.295]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Users\Owner>cd workspace

C:\Users\Owner\workspace>cd my-app

C:\Users\Owner\workspace\my-app>cd functions

C:\Users\Owner\workspace\my-app\functions>npm run dev

> functions@ dev C:\Users\Owner\workspace\my-app\functions
> nodemon local.js

npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! functions@ dev: `nodemon local.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the functions@ dev script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\Owner\AppData\Roaming\npm-cache\_logs\2019-09-03T20_36_58_429Z-debug.log

この時は、 GOOGLE_APPLICATION_CREDENTIALSFIREBASE_CONFIG という環境変数両方に、 service-account.json ファイルへのパスを設定してあげるとうまくいきました。

その時参考にしたドキュメント(私の記憶が定かなら内容変わってる気がします…)
GOOGLE_APPLICATION_CREDENTIALS
https://cloud.google.com/docs/authentication/production?hl=ja#obtaining_and_providing_service_account_credentials_manually
FIREBASE_CONFIG
https://firebase.google.com/docs/functions/config-env?hl=ja#automatically_populated_environment_variables

(2019/10/03 最新)

久しぶりにローカルサーバーを起動しようとしてみたら、以前の設定では失敗するようになってまして。

C:\Users\Owner\workspace\my-app\functions>npm run dev

> functions@ dev C:\Users\Owner\workspace\my-app\functions
> nodemon local.js

[nodemon] 1.19.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] starting `node local.js`
undefined:1
C:\Users\Owner\workspace\my-app\functions\service-account.json
^

SyntaxError: Unexpected token C in JSON at position 0
    at JSON.parse (<anonymous>)
    at Object.setup (C:\Users\Owner\workspace\my-app\functions\node_modules\firebase-functions\lib\setup.js:38:43)
    at Object.<anonymous> (C:\Users\Owner\workspace\my-app\functions\node_modules\firebase-functions\lib\index.js:59:9)
    at Module._compile (internal/modules/cjs/loader.js:778:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at Function.Module._load (internal/modules/cjs/loader.js:585:3)
    at Module.require (internal/modules/cjs/loader.js:692:17)
    at require (internal/modules/cjs/helpers.js:25:18)
[nodemon] app crashed - waiting for file changes before starting...

環境変数設定されていないときのメッセージも変わっている…どれかのバージョンアップが原因?
(確かにこの間に、Regionを変更するためにプロジェクトを別に立て直したり、ついでにfirebase 関連のいろいろのバージョンを上げたりした)

「Initializing firebase-admin will fail」って言ってるってことは、これを使おうとしてるのは firebase-admin SDK?

C:\Users\Owner\workspace\my-app\functions>npm run dev

> functions@ dev C:\Users\Owner\workspace\my-app\functions
> nodemon local.js

[nodemon] 1.19.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] starting `node local.js`
Warning, FIREBASE_CONFIG and GCLOUD_PROJECT environment variables are missing. Initializing firebase-admin will fail

いろいろ悩んでこのドキュメントを読み直したら、

FIREBASE_CONFIG
https://firebase.google.com/docs/functions/config-env?hl=ja#automatically_populated_environment_variables

process.env.GCLOUD_PROJECT: Firebase プロジェクト ID を提供します

とある。

ので、 GCLOUD_PROJECT という環境変数にFirebaseのプロジェクトIDを設定してみたら、起動するようになった!

C:\Users\Owner\workspace\my-app\functions>npm run dev

> functions@ dev C:\Users\Owner\workspace\my-app\functions
> nodemon local.js

[nodemon] 1.19.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] starting `node local.js`
Warning, estimating Firebase Config based on GCLOUD_PROJECT. Initializing firebase-admin may fail

FIREBASE_CONFIG環境変数が設定されていないと上記のWarningが出ますが、起動はします。
FIREBASE_CONFIG にservice-account.jsonのパスを設定すれば、Warningも出なくなります。

そして、どうやら GOOGLE_APPLICATION_CREDENTIALS 環境変数はいらなくなったようです…?

(当初 GCLOUD_PROJECTGOOGLE_APPLICATION_CREDENTIALS に空目して、設定してるはずなんだけどな~…と読み落としていたのでだいぶ悩みました…)

0 件のコメント:

コメントを投稿