- 投稿日:2020-07-10T22:40:23+09:00
【学習記録1】Node.js
はじめに
エンジニア目指して現在独学でプログラミング学習中です。
学習を始めて既に3週間ほど経っていて、HTML,CSS,JavaScriptの基礎の学習を終えたところです。
学習の振り返り・記録として、今日から学習内容を投稿していきたいと思います。今日の学習内容
- Progate Node.js Ⅰ~Ⅲ
- Node.jsのローカル開発環境の準備
- MySQLのインストール(すでにProgateで学習を終えたため、自分のPCにインストール)
- Node.jsとMySQLの接続
Node.jsのローカル開発環境の準備~Node.jsとMySQLの接続は、基本的にProgateのコラムに書いてあった内容をそのまま行いました。
コマンドプロンプトやターミナルについても少ししか知りませんでしたが、書いてあった内容通り進めていくと何とかなりました。
(コマンドについてはもっと勉強すべきだと思いました。。。)
- 投稿日:2020-07-10T19:07:58+09:00
UbuntuにNodeJSをインストールする
sudo apt-get update sudo apt-get install build-essential libssl-dev curl -y curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - sudo apt-get install -y nodejs
- 投稿日:2020-07-10T18:44:42+09:00
初心者|node.jsのREPLでファイルをリネーム
以前に初心者|node.jsでリネーム。フォルダからファイル名一覧を取得し一括変更するという記事を書きました。
でも「いちいちファイルを作成して実行するのは面倒やなぁ」と思い、node.jsのREPL(リプル)で実行することにしました。
今回したかったことは、あるディレクトリ内にある複数の画像ファイルの名称を一括で変更することです。変更した名称には連番もつけます。
要件
- node.js がインストールされている
- node.jsのREPLを使いたい
- 特定のフォルダ内のファイルを一括リネームしたい
- ファイル名に連番もつけたい
- 対象のファイルを種類(拡張子)で抽出したい(フィルタリングしたい)
例
リネーム前 リネーム後 IMG_xxxx.jpg newFileName-●.jpg
- リネーム前の名称は何でもいい
●
は連番そもそもMACには、一括リネーム機能がついています
- 対象ファイルを選択して、右クリック
●項目の名前を変更...
をクリック- あとは、よしなに...
本題
1)ターミナルを起動する
- リネームするファイルがあるディレクトリでターミナルを開いてください
- (例) imeges ディレクトリ内に 24個の jpgファイルがあります
2)REPLの起動
terminal$ node
node
+ エンターキーでnode.jsのREPLが起動します3)モジュール読み込み
REPL(node.js)$ const fs = require("fs"), path = require("path");
- nodeのモジュール
fs
とpath
を読み込みます。4)ファイル名の取得
REPL(node.js)$ const dir = "./", fileNameList = fs.readdirSync(dir), filteredFileNames = fileNameList.filter(RegExp.prototype.test, /.*\.jpg$/);
dir = "./"
では、カレントディレクトリをセットしていますfileNameList = fs.readdirSync(dir)
で、カレントディレクトリ内のファイル名一式を取得しています(この時点では隠しファイルなどjpg以外のファイルも読み込んでいます)。filteredFileNames = fileNameList.filter(RegExp.prototype.test, /.*\.jpg$/)
で、jpgファイルに絞っています(jpg以外のファイルを捨てています)。jpg以外のファイルをリネームする場合は\.jpg
を任意の拡張子に書き換えてください。5)変更後のファイル名を設定
REPL(node.js)$ const newName = "newFileName-"; $ let i = 1;
newName = "newFileName-"
で、新しいファイル名をセットしています。任意に書き換えてください。i = 1
は、ファイル名に付与する連番のスタート値です6)ファイル名の一括変更
REPL(node.js)$ filteredFileNames.forEach((fileName) => { const filePath = {}; filePath.before = path.join(dir, fileName); filePath.after = path.join(dir, newName + i + ".jpg"); fs.rename(filePath.before, filePath.after, (err) => { if (err) throw err; console.log(filePath.before + " -> " + filePath.after); }); i++; });
filePath.after = path.join(dir, newName + i + ".jpg")
でリネーム後のファイル名(名称+連番)をセットしています。- 拡張子を忘れないように、ご注意ください。
i++;
を忘れると、全ファイルが同じ名称 = ファイルが1つだけになるので、ご注意くださいリネームができれば成功です。
7)REPLの終了
REPL(node.js)上で キー
control + c
を2回するとREPLが終了できます。
- 投稿日:2020-07-10T15:03:56+09:00
Node-redでリダイレクトさせるページを作る
目的
「チラシに印刷したQRからの流入か店頭のポスターのQRからの流入か測りたいからリダイレクトページ欲しいよね。」
みたいなよくある話。Node-redのフロー
node.json[{"id":"e64dc550.49f46","type":"http in","z":"d995ebbc.d3d4b","name":"","url":"/qr","method":"get","upload":false,"swaggerDoc":"","x":90,"y":60,"wires":[["caf1c45e.9cfef8"]]},{"id":"f641092c.042f4","type":"http response","z":"d995ebbc.d3d4b","name":"","statusCode":"","headers":{},"x":450,"y":60,"wires":[]},{"id":"40a104b1.96aaf4","type":"http in","z":"d995ebbc.d3d4b","name":"","url":"/redirect","method":"get","upload":false,"swaggerDoc":"","x":110,"y":120,"wires":[["8a5a3300.a937d"]]},{"id":"8a5a3300.a937d","type":"template","z":"d995ebbc.d3d4b","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<h1>This is the payload: {{payload}} !</h1>","output":"str","x":290,"y":120,"wires":[["fb6afad3.a8fc68"]]},{"id":"fb6afad3.a8fc68","type":"http response","z":"d995ebbc.d3d4b","name":"","statusCode":"","headers":{},"x":450,"y":120,"wires":[]},{"id":"caf1c45e.9cfef8","type":"function","z":"d995ebbc.d3d4b","name":"リダイレクト設定","func":"msg.res.redirect(\"/redirect\")\nreturn;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":270,"y":60,"wires":[["f641092c.042f4"]]}]functionノードの中身
function.jsmsg.res.redirect("/redirect") return;functionノードでクエリーを見てDBに追加してねー。もしくは、tenplateでGA追加して計測。
- 投稿日:2020-07-10T13:47:23+09:00
[node.js]スレッドへの引数の渡し方
非同期の動作をさせたくて、スレッド作成の方法を勉強しましたが、
引数の渡し方についてあまり情報がなかったので書き留めます。javascriptにおいてスレッドという呼び方が正しいのかはまだわからないですが
(基本的にシングルスレッドで動作するものなので、どういう仕組みかは厳密にはまだ不明なためです)
動作させてみたところ非同期に動作しているように見えました。環境
macOS : 10.15.5 Catalina
node.js : v14.3.0スレッド呼び出しの種類について
非同期スレッドの作り方は二種類あり、①クラスタ②子プロセス呼び出し
です。簡単に以下にそれぞれの使い方を書きます。
①は呼び出し元のjsファイルが複数個同時に動作するイメージです
②は呼び出し元とは異なるjsファイルが同時に動作するイメージです。クラスタ呼び出し
cluster.jsvar cluster = require('cluster'); if (cluster.isMaster) { console.log('parent'); cluster.fork(); } else { console.log('child'); }上記コードの実行結果は以下です
parent childこの場合、それぞれの(子)スレッドに引数を渡すことは(試した限り)出来ませんでした。
用途的には全く同じイベントハンドラを複数持つとパフォーマンスが上がる場合がよいとおもいますが、
それについてまだ知識はありません。子プロセス呼び出し
以下のような手順です。
引数を渡すには、fork()の第二引数にarrayを指示します。
child_process.fork( 呼び出すjsファイル名 , 引数配列 );
parent.jsvar child_process = require('child_process'); var c = child_process.fork(__dirname+'/child' , ['foo' , 'bar' ] );呼ばれる子プロセスは、
process.argv配列に、親から指示された引数が格納されています。
以下の例は全部の引数要素をダンプします。child.jsprocess.argv.forEach( function(item) {console.log("arg:" + item);} ); //dump args実行の仕方と結果は以下です。
$ node parent arg:/Users/***/.nodebrew/node/v14.3.0/bin/node arg:/Users/***/***/child arg:foo arg:barprocess.argv[0] : node 実行ファイルのパス
process.argv[1] : 実行している js ファイルのパス
process.argv[2] : 引数配列 要素 [0] : 今回は親プロセスが「foo」を指示しています。
process.argv[3] : 引数配列 要素 [1] : 今回は親プロセスが「bar」を指示しています。実際に引数を参照するには process.argv[2]〜先を読み込みするようにします。
この呼び出し方ならば、スレッド毎に親で仕事内容を指示することができます。親-子間の通信
子プロセスの場合には親子間でメッセージやりとりができます。
両者ともにon()が受信ハンドラ、(引数がメッセージ内容)、send()が送信処理。parent.jsvar child_process = require('child_process'); var c = child_process.fork(__dirname+'/child' , ['foo' , 'bar' ] ); c.on('message', function (msg) { console.log('[Parent]received msg = [' + msg + ']'); }); //msg handler c.send('Hello');child.jsprocess.on('message', function(msg) { console.log('[Child]received msg = [' + msg + ']' ); if(msg == 'Hello') process.send('Hello by ' + process.argv[2]); })実行結果は以下。
$ node parent [Child]received msg = [Hello] [Parent]received msg = [Hello by foo]ためしにたくさん子プロセスを呼び出してみた。
これは参考ですが、たくさん呼び出すとどうなるものかを試してみました。
parent.jsvar child_process = require('child_process'); const n_child = 16; var _childlen =[]; for( var i = 0 ; i < n_child ; ++i) { _childlen[i] = child_process.fork(__dirname+'/child' , [ i ] ); _childlen[i].on('message', function (msg) { console.log('[Parent]received msg = ['+ msg + ']' ); }); } _childlen.forEach( function(a) { a.send('Hello'); });child.jsprocess.on('message', function(msg) { console.log('[Child]received msg = [' + msg + ']' ); if(msg == 'Hello') process.send('Hello by ' + process.argv[2]); })実行結果は以下。
parentのsend()の一連の処理と、chilldの返信処理が非同期(同時)に実行されていることがわかります。childからのメッセージを受信したときのダンプ([Parent] received)が順不同なのが興味深いです。
イベントハンドラをどの順番に処理するかの内部的な処理によるものですが、
(あくまでも想像です)
親プロセスのイベント処理待ちキューに、順不同に入っていると思われて、
childの送信処理(16個分)が非同期に行われていることが言えるのではと思います。
単純に考えて、それぞれの子プロセスが個別のスレッドとして動作しているのならかなり効率は良いと思いました。
パフォーマンスに困ったら、これを利用して並列処理なども手段として検討できるでしょう$node parent Child]received msg = [Hello] [Parent]received msg = [Hello by 0] [Child]received msg = [Hello] [Child]received msg = [Hello] [Child]received msg = [Hello] [Child]received msg = [Hello] [Parent]received msg = [Hello by 3] [Parent]received msg = [Hello by 5] [Parent]received msg = [Hello by 6] [Parent]received msg = [Hello by 1] [Child]received msg = [Hello] [Parent]received msg = [Hello by 4] [Child]received msg = [Hello] [Parent]received msg = [Hello by 2] [Child]received msg = [Hello] [Parent]received msg = [Hello by 7] [Child]received msg = [Hello] [Parent]received msg = [Hello by 11] [Child]received msg = [Hello] [Parent]received msg = [Hello by 8] [Child]received msg = [Hello] [Child]received msg = [Hello] [Parent]received msg = [Hello by 10] [Parent]received msg = [Hello by 12] [Child]received msg = [Hello] [Parent]received msg = [Hello by 9] [Child]received msg = [Hello] [Parent]received msg = [Hello by 14] [Child]received msg = [Hello] [Parent]received msg = [Hello by 13] [Child]received msg = [Hello] [Parent]received msg = [Hello by 15]OSのプロセスをみてみると、やはり子ごとにプロセス(スレッド)が生成されていました。
6049 s000 S+ 0:00.01 grep node 6023 s002 S+ 0:00.20 node parent 6024 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 0 6025 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 1 6026 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 2 6027 s002 S+ 0:00.11 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 3 6028 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 4 6029 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 5 6030 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 6 6031 s002 S+ 0:00.11 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 7 6032 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 8 6033 s002 S+ 0:00.09 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 9 6034 s002 S+ 0:00.09 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 10 6035 s002 S+ 0:00.09 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 11 6036 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 12 6037 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 13 6038 s002 S+ 0:00.10 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 14 6039 s002 S+ 0:00.09 /Users/***/.nodebrew/node/v14.3.0/bin/node /Users/***/webserver_nodejs/child 15もっとたくさん呼び出してみた。
上記コードの子プロセス数をたくさんにしてみました。
parent.js: const n_child = 4096; :結果は以下
$node parent child : 0 child : 1 : : child :385 child :405 (libuv) kqueue(): Too many open files in system /Users/***/.nodebrew/node/v14.3.0/bin/node[7040]: ../src/tracing/agent.cc:55:node::tracing::Agent::Agent(): Assertion `(uv_loop_init(&tracing_loop_)) == (0)' failed. : : Error: ENFILE: file table overflow, uv_pipe_open : SystemErr子プロセスの数が400個を超えたところで、エラーが表示されました。
OSのスレッド数の限界がくるのかなと思ったのですが、これはnode.jsシステムのlibuv(IO関連処理を行う部分)が扱うパイプの個数の限界のようです。
親子通信で使用しているものと思われます。
実行中、この記事を書いているブラウザも、読み込みエラーになってしまったので、OSに対しても負荷がかかったと思われます。
プロセスが多くなりすぎた原因で(各々初期化などある程度の処理が走ると思われ)
ネットワークの処理が追いつかなくなったように感じます。がmacOS自身が飛んだりということはありませんでした。
過剰に呼び出しすぎるのはよくないですが、もしかしたら時間を開けて子プロセスの起動を行えば、問題がなくなるかもしれません。
(すべて想像です)
- 投稿日:2020-07-10T09:45:25+09:00
【自分用メモ】JavaScript
- 投稿日:2020-07-10T08:17:43+09:00
プログラムの怖いところ ライブラリのバージョンアップ
はじめに
本記事はライブラリのバージョンアップが原因でプログラムが動かなくなり、どの様に原因を調査して解決まで行き着いたかについて、システム開発の一助となるストーリーをご紹介します。
概要
とあるtoCのシステムにおけるバックエンドのプログラム(API)で軽微な修正を行い、デプロイしたときの話になります。
本番環境と開発環境が存在し、開発環境でテストした上で本番環境でリリース作業を実施。本番環境でデプロイ後、システムの一部機能で正常性が確認できない事態が発生しました。
バックエンドはDockerで開発を行っているため、デプロイ時に
docker compose build
でビルドを毎回行っています。デプロイ自体は成功しているため、ライブラリのバージョンアップが関係しているのではないかと予想していましたが、のちに調査が難航することになるとは、このときは予想していませんでした。。当該事象発生時は原因究明に至らなかったため、切り戻しを実施し、リリース作業は中断。翌日に原因調査を行いました。
原因調査
前提として本番環境と開発環境で使用していたソースは、一部機能をマージしてないなど環境差異は発生していましたが今回発生した事象に関係はありません。
調査を開始し、開発環境で事象を再現させるために確認を行いましたが、同様の事象は発生しませんでした。その後、本番環境で使用したソースを開発環境でデプロイすると事象が再現しました。
開発環境で事象再現後、デバッグするために本番環境で使用したソースの不具合が発生している箇所に
console.log(e)
でログ出力する様にしてデバッグを行いました。不具合が発生している箇所に対するAPIのリクエストを実行し、サーバ側のログを確認すると、データベースのあるテーブルのカラムが見つからないと言うエラーメッセージが出力されていました。
仮説としてプログラムで利用していたORMライブラリのバージョンが上がったため、今まで許容していたデータベースに対する接続がエラーになっているのではないかという考えに辿りつきました。
解決方法
本番環境で稼働中のコンテナで生成された
yarn.lock
ファイルと、開発環境でデプロイしたコンテナのyarn.lock
ファイルのORMライブラリのバージョンを比較すると、開発環境のコンテナのマイナーバージョンが1桁だけ上がっていることを確認しました。検証として
package-lock.json
のORMライブラリのバージョンを、本番環境で稼働中のコンテナで生成されたバージョンと同じバージョンに固定すると、事象が再現しないことが確認できました。よって暫定対応としては、ORMのライブラリのバージョンを固定しないことで一旦は解決しましたが、今後の運用として色々と課題を認識しました。
DevOps
DevOps観点で感じた課題について以下に記載します。
※本記事の内容は、あくまで考え方の一例であり、必ずしも全ての考え方がシステムに適合したり、ここに書いている内容で満たされている訳ではありません。
ライブラリのバージョン固定
保守性を高める場合はライブラリのバージョンを上げないように固定することが望ましいですが、セキュリティとトレードオフになります。また、バージョンアップする場合は確認の工数もかかります。npm outdated
コマンドを実行すると、現在インストールされているバージョン、現在のバージョン指定でインストールされる最新バージョン、リリースされている最新バージョンが表示されます。テストの再現性
当たり前ですが極力環境差異をなくしたテスト方法の考案及びテスト環境を構築し、テストを実施することが望ましいです。※参考:3.6 本番環境とテスト環境の差異に関する教訓(T6)本質的なアプローチ
今回の様な事象は技術的な手段で解決する以前に、開発や運用ポリシー等を定義していればこの様なリスクを軽減する可能性は上がります。おわりに
本事象を通して改めて学んだのは、プログラムは数字1桁が変わるだけで動かなくなるという恐ろしさと、DevOpsの重要性です。
DevOpsは文化です。DevOpsは人が作る必要があるため、言うだけは簡単ですがシステムのあるべき姿を計画し、実行して継続することは大変です。
この失敗を次に生かしてシステム開発のライフサイクルを短縮し、ソフトウェア品質の高い継続的デリバリーを実現していきたいと思いました。
- 投稿日:2020-07-10T03:08:17+09:00
Kabanero を使ったクラウド・ネイティブなアプリ開発(VSCode + Codewind)を体験 - その後
2020年7月8日に実施された 初夏のIBM Dojo #9 Kabaneroを使ったクラウド・ネイティブなアプリ開発を体験 ワークショップに参加してきました。講師 @osonoi さんです。
以前に翻訳した Kabanero と、それと関連する Developer Experience にある「VS Code を使用して Kabanero を試す」を体験できるオンラインセッションでした。セッション資料 がわかりやすいので、ぜひ参照してください。
VSCode + codewind の環境、とっても手軽で便利。監視、ビルド、実行など全て Docker 環境で実行され、処理系のインストール無しでさくさく試せる。いろんな言語/環境を試してみたい。
と、Twitter で呟きましたが、すごく参考になったので、ちょっと自分でも中身を確認してみました。自分なりに迷いつつのメモなので、間違えていたり、意味不明だったらスミマセン。
お手軽に開発環境をセットアップ
セッション資料に従い、アプリケーション開発環境をセットアップしてみます。前提となるソフトウェアは以下の2つだけ。
VSCode の 拡張機能 (Extension) で
Codewind
を探してインストールします。
新規プロジェクト作成で、Kabanero リポジトリにあるKabanero Node.js Express scaffold template
を選択します。
後は開発環境などが自動でセットアップされます。ビルド環境も実行環境も Docker コンテナ化されているため、Node.js など開発に必要なツールがインストールされていなくても問題ありません。今回のセットアップも Docker 上にコンテナが追加されるだけで、ローカルにインストールされないため、気軽に試すことができます。アプリを起動すると Node.js Express が動作し、シンプルな Webページが表示されます。
さあ、後はサンプルコードを修正して、いろいろ試すだけ。ソースコードに修正を保存すれば、ビルドが実施され、すぐにWebページに反映されます。以上、ここまでの手順の詳細は IBM Dojo の セッション資料 をご参照ください。IBM Developer Dojo のどれかに参加すると、Dojo サポート用の Slack チャネルに招待されるので、そこで質問もできます。
生成された環境を眺めてみる
さてこのまま、環境はブラックボックスとして、Node.js + Express のアプリ開発を開始してもいいのですが… せっかくですから、自動生成された環境を少し眺めてみましょう。
VSCode のワークスペース
まず VSCode のワークスペースを見てみると、以下のような構成になっています。
ページを表示しているのはroutes/index.js
ですね。
表示に利用されている Pug 形式のテンプレートviews/index.pug
は以下のように非常にシンプルでした。
これらなのですが、探してみると GitHub の appsody アカウントにある stacks リポジトリ 配下にある /incubator/nodejs-express/templates/scaffold フォルダが元になっているようです。Docker で動作するコンテナについて
さて、上記の Web ページを表示中、Docker は以下のように3つのコンテナを実行していました。
最初のコンテナ (イメージは kabanero/nodejs-express:0.4) がアプリを実行している環境のようですので、コンテナに sh アクセスして、動作しているプロセスの状態を見てみます。
自身の /bin/sh と ps コマンド以外のプロセスは以下のような感じ。USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND default 1 1.8 0.3 105308 7492 pts/0 Ssl+ 09:43 7:34 /.appsody/appsody-controller --mode=run default 42 0.0 2.2 743756 45576 ? Ssl 09:43 0:00 npm default 58 0.2 4.2 1268880 86480 ? Sl 09:43 1:08 node server.jsついでに真ん中のコンテナ(イメージは eclipse/codewind-performance-amd64:0.13.0) のプロセスはこちらで、ちょっと何やっているか不明なのですが、実行しているイメージ名から Codewind 本体のような気がします。アプリのビルド前から居ましたし。
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND 1000 1 0.0 1.5 741756 30936 ? Ssl 07:20 0:00 npm 1000 16 0.0 2.5 680096 52572 ? Sl 07:20 0:00 node server.js三番目のコンテナ (イメージは eclipse/codewind-pfe-amd64:0.13.0) のプロセスはこちらで、こちらはソースコードの更新をチェックしたり、最初のコンテナを起動してたり、いろいろ働いているようです。
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.1 11892 2652 ? Ss 07:20 0:00 sh -c /file-watcher/scripts/root-watcher.sh ${HOST_WORKSPACE_DIRECTORY} ${CONTAINER_WORKSPACE_DIRECTORY} root 33 0.0 2.1 676736 43460 ? Sl 07:20 0:00 npm root 61 0.0 0.1 11896 2916 ? S 07:20 0:00 /bin/bash ./npm-start.sh root 62 0.0 2.1 676368 44708 ? Sl 07:20 0:00 npm root 73 0.0 5.6 972028 115352 ? Sl 07:20 0:16 node server.js root 535 0.0 0.1 11896 2760 ? S 09:43 0:00 /bin/bash /codewind-workspace/.extensions/codewind-appsody-extension/appsody run --name cw-dojoyamacha root 536 0.0 0.0 23032 1408 ? S 09:43 0:00 /usr/bin/coreutils --coreutils-prog-shebang=tee /usr/bin/tee -a /codewind-workspace/.logs/dojo-yamacha root 542 0.0 0.5 116632 11840 ? Sl 09:43 0:00 /codewind-workspace/.extensions/codewind-appsody-extension/bin/appsody run --name cw-dojoyamachan-df39 root 603 0.0 1.4 45688 29636 ? Sl 09:43 0:00 docker run --rm -P --name cw-dojoyamachan-df39b4d0-c0eb-11ea-97e7-d775ccf52e96 --network codewind_netw root 620 0.0 0.0 23032 1320 ? S 16:11 0:00 /usr/bin/coreutils --coreutils-prog-shebang=tail /usr/bin/tail -q -F -c +0 /codewind-workspace/.logs/d三番目のコンテナ内の環境変数に、幾つか興味深い値がありましたので、転記します。
CODEWIND_VERSION=0.13.0 CONTAINER_WORKSPACE_DIRECTORY=/codewind-workspace ENABLE_CODE_COVERAGE=false HELM_HOME=/root/.helm HOSTNAME=72958642fbc6 HOST_HOME=C:\Users\z HOST_MAVEN_OPTS= HOST_OS=windows HOST_WORKSPACE_DIRECTORY=C:\codewind-data IMAGE_BUILD_TIME=20200612-133352 JAVA_HOME=/opt/java/jre LOG_LEVEL=info NODE_ENV=production PATH=/opt/java/jre/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PERFORMANCE_CONTAINER=codewind-performance-amd64:0.13.0そして気がついていませんでしたが、ローカルPCに
C:\codewind-data
なんてフォルダが生成されていました… 作業ディレクトリとほぼ同じ構成ですが、.vscode
フォルダが無いかわりに空のnode_modules
フォルダが存在します。
試しに c:\ ドライブ全体を対象にappsody
でファイル名検索した結果がこちら。c:\work\codewind\dojo-yamachan
がプロジェクト作成時に自身で指定した作業フォルダです。
ふーむ、なかなか興味深いですね。実際の起動プロセス
さて、作成したアプリがどう起動されるか追ってみましょう。実行用コンテナで
node server.js
とあるので、sh でコンテナの中に入り、server.js
ファイルの中でそれっぽい部分を探してみます。// Register the user's app. const basePath = __dirname + '/user-app/'; function getEntryPoint() { let rawPackage = fs.readFileSync(basePath + 'package.json'); let package = JSON.parse(rawPackage); if (!package.main) { console.error("Please define a primary entrypoint of your application by adding 'main: <entrypoint>' to package.json.") process.exit(1) } return package.main; } const userApp = require(basePath + getEntryPoint()); app.use('/', userApp({ server: server, app: app, log: pino, }));まずわかるのが、作成した自身のアプリが
/project/user-app/
ディレクトリに配置されているということです。
そしてこのディレクトリですが、Docker ランタイムにより、さきほど発見したローカルPCの
C:\codewind-data
配下のフォルダがマウントされ、永続化されていることがわかります。たぶんですが、作業フォルダの内容をビルドした結果 (今回は Node.js で webpack など前処理もないので単にファイルコピーのみ?) がこのローカルPC上のフォルダに配置され、それを実行環境の Docker コンテナの
/project/user-app/
ディレクトリにマウントすることで、即時反映できている、という仕組みのようです。で、さきほどの
server.js
ファイルのコードにあるgetEntryPoint()
関数の中を見ると、package.json
のなかのmain
エントリがアプリの本体を指定しているようで、今回だとapp.js
が指定されています。package.json"main": "app.js",そしてこの
app.js
を見ると、これが Express アプリの本体で、これでようやく最初に出てきたroutes/index.js
やviews/index.pug
ファイルに繋がります。app.jsmodule.exports = (/*options*/) => { // Use options.server to access http.Server. Example of socket.io: // const io = require('socket.io')(options.server) const app = require('express')() app.set('views', __dirname + "/views"); app.set('view engine', 'pug'); app.use('/', require('./routes')); return app; };起動順としては、以下のような感じですかね。
- Appsody が用意した実行用コンテナの
/project/user-app
にローカルPCのc\codewind-data
配下のプロジェクト用フォルダがマウントされる 【Appsody共通】- Appsody が用意した実行用コンテナ内の
/project/server.js
が/project/user-app/package.json
ファイルの main エントリを参照する【Appsody Node.js 系共通】- main エントリに指定された
/project/user-app/app.js
が実行される【Appsody Node.js Express 系共通?】- app.js によって Pug テンプレートエンジンがセットされ、
views/index.pug
をテンプレートとしてroute/index.js
が表示される。【今回の Stack 固有】うん、これでやっとスッキリしました。
というわけで
Appsody (VSCode + Codewind) で作成した Node.js + Express サンプルアプリの起動の仕組みをざっくり調べてみました。なかなか良く出来た仕組みだなー、と感心してみたり。
ただ、時間の関係もあり、ソースコードの変更をウォッチしているところ、ビルドしているところ、などはまだ調べていません。また時間を作って、コンパイルする Java などの環境(変化がわかりやすいので)を対象に調べてみたいな、などと思っています。なにかわかったら、メモ公開するかもしれません。
それではまた!
- 投稿日:2020-07-10T00:56:28+09:00
nodemailerでGmailのエイリアスのアドレスから送信できるようにする
以下ページの転載になります。ご了承ください。
nodemailerでGmailのエイリアスのアドレスから送信できるようにする - Yuto Hongo Portfolio
2020/07 時点での記事となります。
G Suite前提での手順となってしまいますが、参考になればと思います。
[ひとことでいうとこんな記事]
nodemailerからメール送信する際、自分の所持しているメルアドを利用せずにエイリアス(Info@, sales@ 等)のアドレスからの送信が可能です!
[こんな人におすすめ]
- メディアを作ることになったが、お問い合わせの自動返信機能で個人アドレスを使うわけにはいかない人
[目次]
- 参考記事
- 手順1. Google OAuth 2.0 設定
- 手順2. G Suite で エイリアスアドレスの追加
- 手順3. Gmail で エイリアスアドレスの追加・デフォルト設定
- 手順4. nodemailerでメール送信スクリプトを作成
- まとめ
参考記事
参考にさせていただいた記事です。ありがとうございます。
node.js 上の nodemailer で OAuth 2.0 を使って gmail からメールを送る
そして、nodemailerでのエイリアスアドレスでの送信に関してはstackoverflowのコメント欄を参考に設定をいたしました
Nodemailer send emails using original user account instead of alias
手順1. Google OAuth 2.0 設定
1-1 Google APIs でプロジェクト作成
- Google APIs ページで、「プロジェクトを作成」をクリック。(もしくは、プロジェクト選択タブをクリックし「新しいプロジェクト」をクリック)
- 「プロジェクト名」を入力し、「作成」をクリック。
1-2 OAuth 同意画面 の設定
- 「OAuth 同意画面」をクリック
- 「アプリケーション名」を入力
- 「保存」をクリック
1-3 OAuth 2.0 クライアントIDの作成
- 「認証情報」をクリック
- 「認証情報を作成」をクリックし、「OAuth クライアント ID」を選択
- 「アプリケーションの種類」で「Webアプリケーション」を選択し、「名前」を入力
クライアントIDとクライアントシークレットが表示されるので、保存しておく。
1-4 Gogle Developers OAuth 2.0 Playground で Reflesh Token の取得
- Google Developers OAuth 2.0 Playgroundページを開く
- 右上の「⚙歯車ボタン」をクリックする
- 以下のような情報を入力する
項目 値 OAuth flow Server-side OAuth endpoints Authorization endpoint そのまま (https://accounts.google.com/o/oauth2/v2/auth) Token endpoint そのまま (https://oauth2.googleapis.com/token) Access token location Authorization header w/ Bearer prefix Access type Offline Force approval prompt No Use your own OAuth credentials チェックボックスをクリック OAuth Client ID さきほど取得した クライアントID OAuth Client secret さきほど取得した クライアントシークレット
- 「Step 1」を選択
- 「Input your own scopes」に「 https://mail.google.com/ 」を入力し「Authorize APIs」を押す
- 「Google OAuth 2.0 Playground に移動」と表示されるので、対象のGoogleアカウントを選択
「Google OAuth 2.0 Playground が Google アカウントへのアクセスをリクエストしています」と表示されるので「許可」を選択
「Step 2」に進んだら「Exchange authorization code for tokens」を押す
手順2. G Suite で エイリアスアドレスの追加
2-1 Google管理コンソール
- Google Adminにアクセスし、「ユーザー」をクリック
2-2 ユーザーに紐づくエイリアスアドレスを作成
- 先程手順1を行ったGmailアカウントをクリック
- 「ユーザー情報」をクリック
- 「メールエイリアス」をクリックし、エイリアス名を入力したら「保存」を押す
手順3. Gmail で エイリアスアドレスの追加・デフォルト設定
3-1 エイリアスアドレスの追加・認証
- Gmail右上の「歯車ボタン」を押し、「すべての設定を表示」を押す
- 「アカウント」タブをクリックする
- 「名前」の欄に「他のメールアドレスを追加」とあるのでクリック
- 「エイリアスとして扱います」にチェックをつけ、「名前」と手順2で準備したエイリアスのアドレスを入力
- 確認メールでの認証が必要といわれるので、「確認メールの送信」をクリック
- 届いた確認メールのリンクをクリックするか、確認コードを入力し「確認」をクリック
3-2 エイリアスアドレスを送信メールのデフォルトに設定
- Gmailの設定画面に戻ると3-1で設定されたエイリアスアドレスが追加されています
- エイリアスアドレスのほうを「デフォルトに設定」をクリックする
(※これがないと、nodemailerで送信した際に、エイリアスのアドレスから送信されたことにならず、元のアドレスからの送付となってしまいます。)
手順4. nodemailerでメール送信スクリプトを作成
4-0 手順1で準備したものの確認
- Client ID
- Client Secret
- Reflesh Tken
4-1 nodemailerのpackageをインストール
npm
でもyarn
でも4-2 OAuthの情報をnodemailerに準備
const nodemailer = require('nodemailer') const auth = { type: 'OAuth2', user: 'alias@adress', // エイリアスのアドレス clientId: 'clientId', // Client ID clientSecret: 'clientSecret', // Client Secret refreshToken: 'refleshToken', // Reflesh Token } const transporter = nodemailer.createTransport({ service: 'gmail', auth })4-3 メール情報を準備・メール送信
const mailOptions = { from: `エイリアス <alias@mail.com>`, // エイリアスのアドレスと名前 to: `send_to@mail.com`, // TO bcc: `blind@mail.com`, // BCC subject: `Subject`, // タイトル text: `text` // 本文 } transporter.sendMail(mailOptions)まとめ
以上の方法で、エイリアスのメールアドレスを用いたnodemailerの送信が可能になりました。
ガッツリとシステムを組む場合にはこのような手法をとらないような気もしますが、スモールビジネスで小さくはじめたい方などは、Googleのようなプラットフォームにのっかるのも手ではないでしょうか?
最終的に Firebase Functions にメール送信は任せたいと思います。
- 投稿日:2020-07-10T00:19:27+09:00
Node.jsで複数ファイルを送るには
こんにちは、wattak777です。
一つだけファイルを送信する、というサンプルは幾つかあるのですが、複数ファイルの場合のサンプルをちょっと作ってみました。
サーバー側はmulterを使った以下のサンプル。
server.jsvar express = require( 'express' ) ; var app = express() ; var multer = require( 'multer' ) ; app.post('/file_upload', multer({dest: ファイルを置くパス}).single('my_file'), function (req, res) { console.log(req.file.path, req.file.originalname) ; res.sendStatus(200) ; }); var server = app.listen(12345, function() { console.log("listening at port %s", server.address().port) ; });で、本題のクライアント側は以下の実装。
request-promiseを使って同期的に送るようにしました。client.jsconst fs = require('fs') ; const request = require('request-promise') ; const FileNameList = [ 'test1.bin', 'test2.bin', 'test3.bin' ] ; var FileNameIndex = 0 ; var returnCode = httpPost() ; function httpPost() { const FormData = { my_file: { value: fs.createReadStream(ファイルのパス + FileNameList[FileNameIndex]), options: { filename: FileNameList[FileNameIndex], contentType: 'application/octet-stream' } } } const options = { uri: "http://サーバーのIPアドレス:12345/file_upload", formData: FormData, method: 'post', headers: { 'Content-Type': 'multipart/form-data' } } var response = request(options) .then( function(body) { console.log( 'then :' + body ) ; onEnd() ; }) .catch( function(err) { console.log( 'catch error :' + err ) ; }) ; return response.statusCode ; } function onEnd() { console.log( 'Index ' + FileNameIndex + ' is finish.' ) ; FileNameIndex = FileNameIndex + 1 ; if ( FileNameIndex >= FileNameList.length ) { console.log( 'End Operation.' ) ; } else { var res = httpPost() ; } } console.log( 'Start Operation.' ) ;とやると、クライアント側の表示は以下のようになります。
$ node client.js Start Operation. then :OK Index 0 is finish. then :OK Index 1 is finish. then :OK Index 2 is finish. End Operation.