20200710のNode.jsに関する記事は10件です。

【学習記録1】Node.js

はじめに

エンジニア目指して現在独学でプログラミング学習中です。
学習を始めて既に3週間ほど経っていて、HTML,CSS,JavaScriptの基礎の学習を終えたところです。
学習の振り返り・記録として、今日から学習内容を投稿していきたいと思います。

今日の学習内容

  • Progate Node.js Ⅰ~Ⅲ
  • Node.jsのローカル開発環境の準備
  • MySQLのインストール(すでにProgateで学習を終えたため、自分のPCにインストール)
  • Node.jsとMySQLの接続

Node.jsのローカル開発環境の準備~Node.jsとMySQLの接続は、基本的にProgateのコラムに書いてあった内容をそのまま行いました。
コマンドプロンプトやターミナルについても少ししか知りませんでしたが、書いてあった内容通り進めていくと何とかなりました。
(コマンドについてはもっと勉強すべきだと思いました。。。)

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

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者|node.jsのREPLでファイルをリネーム

以前に初心者|node.jsでリネーム。フォルダからファイル名一覧を取得し一括変更するという記事を書きました。

でも「いちいちファイルを作成して実行するのは面倒やなぁ」と思い、node.jsのREPL(リプル)で実行することにしました。

今回したかったことは、あるディレクトリ内にある複数の画像ファイルの名称を一括で変更することです。変更した名称には連番もつけます。

要件

  • node.js がインストールされている
  • node.jsのREPLを使いたい
  • 特定のフォルダ内のファイルを一括リネームしたい
  • ファイル名に連番もつけたい
  • 対象のファイルを種類(拡張子)で抽出したい(フィルタリングしたい)

image.png

リネーム前 リネーム後
IMG_xxxx.jpg newFileName-●.jpg
  • リネーム前の名称は何でもいい
  • は連番

そもそもMACには、一括リネーム機能がついています

image.png
image.png

  • 対象ファイルを選択して、右クリック
  • ●項目の名前を変更... をクリック
  • あとは、よしなに...

本題

1)ターミナルを起動する

image.png

  • リネームするファイルがあるディレクトリでターミナルを開いてください
  • (例) imeges ディレクトリ内に 24個の jpgファイルがあります

 

2)REPLの起動

image.png

terminal
$ node
  • node + エンターキーでnode.jsのREPLが起動します

3)モジュール読み込み

image.png

REPL(node.js)
$ const fs = require("fs"),
  path = require("path");
  • nodeのモジュールfspath を読み込みます。

4)ファイル名の取得

image.png

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)変更後のファイル名を設定

image.png

REPL(node.js)
$ const newName = "newFileName-";
$ let i = 1;
  • newName = "newFileName-" で、新しいファイル名をセットしています。任意に書き換えてください。
  • i = 1 は、ファイル名に付与する連番のスタート値です

6)ファイル名の一括変更

image.png

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つだけになるので、ご注意ください

image.png

リネームができれば成功です。

7)REPLの終了

REPL(node.js)上で キーcontrol + c を2回するとREPLが終了できます。

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

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"]]}]

スクリーンショット 2020-07-10 15.00.03.png

functionノードの中身

function.js
msg.res.redirect("/redirect")
return;

res.redirect()

functionノードでクエリーを見てDBに追加してねー。もしくは、tenplateでGA追加して計測。

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

[node.js]スレッドへの引数の渡し方

非同期の動作をさせたくて、スレッド作成の方法を勉強しましたが、
引数の渡し方についてあまり情報がなかったので書き留めます。

javascriptにおいてスレッドという呼び方が正しいのかはまだわからないですが
(基本的にシングルスレッドで動作するものなので、どういう仕組みかは厳密にはまだ不明なためです)
動作させてみたところ非同期に動作しているように見えました。

環境

macOS : 10.15.5 Catalina
node.js : v14.3.0

スレッド呼び出しの種類について

非同期スレッドの作り方は二種類あり、①クラスタ②子プロセス呼び出し
です。簡単に以下にそれぞれの使い方を書きます。
①は呼び出し元のjsファイルが複数個同時に動作するイメージです
②は呼び出し元とは異なるjsファイルが同時に動作するイメージです。

クラスタ呼び出し

cluster.js
var cluster = require('cluster');
if (cluster.isMaster)   { console.log('parent'); cluster.fork();    }
else                    { console.log('child');                     }

上記コードの実行結果は以下です

parent
child

この場合、それぞれの(子)スレッドに引数を渡すことは(試した限り)出来ませんでした。
用途的には全く同じイベントハンドラを複数持つとパフォーマンスが上がる場合がよいとおもいますが、
それについてまだ知識はありません。

子プロセス呼び出し

以下のような手順です。
引数を渡すには、fork()の第二引数にarrayを指示します。
child_process.fork( 呼び出すjsファイル名 , 引数配列 );

parent.js
var child_process = require('child_process');
var c = child_process.fork(__dirname+'/child' , ['foo' , 'bar' ] );

呼ばれる子プロセスは、
process.argv配列に、親から指示された引数が格納されています。
以下の例は全部の引数要素をダンプします。

child.js
process.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:bar

process.argv[0] : node 実行ファイルのパス
process.argv[1] : 実行している js ファイルのパス
process.argv[2] : 引数配列 要素 [0] : 今回は親プロセスが「foo」を指示しています。
process.argv[3] : 引数配列 要素 [1] : 今回は親プロセスが「bar」を指示しています。

実際に引数を参照するには process.argv[2]〜先を読み込みするようにします。
この呼び出し方ならば、スレッド毎に親で仕事内容を指示することができます。

親-子間の通信

子プロセスの場合には親子間でメッセージやりとりができます。
両者ともにon()が受信ハンドラ、(引数がメッセージ内容)、send()が送信処理。

parent.js
var 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.js
process.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.js
var 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.js
process.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自身が飛んだりということはありませんでした。
過剰に呼び出しすぎるのはよくないですが、もしかしたら時間を開けて子プロセスの起動を行えば、問題がなくなるかもしれません。
(すべて想像です)

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

【自分用メモ】JavaScript

Node.js

npmとYarnの違い

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

プログラムの怖いところ ライブラリのバージョンアップ

はじめに

本記事はライブラリのバージョンアップが原因でプログラムが動かなくなり、どの様に原因を調査して解決まで行き着いたかについて、システム開発の一助となるストーリーをご紹介します。

概要

とあるtoCのシステムにおけるバックエンドのプログラム(API)で軽微な修正を行い、デプロイしたときの話になります。

本番環境と開発環境が存在し、開発環境でテストした上で本番環境でリリース作業を実施。本番環境でデプロイ後、システムの一部機能で正常性が確認できない事態が発生しました。

バックエンドはDockerで開発を行っているため、デプロイ時にdocker compose buildでビルドを毎回行っています。デプロイ自体は成功しているため、ライブラリのバージョンアップが関係しているのではないかと予想していましたが、のちに調査が難航することになるとは、このときは予想していませんでした。。

当該事象発生時は原因究明に至らなかったため、切り戻しを実施し、リリース作業は中断。翌日に原因調査を行いました。

question_head_gakuzen_boy.png

原因調査

前提として本番環境と開発環境で使用していたソースは、一部機能をマージしてないなど環境差異は発生していましたが今回発生した事象に関係はありません。

調査を開始し、開発環境で事象を再現させるために確認を行いましたが、同様の事象は発生しませんでした。その後、本番環境で使用したソースを開発環境でデプロイすると事象が再現しました。

開発環境で事象再現後、デバッグするために本番環境で使用したソースの不具合が発生している箇所にconsole.log(e)でログ出力する様にしてデバッグを行いました。

不具合が発生している箇所に対するAPIのリクエストを実行し、サーバ側のログを確認すると、データベースのあるテーブルのカラムが見つからないと言うエラーメッセージが出力されていました。

仮説としてプログラムで利用していたORMライブラリのバージョンが上がったため、今まで許容していたデータベースに対する接続がエラーになっているのではないかという考えに辿りつきました。

animal_chara_computer_azarashi.png

解決方法

本番環境で稼働中のコンテナで生成されたyarn.lockファイルと、開発環境でデプロイしたコンテナのyarn.lockファイルのORMライブラリのバージョンを比較すると、開発環境のコンテナのマイナーバージョンが1桁だけ上がっていることを確認しました。

検証としてpackage-lock.jsonのORMライブラリのバージョンを、本番環境で稼働中のコンテナで生成されたバージョンと同じバージョンに固定すると、事象が再現しないことが確認できました。

よって暫定対応としては、ORMのライブラリのバージョンを固定しないことで一旦は解決しましたが、今後の運用として色々と課題を認識しました。

shinpai_man.png

DevOps

DevOps観点で感じた課題について以下に記載します。

※本記事の内容は、あくまで考え方の一例であり、必ずしも全ての考え方がシステムに適合したり、ここに書いている内容で満たされている訳ではありません。

  • ライブラリのバージョン固定
    保守性を高める場合はライブラリのバージョンを上げないように固定することが望ましいですが、セキュリティとトレードオフになります。また、バージョンアップする場合は確認の工数もかかります。npm outdatedコマンドを実行すると、現在インストールされているバージョン、現在のバージョン指定でインストールされる最新バージョン、リリースされている最新バージョンが表示されます。

  • テストの再現性
    当たり前ですが極力環境差異をなくしたテスト方法の考案及びテスト環境を構築し、テストを実施することが望ましいです。※参考:3.6 本番環境とテスト環境の差異に関する教訓(T6)

  • 本質的なアプローチ
    今回の様な事象は技術的な手段で解決する以前に、開発や運用ポリシー等を定義していればこの様なリスクを軽減する可能性は上がります。

おわりに

本事象を通して改めて学んだのは、プログラムは数字1桁が変わるだけで動かなくなるという恐ろしさと、DevOpsの重要性です。

DevOpsは文化です。DevOpsは人が作る必要があるため、言うだけは簡単ですがシステムのあるべき姿を計画し、実行して継続することは大変です。

この失敗を次に生かしてシステム開発のライフサイクルを短縮し、ソフトウェア品質の高い継続的デリバリーを実現していきたいと思いました。

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

Kabanero を使ったクラウド・ネイティブなアプリ開発(VSCode + Codewind)を体験 - その後

2020年7月8日に実施された 初夏のIBM Dojo #9 Kabaneroを使ったクラウド・ネイティブなアプリ開発を体験 ワークショップに参加してきました。講師 @osonoi さんです。

以前に翻訳した Kabanero と、それと関連する Developer Experience にある「VS Code を使用して Kabanero を試す」を体験できるオンラインセッションでした。セッション資料 がわかりやすいので、ぜひ参照してください。

VSCode + codewind の環境、とっても手軽で便利。監視、ビルド、実行など全て Docker 環境で実行され、処理系のインストール無しでさくさく試せる。いろんな言語/環境を試してみたい。

と、Twitter で呟きましたが、すごく参考になったので、ちょっと自分でも中身を確認してみました。自分なりに迷いつつのメモなので、間違えていたり、意味不明だったらスミマセン。

お手軽に開発環境をセットアップ

セッション資料に従い、アプリケーション開発環境をセットアップしてみます。前提となるソフトウェアは以下の2つだけ。

  • Docker : 仮想マシン実行環境
  • VSCode : テキストエディタ

VSCode の 拡張機能 (Extension) で Codewind を探してインストールします。
image.png
新規プロジェクト作成で、Kabanero リポジトリにある Kabanero Node.js Express scaffold template を選択します。
image.png
後は開発環境などが自動でセットアップされます。ビルド環境も実行環境も Docker コンテナ化されているため、Node.js など開発に必要なツールがインストールされていなくても問題ありません。今回のセットアップも Docker 上にコンテナが追加されるだけで、ローカルにインストールされないため、気軽に試すことができます。

アプリを起動すると Node.js Express が動作し、シンプルな Webページが表示されます。
image.png
さあ、後はサンプルコードを修正して、いろいろ試すだけ。ソースコードに修正を保存すれば、ビルドが実施され、すぐにWebページに反映されます。

以上、ここまでの手順の詳細は IBM Dojo の セッション資料 をご参照ください。IBM Developer Dojo のどれかに参加すると、Dojo サポート用の Slack チャネルに招待されるので、そこで質問もできます。

生成された環境を眺めてみる

さてこのまま、環境はブラックボックスとして、Node.js + Express のアプリ開発を開始してもいいのですが… せっかくですから、自動生成された環境を少し眺めてみましょう。

VSCode のワークスペース

まず VSCode のワークスペースを見てみると、以下のような構成になっています。
image.png
ページを表示しているのは routes/index.js ですね。
image.png
表示に利用されている Pug 形式のテンプレート views/index.pug は以下のように非常にシンプルでした。
image.png
これらなのですが、探してみると GitHub の appsody アカウントにある stacks リポジトリ 配下にある /incubator/nodejs-express/templates/scaffold フォルダが元になっているようです。

Docker で動作するコンテナについて

さて、上記の Web ページを表示中、Docker は以下のように3つのコンテナを実行していました。
image.png

最初のコンテナ (イメージは kabanero/nodejs-express:0.4) がアプリを実行している環境のようですので、コンテナに sh アクセスして、動作しているプロセスの状態を見てみます。
image.png
自身の /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 フォルダが存在します。
image.png
試しに c:\ ドライブ全体を対象に appsody でファイル名検索した結果がこちら。c:\work\codewind\dojo-yamachan がプロジェクト作成時に自身で指定した作業フォルダです。
image.png
ふーむ、なかなか興味深いですね。

実際の起動プロセス

さて、作成したアプリがどう起動されるか追ってみましょう。実行用コンテナで 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/ ディレクトリに配置されているということです。
image.png

そしてこのディレクトリですが、Docker ランタイムにより、さきほど発見したローカルPCの C:\codewind-data 配下のフォルダがマウントされ、永続化されていることがわかります。

image.png

たぶんですが、作業フォルダの内容をビルドした結果 (今回は 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.jsviews/index.pug ファイルに繋がります。

app.js
module.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;
};

起動順としては、以下のような感じですかね。

  1. Appsody が用意した実行用コンテナの /project/user-app にローカルPCの c\codewind-data 配下のプロジェクト用フォルダがマウントされる 【Appsody共通】
  2. Appsody が用意した実行用コンテナ内の /project/server.js/project/user-app/package.json ファイルの main エントリを参照する【Appsody Node.js 系共通】
  3. main エントリに指定された /project/user-app/app.js が実行される【Appsody Node.js Express 系共通?】
  4. app.js によって Pug テンプレートエンジンがセットされ、views/index.pug をテンプレートとして route/index.js が表示される。【今回の Stack 固有】

うん、これでやっとスッキリしました。

というわけで

Appsody (VSCode + Codewind) で作成した Node.js + Express サンプルアプリの起動の仕組みをざっくり調べてみました。なかなか良く出来た仕組みだなー、と感心してみたり。

ただ、時間の関係もあり、ソースコードの変更をウォッチしているところ、ビルドしているところ、などはまだ調べていません。また時間を作って、コンパイルする Java などの環境(変化がわかりやすいので)を対象に調べてみたいな、などと思っています。なにかわかったら、メモ公開するかもしれません。

それではまた!

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

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 でプロジェクト作成

  1. Google APIs ページで、「プロジェクトを作成」をクリック。(もしくは、プロジェクト選択タブをクリックし「新しいプロジェクト」をクリック)
  2. 「プロジェクト名」を入力し、「作成」をクリック。

nodemailer-send-emails-using-alias-address-for-gmail.001

1-2 OAuth 同意画面 の設定

  1. 「OAuth 同意画面」をクリック
  2. 「アプリケーション名」を入力
  3. 「保存」をクリック

nodemailer-send-emails-using-alias-address-for-gmail.002

1-3 OAuth 2.0 クライアントIDの作成

  1. 「認証情報」をクリック
  2. 「認証情報を作成」をクリックし、「OAuth クライアント ID」を選択
  3. 「アプリケーションの種類」で「Webアプリケーション」を選択し、「名前」を入力

nodemailer-send-emails-using-alias-address-for-gmail.003

クライアントIDクライアントシークレットが表示されるので、保存しておく。

nodemailer-send-emails-using-alias-address-for-gmail.004

1-4 Gogle Developers OAuth 2.0 Playground で Reflesh Token の取得

  1. Google Developers OAuth 2.0 Playgroundページを開く
  2. 右上の「⚙歯車ボタン」をクリックする
  3. 以下のような情報を入力する

nodemailer-send-emails-using-alias-address-for-gmail.005

項目
OAuth flow Server-side
OAuth endpoints Google
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 さきほど取得した クライアントシークレット
  1. 「Step 1」を選択
  2. 「Input your own scopes」に「 https://mail.google.com/ 」を入力し「Authorize APIs」を押す
  3. 「Google OAuth 2.0 Playground に移動」と表示されるので、対象のGoogleアカウントを選択
  4. 「Google OAuth 2.0 Playground が Google アカウントへのアクセスをリクエストしています」と表示されるので「許可」を選択
    nodemailer-send-emails-using-alias-address-for-gmail.006

  5. 「Step 2」に進んだら「Exchange authorization code for tokens」を押す

  6. Reflesh tokenが表示されるので、保存しておく
    nodemailer-send-emails-using-alias-address-for-gmail.007

手順2. G Suite で エイリアスアドレスの追加

2-1 Google管理コンソール

  1. Google Adminにアクセスし、「ユーザー」をクリック

2-2 ユーザーに紐づくエイリアスアドレスを作成

  1. 先程手順1を行ったGmailアカウントをクリック
  2. 「ユーザー情報」をクリック
  3. 「メールエイリアス」をクリックし、エイリアス名を入力したら「保存」を押す

nodemailer-send-emails-using-alias-address-for-gmail.008

手順3. Gmail で エイリアスアドレスの追加・デフォルト設定

3-1 エイリアスアドレスの追加・認証

  1. Gmail右上の「歯車ボタン」を押し、「すべての設定を表示」を押す
  2. 「アカウント」タブをクリックする
  3. 「名前」の欄に「他のメールアドレスを追加」とあるのでクリック
  4. 「エイリアスとして扱います」にチェックをつけ、「名前」と手順2で準備したエイリアスのアドレスを入力
  5. 確認メールでの認証が必要といわれるので、「確認メールの送信」をクリック
  6. 届いた確認メールのリンクをクリックするか、確認コードを入力し「確認」をクリック

nodemailer-send-emails-using-alias-address-for-gmail.009

3-2 エイリアスアドレスを送信メールのデフォルトに設定

  1. Gmailの設定画面に戻ると3-1で設定されたエイリアスアドレスが追加されています
  2. エイリアスアドレスのほうを「デフォルトに設定」をクリックする

(※これがないと、nodemailerで送信した際に、エイリアスのアドレスから送信されたことにならず、元のアドレスからの送付となってしまいます。)

手順4. nodemailerでメール送信スクリプトを作成

4-0 手順1で準備したものの確認

  • Client ID
  • Client Secret
  • Reflesh Tken

4-1 nodemailerのpackageをインストール

  1. 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 にメール送信は任せたいと思います。

サーバーレス開発プラットフォーム Firebase入門 (日本語) 単行本

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

Node.jsで複数ファイルを送るには

こんにちは、wattak777です。

一つだけファイルを送信する、というサンプルは幾つかあるのですが、複数ファイルの場合のサンプルをちょっと作ってみました。

サーバー側はmulterを使った以下のサンプル。

server.js
var 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.js
const 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.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む