Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all 9050 articles
Browse latest View live

常に動くLINEBOTにお引っ越し(now編)(未解決版)

$
0
0

前回つまって諦めたnowに再挑戦した記録。

ngrokで作った時の記事:
WikipediaのAPIを使ってLINEbotに調べてもらう

はじめに

前回同様、課題感とテーマとしては「ngrokだと使いたいときに使えない!だから常時使えるようにしたい!」です。

目次としては、

  • now でだいぶハマる
  • さらに now にハマる
  • 対応したこと

です。

nowでだいぶハマる

nowを正しく動かすところでまずハマりました。
いくつかハマった気がして、だいたいはいじったりnow自体を消してやり直したりしたらできた印象だが、うっかりでハマったのがこの記事参照。
https://qiita.com/shima-07/items/64c051c9982ac0899b21

さらにnowにハマる

無事、ngrokを卒業して、nowを使ってLINE botができました。
が、、
なんか挙動がおかしい。ソースコードはngrokの時と何も変えてないのに。
botなのになんだか頭が悪い人間っぽさが出てきた・・・。

具体的には、

1. 初めて検索するキーワードにはwikipediaからの返り値を返してくれない。 (「初めてなので・・・」とかウブさはbotに求めてない)

image.png
(2回同じこと聞くと返してくれる・・・)

2. 前回の回答を返してくる。(質問をちゃんと聞いて)

今回は「カラス?」と聞いているのに、「くま」について説明し始めた、、

3. 自動応答したいreplayMessageとWikipedia APIのpushMessageの順番がバラバラ(落ち着いてほしい…)

本当は「カラスの説明:」が上にきて、その下にwikipediaからの返答をのせたいのだけど、ずれる時がある。

image.png

nowにしたことで、何やら処理の順番とかが変わるようだ。。

対応したこと

https://qiita.com/n0bisuke/items/fb19ed3cd0138135ae69
この記事を見て、

asyncとawaitを使い、replyとpushの場所も変えてみた

変更前↓

before.js
.省略...functionhandleEvent(event){if(event.type!=='message'||event.message.type!=='text'){returnPromise.resolve(null);}letmes=''// console.log(event.message.text);if(event.message.text.indexOf('')>-1){// ?を含んでいる場合にはwikiで検索したものを出して、含んでない場合はurlを返すvarstr=event.message.text;varresult=str.split('').join('');//?を取り除く処理mes=result+'の説明:';//wikiのbodyの前の一言getBody(event.source.userId,result);//wiki APIで取得できたらプッシュメッセージ}else{varresult=event.message.text;mes=result+'のURL:';//wikiのurlの前の一言getUrl(event.source.userId,result);//wiki APIで取得できたらプッシュメッセージ}returnclient.replyMessage(event.replyToken,{type:'text',// text: event.message.text //実際に返信の言葉を入れる箇所text:mes});}constgetBody=async(userId,word)=>{constres=awaitaxios.get('http://wikipedia.simpleapi.net/api?keyword='+encodeURIComponent(word)+'&output=json');constitem=res.data;// console.log(item); awaitclient.pushMessage(userId,{type:'text',text:item[0].body,});}constgetUrl=async(userId,word)=>{constres=awaitaxios.get('http://wikipedia.simpleapi.net/api?keyword='+encodeURIComponent(word)+'&output=json');constitem=res.data;// console.log(item); awaitclient.pushMessage(userId,{type:'text',text:item[0].url,});}(process.env.NOW_REGION)?module.exports=app:app.listen(PORT);console.log(`Server running at ${PORT}`);

変更後↓(replyMessageをasyncとawaitを使って書き換え、場所も変えてみた。)
※?がある場合の分岐の抜粋。

after1.js
asyncfunctionhandleEvent(event){if(event.type!=='message'||event.message.type!=='text'){returnPromise.resolve(null);}letmes=''// console.log(event.message.text);if(event.message.text.indexOf('')>-1){// ?を含んでいる場合にはwikiで検索したものを出して、含んでない場合はurlを返すvarstr=event.message.text;varresult=str.split('').join('');//?を取り除く処理mes=result+'の説明:';//wikiのbodyの前の一言awaitclient.replyMessage(event.replyToken,{type:'text',// text: event.message.text //実際に返信の言葉を入れる箇所text:mes});getBody(event.source.userId,result,mes);//wiki APIで取得できたらプッシュメッセージ}

こういう対応ではダメだった?
これにしても直らず。
上記3つのおとぼけbotさんのままでした。。

次に、

いっそwikiの内容をとってくる関数の中にreplyMessage入れたらいいんじゃない?

と思い、やってみた。(完全に素人の悪あがき感。。)

getBody.js
constgetBody=async(userId,word,message)=>{awaitclient.replyMessage(event.replyToken,{type:'text',// text: event.message.text //実際に返信の言葉を入れる箇所text:message});constres=awaitaxios.get('http://wikipedia.simpleapi.net/api?keyword='+encodeURIComponent(word)+'&output=json');constitem=res.data;console.log(item);awaitclient.pushMessage(userId,{type:'text',text:item[0].body,});}

これでも、相変わらずおとぼけbotは治らず。。

今回も失敗…

前回の諦めポイントのnow自体からは進んだのですが、結果botがおとぼけさんになってしまった。
(もっと人間的な振る舞いをしたら可愛げがあって多少のミスも許せるが、、まだ十分な人間味もない。)

時間的にもきついので、しばらくはこれ以上深掘らず、いったんここまで。


DOM, Node, Elementについて

$
0
0

備忘録です。
間違いなどございましたらご指摘ください。

DOMとは

HTMLとXMLドキュメントへのAPI
(わかりやすくいうと、プログラムからHTMLやXMLを自由に操作するための仕組み、インターフェイス)

JavaScriptからHTMLに要素を追加したり、ボタンクリック時のイベントを登録したり、スタイルや属性を追加したり、要素のサイズや位置を追加したり、こういったものはすべてDOMのAPIを使うことで操作できる。

DOMツリー
DOMはHTMLドキュメントを『オブジェクトのツリー』として扱っている。これを『DOMツリー』という。

image.png

Nodeとは

ノードとは各要素(HTMLではエレメントやタグという)自体のことを表す。(つまり、上の図のひとつひとつのボックスがNode)

特定のノードを基準としたときに、その上にあるノードを「親:parent」ノードと表現し、その下にあるノードを「子:childまたはchildren」ノードと表現する。
例えば「そのタグの子ノード全体を取得して、その親ノードから削除する」のような言い方ができる。

firstChildやparentNodeなどのプロパティ、appendChild、removeChildなどのメソッドはNodeが提供している機能。

Nodeの種類
- Document(Document型Node・ドキュメントノード)
- Element(Element型Node・エレメントノード)
- Attr(Attribute型Node・属性ノード)
など

Elementとは

さきほどNodeにはいくつか種類があると説明した。
ElementはそのNodeの中のひとつ、Element型のNodeのこと。

HTMLの要素はElementを継承しておりElement<-HTMLElement<-HTMLDivElementのようになっている。

classListやinnerHTMLなどのプロパティ、getElementByIdやquerySelectorAll、setAttributeなどのメソッドはElementが提供している機能。

まとめ

DOM
- HTMLドキュメントとJavaScriptをつなぐインターフェース
- DOMツリーはNode構成されている

Node
- DOMツリーを意識して操作を行うときのオブジェクト
- 例: appendChild、removeChild、parentNodeなど

Element
- HTMLドキュメントの要素を意識して操作を行うときのオブジェクト
- 例: style、attribute、width、heightなど

参照

https://kuroeveryday.blogspot.com/2018/11/difference-between-dom-and-node-and-element.html
https://eng-entrance.com/what-is-dom

(小ネタ)Node.jsのWebアプリでclusterを使いながら定期的に子プロセスを再起動させる

$
0
0

Node.jsでサーバサイドWebアプリを開発中、なぜかメモリリークがあるライブラリに遭遇してしまったので、ワークアラウンドとして、定期的にプロセスを再起動させて、メモリリークの問題を緩和したいと思いました。

サーバサイド

http://0.0.0.0:10080をリスンするプロセスが4つ立ち上がり、5秒未満で子プロセスを停止させ、その後に再起動します。(実用上は、もっと長い時間でプロセスを殺すべきです。)

src/index.js
constcluster=require("cluster");consthttp=require("http");constsleep=time=>newPromise(done=>setTimeout(done,time));constclusterCount=4;constportNumber=10080;if(cluster.isMaster){constspawnProcess=()=>{// プロセスを終了させるまでの時間: 0 〜 5000 msecconstttl=~~(5000*Math.random());constchild=cluster.fork();lettimeout;child.on("listening",()=>{// 指定時間で終了(Graceful kill)させるconsole.log(`誕生! 死まで ${ttl} msec.`);timeout=setTimeout(()=>{console.log(`死: ${child.id}`);child.kill();},ttl);});child.on("disconnect",()=>{// 別の理由で死んだ場合はkillをキャンセルif(timeout){clearTimeout(timeout);}});child.on("exit",()=>{// 子プロセスが終了したら代わりのものを1つ起動するspawnProcess();});};// 子プロセスを複数起動するfor(leti=0;i<clusterCount;i++){spawnProcess();}}if(cluster.isWorker){// Express や Koa など好きに使いましょうhttp.createServer(async(req,res)=>{// リクエスト終了までやや時間がかかる設定awaitsleep(1000);res.writeHead(200);res.end("Request done\n");}).listen(portNumber);}

起動すると、下記のようなログを吐きながら、プロセスの終了と生成を延々と繰り返します。

誕生! 死まで 2712 msec.
誕生! 死まで 3984 msec.
誕生! 死まで 4297 msec.
誕生! 死まで 1547 msec.
死: 4
誕生! 死まで 4276 msec.
死: 2
:
:

テスト

abコマンドできちんとリクエストが中断されずにいるか、テストしてみます、

$ ab -c 20 -n 500 http://localhost:10080/
This is ApacheBench, Version 2.3 <$Revision: 1826891 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Finished 500 requests


Server Software:        
Server Hostname:        localhost
Server Port:            10080

Document Path:          /
Document Length:        13 bytes

Concurrency Level:      20
Time taken for tests:   26.226 seconds
Complete requests:      500
Failed requests:        0
Total transferred:      44000 bytes
HTML transferred:       6500 bytes
Requests per second:    19.07 [#/sec] (mean)
Time per request:       1049.029 [ms] (mean)
Time per request:       52.451 [ms] (mean, across all concurrent requests)
Transfer rate:          1.64 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   1.0      0       7
Processing:  1000 1008   5.4   1007    1025
Waiting:     1000 1007   4.7   1006    1023
Total:       1000 1008   5.5   1007    1025

Percentage of the requests served within a certain time (ms)
  50%   1007
  66%   1010
  75%   1012
  80%   1013
  90%   1016
  95%   1019
  98%   1020
  99%   1022
 100%   1025 (longest request)

特に何も問題なくリクエスト処理は完了しているようです。

Complete requests:      500
Failed requests:        0

まとめ

  • cluster でマルチプロセス化できるし、定期的にプロセスを再起動して、健全性を保つことができるはず
    • こういうゴミ掃除はマルチスレッドモデルだとできなさそう

AWS LambdaのCustom Runtimeを使い、Node.js v8などEoLとなったランタイムを動かす

$
0
0

はじめに

Node.js、バージョンアップの足がかなり早いですよね。
AWS Lambdaにおけるランタイムサポート期間も、これにあわせてハイテンポになっています。

ちゃんとバージョンアップをしろというご意見は重々承知の上ではありますが、
Node.js v8.10でLambda Functionを使い続けざるを得ない場合に、カスタムランタイムを使ってEoLとなったランタイムを動かし延命処置を図ります。

動作確認環境

  • Arch Linux (2020.04.04)
  • Docker 19.03.8-ce
  • aws-cli 1.18.36

カスタムランタイムの使い方

カスタムランタイムの仕様については、公式ドキュメントが詳しいので割愛します。

カスタムランタイムを使用するには、デプロイパッケージあるいはLayerに、node実行ファイルと、ハンドラー関数を起動するためのbootstrap実行ファイルを含める必要があります。
今回は既存のLambda Functionを使用することを想定していますので、関数のデプロイパッケージには手を加えずLambda Layerでランタイムを読み込ませます。

Lambda Layerの作成には、以下のリポジトリを活用させていただきます。
https://github.com/lambci/node-custom-lambda

こちらのリポジトリにはNode v10、v12のファイルが含まれています。(2020/04/04現在)
今回はこのリポジトリをフォークし、v8.10用のファイルを追加することでLayerを作成します。

bootstrapについては、CとJavascriptで書かれたものがそれぞれv12.x/bootstrap.cv12.x/bootstrap.jsにあります。(v10.xも同様)

bootstrap.c(をコンパイルしたbootstrap)がまずAWS Lambdaによって起動され、これがbootstrap.jsスクリプトをカスタムランタイムのNodeで実行します。
bootstrap.jsは、AWS Lambda ランタイムインターフェイスから関数の呼び出しイベントの受け取り、デプロイパッケージのスクリプト実行、実行結果のPOSTを行います。

上記リポジトリのbootstrap.jsはNode v8.10でも問題なく動くので、
必要な変更点はLambda Layerに含めるnode実行ファイルをv8.10のものに変更するだけとなります。

カスタムランタイムの作成

前節で紹介したリポジトリをクローンするところからはじめます。
Dockerが必要となります。

$ git clone https://github.com/lambci/node-custom-lambda.git
$ cd node-custom-lambda

v12.xのディレクトリを元に、v8.10のディレクトリを作成します。

$ cp-r v12.x v8.10
$ cd v8.10

v12.xのLayerファイルを削除しておきます。

$ rm layer.zip

このプロジェクトでは、Docker上でbootstrap.cのビルドとNodeのダウンロードを行います。

config.shを編集し、Nodeのバージョンを指定します。

config.sh
< export NODE_VERSION=12.16.1
---
> export NODE_VERSION=8.10.0

ビルドします。

$ ./build.sh

v8.10/layer.zipファイルが出来上がります。
これを解凍すると以下のようなファイルが入っています。

layer
├── bin
│   └── node
├── bootstrap
└── bootstrap.js

Nodeのバージョンを確認しておきます。

$ ./layer/bin/node -v
v8.10.0

テストが用意されていますので、実行してみます。

$ ./test.sh

以上でカスタムランタイムのLayerが完成しました。

カスタムランタイムのデプロイ

リポジトリにはpublish.shが用意されていますが、このスクリプトは全てのリージョンにデプロイされてしまいます。
今回はap-northeast-1にのみデプロイできればよいので、AWS CLIを使って手動でLayerを作成します。

まず、作成したレイヤーのファイル(layer.zip)を任意のS3にアップロードします。

aws s3api put-object --bucket ${BUCKET_NAME} --key nodejs/8.10.0/layer.zip --body layer.zip --output json

次に、Lambda Layerを作成します。
ここではCloudFormationで作成します。

template.yml
AWSTemplateFormatVersion:2010-09-09Parameters:S3BucketName:Description:A S3 bucket name contains layer.zipType:StringResources:Nodejs8Runtime:Type:AWS::Lambda::LayerVersionProperties:Content:S3Bucket:!RefS3BucketNameS3Key:nodejs/8.10.0/layer.zipDescription:Layer for Node.js 8.10.0 Custom RuntimeLayerName:custom-runtime-nodejs-8Outputs:Nodejs8RuntimeLayerARN:Description:A lambda layer ARN of Node.js 8.10.0 Custom RuntimeValue:!RefNodejs8RuntimeExport:Name:!Sub${AWS::StackName}-runtime-nodejs8

Stackを作成。

aws cloudformation create-stack --stack-name dev-lambdalayers-nodejs \
--template-body file://template.yml \
--parameter ParameterKey=S3BucketName,ParameterValue=${S3_BUCKET_NAME}

Layerができていることを確認します。

$ aws lambda list-layer-versions --layer-name custom-runtime-nodejs-8
{
  "LayerVersions": [
    {
      "LayerVersionArn": "arn:aws:lambda:ap-northeast-1:123456789012:layer:custom-runtime-nodejs-8:1",
      "Version": 1,
      "Description": "Layer for Node.js 8.10.0 Custom Runtime",
      "CreatedDate": "2020-04-04T16:59:31.629+0000"
    }
  ]
}

Lambda Functionの作成とテスト

上記で作成したカスタムランタイムをテストします。

Lambda FunctionはServerless Frameworkを使用して作ることにします。
provider.runtimeprovidedを指定することでカスタムランタイムを使用できます。
Lambda Layerは、先程のCloudformation StackのOutputをインポートしてARNを指定します。

serverless.yml
service:test-lambda-functionprovider:name:awsruntime:providedstage:devregion:ap-northeast-1functions:hello:handler:handler.hellolayers:-'Fn::ImportValue':dev-lambdalayers-nodejs-runtime-nodejs8

関数のコードはシンプルに、実行しているNode.jsのバージョンを返すだけです。

handler.js
module.exports.hello=asyncevent=>{returnprocess.version;};

これを実行して、v8.10.0という文字列が帰ってきたら成功です。

$ sls invoke -f hello
"v8.10.0"

注意点

  • AWS公式のNode.jsランタイムにはaws-sdkが含まれていますが、この方法で作成したカスタムランタイムにはいずれのnpmパッケージも含まれていません。

おわりに

以上でNode v8を使用するLambda Functionの延命措置ができました。
同様の方法で、Node v6、v4も動かすことが可能です。

しっかりバージョンアップしていくのがベストであることは言うまでもありませんが、
node-gypなどネイティブモジュールはバージョンアップで動かなくなることも多々ありますので、とりあえずの措置には使えるかと思います。

また今回使用したコードはすべて以下リポジトリにアップしています。
https://github.com/uhey22e/node-custom-lambda

brew install nodeで色々詰まった自分用メモ

$
0
0

前提

https://qiita.com/okohs/items/ced3c3de30af1035242d
この記事を読んで

brew install node

としたがエラーに出会った。

そこで、
http://www.gworks.jp/2014/05/homebrew-node/
にしたがって

brew doctor

のところまで行き、問題が生じたので一つ一つ解決したあと、

brew install node

をしたらまたエラーが出た。
今度はbrew linkできないよ〜というエラーだった。
そこで、エラーメッセージに素直にしたがって解決しようとした。
僕が選んだ選択肢は

brew link --overwrite node

こうしたら、とあるファイルに権限がないからエラーになった。
というわけで、
https://wtnvenga.hatenablog.com/entry/2017/11/15/125430
を参考にしてエラーを1つ1つ解決することになった。

その時のコマンドとエラーと解決方法の組み合わせが以下になる。

出会ったコマンドとエラーの組み合わせが以下だったら読んでも良いかも

コマンド1

brew install node

エラー1

Could not symlink share/systemtap/tapset/node.stp
以下略

コマンド2

brew link --overwrite node

エラー2

Error:Could not symlink 〇〇
△△ is not writable.

解決方法

 エラー1:
brew link --overwrite node
 エラー2:
cd (△△より手前)
sudo chown -R $USER △△
brew link node

もしまた同様のエラーが出たら上記手順を繰り返す。

感想

わからなくて人に聞けないなら、エラーの奴隷にまずはなることが一番だと思った。
ここら辺は半年近く経っても変わらないスタンスだと思う。
Quoraのどこかの質問に対する回答にも、ベテランエンジニアは初めて触るFWやライブラリに慣れるのが速いのは、エラー解決速度が異常に速いだからという話があった。

上の方法がベストかどうかは知らない。
とはいえ、やりながらミスをして向き合って一つ一つ解決しながら実装するのが上達のために必要な条件だとつくづく思った。
(一番良いのは作りたいプロダクトがある時だ思うけど。JS知らなくても3日くらいやってりゃ初級レベルだったら実装できるようになるし、中級知識と言われるものがすぐに必要になってくるから。しかも必要だったらレベルが如何の斯うのと御構い無しになる。)

参考文献

https://wtnvenga.hatenablog.com/entry/2017/11/15/125430
http://www.gworks.jp/2014/05/homebrew-node/
https://qiita.com/okohs/items/ced3c3de30af1035242d

【node.js】validationを新しいバージョンに対応コードに変更する

$
0
0

解決したい問題

パッケージvalidationのバージョンが古いので新しいバージョンの書き方に変更したい。

環境

OS: macOS
express: ^4.17.1
ejs: ^2.6.2
express-validator: ^6.4.0

変更前のコード(古いコード)

router.post('/',(req,res,next)=>{varrequest=req;varresponse=res;req.check('name','NAMEは必ず入力して下さい。').notEmpty();req.check('password','PASSWORDは必ず入力して下さい。').notEmpty();req.getValidationResult().then((result)=>{if(!result.isEmpty()){varcontent='<ul class="error">';varresult_arr=result.array();for(varninresult_arr){content+='<li>'+reault_arr[n].msg+'</li>'}content+='</ul>';vardata={title:'Login',content:content,form:req.body}response.render('login',data);}else{varnm=req.body.name;varpw=req.body.password;User.query({where:{name:nm},andWhre:{password:pw}}).fetch().then((model)=>{if(model==null){vardata={title:'再入力',content:'<p class="error">名前またはパスワードが違います</p>',form:req.body};respose.render('login',data);}else{request.session.login=model.attributes;vardata={title:'Login',content:'<p>ログインしました!<br>トップページに戻ってメッセージを送信下さい。</p>',form:req.body}respose.render('login',data);}});}})});

このreq.checkがエラーになる。

変更後

上に↓のコードを書き足す。

const{check,validationResult}=require('express-validator');
router.post('/',[check('name','NAMEは必ず入力して下さい。').notEmpty(),check('password','PASSWORDは必ず入力して下さい。').notEmpty()],(req,res,next)=>{varrequest=req;varresponse=res;consterrors=validationResult(req);if(!errors.isEmpty()){varcontent='<ul class="error">';varresult_arr=errors.array();for(varninresult_arr){content+='<li>'+result_arr[n].msg+'</li>'}content+='</ul>';vardata={title:'Login',content:content,form:req.body}response.render('login',data);}else{varnm=req.body.name;varpw=req.body.password;User.query({where:{name:nm},andWhere:{password:pw}}).fetch().then((model)=>{if(model==null){vardata={title:'再入力',content:'<p class="error">名前またはパスワードが違います</p>',form:req.body};response.render('login',data);}else{request.session.login=model.attributes;vardata={title:'Login',content:'<p>ログインしました!<br>トップページに戻ってメッセージを送信下さい。</p>',form:req.body}response.render('login',data);}}).catch((error)=>{vardata={title:'再入力',content:'<p class="error">名前またはパスワードが違います。</p>',form:req.body};res.render('login',data);console.log(error);});}});

大きく変わったのはcheckの位置です。

参考

↓この本の学習中に、バージョンが古くてエラーになった。
https://www.amazon.co.jp/Node-js%E8%B6%85%E5%85%A5%E9%96%80-%E7%AC%AC2%E7%89%88-%E6%8E%8C%E7%94%B0-%E6%B4%A5%E8%80%B6%E4%B9%83/dp/4798055220

↓最新バージョンの参考
https://express-validator.github.io/docs/

Node.js Express  で出てくる req, res, next

$
0
0

はじめに

Node.jsのExpressの雛形を生成するコマンド

express project-name

で出力されるコードの一部ですが

index.js
varexpress=require('express');varrouter=express.Router();router.get('/',function(req,res,next){res.render('index',{title:'Express',user:req.user});});

このreq, res, nextは何なのか書いていこうと思います。

reqについて

reqapp.getの第一引数で指定されたパスに入ってきたHTTPリクエストを表すオブジェクトです。

varexpress=require('express');varapp=express();app.get('/',func);functionfunc(req,res){console.log(req.ip);console.log(req.method);console.log(req.path);console.log(req.protocol);console.log(req.query.name)res.end();}

reqapp.getの第一引数で指定されたパスに入ってきたHTTPリクエストを表すオブジェクトです。
サーバを起動し(Portは8000)、

curl -X GET localhost:8000/hoge\?name=user1
::ffff:127.0.0.1
GET
/hoge
http
user1

とリクエストを送って出力を見てみましょう。ちなみにapp.getはGETメソッドでリクエストを受け付けます。最後のres.end()はHTTPレスポンスのプロセスを終了する関数です。これがないと処理が正常に完了しません。
このように来たHTTPリクエストに関する様々な情報を取得することができます。reqのプロパティやメソッドは他にもまだまだありますので詳しくは、
https://expressjs.com/ja/4x/api.html#req
を見てください。

resについて

resは指定されたパスに入ってきたリクエストに対するHTTPレスポンスを構成するためのオブジェクトです。

varexpress=require('express');varapp=express();app.get('/',func);functionfunc(req,res){if(req.query.name){res.status(200).send('Hello! your name :'+req.query.name);}else{res.status(400).send('Who are you?');}}

サーバを立てて、

curl -X GET localhost:8000/hoge\?name=user1
> Hello! your name : user1
curl -X GET localhost:8000/hoge
> Who Are you?

とするとHTTPレスポンスが帰ってきます。status()でステータスコードを設定でき、send()でレスポンスボディを設定できます。
send()にはHTTPレスポンスプロセスの終了処理が含まれるので、end()は不要です。
resのプロパティやメソッドは、https://expressjs.com/ja/4x/api.html#res
を参考にしてください。

nextについて

通常は上記の、app.get('/hoge', func);のように、あるpath(/hoge)に対する、手続き(func)を呼んで、その中で、HTTPレスポンスを返して(res.end())終了します。

次のコードを見てください。

app.get('/hoge',preProcess1,preProcess2,mainProcess);functionpreProcess1(req,res,next){console.log('1個目の前処理したよ!');}functionpreProcess2(req,res,next){console.log('2個目の前処理したよ!');}functionmainProcess(req,res){res.send('メインの処理をしたよ!');}

実はこのように/hogeに対するアクションを何個も登録することができます。
まずはこれにリクエストを送ってみましょう。

curl -X GET localhost:8000/hoge

%% server 側
1個目の処理をしたよ!

このようにpreProcess1の1個のみが実行されて、他は実行されませんでした。
preProcess1->preProcess2->mainProcessと処理を継続させたい時に使用するのがnextです。

app.get('/hoge',preProcess1,preProcess2,mainProcess);functionpreProcess1(req,res,next){console.log('1個目の前処理したよ!');next();}functionpreProcess2(req,res,next){console.log('2個目の前処理したよ!');next();}functionmainProcess(req,res){res.send('メインの処理をしたよ!');}

この場合、

curl -X GET localhost:8000/hoge
メインの処理をしたよ!
%% server 側
1個目の処理をしたよ!
2個目の処理をしたよ!

と出力され、無事にHTTPレスポンスを返して処理を終えることができました。
next()関数によって、次の処理へ制御を渡すことができます。
https://expressjs.com/ja/guide/writing-middleware.html
ここによると、この場合の関数preProcess1やpreProcess2はmainProcessを行うための中間的な役割を行うものとして「ミドルウェア」と命名されています。
もしかしたらmainProcessも定義的にはミドルウェアかもしれないけど個人的にちょっと違和感があります。。。

参考文献

https://expressjs.com/ja/4x/api.html#req
https://expressjs.com/ja/4x/api.html#res
https://expressjs.com/ja/guide/writing-middleware.html

obnizとブザーで兄弟配管工ゲームのコイン音を出してみた

$
0
0

作ろうと思ったきっかけ

  • ProtoOutoStudio 3回目授業でobnizに触れる
  • ブザーが面白かった

 兄弟配管工ゲームでは、ブロックを叩くとコインが出てきますよね。その時の効果音を再現してみようと思いました。

”あの”効果音の音階を調べてみた

コインの音

が、楽譜が読めないぞ....。オクターブもよく分からん....。
よし、トライ&エラーか???(音痴です)

まずはベースとなる”ド”を鳴らす

”ド”の音が何Hzか調べました。今回はこちらのサイトを参考に周波数を設定していきます。

【鳴らしてみた様子】

音を鳴らすだけなら、obnizのJavascriptパーツライブラリから試すと爆速で確認できます。

 obniz IDを入力し、Test Runをクリックすると、指定した音が鳴ります。デフォルトは1000Hzになっているので、鳴らしたい音に合わせるといいかなと思います。
image.png

 今回はobnizのボタンを押すと、指定の音が鳴るようなプログラムをNode.jsで動かしています。ソースコードはこちら。

constObniz=require('obniz');varobniz=newObniz("OBNIZ-ID");// OBNIZ-IDに自分のIDを入れますobniz.onconnect=asyncfunction(){// スピーカーを呼び出すvarspeaker=obniz.wired("Speaker",{signal:0,gnd:1});// ディスプレイ処理obniz.display.clear();// 一旦クリアするobniz.display.print("Hello obniz!");// Hello obniz!という文字を出す// スイッチの反応を常時監視obniz.switch.onchange=function(state){if(state==="push"){// 押されたときconsole.log("pushed");// ディスプレイ処理obniz.display.clear();// 一旦クリアするobniz.display.print("pushed");// pushed という文字を出す// 音を鳴らすspeaker.play(523.25);// ドの音}elseif(state==="none"){// none で押してないときobniz.display.clear();// 一旦クリアする// スピーカーで音を鳴らさない stopspeaker.stop();}}}

 上述のプログラムを使う際は、obniz.jsが必要となります。次のコードでインストールしておきましょう。

npm i obniz

あの効果音に使われている音を鳴らす

あの効果音は””と””の2音で構成されているようです。といっても”ミ”は1つ上のオクターブになっているようです。

では、音階表を参考にしてみると....。

image.png

”シ”は987.76Hz、”ミ”は1318.51Hzでつくります。

 音を鳴らしてみるとこのようになりました。

 よさそうですね!あとは自動化してタイミング調整だな....

組み合わせる

 音を順番に出力するのは簡単ですが、タイミングがわからない!!!!!!!!
楽譜を見ると次のようになっています。

image.png

 こちらのサイトを参考にしてみると、200bpsのテンポであると読み解きました。200bpsとは、1分間に200拍の速度です。つまり、

  1分間(60秒) ÷ 200 = 0.3秒

となります。楽譜をみると、どうやら”シ”と”ミ”の間は何も記号がないので1拍だとして、”ミ”の後は2拍ありそうなので0.6秒として作りました。

結果....。


 
 思っていたより音が低そうなので、1オクターブあげてみましょう。

 こちらの方が近そうですね!でも何か足りない気がしてくる....
何度も同じ音を聞いていると分からなくなってしまうなぁ。でもこれで良しとしますか。

余談

 圧電スピーカーで効果音作るの楽しそう....沼に片足いれはじめてしまうぅ!

最終的なソースコード

constObniz=require('obniz');varobniz=newObniz("Obniz_ID");// Obniz_IDに自分のIDを入れますobniz.onconnect=asyncfunction(){// スピーカーを呼び出すvarspeaker=obniz.wired("Speaker",{signal:0,gnd:1});// ディスプレイ処理obniz.display.clear();// 一旦クリアするobniz.display.print("Hello obniz!");// Hello obniz!という文字を出す// スイッチの反応を常時監視obniz.switch.onchange=asyncfunction(state){if(state==="push"){// 押されたときconsole.log("pushed");// ディスプレイ処理obniz.display.clear();// 一旦クリアするobniz.display.print("pushed");// pushed という文字を出す// 音を鳴らすspeaker.play(1975.53);// シawaitobniz.wait(300);// Waitspeaker.play(2637.02);// ミawaitobniz.wait(600);// Waitspeaker.stop();}elseif(state==="none"){// none で押してないときobniz.display.clear();// 一旦クリアする// スピーカーで音を鳴らさない stopspeaker.stop();}elseif(state==="left"){speaker.play(1318.51);// ミ}}}

Serverless Components はオレたちの未来を劇的にスケールさせるか

$
0
0

Serverless.inc 社より、Serverless Componentsがついに GA されました。

近年のアプリケーション開発では、いくつかの SaaS を組み合わせることで超高速に開発を行うことができます。例えば「認証は Auth0、ホスティングは Netlify、バックエンド API は AWS Lambda を使用する」といった具合です。このように複数のサービスを組み合わせることで、Undifferentiated Heavy Lifting な作業を排除できます。開発者は価値を生み出すビジネスロジックにのみ集中できるようになるのです。

以下はサーバレスアーキテクチャの例です。複数の SaaS を組み合わせて構築しています。

architecture.png

SaaS を組み合わせるだけで、一定の機能群を作り上げることができる時代になりました。しかし、それにしても複雑な管理は残ります。アプリケーション開発者は複数のサービスを手作業で組み合わせなければならず、構築と構成管理に一定の複雑度を残してしまいます。

そこで、Serverless Components です。

Serverless Components は、全てのクラウドベンダー、 SaaS ベンダーに対してインフラとアプリをプロビジョニングします。すでに提供されているコンポーネントを組み合わせるだけで即座にアプリケーションを構築できます。

従来 Serverless Framework は function と event は簡単に定義できていましたが、インフラリソースをプロビジョニングするためにはどうしても Cloudformation を定義する必要がありました。
また、プロビジョニングできる対象も AWS や GCP などの特定のクラウドベンダーのサービスにしか対応しておらず、Auth0 などを使用する場合は自前で構成管理をする必要がありました。Serverless Components なら様々な SaaS に対応できます。

コンポーネント

Serverless Component は React のコンポーネントと同じ構造に基づいています。個々のコンポーネントを参照したり、複数のコンポーネントを同時に作成できます。

先の例をコンポーネントを使用して構築すると以下のように分解ができます。もちろん、より小さくコンポーネントを分割することもできます。ここでは最小の意味のある単位で分割した場合を図示しています。

components.png

サーバレスコンポーネントは、高次のユースケース(ウェブサイト、ブログ、請求システムなど)を中心に構築されています。関係のない低レベルなインフラの詳細は抽象化され、代わりに単純な構成が提供されます。例えば、S3 の静的サイト公開設定や、ログ記録、暗号化設定などはユースケースに対しては意識する必要はありません。事前に設定済みのコンポーネントを使用すれば良いのです。

これはソフトウェア開発における基本的な考え方 関心の分離 (Separation of Concerns: SoC)に基づいていますね。確かに Infrastructure as Code をやっていると、インフラリソースの細部まで理解して構築する必要があり、多大な学習コストを要していました。

ServerlessComponents を使用したウェブサイト

さて、まずは最もシンプルなアプリケーションを Serverless Component を使用して構築してみましょう。
ここでは、GitHub のリポジトリでも例としてあげている サーバレスウェブサイトを構築することにします。

今回構築するウェブサイトは、AWS S3 でホストされます。無料の AWS ACM SSL 証明書で保護された AWS Route 53 のカスタムドメインでアクセスします。さらに、静的コンテンツは、AWS Cloudfront を使用して迅速かつグローバルに配信されます。

website.png

従来このような構成を実現する場合、ServerlessFramework に直接 Cloudformation を書き下し、各リソース間の詳細な設定と依存関係を意識する必要がありました。数百行の Cloudformation のテンプレートが必要でしょう。

Serverless Components を使用する場合はこれだけです。

component:websitename:websiteinputs:src:src:./srchook:npm run builddist:./distdomain:geeawa.ga

驚異的なほど、シンプルです。
component: websiteとして、website コンポーネントを使用することを宣言し、そのコンポーネントに渡す設定値を input:にて指定しています。ただ、それだけです。

Serverless Components を使用してウェブサイトを構築する

実際に website コンポーネントを使用してこの構成を作ってみましょう。

Install

まずは最新バージョンの Serverless Framework をインストールします。

$ npm install -g serverless

Create

サンプルとして、以下のようにテンプレート URL を指定して構築します。

$ serverless create --template-url https://github.com/serverless/components/tree/master/templates/website
$ cd website

次に、.envファイルを使用して AWS アクセスキーの情報を記載しましょう。.envファイルは websiteのルートディレクトリに作成します。

AWS_ACCESS_KEY_ID=XXX
AWS_SECRET_ACCESS_KEY=XXX

ディレクトリ構成は以下のようになっているはずです。

|- src
  |- index.html
|- serverless.yml
|- .env

Deploy

あとはデプロイをするだけです。

$ serverless deploy

デプロイが完了すると以下のようにウェブサイトの URL が発行されます。Cloudfront へ配信が完全に完了するまでに少し時間がかかることに注意してください。

serverless ⚡ framework
Action: "deploy" - Stage: "dev" - App: "landing-page" - Instance: "landing-page"

bucket:    website-089y5jm
bucketUrl: http://website-089y5jm.s3-website-us-east-1.amazonaws.com
url:       https://d2u9prrumf2g64.cloudfront.net
domain:    https://www.geeawa.ga

以下のようなサイトがデプロイされています。

page.png

Dev Mode

さらに興味深い機能として Dev Mode があります。開発中にソースディレクトリの変更を監視し、保存すると即座にデプロイできます。

sls.gif

Remove

作成されたインフラを破棄する場合は、serverless.yml ファイルが含まれているディレクトリで次のコマンドを実行するだけです。

$ serverless remove

コンポーネントを自作する

コンポーネントを自作するためには以下の2つのファイルが必要です。

  • serverless.component.yml: ServerlessComponent の定義を記入します。
  • serverelss.js: ServerlessComponent のコード(実装)を記述します。

serverless.component.yml

コンポーネントをサーバーレスレジストリ内で利用できるようにするためには serverless.component.ymlを作成する必要があります。

# serverless.component.ymlname:express# 必須 コンポーネント名version:0.0.4# 必須 バージョンauthor:eahefnawy# 必須 著者org:serverlessinc# 必須 開発組織名description:Deploys Serverless Express.js Apps# 任意 説明keywords:aws, serverless, express# 任意 registry.serverless.comで検索するときに引っかかるキーワードrepo:https://github.com/owner/project# 任意 ソースコードのリポジトリURLlicense:MIT# 任意 ライセンスmain:./src# 任意 コンポーネントのソースコードが格納されているディレクトリ

serverless.js

serverless.jsには以下のように実装していきます。React の思想にインスパイアされており、Componentを継承したつくりになっています。deploy, removeなどの Function を書いていくことになります。

// serverless.jsconst{Component}=require("@serverless/core");classMyBlogextendsComponent{asyncdeploy(inputs){console.log("Deploying a serverless blog");// --debug モードで実行するとログが出力されます。this.state.url=outputs.url;// stateに保存するreturnoutputs;}}module.exports=MyBlog;

deploy()は必ず書く必要があります。deploy()は、コンポーネントが何らかのクラウドリソースを作成するためのロジックが存在する場所です。serverless deployコマンドを実行すると、常に deploy()メソッドが呼び出されます。

このクラスに他のメソッドを追加することもできます。remove()ではサーバレスコンポーネントが作成したクラウドリソースを全て削除します。

他にもメソッドはいくつでも追加できます。コンポーネントを追加機能付きでプロビジョニングしたい場合などに拡張できる作りになっています。

Serverless Components は test(), logs(), metrics()機能を備えたコンポーネント、またはデータベースコンポーネントの初期値を確立するためのコンポーネント seed()の開発に着手しているようです。

メソッド以外の deploy()メソッドはすべてオプションです。すべてのメソッドは、inputs個別の引数ではなく単一のオブジェクトを取り、単一のオブジェクト outputsを返します。

まとめ

クラウドや SaaS ベンダーのリソースを統一的に管理し、再利用可能にするプラットフォームが Serverless Components です。

現在公式が提供しているコンポーネントは 67 種類(2020/04/06 現在)です。
まずは AWS や Kubernates 関連がラインナップし始めています。急激に拡大しています。もう 本当に必要なものだけを実装し、組み合わせるだけという開発スタイルに変わりはじめています。

これこそがクラウドテクノロジーの目指していたところかもしれません。さらなる発展が楽しみですね。

macOS環境構築雑多メモ

$
0
0

macの最近の環境構築。winもほぼ変わらないと思うのでURL見つけておこう。

  • homebrew
  • git
  • nodebrew
  • npm

インストール内容

homebrew

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

...
  ==> The Xcode Command Line Tools will be installed.

  Press RETURN to continue or any other key to abort

RETURNしてpassword入力

  Already up-to-date.
  ==> Installation successful!

git

$ brew install git

参考にしたサイト
https://qiita.com/micheleno13/items/133aee005ae37c28960e

nodebrew

$ brew install nodebrew

参考にしたサイト
https://qiita.com/mame_daifuku/items/373daf5f49ee585ea498

Azure QnA Makerを使ってローカルで動くChatBotを作ってみた

$
0
0

前提

  • ローカルにNode.js環境があること(ない人はNode.jsをインストールしておいてください)

概要

Azure QnA Makerを使ってローカルで動くChatBotを作成し、Emulatorを使って実際にBotと会話することをゴールとしています。
chatbot.png

以下の手順でローカルにBotを構築していきます。この回でコーディングは特にしませんが、Botアプリで利用する言語はNode.jsです。

  • 1. QnA Maker ServiceとKnowledge baseの構築
  • 2. ローカルでBotアプリを構築
  • 3. Bot Framework EmulatorをインストールしてBotに接続
  • 4. リソースの後片付け(お金がかからないように)

下記についてはこの記事で説明しないのでご注意ください。

  • Botアプリのロジックについて
  • 生成されるAzureリソースの役割について

1. QnA Maker ServiceとKnowledge base(KB)の構築

QnA Portalにログインして、「Create a Knowledge base」を押下。
基本は、遷移先のページに書いてあるSTEP通りに進めていきます。
create-kb-overview.png
※画面は見切れてるけどSTEP4まであるよ。

STEP1 Create a QnA service in Microsoft Azure.

「Create a QnA service」を押下すると、AzureのQnA Maker作成画面に遷移します。そこで下記のように入力し、作成をクリック。
価格レベルはF0とFを選択すれば無料になります。僕の場合は無料枠を既に使ってしまったので、S0とBを選択しました。
とはいえお金がかかるのは嫌なので、このチュートリアル後にすぐに消そうと思います。記事の後半でそのことに触れているのでお金かかる怖いやりたくないって思う人も不安がらなくて大丈夫です。

無料の場合↓
create-qna-service-for-free.png

ちょっとお金かかるよって場合↓
create-qna-service-for-fee.png

しばらくすると、「デプロイが完了しました」と画面に表示されます。先ほど指定したリソースグループをみると下記のようにいくつかのリソースが作られていることが分かります。成功です。
azure-resources.png

STEP2 Connect your QnA service to your KB.

QnA Portalに戻って「Refresh」すると、Azure QnA Service欄で先ほど作ったリソースが選べるようになります。
日本語のQnAを作りたいのでLanguageはJapaneseにします。
connect-to-kb.png

STEP3 Name your KB.

ここはなんでもいいです。好きな名前をつけてください。
name-kb.png

STEP4 Populate your KB.

KBにQnAを登録します。QnAが記載されたページのURLを指定してもいいんですが、実用的な側面を考えて今回はTSVファイルを指定することにします。使うTSVファイルは手抜きですがこんな感じでいきます。
kb-data-tsv.png
「+ Add file」を押下し、作成したTSVファイルを指定しましょう。
Chit-chatはなくてもいいですが、愛着のあるBotを作成したいのでひとまずFriendlyを選択します。
populate-kb.png

STEP5 Create your KB

「Create your KB」を押下してKBを作成しましょう。完了すると、今回作成されたKnowledge baseのEDIT画面に遷移します。
先ほど作成したTSVファイルの内容と、Chit-chatで指定したFriendlyなQnAが登録されているのが分かります。
kb.png

右上の「Save and train」を押下して学習を行い、「Publish」タブの「Publish」を押下してエンドポイントを発行します。エンドポイントは後程使います(SETTINGタブからいつでも見れるので、画面をそのままにしておく必要はありません)。
KBの構築は以上になります。

2. ローカルでBotアプリを構築

ソースコードはSampleとして提供されているものを使用します(1から書くとめちゃくちゃ大変)。

1. BotBuilder-Samplesリポジトリをクローン

実際に利用するソースはQnAの呼び出しまで提供してくれている下記のSampleになります。
https://github.com/microsoft/BotBuilder-Samples/tree/master/samples/javascript_nodejs/11.qnamaker

2. KBへの接続情報を記述

QnA Maker Portal画面を開き、作ったKBのSETTINGタブの「Deployment details」を参照しながら、.envファイルに下記のように記述します。

MicrosoftAppId=MicrosoftAppPassword=QnAKnowledgebaseId=[POST /knowledgebases/{この部分}/generateAnswer]QnAEndpointKey=[Authorization: EndpointKey {この部分}]QnAEndpointHostName=[Host: {この部分}]

3. パッケージのインストール

TerminalやCMDなどで11.qnamakerディレクリに移動し、npm installコマンドを実行します。

4. Botの起動

続いてnpm startでBotを起動します。

3. Bot Framework EmulatorをインストールしてBotに接続

このページからご自身の環境に合わせたAssetsをダウンロードし、インストールします。
Bot Framework Emulatorとは、主にローカルでチャットクライアントとして動作するテスト用のデスクトップアプリ(ツール)です。
Emulatorを立ち上げると、Visual Studio Codeを触っている人には見慣れた画面が現れます。Microsoft製品なので当然ですが。
bot-framework-emulator.png

「Open Bot」を押下し、Bot URLにhttp://localhost:3978/api/messagesと入力して「Connect」をクリックすると、EmulatorからローカルのBotにアクセスできるようになります。
connect-to-bot.png

試しに「Qiitaとは」などとBotに話しかけてみましょう。期待通りに返してくれるはずです。
また、「こんにちは」などと話しかけると、Friendlyに応答してくれます。
chatbot.png

4. リソースの後片付け(お金がかからないように)

無料枠で構築した人は特にお金はかからないのでそのまま放置しても大丈夫です。

Azureのリソースグループ画面へ移動し、今回構築したすべてのリソースにチェックをつけ、削除ボタンを押します。
確認のための質問がありますが「はい」を入力して削除してしまいましょう。
resource-group.png

おわりに

AzureのQnA Makerとを使えばかなり簡単に機械学習を用いたQnA Chatbotを作ることができました。
KBにいろいろなQnAを登録することで、いい感じに表記ゆれを吸収し、目的の回答を得ることができるので是非試してみてください。

ただ正直Microsoftのドキュメントが分かりにくい…
なのでこのような記事を書きましたが、もっとQiitaにChatbot系の記事が増えると嬉しい限りです。

【node.js】bookshelfとknexを使ってCRUD操作

$
0
0

expressを使ったbookshelfの操作方法について書かれております。

Bookshelfとは

SQLを使わずデータベースを使うことができるORM

Bookshelfは、 Knex SQLクエリビルダー上に構築されたNode.js用のJavaScript ORMです。 Promiseベースのコールバックインターフェイスと従来のコールバックインターフェイスの両方、トランザクションサポート、eager / nested-eagerリレーションのロード、ポリモーフィックな関連付け、1対1、1対多、および多対多のリレーションのサポートを備えています。
PostgreSQL、MySQL、SQLite3で動作するように設計されています。

knexとは

Bookshelfは、内部で「クエリービルダー」と呼ばれるknexモジュールを利用しています。ですから、インストールの際には、まずknexを入れておく必要があります。

良く分からないけど、Bookshelfを使う際にはついてくる物らしい。。。

モジュールのインストール

npm install--save knex
npm install--save bookshelf

Bookshelfの初期設定

varknex=require('knex')({client:'mysql',connection:{host:'127.0.0.1',user:'root',password:'',database:'データベース名',charset:'utf8'}});varBookshelf=require('bookshelf')(knex);varMydata=Bookshelf.Model.extend({tableName:'Mydata(テーブル名)'});

上記のコードで行っている事は、
knexのロード、
knexの初期設定、
Bookshelfのロード、
モデルの作成

モデルとは?

テーブルにアクセスするオブジェクトです。
これを使って、テーブルのデータをCRUD操作します。

モデルの項目追加

hasTimestamps:trueタイムスタンプのデータをレコードに追加する
user: function() {return this.belongsTo(テーブル名)他のテーブルの値を取得可能にする

これでBookshelfの初期設定は完了しました。
いよいよ、CRUD操作をしていきます。

Bookshelfのメソッド

Bookshelfで操作する際に使用するメソッドです。使いながら覚えていきましょう。

fetchAll全てのレコードを取得する物
thenデータベースアクセス完了後の処理をコールバック関数として設定します。
catchthenを実行した時にエラーが発生した時の処理です。
saveオブジェクトの引数をテーブルに保存する処理をします。
where検索する対象に条件を設定して絞り込む
fetch最初の一つだけレコードを取り出す。
fetchPage得られたページ番号を元にレコードを取り出します。
orderByレコードを並べ変える。
query複数項目をチェックすする

レコードを取り出す

全レコードを取り出す

newMydata().fetchAll().then((collection)=>{-----取得できた場合の処理-----}).catch((err)=>{-----エラー発生時の処理-----});

レコードを保存する

新しいレコードの追加をしていきます。

router.post('/add',(req,res,next)=>{newMydata(req.body).save().then((model)=>{ー追加した後の操作ー});});

検索

router.post('/',(req,res,next)=>{nexMydata().where('id','=',req.body.fstr).fetch().then((collection)=>{取得後の操作}}

レコードを並べかえる

router.get('/:page',(req,res,next)=>{nexMessage().orderBy('created_at','DESC').fetchAll().then((collection)=>{取得後の操作});})

orderByについて

第一引数にフィールド名、第二引数には「ABC」「DESC」の何かを指定します。
(DESCなら降順、ABCなら逆順)

複数項目をチェックする

router.post('/',(req,res,next)=>{varnm=req.body.name;varpw=req.body.password;Mydata.query({where:{name:nm},andWhere:{password:pw}}).fetch().then((model)=>{取得後の操作}}

関数query()に関して

where:{name: nm}nameの値が変数nmである
andWhere:{password: pw}それに加えて、passwordの値が変数pwである

ページネーション

Bookshelfを使う事で、ページネーションも使えるようになります。

router.get('/:page',(req,res,next)=>{varpg=req.params.page;pg*=1;if(pg<1){pg=1;}newMyData().fetchPage({page:pg,pageSize:3}).then((collection)=>{取得した後の操作}).catch((err)=>{エラーを取得した後の操作}}

関数.fetchPageに関して

page現在のページ番号
pageCountページ数(最後のページのページ番号)
pageSize1ページ辺りのレコード数
rawCountレコードの総数

参考

node.js超入門第2版

Bookshelfとは
https://bookshelfjs.org/

paginationプラグイン
https://github.com/bookshelf/bookshelf/wiki/Plugin:-Pagination

obnizとLineBotで防犯ツールを作ってみた(NO MORE XX 泥棒)

$
0
0

この記事を見てできること

LINEBOTに「スタート」とメッセージを送ると遠隔操作で監視をしてくれるものが作れます。
異常があったら必要以上にしつこく知らせてくれます。
(処理をストップさせる処理は書いてないです)

obnizの配線

配線はこんな感じ。
IMG_0140.JPG

コード内にも書いてありますが、人感センサーは{vcc:11, signal:10, gnd:9}につなぎます。

ソースコード

はじめobniz.onconnectをするタイミングでハマって動かなかった。
handleEventよりも先にobniz.onconnectをしておく必要がある。

line_sensor.js
'use strict';// obniz呼び出しconstObniz=require('obniz');varobniz=newObniz("XXXXXX");// Obniz_ID に自分のIDを入れますconstexpress=require('express');constline=require('@line/bot-sdk');constPORT=process.env.PORT||3000;constconfig={channelAccessToken:'XXXXXX',channelSecret:'XXXXXX'};constapp=express();app.post('/webhook',line.middleware(config),(req,res)=>{console.log(req.body.events);Promise.all(req.body.events.map(handleEvent)).then((result)=>res.json(result));});constclient=newline.Client(config);// obniz接続//////////// スコープ(変数の影響範囲)を最上部に置いておくvarsensor;varled;// 接続した段階でセンサーの準備はしておくobniz.onconnect=asyncfunction(){obniz.display.clear();obniz.display.print("obniz LINE bots");sensor=obniz.wired("HC-SR505",{vcc:11,signal:10,gnd:9});led=obniz.wired("LED",{anode:0,cathode:1});}functionhandleEvent(event){if(event.type!=='message'||event.message.type!=='text'){returnPromise.resolve(null);}letmes=''if(event.message.text==='スタート'){mes='OK!監視始めるね!';//メッセージだけ先に処理// ディスプレイ処理obniz.display.clear();// 一旦クリアするobniz.display.print("Hello obniz!!!!!!");// Hello obniz!という文字を出す// setIntervalで間隔を作るsetInterval(asyncfunction(){// 非同期で取得vardetected=awaitsensor.getWait();// console.log(detected);// displayに反映obniz.display.clear();// 一旦クリアする// obniz.display.print(detected);  // 英語が出力できる// 近づいてきたら判定するif(detected==true){// 何かを感知したらobniz.display.clear();// 一旦クリアするobniz.display.print("someone moving!");led.on();// ライトONgetAskObniz(event.source.userId);// message}else{led.off();}},1000);// 1000ミリ秒 = 1秒}else{mes=event.message.text;}returnclient.replyMessage(event.replyToken,{type:'text',text:mes});}constgetAskObniz=async(userId)=>{awaitclient.pushMessage(userId,{type:'text',text:"誰かいるかも?",});}app.listen(PORT);console.log(`Server running at ${PORT}`);

XXXXXXになっている箇所は自分のObniz IDや、channelAccessToken、channelSecretを入れることをお忘れなく。

ほぼ完成

ここまで機能としては完成です。こんな感じで動きます。
IMG_0141.jpg
人を感知すると知らせてくれます。

このままでは終われない

こういった手で触れるものは見た目も大事。そして遊び心も必要です(たぶん)

この時点で平日の夜中2時を回っている。当然明日も朝から仕事である。

考えても何もアイデア浮かばない。。
ティッシュがあるからてるてる坊主?
いや、最近ティッシュやトイレットペーパーがなかなか売ってないから無駄使いはダメだ...

「よし、映画館のあいつを作ろう」
IMG_0134.JPG

こんな感じで動きます

最後に

物理的に触れるものは愛着湧きますね。もっと時間がある時にもっとそれっぽく作りたい。
あと、LINEでストップとメッセージしたらモニタリングがストップするような処理も書きたい。

npmパッケージのvulnerability対応フロー

$
0
0

概要

  • npmプロジェクトで利用しているnpmパッケージ(依存パッケージ)でvulnerability(脆弱性)が見つかったときの対処フローについて記載します。
    image.png

(GitHub等が親切に"We found potential security vulnerabilities in your dependencies."のように通知してくれるので便利)

対応フロー

ざっくり全体像は以下のとおり。

image.png

①最新のコードを取得する

いうまでもないが、手元のコードは最新のコードにしておく

git pull

最新であることが確認された

Already up to date.

ちなみに、今回は自作プロジェクト(https://github.com/riversun/simple-date-format) で実際にハンズオンした

②プロジェクトが使用しているnpmパッケージが最新かどうか確認する

脆弱性対応の前に、いま自分のプロジェクトが使っているnpmパッケージを最新のものにする。

プロジェクトが使っているnpmパッケージが最新かどうかはnpm outdatedコマンドで確認することができ、最新バージョンがある場合は、その情報を表示してくれる

npm outdated

するとこのように、最新版が存在するパッケージを一覧表示してくれる
いくつかのパッケージで最新版があるようだ

image.png

パッケージをアップデートする

package.jsonに記載されるnpmパッケージ一覧は以下のようになっている。
(「^」がついたキャレット表記についてはこちらで説明)

package.json抜粋
"@babel/core":"^7.8.4","@babel/preset-env":"^7.8.4","babel-jest":"^25.1.0",...

パッケージのバージョンの付け方セマンティックバージョニングに従っている。

セマンティックバージョニングにおいて、メジャーバージョン、マイナーバージョンは以下の意味をもっている。
image.png

これをふまえ、「パッケージを最新にする」には2種類の方法があるといえる。(パッチバージョンを上げるというのもいれれば3種類だが本筋にあまり影響ないので割愛)

  • ②-1 マイナーバージョンまで最新にする
  • ②-2 メジャーバージョンまで最新にする

②-1をとるか ②-2をとるかはポリシー次第だがメジャーバージョンを最新にする場合はAPIの後方互換が無いことを想定しておいたほうがいい。

②-1 マイナーバージョンまで最新にする場合

キャレット表記「^」つきで定義されたパッケージのバージョンを、マイナーバージョンまでを最新にするにはnpm updateコマンドで可能

npm update

以下のように、マイナーバージョンまでが最新になった
(ただし、メジャーバージョンは最新になっていない。)

+ jest@25.2.7
+ babel-loader@8.1.0
+ cross-env@7.0.2
+ @babel/core@7.9.0
+ babel-jest@25.2.6
+ @babel/preset-env@7.9.0
+ webpack@4.42.1
added 36 packages from 17 contributors, removed 6 packages, updated 140 packages, moved 1 package and audited 263490 packages in 18.399s

②-2 メジャーバージョンまで最新にする場合

npm updateでは、マイナーバージョンまでしかアップデートしてくれなかったが、ここではメジャーバージョンまで容赦なくアップデートしてくれるパッケージnpm-check-updatesを導入する

以下のようにしてnpm-check-updatesをインストールする

npm install -g npm-check-updates

npm-check-updatesをインストールするとncuというコマンドが使えるようになる。

ちなみに、ncuコマンドだけをたたくと現在のパッケージバージョンと最新のパッケージバージョンを表示してくれるnpm outdatedコマンドのような動作をする

ncu

を実行すると、以下のように現在のバージョンと最新バージョンが表示される。

image.png

つづいて、ncu -uを実行すれば、上でみたとおりパッケージが最新バージョンになるようにpackage.jsonを書き換えてくれる

ncu -u

image.png

npm updateと違ってパッケージのインストールそのものはやってくれない。package.jsonが書き換わるだけなので、自分で npm install する必要がある

npm install

これでパッケージは最新になった

③npm auditで脆弱性のある依存パッケージを確認する

今、最新のパッケージにした状態だが、この状態でも脆弱性(valnerability)のあるパッケージが含まれていることがある。
npm auditコマンドを使えば脆弱性のあるパッケージを洗い出すことができる。

npm audit

をやってみたら、180個の脆弱性がみつかった。レベルはlow

found 180 low severity vulnerabilities in 263397 scanned packages
  run `npm audit fix` to fix 174 of them.
  6 vulnerabilities require manual review. See the full report for details.

④npm audit fixで自動修復をこころみる

npm audit fixをすると、脆弱性のあるパッケージのバージョンを自動的に脆弱性の無いバージョンに置き換えてくれる(努力をしてくれる)

npm audit fix

すると以下のようになった。
180個の脆弱性のうち174個が修正された。

removed 1 package and updated 2 packages in 4.641s

31 packages are looking for funding
  run `npm fund` for details

fixed 174 of 180 vulnerabilities in 263397 scanned packages
  6 vulnerabilities required manual review and could not be updated

残り6個はマニュアルレビューしてくれとかいてある。

⑤npm dedupeで重複したパッケージの整理統合をこころみる

npm dedupeを理解する

まず、dedupeの概念を理解するために以下の状態を考える

  • 自プロジェクトnpmパッケージAnpmパッケージBに依存している
  • npmパッケージAnpmパッケージC @2.1.1に依存している。
  • npmパッケージBnpmパッケージC @2.2.0に依存している。

この場合、npmを使っていると1npmパッケージC @2.1.1npmパッケージC @2.2.0もインストールされる。

パッケージとしてはnpmパッケージCで同じなのに、npmパッケージC @2.1.1npmパッケージC @2.2.0でバージョンが違いがあるので両方保持されしまう。

だったら、バージョンを新しいほうの@2.2.0のほうに合わせて、npmパッケージC @2.2.0のほうを、npmパッケージAnpmパッケージBで共通化して使いましょう、というのがdedupeの発想。

npm dedupeは新しいバージョンをインストールしてくれない

npm update等でdedupeはひととおりのパッケージを最新にした後にやる。
なぜなら、dedupe自身はパッケージの重複排除などはやってくれるが、最新のパッケージを入れてくれるわけではないので古いパッケージバージョンで状態でdedupeしてもあまり意味がない。

dedupeする

さて、ではnpm dedupe (npm ddpでもOK)してみる

removed 10 packages and audited 263298 packages in 3.622s
found 0 vulnerabilities

脆弱性が0になった!

なぜdedupeで脆弱性がゼロになった?

これは、dedupeが脆弱性を排除しているというより、古いパッケージバージョンに依存していたライブラリが新しいパッケージバージョンを参照するようにdedupeが変更してくれた効果のため。

上の例でいうと以下のようになる。

  • 自プロジェクトnpmパッケージAnpmパッケージBに依存している
  • npmパッケージAnpmパッケージC @2.1.1(脆弱性ありバージョン)に依存している。
  • npmパッケージBnpmパッケージC @2.2.0(安全バージョン)に依存している。

の状況をdedupeが↓のように変更してくれた効果

  • 自プロジェクトnpmパッケージAnpmパッケージBに依存している
  • npmパッケージAnpmパッケージC @2.2.0(安全バージョン)に依存している。
  • npmパッケージBnpmパッケージC @2.2.0(安全バージョン)に依存している。

⑦回帰テストを実行する

さてここまでで、脆弱性の無い状態にできたら、最後にテストをしてパッケージバージョンを変更したことによるプロジェクトのデグレが発生していないかどうかを確認する。

npm test

image.png

テスト無事通過!

(といっても、今回はdevDependenciesの依存のみだったけど)

これで対応完了!

途中でつまずいた場合

上述のコマンドだけでうまくいかず、つまずくことも多々ある。そうした場合は、コマンドだけで楽に突破できない状況になっている可能性があるので、それなりの調査分析工数を覚悟するしかない。

つまずき例

  • vulnerabilityが無くならなかった

    • 対応案:npm auditでvulnerabilityのあるパッケージに依存している上位のパッケージを特定する。そのパッケージがdeprecateになっていないか。ちゃんとメンテされているか確認する。deprecatedだったりメンテされていなければ使うのをやめたりPR送ってみたり、自分で直してみたり、代替パッケージを探したり。そういった工数を覚悟、確保する。
  • 最後のテストでつまづいた

    • 対応案:手順を1手ずつロールバック(手順を戻す)しながら、都度npm testを実行し、どの段階でつまづいたのか特定する。ありがちなのは②-1メジャーアップデートでAPIに破壊的変更

まとめ


  1. yarnは自動的にdedupeしてくれる(https://classic.yarnpkg.com/ja/docs/cli/dedupe/

IoT Agency PlatformをHeroku上にデプロイする

$
0
0

Sigfoxが提供していたIoT Agency PlatformがGNU Affero General Public Licenseに基づくオープンソースとなりました。ここでは、IoT Agency PlatformをHeroku上にデプロイする方法を説明します。
image.png

IoT Agency Platformは、SigfoxやMQTT、Webhookに対応したIoTデバイスからデータを取得し、グラフや地図形式でダッシュボード化できるアプリケーションです。使い方に関しては、こちらを参考にしてください。

全体の流れ

Sigfox IoT Agency PlatformをHeroku上にデプロイする方法は下記の通りの流れとなります。
1. sigfox platformのGithubからHerokuにアプリケーションをデプロイする
2. MongoDBを立ち上げ、データベースを紐づける

sigfox platformのGitHubからHerokuにアプリケーションをデプロイする

デプロイ方法は2種類あります。下記ボタンをクリックして、UIウィザードに従いデプロイする方法と
image.png
Heroku CLIで下記のようにデプロイする方法です。

git clone https://github.com/IoT-Makers/sigfox-platform.git my-project
cd my-project
heroku apps:create my-project
git push heroku master

ここでは、前者の方法で説明します。

新しいアプリケーションを生成する

先程の"Deploy to Heroku"ボタンをクリックしApp nameとregionを選択します。App nameはユニークな名前になるように設定してください。
image.png
設定後、Deploy appボタンをクリックすると、デプロイが開始します。数分かかりますが、完成すると下記のような画面になります。
image.png
Manage Appボタンをクリックし、アプリケーションの管理画面に遷移します。

MongoDBを立ち上げ、データベースを紐づける

ここでは、MongoDBのアドオンをアプリケーションにアタッチします。mLabはMongoDBをDBaaS(Database-as-a-Service)として提供するものです。

mLabはmongoDB.Altasに統合されたため、mongoDB.Altasでも同じようなことができると思いますが、ここでは、mLabで進めます。

mLab MongoDBアドオンの追加

Heroku管理画面のResourcesタグ内にあるQuickly add add-ons from Elements検索フィールドで"MongoDB"をキーに検索するとmLab MongoDBというアドオンが表示されます。
image.png
mLab MongoDBを選択すると、下図の画面になりますので、有償版でも結構ですが、まずの動作確認としては、無償版(Sandbox - Free)を選択し、Provisionボタンをクリックしてください。
image.png

mLab MongoDBとの接続

追加された"mLab MongoDB"をクリックします。
image.png
mLabの管理画面が表示されます。
画面上に、MongoDB URIで接続する(To connect using a driver via the standard MongoDB URI)と書かれた下にmongodB://からはじまるURIが表示されています。このURIをHeroku側に設定してあげればOKです。
image.png
HerokuのSettingタグに遷移し、Config Vars(環境変数設定)をします。
image.png
新たに、MONGODB_URIをキーとし、先程のURIを貼り付けます。ただ、のところは、データベースの接続アカウントに置き換える必要があります。(MONGOLAB_CYAN_URIの値をコピー&ペーストしても良いかも)

アプリケーションの再起動

Herokuの"More"のところにある**Restart all dynos"を選択し、アプリケーションを再起動します。
image.png
再起動は1,2分内で終わると思いますので、再起動後、"Open app"をクリックし、アプリケーションを表示します。
下図のようなログイン及びサインアップ画面が表示されれば成功です。
image.png

なお、ソースコードは、[リポジトリ名]\frontend\srcの中にあります。

補足

ちなみに、下記のフレームワークを使っているようです。

  • Backend: Loopback 3+
  • Frontend: Angular 6+
  • Real-time: Primus
  • Database: MongoDB
  • Pub-sub & queuing: RabbitMQ

なお、本投稿に当たっては、Antoine de ChasseyLouis Moreauそして、Guillaume Noelに感謝します。

Sigfox Japan KCCS
Twitter @ghibi


『コピペOK』NodeでS3へ画像アップロード【2020】

$
0
0

まずは、ここからs3へのアクセスキーと、シークレットきーを取得してください。

S3 Bucketを作る

$ npm i --save aws-sdk
create-bucket.js
constAWS=require('aws-sdk');// Enter copied or downloaded access ID and secret key hereconstID='';constSECRET='';// The name of the bucket that you have createdconstBUCKET_NAME='test-bucket';//同じバケット名は作れない ユニーク必須consts3=newAWS.S3({accessKeyId:ID,secretAccessKey:SECRET});constparams={Bucket:BUCKET_NAME,CreateBucketConfiguration:{// アジアパシフィック (東京)LocationConstraint:"ap-northeast-1"}};s3.createBucket(params,function(err,data){if(err)console.log(err,err.stack);elseconsole.log('Bucket Created Successfully',data.Location);});

こんな感じ。で、実行

$ node create-bucket.js

S3で画像をアップする

uplode-bucket.js
constfs=require('fs');constAWS=require('aws-sdk');// Enter copied or downloaded access ID and secret key hereconstID='';constSECRET='';consts3=newAWS.S3({accessKeyId:ID,secretAccessKey:SECRET});constuploadFile=(fileName)=>{// Read content from the fileconstfileContent=fs.readFileSync(fileName);// Setting up S3 upload parametersconstparams={Bucket:"test-bucket",Key:'uniquename.jpg',// s3へ保存される名前 ユニーク必須Body:fileContent};// Uploading files to the buckets3.upload(params,function(err,data){if(err){throwerr;}console.log(`File uploaded successfully. ${data.Location}`);});};uploadFile('/Users/user-name/Desktop/test.png');//ここにファイルの実体を入れる

こんな感じ。で、実行

$nodeuplode-bucket.js

参考

https://stackabuse.com/uploading-files-to-aws-s3-with-node-js/

【ReactNative+Typescript】ローカルDBでRealmを使う

$
0
0

概要

ReactNativeTypescriptのプロジェクトにローカルDBとしてRealmをインストール・使用する際のメモ。
環境は以下の通り。

  • node @10.15.0
  • react-native @0.61.5
  • typescript @3.5.3

インストール

nodeのバージョンが10.X系にする必要がある。

install --save realm

react-nativeのバージョンが0.59以下の場合は要link

iOSの場合

CocoaPods

cd ios
pod install

実装

importRealmfrom'realm'// スキーマ名constHOGE_SCHEMA_NAME:string='HOGE';// スキーマ定義constHOGE_SCHEMA:Realm.ObjectSchema={// スキーマ名name:HOGE_SCHEMA_NAME,// 主キー(省略可)primaryKey:'prop1',// プロパティproperties:{// 型のみ指定prop1:'int',// 型と初期値を指定prop2:{type:'string',default:'hoge'},}}// データIFinterfaceHoge{prop1:number,prop2:string,}// DB操作開始Realm.open({schema:[HOGE_SCHEMA]}).then((realm:Realm)=>{// write()でトランザクションを開始するrealm.write(()=>{// primaryKeyを指定しているならcreate()はupsertとして働かせることもできる(その場合は第3引数にtrue)を渡すrealm.create(HOGE_SCHEMA_NAME,{prop1:0,prop2:'test'});// 削除はdelete()もしくはdeleteAll()});// objects(schemaName : string)でデータを取得constdatas:Realm.Results<Hoge>=realm.objects(HOGE_SCHEMA_NAME);});

参考

Node.js: child_process.fork()で起動したプロセスを子子孫孫殺す方法

$
0
0

本稿では、Node.jsにて、子プロセス、そこから派生した孫プロセス、さらにそこから派生したひ孫プロセス……を、一括して終了する方法を説明します。

※説明にあたって、実行環境はUNIX/Linuxを前提にしています。

子プロセスを殺しても、孫プロセスは死なない

Node.jsのchild_process.fork()は、子プロセスを起動できて便利です。子プロセスの中で、fork()を使って、孫プロセスを起動することもでき、さらに、孫プロセスでfork()して、ひ孫プロセスを、といった具合に子プロセスはネストして起動することができます。

起動した子プロセスはsubprocess.kill()で終了することができます。しかし、これは直接の子プロセスしか殺すことができません。どういうことかというと、

  1. oya.js が ko.js のプロセスを起動する。
  2. ko.js が mago.js のプロセスを起動する。
  3. このとき、 oya.js が ko.js のプロセスをkill()したとする。
  4. ko.js は終了する。
  5. mago.js は生存する。 (※このとき、 mago.js はinitプロセスの養子に出され、親pidは1になる)

といった事態が発生します。

孫プロセスが残存するサンプルコード

上のようなシナリオを再現できるコードを書いてみたいと思います。

まず、oya.jsの実装:

oya.js
console.log('oya.js: running')// SIGINTを受け付けたときprocess.on('SIGINT',()=>{console.log('oya.js: SIGINT')process.exit()})// プロセスが終了するときprocess.on('exit',()=>{console.log('oya.js: exit')})// 子プロセスを起動constko=require('child_process').fork(__dirname+'/ko.js')// 3秒後にproc2.jsを終了するsetTimeout(()=>{console.log('oya.js: ko.jsを終了させてます...')ko.kill('SIGINT')},3000)// ko.jsが終了したときko.on('exit',()=>{console.log('> Ctrl-Cを押してください...')})// このプロセスがずっと起動し続けるためのおまじないsetInterval(()=>null,10000)

oya.jsはko.jsを起動し、3秒後にko.jsを終了するコードになっています。ko.jsをkill()する際には、SIGINTシグナルを送るようにしています。Linuxのシグナルについては、ここでは詳しく説明しません。ここでは単にSIGINTシグナルはプロセス終了を指示するものと考えてください。

次に、ko.js:

ko.js
console.log('ko.js: running')// SIGINTを受け付けたときprocess.on('SIGINT',()=>{console.log('ko.js: SIGINT')process.exit()})// プロセスが終了するときprocess.on('exit',()=>{console.log('ko.js: exit')})// 孫プロセスを起動するrequire('child_process').fork(__dirname+'/mago.js')// このプロセスがずっと起動し続けるためのおまじないsetInterval(()=>null,10000)

最後に、mago.js:

mago.js
console.log('mago.js: running')// SIGINTを受け付けたときprocess.on('SIGINT',()=>{console.log('mago.js: SIGINT')process.exit()})// プロセスが終了するときprocess.on('exit',()=>{console.log('mago.js: exit')})// このプロセスがずっと起動し続けるためのおまじないsetInterval(()=>null,10000)

このコードを実行してみます:

$ node oya.js
oya.js: running
ko.js: running
mago.js: running
oya.js: ko.jsを終了させてます...
ko.js: SIGINT
ko.js: exit
> Ctrl-Cを押してください...

3秒後にこのような出力がされ、oya.jsがko.jsをkill()し、ko.jsが終了したことが確認できます。

一方、mago.jsはまだSIGINTを受け取っていませんし、終了もしておらず、残存しています。

ここで、Ctrl-Cを押すと、oya.jsとmago.jsにSIGINTが送信されます:

...
> Ctrl-Cを押してください...
^Coya.js: SIGINT
mago.js: SIGINT
mago.js: exit
oya.js: exit

このタイミングではじめて、mago.jsが終了することが分かります。

感想を言うと、ko.jsにSIGINTを送信したら、mago.jsにもSIGINTが伝搬されていくものと誤解していたので、この結果は意外でした。

起動したプロセスを子子孫孫殺す方法

では、起動した子プロセスをkill()したタイミングで、孫プロセスも終了になるようにするにはどうしたらいいのでしょうか? それについて、ここで説明したいと思います。

プロセスグループ = 「世帯」

まず、Linuxのプロセスの基本として、プロセスグループというものがあります。これはプロセスの「世帯」のような概念で、親プロセス、子プロセス、孫プロセスをグループ化するものです。たとえば、Bashでnodeプロセスであるoya.jsを起動すると、そこからfork()したko.jsやmago.jsは、同じプロセスグループに属し、同一のグループIDが与えられます。

psコマンドでグループID(GPID)を確認すると、現に同じグループIDが3つのnodeプロセスに割り当てられていることが分かります:

$ ps -xo pid,ppid,pgid,command | grep node | grep .js
PID   PPID  GPID  COMMAND
17553  3528 17553 node oya.js
17554 17553 17553 node ko.js
17555 17554 17553 node mago.js

この結果をよく見ると分かりますが、GPIDはoya.jsのプロセスID(PID)と同じです。つまり、親のPIDが子孫のGPIDになるわけです。

プロセスを「世帯」ごと殺す方法

Node.jsでは、グループIDを指定して、プロセスを終了させることができます。やりかたは、process.kill()にGPIDを渡すだけです。このとき、与える値は負の数にしてあげます。正の数を渡してしまうと、プロセスグループではなく個別のプロセスをkill()するだけになるので注意です。

constgroupId=123456process.kill(-groupId,'SIGINT')

ちなみに、シェルでCtrl-Cを押したときに、親・子・孫がもろとも終了されるのは、Ctrl-Cが送るSIGINTが親プロセスに対してではなく、プロセスグループに対して送られているからです。(要出典)

detached = 別世帯を作る

今回やりたいことは、oya.jsのプロセスは生かしつつ、ko.jsとmago.jsをkill()したいことです。しかし、GPIDを指定したkill()では、oya.jsまで終了してしまいます。三者とも同じGPIDだからです:

PID   PPID  GPID  COMMAND
17553  3528 17553 node oya.js
17554 17553 17553 node ko.js
17555 17554 17553 node mago.js

ko.jsとmago.jsを別のGPIDを割り振る必要があります。それをするには、fork()のオプションにdetachedを指定します。

oya.js
// 子プロセスを起動constko=require('child_process').fork(__dirname+'/ko.js',[],{detached:true})

これを指定すると、ko.jsとmago.jsがいわば「別世帯」になり、別のプロセスグループに属するようになります。GPIDもoya.jsとは別のものが割り当てられているのが確認できます:

$ ps -xo pid,ppid,pgid,command | grep node | grep .js
PID   PPID  GPID  COMMAND
21404  3528 21404 node oya.js
21405 21404 21405 node ko.js
21406 21405 21405 node mago.js

プロセスを子子孫孫殺すoya.jsの完成形

以上を踏まえて、oya.jsを子プロセス、孫プロセスを一括して終了できるように変更すると次のようになります:

oya.js
console.log('oya.js: running')// SIGINTを受け付けたときprocess.on('SIGINT',()=>{console.log('oya.js: SIGINT')process.exit()})// プロセスが終了するときprocess.on('exit',()=>{console.log('oya.js: exit')})// 子プロセスを起動constko=require('child_process').fork(__dirname+'/ko.js',[],{detached:true})// 重要な変更箇所!// 3秒後にko.jsを終了するsetTimeout(()=>{console.log('oya.js: ko.jsを終了させてます...')process.kill(-ko.pid,'SIGINT')// 重要な変更箇所!},30000)// ko.jsが終了したときko.on('exit',()=>{console.log('> Ctrl-Cを押してください...')})// このプロセスがずっと起動し続けるためのおまじないsetInterval(()=>null,10000)

最後に、このoya.jsを実行して、ko.jsとmago.jsが一緒に終了しているか確認してみましょう:

$ node oya.js
oya.js: running
ko.js: running
mago.js: running
oya.js: ko.jsを終了させてます...
mago.js: SIGINT
ko.js: SIGINT
mago.js: exit
ko.js: exit> Ctrl-Cを押してください...
^Coya.js: SIGINT
oya.js: exit

期待通り、ko.jsとmago.jsは同じタイミングでSIGINTを受け取り終了しています。oya.jsはCtrl-Cを押すまで生存していることも分かります。

以上、Node.jsのchild_process.fork()で起動したプロセスを子子孫孫殺す方法についての説明でした。

dotenvで環境変数ではなく .env 使う | Node.js

$
0
0
$ npm install dotenv --save

プロジェクトのルートディレクトリに .envファイルを作成します。

# これはコメント行
KEY1=VALUE1
KEY2=VALUE2

実装

require('dotenv').config();if(typeofprocess.env.KEY1=='undefined'){console.error('Error: "KEY1" is not set.');process.exit(1);}console.log(process.env.KEY1);//=> VALUE1

【Node.js】複数のファイルパスをオブジェクトでの階層表現に変換する

$
0
0

経緯

Node.jsのfsを使って特定のディレクトリの中身をファイルパスにて取得した際に、ディレクトリの階層と互換性のあるオブジェクトに変換したかった。

結論

index.js
constdata=['/public/aaa/1.file','/public/aaa/2.file','/public/bbb/1.file','/public/ccc/1.file','/public/ccc/2.file','/public/ccc/3.file','/public/ddd/1.file'];constoutput={};letcurrent;for(constpathofdata){current=output;for(constsegmentofpath.split('/')){if(segment!==''){if(!(segmentincurrent)){current[segment]={};}current=current[segment];}}}console.log(output);/* 実行結果
{ public:
   { 
     aaa: { '1.file': {}, '2.file': {} },
     bbb: { '1.file': {} },
     ccc: { '1.file': {}, '2.file': {}, '3.file': {} },
     ddd: { '1.file': {} } 
   } 
}
*/

あとは取得した結果から

子要素に値が入っていたらディレクトリ
子要素に値が入っていなかったらファイル

と認識して分岐させれば色々使えると思う。

Viewing all 9050 articles
Browse latest View live