Quantcast
Browsing Latest Articles All 1922 Live
Mark channel Not-Safe-For-Work? (0 votes)
Are you the publisher? or about this channel.
No ratings yet.
Articles:

browser-syncのport番号をランダムにする

複数の案件を同時に進めている都合上、複数個のbrowser-syncを同時に走らせたいときportが衝突してイライラしちゃうので、そもそもportをランダムにしとけばいいんじゃないか説。

他にいい方法があれば教えてください。。。

// browser-sync.jsmodule.exports=()=>{constpath=require('path');constbrowser=require('browser-sync').create();browser.init({files:[// 監視したいファイルのパターンリスト],// 動的・プライベート ポート番号からランダムでport:Math.floor(Math.random()*(65535-49152))+49152,startPath:'/',server:{baseDir:path.join(__dirname,'public')}});};
// 呼び出し側のjsrequire('./browser-sync')();

参考

【アプリ編】textlintではじめる自動文章校正

textlintとは

textlint(テキストリント)は、設定した校正ルールに基づいたミスの指摘・修正を自動化するツールです。

本来はコマンドプロンプト等の『黒い画面』からインストールして使うのですが、作者の方(@azu_reさん)がより使いやすいデスクトップアプリ版をリリースしてくださっています。
こちらで十分な方にとっては圧倒的におすすめです。

アプリのダウンロード

https://github.com/textlint/textlint-app/releases/tag/v1.4.1

↑こちらのページからインストーラをダウンロード、実行します。

アプリを開くとこのような画面が出ます。▼
スクリーンショット 2020-07-07 11.47.51.png

基本的にはメモ帳やテキストエディットのような「エディタアプリ」と同じだということがわかと思います。

しかしこのアプリを使うためには、以下の2点を用意する必要があります。

・ワーキングディレクトリ(作業用フォルダ)
・校正ルールプリセット

順番に説明します。

ワーキングディレクトリの設定

”ディレクトリ”というのは、つまりフォルダのことです。
筆者も最初わからなかったんですが、プログラミングの工程でコマンド操作をする際には、フォルダのことをディレクトリと呼ぶみたいです。

textlintを使う上では、どこのディレクトリで校正作業するのか?という設定が必要になります。

その設定をするのは、左カラムの『Settings』画面。▼
スクリーンショット 2020-07-07 11.48.02.png

上の方にあるWorking directoryの部分に、作業するフォルダのパスを入力します。
何も設定をしないとデフォルトでtextlint-appのフォルダが使われますが、実際に使う際にはデスクトップ等の見やすい場所に置いておく方が何かとスムーズかと思います。お好みでフォルダをご用意ください。
スクリーンショット 2020-07-07 12.00.18.png
↑今回はこのフォルダを使います。

作業用のフォルダを決めたら『.textlintrc』というファイルを用意します。
『.』から始まるこのファイルは”隠しファイル”といって、フォルダを開いても表示されません。

ファイルを作る時はエディタを使いましょう。
サクラエディタやAtomで空のファイルを作成し、名前をつけてフォルダへ保存します。
スクリーンショット 2020-07-07 12.24.42.png
(Macの場合「command」 + 「shift」 + 「.」でこのように隠しファイルを表示できます)

ファイルを用意できたら、同じフォルダ内に『rule』というフォルダを作ります。
スクリーンショット 2020-07-07 12.34.49.png

ここまでできたら、次はルールの設定をしていきます。

校正ルールの設定

現状では、textlintは何の校正ルールも持っていません。
実際に校正を行うには、ルールを自分で設定するか、誰かの作ったルールプリセットをインストールする必要があります。

今回やりたいのは『表記揺れを統一させること』(『是非』は『ぜひ』の表記に統一したいなど)なので、textlint-rule-prhというプリセットを使ってみます。

prhを使うためには、間違い表現と正しい表現をまとめたymlファイルが必要です。
エディタで『jisho』フォルダを開いて、『rule』フォルダ内にファイルを作成しましょう。
スクリーンショット 2020-07-07 12.44.52.png
今回は『jisho.yml』というファイル名にしました。
(画像で使っているVSCodeでは、ymlファイルを開くと紫の!マークがつきます。特にエラーではないのでご心配なく)

ymlファイルにルールを記述する

https://github.com/textlint-rule/textlint-rule-prh
▲こちらのページに詳しい記述方法が書いてありますが、基本的にはこんな感じです。

jisho.yml
version:1rules:-expected:正しい表現です。pattern:間違った表現です。-expected:正しい書き方。patterns:-こんなパターン-どんなパターン-あんなパターン

このように『patternに該当する表記があったらexpectedが正しいですよ』という指示を書きます。
「是非」→「ぜひ」のような統一の場合はこの書き方。

もし書き間違いなど、正解パターンに対して間違いパターンをいくつも指定したいときは、下にあるようにpatternsと書いたあと間違いパターンを書き連ねていきます。

.textlintrcの中身を書く

最初に作った隠しファイルは、使うルールを指定するための設定ファイルです。
その中にymlファイルのパスを引いて、「これを使います」と指示しましょう。

{
  "rules": {
    "prh": {
      "rulePaths": ["rule/jisho.yml"]
    }
  }
}

(コピペでOKです)

アプリ側での設定

ここまで、作業用フォルダとその中身を用意しました。

jisho
 └ rule - jisho.yml
 └ .textlintrc

このようなファイル構成になっているかと思います。
このjishoフォルダを、textlintアプリで開いてみます。
スクリーンショット 2020-07-07 13.13.58.png
Working directoryの入力欄に、jishoフォルダのパスを書きます。
Windowsだと、フォルダを開けば上の方にパスが表示されるので、それをコピーして貼り付ければOKです。
スクリーンショット 2020-07-07 13.16.35.png
パスを書いてLoadを押すと、中に入っている.textlintrcの中身が下に出てきます。この状態で下の方にあるinstallsaveを押しましょう。

すると、アプリに内蔵されているnpmによってプリセットがインストールされ、アプリでprhが使えるようになります。

試しにエディタに戻って、間違い表現を入力してみましょう。
(最初に書いてあるガイド文は消して大丈夫です)
スクリーンショット 2020-07-07 13.28.51.png
右のカラムに『正規表現はこっちですよ?』という指摘が出ました。
間違いの数だけこの指摘が表示されます。

Fix all errorsと書かれた白いところを押せば、一括で正しい方に修正されます。
スクリーンショット 2020-07-07 13.29.02.png
何個あってもワンクリックです。とても便利!
ただ逆にいうと一括での修正しかできないので、「ここだけは例外的に直さない」という使い方はできません。
日本語的に変な箇所がないかどうか注意しましょう。

(アプリ版ではなくCLIで導入してVSCode上で使う場合なら、そういう融通も利かせられます。環境構築が難しいですがよろしければそちらも挑戦してみてください。)

その他のルール

prhは表記揺れを直すだけですが、他のルールを使えば

・一文で「、」は4つまで
・「かもしれない」などの弱い表現は禁止する

などの校正ルールを追加することもできます!

ルールの自作・カスタマイズができるのもtextlintの魅力ですが、そちらはプログラミング初学者にはけっこうハードルが高いようなので、筆者はまだチャレンジしていません。
おもしろそうなのでJavaScriptに慣れてきたらいずれ…

他にもいろんな方のルールがGitHub上で公開されているので、是非探してみてください。

typeScriptにWebpackを使用する

※あくまで個人の学習ノートなので参考程度に

1.package.jsonを追加

npm init -y

package.jsonが作成される

2.webpackインストール

npm install --save-dev webpack webpack-cli

node_moduleのフォルダとpackage-lock.jsonが作成される。

※作成されるnode_moduleはgitにpushしない

3.webpack.config.jsをディレクトリに追加

webpack.config.jsはwebpackの設定ファイル

webpack.config.js
constpath=require('path');//requireimportと同じ扱いでpathnodejsがもっているモジュールmodule.exports={entry: './dist/main.js',//一番最初に読み込ませるjsファイルoutput: {//生成したファイルをどこに格納するかを指定filename: 'bundle.js',//生成されるファイル名path: path.resolve(__dirname,dist),//生成されるファイルの格納ディレクトリ}}

4.bundle.jsを作成

npm run build

bundle.jsが作成される。

5.HTMLファイルの記述変更

jsファイルの読み込み先を先ほど作成したbundle.jsに変更
(bundle.jsに全てのjsファイルのコードがまとまっているため)

index.html
<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><metahttp-equiv="X-UA-Compatible"content="ie=edge"><title>Document</title>
  <link rel="stylesheet" href="style.css">
  <script src="dist/bundle.js" defer></script>
</head>

以上で終了。

-----------ここからはやってもやらなくても良い-------------

・sourceマップにjsファイルを表示したい場合

1.現在の状態だと以下のように検証ツール上にはbundle.jsしか表示されていない。
スクリーンショット 2020-07-07 14.32.57.png

2.webpackの設定ファイルにdevtool: 'inline-source-map'と追加する

webpack.config.js
constpath=require('path');module.exports={entry: './dist/main.js',output: {filename: 'bundle.js',path: path.resolve(__dirname,dist),},devtool: 'inline-source-map'//この行を追加
}

3.再度buildすると以下のように変わる
スクリーンショット 2020-07-07 14.39.24.png

typescriptから直接bundle.jsを作成する方法

本記事ではtypescriptから直接bundle.jsを作成する方法について書いていきます。

<今までのbundle.jsの作成方法>
1.tsファイルをコンパイルしてjsファイルを作成
2.jsファイルをbundleしbundle.jsを作成

この流れが面倒なので直接やってしまおう。

1.ts-loaderをインストールする

npm install --save-dev ts-loader typescript

2.webpackの設定ファイルに記述

webpack.config.jsにどのファイルにts-loaderを実行するかを記述する。

webpack.config.js
constpath=require('path');module.exports={entry: './src/main.ts',//最初に読み込ませるファイルもtsファイルに変更output: {filename: 'bundle.js',path: path.resolve(__dirname,'dist'),},devtool: 'inline-source-map',module: {rules: [{test: /\.ts$/,  //どういうファイルに対して
      use: 'ts-loader',  //何をするかexclude: /node_modules/  //このファイルは例外
    }]
  }
}

3.buildしてみる

1.以下のコマンド実行。
npm run build

あれ、エラーだけど、、、、
ERROR in ./src/main.ts
Module not found: Error: Can't resolve './foods.js' in '/Users/*****/*****/******/src'
@ ./src/main.ts 1:0-35 2:0-5

ここでのエラーで原因は、main.tsの中でjsファイルをインポートしようとしているため。

main.ts
import{Foods}from"./foods.js";Foods.getInstance();

そしたらfoods.tsに書き換えればいいんじゃない?
スクリーンショット 2020-07-07 15.23.58.png

それでもエラーが出てしまいます。

しかし、Webpackを使用する場合は拡張子は.tsで正解なんです!

でもエラーが出ているままって気持ち悪いですよね。。。

こうすることで解決できます!

1.importもとの拡張子を消しちゃいます。
スクリーンショット 2020-07-07 15.28.53.png
※このままだとbuild時にエラーになります

2.webpackの設定ファイルに以下を記述します。

webpack.config.js
constpath=require('path');module.exports={entry: './src/main.ts',output: {filename: 'bundle.js',path: path.resolve(__dirname,'dist'),},devtool: 'inline-source-map',module: {rules: [{test: /\.ts$/,use: 'ts-loader',exclude: /node_modules/}]}--------ここから--------------------resolve: {extensions: ['.ts','.js']}}

これを書くことで拡張子がなくてもOK。

この記述でインポートしようとした時に拡張子がなかったら左から調べてくれるようになります。

Webpackって本当に便利ですね。。。

3.再度buildしてみる

以下のコマンド実行。
npm run build

今回は無事にエラーなくbundle.jsが作成されましたね!!!

以上になります。

SiriっぽいTeamsアプリを作ってみる

はじめに

仕事にしても何にしても使えるものは使っておくに限る。
最近はリモートワークの影響でteamsを使い始めたので、これも使っておきたい。
どうせなら勤怠とかの自動化に使おうとも思ったが、敷居が高そうなので途中から計画変更してSiriにした。
だって、Siriっぽいもの作った事ありますって言っておけば何かしら仕事もらえるんじゃなかろうか。

結果

image.png
何も分からない俺のSiri

Teams API

TeamsにはTeamsに蓄えられた情報を扱うためのRest APIが用意されている。

これらのRestAPIを使ってタブやボットなどのアプリを作成する。
尚、APIで使用可能なリソースは下記種類がある。

リソースメソッド
team各自のチームの一覧表示、すべてのチームの一覧表示、作成、読み取り、更新、削除、複製、アーカイブ、アーカイブ解除
groupメンバーの追加、 メンバーの削除、所有者の追加、 所有者の削除、ファイルの取得、ノートブックの取得、プランの取得、予定表の取得
channel一覧表示、作成、読み取り、更新、削除
teamsTab一覧表示、作成、読み取り、更新、削除
teamsApp一覧表示、公開、更新、削除
teamsAppInstallation一覧表示、インストール、アップグレード、削除
chatMessage送信
call応答、拒否、リダイレクト、ミュート、ミュート解除、画面共有ロールの変更、参加者の一覧表示、参加者の招待
schedule作成または置換、取得、共有
schedulingGroup作成、一覧表示、取得、置換、削除
shift作成、一覧表示、取得、置換、削除
timeOff作成、一覧表示、取得、置換、削除
timeOffReason作成、一覧表示、取得、置換、削除

2020-06-27現在では.NET, JS, Pythonでサンプルが用意されている。

参考になるサンプルを探す

サンプルを眺めていると、nodejs配下に50.teams-messaging-extensions-searchなるジャストなものがある。
このサンプルはなにをするものかと言うと、アプリケーションコマンドにnodeのパッケージ名を入力すると詳細を教えてくれる。と言うもの。
アプリケーションコマンドが何かと言うと、

image.png
ココ

これを元にsiriを作っていきたい。まずはREADMEに沿って環境を整える。

必要なパッケージを入れる。

nodejsのサンプルなので、必要なのはnodejs。特にバージョンの指定がないのでstableで良いと思う。
自分の端末にはもともと入っていたので特に変えてない。

image.png

nodejs入れたらinstall

npm install

ngrokの導入

READMEを読むとngrokを利用するらしいので、早速本家から落としてインストールする。尚ngrokに関しては有志の強い方が記事を書いてくださっているので、そちらを参照してほしい。
この記事でngrokに関する有用な情報はない。だって今回初めて触った。

インストール方法はngrokに登録してソフトをダウンロードして展開するだけ。
展開したらexeが出てくるのでパス通す。特に困る事はないハズ。

公式サイトの指示通りngrokをアカウントに接続させたら完了。

image.png
とりあえずヘルプ出してみた

あとは実行するだけ

ngrok http -host-header=rewrite 3978

Azureの準備

teamsを使う以上、Azureを使う。で、Azureの準備。

Bot Framework registration resourceの手順通りに進める。
Azure Portalを開き、リソースを作成する。

image.png

botと打つとBot Channel Registrationの候補が現れるのでこれを選ぶ。(公式手順ではWeb App Botでもできるらしい)

image.png

一礼して作成

image.png

下記設定で作成ボタンをクリック。するとデプロイまで進んでくれる。Azure賢い。

項目設定値
ボットハンドルmiyatama-worksheet-hater
サブスクリプション従量課金(デフォルト)
リソースmiyatama-teams-learning(新規作成)
場所米国西部(かっこいいから)
価格レベルS1
メッセージングエンドポイント
ApplicationInsightオン
ApplicationInsightの場所East US
MicrosoftアプリIDとパスワード自動生成

で、しばらく放っておくと完了の通知が来る。

image.png
や っ た ぜ

すべてのリソースを開くと2つリソースが追加されている(botとApplication Insight)。Botの方をクリック。

image.png

image.png

設定をクリック

image.png

右側ペインを下にスクロースるとMicrosoft App IDの項目があるので、管理のリンクをクリック

image.png

新しいクライアントシークレットをクリック

image.png

説明を入力して追加をクリック

image.png

ちゃんと追加される。この時にキーをコピーしてファイルに保存しておく。大事な事なのでもう一回書いておく。
シークレットをコピーしてファイルに保存しておく

image.png
ちゃんと追加出来ててえらい。

Azureでteams使える様にする。

Azure Portalで発行したAppIDとパスワードを.envファイルに追記する。

項目設定値
MicrosoftAppId後述
MicrosoftAppPassword前述のシークレット発行時にコピーした文字列 

AppIDはbotの設定に記載されている

image.png

これで準備ができたので早速動かす。

npm start

teams用の設定を行う

teamsAppManifest/manifest.jsonを編集する。

  • idへMicrosoft App IDを設定
  • composeExtensions.botIdへMicrosoft App IDを設定

その後、teamsAppManifest内のファイルをZipに固める(ファイル名はmanifest.zip)。
大事な事なのでもう一度書こう。
teamsAppManifest内のファイルをZipに固める

image.png

teamsからカスタムアプリのアップロードを行う。アプリのアイコンをクリックし、ペインをスクロールするとカスタムアプリをアップロードの項目が出てくるのでクリック。

image.png

アップロードで先ほど作成したzipファイルを選択する。成功した場合はアプリが画面上に表示される。

image.png
アイコン変だけど気にしない

アプリをクリックして追加を行う

image.png

早速よびだす

image.png
だめでした。

これはなぜかと言うと下記図で言う所のAzureとngrokが連携できていないから。

flow.png

と言うわけでngrokで用意したendpointをAzureに教えてあげないといけない。
まずはココを参考にchannelを作成する。
次に、ngrokeのendpointをAzureに教える。ngrokのendpointはngrok http -host-header-rewrite 3978で実行した時の結果を参照してほしい。
万が一gitbashで実行してしまった場合はwinptyを使うなりコマンドプロンプトで実行するなりなんなりで対処してほしい。

image.png
上記例だとhttps://0a3496160a1a.ngrok.io/api/messages

Azure PortalからBotのリソースを開き、設定を表示する。すると、メッセージングのエンドポイントなる項目があるので、ここにngrokのendpointを張り付ける。

image.png

もう一回試す

image.png
や っ た ぜ

Siriっぽくする

サンプル実行できただけでもかなりお腹いっぱいなのだが、元来の目的であるSiriの作成に入る。
Siriと言えば、すみません。よくわかりません。だ。異論もあるだろうか、私のイメージではそうだ。

botのコードを見てもらうと分かる通り、検索した結果を返すだけだ。これをSiri化するのは簡単だ。
固定ですみません。よくわかりません。を返せばよいだけだ。

中の仕様がよく分からない方はここを見てほしい。めちゃくちゃ親切に書いてある。
書き変えた(?)後のコードは以下の通り。

constaxios=require('axios');const{TeamsActivityHandler,CardFactory}=require('botbuilder');classTeamsMessagingExtensionsSearchBotextendsTeamsActivityHandler{asynchandleTeamsMessagingExtensionQuery(context,query){constattachments=[];constheroCard=CardFactory.heroCard("result");constpreview=CardFactory.heroCard("すみません。よくわかりませんでした。");constattachment={...heroCard,preview};attachments.push(attachment);return{composeExtension:{type:'result',attachmentLayout:'list',attachments:attachments}};}}module.exports.TeamsMessagingExtensionsSearchBot=TeamsMessagingExtensionsSearchBot;

早速試す

image.png
や っ た ぜ

ふりかえり

冒頭にも記載した通り、最初は勤怠の自動化をやろうかと思って色んなサンプルを眺めたりしていた。
が、途中でめんどくさくなって、何でもいいからアウトプットしておく運びとなった。
まぁ、ここまでやっておけば後は自然言語処理でも賢さ演出するぐらい誰でもできるし、何とかなるだろう。
teamsの記事を何個か書く予定なので無駄にはならないハズだ。

何よりも、勉強の方法を議論する位なら勉強した方が早い。

SendGridでいい感じにメールの配信停止グループを管理する

個人で開発しているサービスにメールの配信基盤を整えようと思って色々調べていたので、その結果わかったことを備忘録としてまとめておきます。

メールの配信停止設定で最低限やっておきたいこと

メールの配信設定を行うにあたって、以下の項目が守られていることが個人的に望ましかったです。

  1. 簡単にメールを配信停止できること
  2. 自分のサービスとメール配信サービスでメールの配信停止設定を共有できること

1. 簡単にメールを配信停止できること

まず、送られるメールは簡単に配信停止できるようにすべきです。配信停止のベストプラクティスと、SendGrid の便利な機能にも以下のように書かれています。

  1. 簡単に配信停止できるようにする

配信停止の手順が簡単であればあるほど、受信者と送信者、双方にとってメリットがあります。配信停止のボタンやリンクをどこに置くか決める時、隠すように配置してはいけません。また、リンクがきちんと機能するか複数回チェックしましょう。リンクをフッタに配置するケースをよく見かけますが、どこに配置したとしても、見つけやすくわかりやすくする必要があります。

サービス運営側としては宣伝のためメールを送りたいと思うので、できればメールの配信を続けたいかと思いますが、そのあまり配信停止の設定が不便だとユーザーからの信用を損ないますし、UX も悪いです。

2. 自分のサービスとメール配信サービスでメールの配信停止設定を共有できること

SendGrid にて、メールの配信停止を行う手順は メールの種類ごとに配信停止を管理する - ドキュメント | SendGridにもあるように、わりと簡単に行えそうでした。

しかし、実際にメールの配信設定を管理する際は、自分で作っているサービス側でもメールの配信設定ができる必要があります。

例えば、SendGrid 側でメールの配信を停止した場合、自分で作っているサービス側のメール配信設定もオフになっているべきです。この実装をどのようにすればよいか、あまりメール配信に慣れていない自分は調べるのに苦労しました。

メールの配信停止設定を管理する方法

いろいろ試した結果、SendGrid の Suppressionsの API を利用すると、やりたいことが実現できそうでした。

SendGrid ではメールの配信停止グループを設定できるのですが、こちらの API を使うことで特定のメールアドレスと配信停止グループに対して

  • メールの配信設定の取得
  • メールの配信停止
  • メールの配信停止の取り消し

ができます。

あとは、自分のサービス上でこの API を呼び出してあげれば良いです。自分は Node.js を使っていたので、sendgrid/sendgrid-nodejsでのコードを参考までに載せておきます。

メールの配信設定の取得
importclientfrom"@sendgrid/client";importRequestOptionsfrom"@sendgrid/helpers/classes/request";client.setApiKey("SG.xxxxx.yyyyy");(async()=>{constrequest={}asRequestOptions;constemail="example@gmail.com";request.method="GET";request.url=`/v3/asm/suppressions/${email}`;const[response]=awaitclient.request(request);console.log(response.body);})();
メールの配信停止
importclientfrom"@sendgrid/client";importRequestOptionsfrom"@sendgrid/helpers/classes/request";client.setApiKey("SG.xxxxx.yyyyy");(async()=>{constrequest={}asRequestOptions;constemail="example@gmail.com";constgroupId=12345;constdata={recipient_emails:[email]};request.body=data;request.method="POST";request.url=`/v3/asm/groups/${groupId}/suppressions`;const[response]=awaitclient.request(request);console.log(response.body);})();
メールの配信停止の取り消し
importclientfrom"@sendgrid/client";importRequestOptionsfrom"@sendgrid/helpers/classes/request";client.setApiKey("SG.xxxxx.yyyyy");(async()=>{constrequest={}asRequestOptions;constemail="example@gmail.com";constgroupId=12345;request.method="DELETE";request.url=`/v3/asm/groups/${groupId}/suppressions/${email}`;const[response]=awaitclient.request(request);console.log(response.body);})();

ちなみに、余談ですがメールの送信も以下のような感じでできます。

importsendgridfrom"@sendgrid/mail";sendgrid.setApiKey("SG.xxxxx.yyyyy");constmsg={to:"example@gmail.com",from:"no-reply@example.com",subject:"Sending with Twilio SendGrid is Fun",html:`
    <html>
        <body>
            ここが本文です<br />
            <a href="<%asm_preferences_raw_url%>">配信停止を管理する</a><br />
        </body>
    </html>`,asm:{groupId:12345}};(async()=>{try{awaitsendgrid.send(msg);}catch(error){console.error(error);if(error.response){console.error(error.response.body);}}})();

これにより、自分のサービスの設定画面からメールの受信・停止設定を行うことができますし、送信したメールの「メール配信停止」リンクからメールの配信を停止した際も、自分のサービス側で設定を同期できます。

おわりに

今回は SendGrid を利用しましたが、似たような API があるサービスであれば今回実現したかったことは実装できるかと思います。

ただ自分はあまりこの手のサービスを知らないので、もしおすすめのサービスをご存じの方がいらっしゃれば、ぜひコメント欄にて教えていただけますと嬉しいです!😄


というわけで、この記事で書いた内容をもとにメールの配信機能を実装してみました!

メール通知機能を追加しました!|AnyMake|note

もしよろしければ、AnyMakeもチェックしてみてください👍

csv-parser モジュールでCSV文字列を処理したい場合の方法

csv-parser で CSV文字列をパースする。

https://www.npmjs.com/package/csv-parser

上のモジュールで単純なCSV文字列をパースしたい場合はリーダブルストリームを
一旦生成することで csv-parserのインターフェイスを合わせる事ができる。
(もっと簡単な方法がありそう

importcsvParserfrom"csv-parser"import{Readable}from"stream"//CSVが格納された文字列constcsvString=`col1,col2,col3
row1,row2,row3
row1,row2,row3`// Readable Stream を作成constreadable=newReadable({read:(size)=>{//この処理の記述の必要性が実は良く分からないreturntrue}})readable.on("data",(chunk)=>{//parse されたデータconsole.log(chunk)}).on("err",(err)=>{//error 時の処理}).on("end",()=>{//終了時の処理}).write(csvString,(err)=>{//CSV文字列をストリームに書き込む//close イベントを emit するreadable.emit("end")})

Promiseが必要であれば end コールバックでResolveすれば多分OK。

GitHub Pagesを使った自前無料ブログの作り方(Hexo)

本文の前に

この記事はまどれーぬさんの公開された記事ゼロからわかる!GitHub Pagesを使った自前無料ブログの作り方(Jekyll)からインスパイアを受けています。ぜひ読んでみてください。とてもわかり易くまとまっています。

※今回の記事はLinux(Ubuntu)で操作を行っています。それ以外のOSをお使いの方は少しやり方が異なる場合があります。ご了承ください。

この記事でわかること

  • HexoというNode.js.製の静的サイトジェネレーターを使ったサイトの制作方法

  • Github Pagesでの公開方法

余談と事前準備

余談ですがなぜJekyllではなくHexoを使うのかについてです。理由は唯一つ

私がRubyを使ったことがなくJSのほうが使いやすかったから

これだけです(笑)

はい、では事前に準備するものです

  • PC(今回はLinuxを使用しています)
  • GitHubアカウント
  • テキストエディタ(今回はVSCodeを使用しています。)

環境構築

ではまずNode.jsとHexoの環境構築をしていきましょう。

まずはNode.jsです。こちらの記事を参考にしてみてください。

そして次はYarnというパッケージ管理システムをインストールしていきます。
コンソールに

curl -o- -L https://yarnpkg.com/install.sh | bash

と入力してください。

最後の方に export PATH~という文字列があるのでそれをコピーして実行してください。そうするとPATHが通り、yarnを使用することができるようになります。

次に今回の目的であるHexoをインストールしていきます。

コンソールに

yarn global add hexo-cli

と打ち込んでください。

ディレクトリを作成します。

hexo init {ファイル名}

作成されたディレクトリに移動して、モジュールをインストールします

cd {ファイル名}
yarn install

サーバーを起動します。

hexo server

http://localhost:4000/にアクセスするとページが表示されます。

ここまでできれば環境構築は完了です。

初期設定

ブログの詳細な設定は_config.ymlで行います。
yaml_hexo.jpg
ここではブログタイトルや言語、URLの設定などを行います。テーマ設定やGitへのデプロイ設定などもここで行います。

テーマ選びと設定

では、今度は好きなテーマを設定してみましょう。今回はicarusというテーマを設定していきます。テーマに関してはこちらにまとまっています。

ほとんどのテーマには独自の設定項目が存在するためHexoと別に管理する必要があります。そのためにgitsubmoduleという機能を使用します。

まずは既存のテーマをForkしていきましょう。

icarus-fork.jpg

icarusテーマのGithubページです。こちらの右上にForkというボタンがあるのでクリックしてください。そうするとリポジトリをForkすることができます。Forkが完了するとページが切り替わると思います。自身のForkができたら再びLinuxのコンソールに戻り、作成したディレクトリに移動します。そして

git submodule add {フォークしたリポジトリのURL} themes/{テーマ名}

を入力します。そうするとtheme/{テーマ名}にフォークしたリポジトリが追加されます。

Hexoのテーマ設定を行う

作成したディレクトリの_config.ymlに設定を行っていきます。私の場合テーマ名をtheme-icarusにしたので

theme:theme-icarus

と設定をしました。

テーマの詳細設定

では今度はテーマの詳細設定を行っていきます。theme/{テーマ名}の_config.ymlに設定を書いていきます。
少し長いのでGithubのURLを貼っておきます_config.yml

記事を書く

さぁではお待ちかね記事を書いていきましょう!と、その前に少し設定をしなければなりません。
Hexoのルートディレクトリの_config.ymlpost_asset_folderという設定をtrueにします。
これをしないと記事内に画像を埋め込むのが面倒になります。
次に記事を管理するフォルダを作っていきます。

hexo new {記事の名前}

これで記事の画像などの管理フォルダと記事のmarkdownファイルが作成されます。

markdownファイルはこんな感じです。
md_hexo.jpg
では細かいところを説明していきます。
一番上の部分

---
title: タイマーアプリを作ってみた
date: 2020-03-26
tags:
---

この部分は記事のタイトル、更新日時、タグなどの概要を設定する部分です。
この部分はレイアウトとしてhexo newを打ったときに同時に生成されます。
そして重要なポイント<!--more-->というタグについて説明します。
このタグはHexo独自のタグであり、これを設定しないと記事一覧ページで全文が表示されてしまいます。
moreタグあり
timer-more.png

moreタグなし
timer-no-more.png

このようになってしまうのでmoreタグはとりあえず付けておきましょう。(中にはmoreタグに対応していないテーマもあるのでmoreタグに対応しているかもテーマを選ぶ基準にするといいと思います。)

GitHub Pagesを使って全世界に公開

Gitへのデプロイ設定

こちらの記事にとても良くまとまっているのでご覧ください。

終わりに

お疲れさまでした!これでブログを作って公開する環境を整えることができましたね!
これからは記事を書いたら

hexo deploy

で公開することができます。

AWS Lambda、API Gateway、SESを使用し、S3静的ウェブサイトの動的な連絡フォームを作りましょう!💪

AWS S3にサイトを安くて簡単にホスティングできますが、S3は静的なサイトしかホスティングできないですので、従来ならバックエンド処理が必要となるフォーム送信の実装などは難しそうですね。しかし、クラウド時代ではそんな心配はありません!ソリューションはサーバーレスアーキテクチャです!AWSはすでに色々なサーバーレス構築のツールを提供しています。

今回はユーザからお問い合わせやフィードバックを送信するたびに、任意な宛先に通知メールを送信するシンプルなサーバーレスメールサービスを一緒に作ります。AWS LambdaとAPI Gatewayを利用し、簡単なAWSサーバーレス構築を紹介します。

構成図はこんな感じです。
diagram.PNG

処理の流れとしては、ユーザーから連絡フォームより入力した情報を収集し、クライアント側のブラウザからAmazon API Gateway RESTfulサービスに投稿します。Amazon API Gatewayは、収集されたユーザー情報をAWS lambda関数に渡します。AWS Lambda関数は、Eメールを自動生成し、Amazon SESを使用してメールサーバーに転送します。

1.フォームを作成

  • index.htmlファイルを作成し、下記のコードを貼り付けます。
index.html
<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>連絡フォーム</title><script src="script.js"></script><linkrel="stylesheet "href="style.css "><script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js "></script></head><body><h1>連絡フォーム</h1><formid="contact-form"method="post"><h4>名前:</h4><inputtype="text"id="name-input"placeholder="お名前を入力してください。"class="form-control"/><br/><h4>件名:</h4><inputtype="subject "id="subject ""placeholder="お名前を入力してください。 "class="form-control "/><br/><h4>メール:</h4><inputtype="email "id="email-input "placeholder="メールを入力してください。 "class="form-control "/><br/><h4>メッセージ:</h4><textareaid="description-input "rows="3 "placeholder="メッセージを入力してください。 "class="form-control "></textarea><br/><divclass="button-wrapper "><buttontype="button "onClick="submitToAPI(event) "class="btn btn-lg ">送信</button></div></form></body></html>

ブラウザでプレビューを確認できます。こんな感じです。
basic-html-form.PNG

  • 見た目はあまり良くないですので、改善しましょう。style.cssを同じフォルダ内で作成し、下記のコードを貼り付けます。
style.css
*{box-sizing:border-box;}body{text-align:center;color:rgb(255,255,255);background-color:#194680;padding-top:20px;}#contact-form{margin:auto;text-align:left;padding:20px;max-width:430px;border:whitesolid2px;border-radius:10px;}button{text-align:center;margin-top:20px;font-size:18px;border:whitesolid2px;background-color:white;border-radius:5px;}.button-wrapper{text-align:center;}input,textarea{width:380px;}textarea{height:200px;padding:10px;}input{height:35px;}

ブラウザではこんな感じになります。
styled-form.PNG

2. AWS Lambda関数の定義

  • AWS Lambda画面で新規関数を作成し、hello-worldのnodejsテンプレを選択します。
    lambda2.PNG

  • 基本的な情報の画面では関数を任意に名付けて、実行ロールは最初選択のままで進みます。
    lambda3.PNG

  • 次の画面の関数コードに下記のコードを切り替えます。

index.js
varAWS=require('aws-sdk');varses=newAWS.SES();//宛先のメールアドレスを入力してください。varRECEIVER='example-receiver@gmail.com';//送信先のメールアドレスを入力してください。//送信先のメールはAWSに認証登録する必要があります。varSENDER='example-sender@gmail.com';//クライアントへのレスポンスヘッダーを設定varresponse={"isBase64Encoded":false,"headers":{"Content-Type":'application/json',"Access-Control-Allow-Headers":"Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Accept","Access-Control-Allow-Methods":"POST,GET,OPTIONS","Access-Control-Allow-Origin":"*",},"statusCode":200,"body":"{\"result\": \"テスト成功です!\"}"};exports.handler=function(event,context,callback){console.log('受理したイベント:',event);sendEmail(event,function(err,data){context.done(err,null);});callback(null,response);};//メール送信処理functionsendEmail(event,done){varparams={Destination:{ToAddresses:[RECEIVER]},Message:{Body:{Text:{Data:'● お名前: '+event.name+'\n● 件名: '+event.subject+'\n● メール: \n'+event.email+'\n● 内容: \n'+event.desc,Charset:'UTF-8'}},Subject:{Data:event.name+'からの連絡フォームが来ました!',Charset:'UTF-8'}},Source:SENDER};ses.sendEmail(params,done);return{response,body:JSON.stringify(params),statusCode:200}}
  • 同じLambda管理画面で、上にスクロールし、アクセス権限のタブに切り替え、ロール名のリンクをクリックします。

lambda-policy.PNG

  • 当関数のIAMポリシーをSES権限を追加します。IAM画面で該当ポリシーをクリックしポリシーを編集 ->JSONのフォーマットに切り替えし、下記のコードを追加します。

lambda-policy3.PNG

{
  "Sid": "FormPolicy",
  "Effect": "Allow",
  "Action": "ses:SendEmail",
  "Resource": "*"
}

こんな感じです。
lambda-policy2.PNG

3. APIゲートウェイでAPI構築

  • AWSメインコンソールよりAPI Gateway管理画面にアクセスし、APIを作成をクリックします。
    api.PNG

  • 次の画面ではREST API構築をクリックします。

api2.PNG

  • 次の画面はREST APIの選択のままで、API名前を任意に付けます。

api2a.PNG

  • 次の画面は上にあるアクションをクリックし、メッソド作成をクリック、POSTを選択します。

api3.PNG

  • 右側の設定画面では、統合タイプLambdaで、Lambda関数は先ほど作成したものを選択します。

api4b.PNG

  • CORSエラーにならないように、CORS機能も有効にします。アクションのドロップダウンをCORS有効化をクリックし、設定はそのままで保存します。

api4c.PNG

  • API Gatewayの最新アップデートでは、ゲートウェイのレスポンスも設定しないと、CORSエラーが発生するため、設定しましょう。右のゲートウェイレスポンス→ DEFAULT 4XX編集の順番にナビゲートします

api5a.PNG

  • レスポンスヘッダーに下記の値を追加し、保存します。
  Access-Control-Allow-Origin: '*'

こんな感じです。
api5b.PNG

これでAPIの構築は完了です。

4. SES送信用のメール検証登録

mail.PNG

verifiedの状態になったら、OKです!

5.全コンポーネントを連携する

  • 最後のステップではすべてを繋ぎます!index.htmlと同じいフォルダでscript.jsを作成し、下記のコードを貼り付け、var URLurlの2ヶ所に先ほど設定したAPI GatewayのエンドポイントURLを差し替えます。
script.js
functionsubmitToAPI(e){e.preventDefault();//設定したAPI GatewayのエンドポイントURLをここに入れます。varURL="API-GATEWAY-ENDPOINT-URL";//フォームの入力値をチェックvarname=/[A-Za-z]{1}[A-Za-z]/;if(!name.test($("#name-input").val())){alert("2文字以上記入してください。");return;}varsubject=/[A-Za-z]{1}[A-Za-z]/;if(!subject.test($("#subject").val())){alert("2文字以上記入してください。");return;}if($("#email-input").val()==""){alert("メールを入力してください。");return;}varemail=/^([\w-\.]+@([\w-]+\.)+[\w-]{2,6})?$/;if(!email.test($("#email-input").val())){alert("メールアドレスは正しくありません。");return;}varname=$("#name-input").val();varsubject=$("#subject").val();varemail=$("#email-input").val();vardesc=$("#description-input").val();vardata={name:name,subject:subject,email:email,desc:desc};$.ajax({type:"POST",//設定したAPI GatewayのエンドポイントURLをここに入れます。url:"API-GATEWAY-ENDPOINT-URL",dataType:"json",crossDomain:"true",contentType:"application/json; charset=utf-8",data:JSON.stringify(data),success:function(){// フォームをクリアし、送信成功のメッセージを表示するalert("メッセージが送信されました!");document.getElementById("contact-form").reset();location.reload();},error:function(){// 送信エラーのメッセージを表示するalert("メッセージ送信失敗!");}});}

6. 結論

フォームより受信するメールはこんな感じです。
demo.PNG

これでAmazon S3で静的Webサイトをホストしても、AWSアーキテクチャを利用し、ユーザよりサイト管理者へフォーム送信機能を実装できました!

初投稿です、、いかがでしょうか(´・ω・`)
コメント、フィードバックなど是非よろしくお願いいたします!
それでは、また今度! (`・∀・´)ノ

Node.jsで動くオンライン対戦リバーシを作ってみた

ソース

https://github.com/inari2019/osero
正直かなり醜いコードだと思ってます。

実際に作ったもの

http://pocketmikan.ml:3000/
※予期なくリンクが変わったり、封鎖している可能性があります。

環境

バックエンド:Node.js(Express.js)
ソケット通信:socket.io
フロントエンド:javascript
描画:HTML5 Canvas

構成

無題の図形描画 (1).jpg

コードの解説

大体のことはgithubのコードに書いてるので割愛

実行の流れとしては
プレイヤーから駒を置く場所を受け取る(index,js)

駒を置けるか確認したのち駒を置く(modules/gameclass.jsのput関数)

置いた後の盤面データなどをプレイヤーに送信(index.js)

リバーシのアルゴリズム

https://techacademy.jp/magazine/22767
まんまこれですすみませんでした

終わりに

自分でもわかるぐらいひどいコードです。改善の余地しかありませんが、テストがあるので改善出来てません。

$ npm run watch しようとしたら Sorry, there's a problem with nodist. Couldn't resolve node version ~~~と出たときの対処法

課題

$ npm run watch

しようとしたら
Sorry, there's a problem with nodist. Couldn't resolve node version spec %s: %s 4.5.0 Couldn't find any matching version
と怒られた。

なんでだろうと調べたりいろいろ試したりしましたが小1時間ほどハマり心優しい同僚に助けてもらったので覚書しておきます。

対処法

1. nodeのバージョンを確認する

node -v
v0.0.0

2. .node-version(node-dev-env.node-version)を開いて記載されているバージョンを確認する

私の場合「v11.13.0」と記載されていました。
このバージョンとnode-v したときのバージョンを合わせてやります。

3. 今適用されているnode.jsのバージョンを確認

$ nodist
  (x64)
> 4.5.0  (C:\xampp\htdocs\hogehoge.jp\node-dev-env\.node-version: v4.5.0)
  10.15.1  (global: 10.15.1)
  11.0.0
  11.13.0

つまり現在のnode.jsのバージョンが4.5.0になってしまっている。(私の場合11.13.0も一応存在はしました)

4. v11.13.0がなければインストール

$ nodist + v11.13.0

5. v11.13.0がインストール済になったので切り替える

$ nodist v11.13.0
v11.13.0
v11.13.0 (global)

6. node.jsのバージョンが切り替わったか確認しましょ

$ node -v
v11.13.0

これで切り替え完了!

7. 頼む、動いてくれ!

$ npm run watch

8. それでも動かなかったとき

魔法の呪文
(使用中の node にあったバージョンの npm をインストールしてくれます)

$ nodist npm match

9. それでもまだ動かなかったとき

おまじない
(npm config スクリプトの設定でパスを先頭に追加)

$ npm config set scripts-prepend-node-path true

からの

npm config list

; userconfig C:\Users\minami-inayama\.npmrc
scripts-prepend-node-path = true

が追加されていることを確認します。
そして

nmp run watch

終わり

ここまでの手順でわたしは動きました。

参考:

nodist 環境下で npm script 使用時にエラー – blog
https://www.namu-ws.com/blog/dev/nodist-npm-script-error

npm がどうしてもエラーになってしまったら - Qiita
https://qiita.com/tana08/items/d50212a1919308cc1480

Laravel-Mixコンパイル時のcode ELIFECYCLEエラーに対応する - Qiita
https://qiita.com/ishizukih/items/9673e709832dacaa5155

nodistでnode.jsのバージョン管理をする | IT王子の技術ブログ
https://www.it-ouji.com/2019/10/26/nodist%E3%81%A7node-js%E3%81%AE%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E7%AE%A1%E7%90%86%E3%82%92%E3%81%99%E3%82%8B/

Macにnodebrewでnodeをインストールするまでにしたこと

Macにnodebrewnodeをインストールしようとおもったところ、nodebrewでインストールしたnodeのバージョンとnode -vのバージョンが合っていなかったので、解決した方法を備忘録として残します。

npm、node、nodebrewをアンインストールする

まずはnpmnodenodebrewをインストールしているかを確認します。

ターミナル
$ npm -v
$ node -v
$ nodebrew -v

コマンドでバージョンが確認できない場合はインストールの段階に進みましょう。

バージョンが確認できた場合はアンインストールします。

npmをアンインストールする

まずnpmをアンインストールします。

次のコマンドを順番に実行してください。

ターミナル
$ npm uninstall -f npm
$ rm -rf .npm \
> node_modules

npm -vで確認してバージョンが表示されなければ成功です。

nodeをアンインストールする

nodeをアンインストールします。
nodeをインストールした方法でアンインストールの方法が違います。

よくわからない場合は全て実行してください。

パッケージ版

homebrewでインストールした場合のアンインストール方法

ターミナル
$ brew uninstall node

その他でインストールした場合のアンインストール方法

次のコマンドを順番に実行してください。

ターミナル
$ lsbom -f -l -s -pf /var/db/receipts/org.nodejs.node.pkg.bom \
> | while read i; do
> sudo rm /usr/local/${i}
> done
$ sudo rm -rf /usr/local/lib/node \
>     /usr/local/lib/node_modules \
>     /var/db/receipts/org.nodejs.*

ソース版

which nodeで確認したコードをrm -rfで削除してください。
必ずしも/usr/local/bin/nodeとは限りません。

ターミナル
$ which node
/usr/local/bin/node
$ rm -rf /usr/local/bin/node \
> node_modules

アンインストール後

アンインストール後にnode -vで確認して、バージョンが表示されなければ成功です。

nodebrewをアンインストールする

nodebrewをアンインストールする方法は二つあります。
最初の方法でアンインストールできると思いますが、できなかった場合は次の方法も実行してみてください。

以下がnodebrewをアンインストールするコマンドです。

ターミナル
$ brew uninstall nodebrew

このコマンドを実行した後nodebrew -vでバージョン確認をしましょう。
バージョンが表示されなければ成功です。

この方法でアンインストールができなかった場合は次の方法を試してみてください。

ターミナル
$ brew search nodebrew

このコマンドでnodebrewというディレクトリがあるかを確認します。
あった場合は次を実行してnodebrewのある場所を確認します。

ターミナル
$ brew info nodebrew

場所が確認できた場合はFinderからそこの場所に移動して、nodebrewを直接ゴミ箱に捨ててください。

これでnpmnodenodebrewのアンインストールが終了しました。

nodeのインストール

アンインストールが終了したら、nodeをインストールしていきます。

nodebrewのインストール、環境変数の設定

次のコマンドを順番に実行してください。

ターミナル
$ curl -L git.io/nodebrew | perl - setup
$ export PATH=$HOME/.nodebrew/current/bin:$PATH

nodeのインストール

次にnodeのインストールをします。
インストールするバージョンは最新版にします。

ターミナル
$ nodebrew install-binary latest

インストールしたバージョンの確認

インストールしたバージョンを確認します。

ターミナル
$ nodebrew ls
v14.5.0

current: node

current: nodeとありますが、ここには現在使用しているバージョンが表示されます。
この状態だとまだ使用することができないのでバージョンを設定します。

バージョンの設定

使用するバージョンを設定します。
今回はv14.5.0というバージョンを設定します。

ターミナル
$ nodebrew use v14.5.0

これでバージョンが設定できました。

ターミナル
$ nodebrew ls
v14.5.0

current: v14.5.0

確認すると設定できています。

動作確認

最後にnodeが動作しているか確認しましょう。

ターミナル
$ node -v
v14.5.0

設定したバージョンが表示されれば成功です。

参考サイト

外部サイト

Qiita

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

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

① Homebrewのインストール
② nodebrewのインストール
③ node.jsのインストール
上記の手順で行います。
node.jsがインストールされれば、同時にnpmもインストールされます。

Homebrewのインストール

terminalコマンド
$/usr/bin/ruby-e"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

nodebrewのインストール

terminalコマンド
$brewinstallnodebrew

nodebrewのバージョンの確認

バージョンを確認することで、インストールがうまく行っているのかを確認できる。

terminalコマンド
$nodebrew-v

node.jsのインストール

terminalコマンド
$nodebrewinstall-binarylatest

インストールされたnode.jsのバージョンを確認する。

terminalコマンド
$nodebrewlist

上記を入力すると、下記のようなログが出力される。

出力ログ
v12.14.1current: v12.14.1

currentの部分が、現在動いているnode.jsです。
次のように出力された場合には、node.jsが有効になっていません。

出力ログ
current: none

有効化するためには、インストール済みのバージョンをuseコマンドで有効化します。

terminalコマンド
$nodebrewusev12.14.

node.jsがインストールされれば、npmも同時にインストールされます。

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

こんにちは、wattak777です。

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

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

server.js
varexpress=require('express');varapp=express();varmulter=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);});varserver=app.listen(12345,function(){console.log("listening at port %s",server.address().port);});

で、本題のクライアント側は以下の実装。
request-promiseを使って同期的に送るようにしました。

client.js
constfs=require('fs');constrequest=require('request-promise');constFileNameList=['test1.bin','test2.bin','test3.bin'];varFileNameIndex=0;varreturnCode=httpPost();functionhttpPost(){constFormData={my_file:{value:fs.createReadStream(ファイルのパス+FileNameList[FileNameIndex]),options:{filename:FileNameList[FileNameIndex],contentType:'application/octet-stream'}}}constoptions={uri:"http://サーバーのIPアドレス:12345/file_upload",formData:FormData,method:'post',headers:{'Content-Type':'multipart/form-data'}}varresponse=request(options).then(function(body){console.log('then :'+body);onEnd();}).catch(function(err){console.log('catch error :'+err);});returnresponse.statusCode;}functiononEnd(){console.log('Index '+FileNameIndex+' is finish.');FileNameIndex=FileNameIndex+1;if(FileNameIndex>=FileNameList.length){console.log('End Operation.');}else{varres=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.

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 flowServer-side
OAuth endpointsGoogle
Authorization endpointそのまま (https://accounts.google.com/o/oauth2/v2/auth)
Token endpointそのまま (https://oauth2.googleapis.com/token)
Access token locationAuthorization header w/ Bearer prefix
Access typeOffline
Force approval promptNo
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に準備

constnodemailer=require('nodemailer')constauth={type:'OAuth2',user:'alias@adress',// エイリアスのアドレスclientId:'clientId',// Client IDclientSecret:'clientSecret',// Client SecretrefreshToken:'refleshToken',// Reflesh Token}consttransporter=nodemailer.createTransport({service:'gmail',auth})

4-3 メール情報を準備・メール送信

constmailOptions={from:`エイリアス <alias@mail.com>`,// エイリアスのアドレスと名前to:`send_to@mail.com`,// TObcc:`blind@mail.com`,// BCCsubject:`Subject`,// タイトルtext:`text`// 本文}transporter.sendMail(mailOptions)

まとめ

以上の方法で、エイリアスのメールアドレスを用いたnodemailerの送信が可能になりました。

ガッツリとシステムを組む場合にはこのような手法をとらないような気もしますが、スモールビジネスで小さくはじめたい方などは、Googleのようなプラットフォームにのっかるのも手ではないでしょうか?

最終的に Firebase Functions にメール送信は任せたいと思います。

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

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
アクセスのための 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.constbasePath=__dirname+'/user-app/';functiongetEntryPoint(){letrawPackage=fs.readFileSync(basePath+'package.json');letpackage=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)}returnpackage.main;}constuserApp=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)constapp=require('express')()app.set('views',__dirname+"/views");app.set('view engine','pug');app.use('/',require('./routes'));returnapp;};

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

  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 などの環境(変化がわかりやすいので)を対象に調べてみたいな、などと思っています。なにかわかったら、メモ公開するかもしれません。

それではまた!

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

はじめに

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

概要

とある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は人が作る必要があるため、言うだけは簡単ですがシステムのあるべき姿を計画し、実行して継続することは大変です。

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

【自分用メモ】JavaScript

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

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

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

環境

macOS : 10.15.5 Catalina
node.js : v14.3.0

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

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

クラスタ呼び出し

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

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

parent
child

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

子プロセス呼び出し

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

parent.js
varchild_process=require('child_process');varc=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
varchild_process=require('child_process');varc=child_process.fork(__dirname+'/child',['foo','bar']);c.on('message',function(msg){console.log('[Parent]received msg = ['+msg+']');});//msg handlerc.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
varchild_process=require('child_process');constn_child=16;var_childlen=[];for(vari=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
:constn_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自身が飛んだりということはありませんでした。
過剰に呼び出しすぎるのはよくないですが、もしかしたら時間を開けて子プロセスの起動を行えば、問題がなくなるかもしれません。
(すべて想像です)

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追加して計測。

Browsing Latest Articles All 1922 Live