Node.js まとめ
参考
リスト
-
- Nodeのパッケージマネージャ
# Nodeで作成する前に実行してpacakge.jsonを作成しておく npm init # npm でパッケージを追加 npm install [package name] --save npm install [package name] --save-dev # どこかから持ってきた環境でpacakge.jsonがあれば以下で必要なパッケージがインストールされる npm install
-
- NodeでNodeを学習
-
- NodeのデファクトのMVCフレームワーク
- RouteとMiddleware
- MSの情報
-
- ソケット接続ライブラリ
メモ
Node
- イベントとストリームは理解しておく。
イベント
- EventEmitter クラスのインスタンス
- eventEmitter.on() でリスナー(イベント?コールバック?)を登録
- eventEmitter.emit() でイベントを実行
- リスナーはすべて同期的に実行される
- eventEmitter.once() はリスナーが1回しか実行されない
- 複数リスナーを登録するとリスト化されて登録される
- デフォルトでは10個までリスナーを登録できる
- 'error'というリスナーは特別。登録されていない場合に、eventEmitter.emit('error') で例外が出る。
const EventEmitter = require('events');
// EventEmitter クラスを継承
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
// 'event'というイベントを登録
myEmitter.on('event', () => {
console.log('an event occurred!');
});
// 'event'イベントを実行。'an event occurred!'が表示される
myEmitter.emit('event');
myEmitter.on('args', (a, b) => {
console.log(a, b, 'an event occurred!');
});
myEmitter.emit('args', 'abc', '123');
// 1回しか実行しないイベント
myEmitter.once('once', () => {
console.log('once!');
});
myEmitter.emit('once');
myEmitter.emit('once'); // 無視される
ストリーム
-
ストリームを扱うための抽象インタフェース
-
stream モジュールを使用して作成する
-
種類としては、readble, writable, その両方があり、EventEmitter のインスタンスである
- Readable: 読み込みができるストリーム (例 fs.createReadStream()).
- Writable: 書き込みができるストリーム (例 fs.createWriteStream()).
- Duplex: 読み書きができるストリーム (例 net.Socket).
- Transform: 読み書きするときにデータをを変換するストリーム (例 zlib.createDeflate()).
-
文字列(string)またはバッファ(Buffer) を処理する
-
ストリームが生成されると、objectMode オプションを使用してオブジェクトモードにスイッチする
-
Readable と Writable ストリームは内部バッファにデータを保持する
- readbale.push(chunk) をコールすると Readable ストリームにバッファされる。使用する側は readbale.read() をコールしてはダメ)
- 作成時に指定する highWaterMark (= バッファ量)を超えるとバッファリングを停止して、バッファリングされたデータが消費されるまで停止する
- writable.write(chunk) をコールすると Writable ストリームにバッファされる。writable.write() は バッファ量が highWaterMark 以内であれば true を返す。超えていれば false を返す
- readbale.push(chunk) をコールすると Readable ストリームにバッファされる。使用する側は readbale.read() をコールしてはダメ)
-
Duplex and Transform は、 Readable かつ Writable であるのでそれぞれ用に別のバッファを持つ。(Readable用とWritable用をもつということ)
-
Writable ストリームは、write() や end() のようなメソッドを提供する
-
Readable ストリームは、EventEmitter を使って読み込み中や終了時にイベントを実行する
Writable ストリーム
// Writable(write() で書き込んで end() 終了する)
const myStream = getWritableStreamSomehow();
myStream.write('some data');
myStream.write('some more data');
myStream.end('done writing data');
// イベント
myStream.on("close", () => {});
myStream.on("drain", () => {}); // Writable.write(chunk) が false を返すときにコールされる
myStream.on("error", () => {});
myStream.on("finish", () => {}); // Writable.end() がコールされたときにコールされる
myStream.on("pipe", (src) => {}); // Readable ストリームでReadable.pipe()
// がコールされたときにコールされる
myStream.on("unpipe", (src) => {});
Readable Streams
- 2つのモード
- flowing: 下流からデータを自動的に読み込む。EventEmitter を使ってできる限り早くアプリケーションに提供する。
- paused: ストリームからデータを読み込むには、stream.read() をコールしないとダメ。
- 通常は、pausedから始まり以下の場合に、flowingモードに移行する
- 'data' イベントハンドラを追加したとき
- stream.resume() メソッドをコールしたとき
- Writable にデータを送るために、stream.pipe() をコールしたとき
- 以下の場合に、pausedモードに移行する
- pipe の宛先がないときに、stream.pause() をコールしたとき
- pipe の宛先があるが、すべてのpipの宛先を削除したとき。(stream.unpipe() をコールして削除したときなど)
// Readable
const readable = new stream.Readable();
readable.on('data', (chunk) => {
console.log(`Received ${chunk.length} bytes of data.`);
readable.pause(); // dataイベントを停止する
console.log('Restart.');
readable.resume(); // dataイベントを再開する
});
// pipe ※適用した writable が返されるのでチェーン化できる
const r = fs.createReadStream('file.txt');
const z = zlib.createGzip();
const w = fs.createWriteStream('file.txt.gz');
r.pipe(z).pipe(w);
// イベント
readable.on("close", () => {}); // ストリームや下位のリソースが切断されたときにコールされる
readable.on("data", (chunk) => {}); // 使う側にデータの塊を受け渡すときにコールされる
readable.on("end", () => {}); // 読み込むデータが無くなったときにコールされる
readable.on("error", () => {});
readable.on("readable", () => {}); // ストリームからデータ読み込みが可能になったらコールされる
ストリームの実装
- stream を読み込む
- 継承したい機能から4つのストリーム(Readable, Writable, Duplex, Transform)をextendする
const { Writable } = require('stream');
class MyWritable extends Writable {
construcotr(options) {
super(options);
}
_write(chunk, encoding, callback) {
}
}
- ストリームにより以下のメソッドを実装する
- Readable: _read()
- Writable: _write, _writev, _final
- Duplex: _read, _write, _writev, _final
- Transform: _transform, _flush, _final
Writable
// extends して実装するパターン
const { Writable } = require('stream');
class MyWritable extends Writable {
constructor(options) {
super(options);
// 初期化処理
}
_write(chunk, encoding, callback) {
}
}
// 簡単アプローチ(ストリームのインスタンス化)
const { Writable } = require('stream');
// Writableのコンストラクタに渡せるオプションはコンストラクタ参照
// メソッドとしては、write, writeev, destory, finalが指定できる。
const myWritable = new Writable({
write: function(chunk, encoding, callback) {
// ...
},
writev: function(chunks, callback) {
// ...
}
});
// サンプル
const { Writable } = require('stream');
class LogStream extends Writable {
constructor(options) {
super(options)
}
_write(chunk, encoding, callback) {
if (chunk.toString().indexOf('4') >= 0) {
callback(new Error('chunk has unfortune number "4"'));
} else {
console.log(chunk);
callback();
}
}
}
Readable
// extendsして実装するパターン
const { Readable } = require('stream');
class MyReadable extends Readable {
constructor(options) {
super(options);
}
_read(size) {
}
}
// 簡単アプローチパターン
const { Readable } = require('stream');
// Readableのコンストラクタに渡せるオプションはコンストラクタ参照
// メソッドとしては、read, destoryが指定できる。
const myReadable = new Readable({
read: function(size) {
}
});
// サンプル
const { Readable } = require('stream');
class CountupStream extends Readable {
constructor(max) {
super(option)
this._max = (max > 0)? max: 5;
this._index = 0;
}
_read(size) {
this._index++;
if (this._index > max) {
this.push(null);
} else {
this.push('' + this._index);
}
}
}
デバッグ
// アプリケーションを --inspect 付きで実行すると以下のような表示がされる。
node --inspect app.js
Debugger listening on ws://127.0.0.1:9229/68a0f7dd-3ec3-42aa-a30e-5d1c9e81a45d
For help see https://nodejs.org/en/docs/inspector
Debugger attached.
// 以下のURLにブラウザでmeta情報にアクセスする
// "devtoolsFrontendUrl" を参照しchromeでアクセスするとブレイクポイント等のデバッグができる。
// またはchromeで"chrome://inspect" にアクセスし、configureボタンから "127.0.0.1:9229" を追加する
http://127.0.0.1:9229/json/list
[ {
"description": "node.js instance",
"devtoolsFrontendUrl": "chrome-devtools://devtools/bundled/inspector.html
?experiments=true&v8only=true&ws=127.0.0.1:9229/39b0d0bc-3d8f-41b8-8844-d3185157987a",
"faviconUrl": "https://nodejs.org/static/favicon.ico",
"id": "39b0d0bc-3d8f-41b8-8844-d3185157987a",
"title": "./bin/www",
"type": "node",
"url": "file://D:_data_study_nodejs_http_expresssample_myapp_bin_www",
"webSocketDebuggerUrl": "ws://127.0.0.1:9229/39b0d0bc-3d8f-41b8-8844-d3185157987a"
} ]
-
- 実行は三角ボタンで(自動的に--inspect-brkを追加して実行される)
- ソースを参照してブレイクポイントを設定できる。
- ステップ実行も可能
- 最下部の入力欄に変数を入力することで中身の確認が可能
// デバッグで、設定ボタンからlaunch.jsonを作成する。以下を設定。
{
"version": "0.2.0",
"configurations": [{
"type": "node",
"request": "launch",
"name": "プログラムの起動",
"program": "${workspaceFolder}\\bin\\www" // これはpackage.jsonを参照して追加される
}]
}
express
-
RouteとMiddleware
-
テンプレートエンジン
-
バリデータ:express-validator
-
- ORM: Mongoose
認証:Passport
-
Strategy を変えることで色々な認証方法に対応できる
-
OpenID や OAuth にも対応
-
使い方
// 認証処理はpassport.authenticate()メソッドを使う。引数は使用するStrategy // authenticate() は express の middleware になっている。 var passport = require('passport'); var app = require('express'); // passportの初期化処理 app.use(passport.initialize()); // 認証情報を永続化する場合は必要。 // これを有効にした場合はセッション処理はpassport.session()を使うこと app.use(passport.session()); // デフォルトでは、認証処理で失敗すると、「401 Unauthorized」を返す。 // 成功した場合は、req.user に情報を入れる。 app.post('/login', passport.authenticate('local'), function(req, res, next) { res.redirect('', req.user); } ); // 成功/失敗に応じてリダイレクトする場合 app.post('/login', passport.authenticate('local', { successRidirect: '/top', failureRidirect: '/error', // failureFlash: true, // 各認証処理側でflashメッセージを設定する場合 // successFlash: 'Success!!', // 固定でflashメッセージを設定する場合 // failureFlash: 'Username or Password is wrong' // 固定でflashメッセージを設定する場合 // failureFlash: true // Verify Callbackで「message」プロパティに指定する場合 // req.flash()を使うほうがいいみたい? })); // 通常は認証処理正常終了後、セッションは持続されるが、API等で持続したくない場合はsessionオプションにfalse app.post('/login', passport.authenticate('local-login', { session: false, // API等の場合は毎回認証をかけたいのでセッションを切る }); // 認可処理の設定 var authorizeMiddleware = function(req, res, next) { if (req.isAuthenticated()) { return next(); } res.redirect('/login'); } // 認証が必要なURLに認可処理を追加する app.get('/profile', authorizeMiddleware, // 認可処理を通過してから次の処理をする function (req, res, next) { ... } ); // ロールを指定したりする場合 var authorizeMiddleware = function(role){ function(req, res, next) { // ユーザ情報のroleと指定したroleが一致したらOK if (req.isAuthenticated() && req.user.role == role) { return next(); } res.redirect('/login'); } } // 認証が必要なURLに認可処理を追加する app.get('/profile', authorizeMiddleware('admin'), // 認可処理を通過してから次の処理をする function (req, res, next) { ... } );
-
Strategy の設定
- passport-local Strategy:ID/PASSを使った認証
// Strategy には Verify Callbackが必要。 // Verify Callback は「ユーザ検索」と、「認証情報の保持」を処理する。 // Verify Callback では、done()メソッドを実行する。 // 成功時: done(null, user, {message: "ようこそ!"}); // userは保持する情報 // 失敗時: done(null, false, {message: "認証に失敗しました"}); // エラー: done(err); var passport = require('passport'); var LocalStrategy = require('passport-local').Strategy; // シンプルなパターン passport.use(new LocalStrategy( // reqがない function(username, password, done) { User.findOne({ username: username }, function (err, user) { if (err) return done(err); if (!user || !user.validPassword(password)) return done(null, false); return done(null, user); }); } )); passport.use(new LocalStrategy({ usernameField: "user", // POSTするときのNAME属性名(デフォルト: username) passwordField: "pass", // POSTするときのNAME属性名(デフォルト: password) session: true, // セッションを使用するかどうか passReqToCallback: true, // チェック用のコールバックにリクエストオブジェクトを渡すかどうか。 }, // passport.authenticate()でfailureFlash: trueの場合はmessageを使ってflashできる function(req, username, password, done) { User.findOne({ username: username }, function (err, user) { if (err) { return done(err); } if (!user) { return done(null, false, { message: 'Incorrect username.' }); } if (!user.validPassword(password)) { return done(null, false, { message: 'Incorrect password.' }); } return done(null, user); }); } )); // form <form action="/login" method="post"> <div> <label>Username:</label> <input type="text" name="user"/> </div> <div> <label>Password:</label> <input type="password" name="pass"/> </div> <div> <input type="submit" value="Log In"/> </div> </form>
セッション
```js
// セッションを保持するためにCookieを使用する。Cookieにデータを出し入れする処理を記述する。
passport.serializeUser(function(user, done){
// IDだけを保存する
done(null, user.id);
});
passport.deserializeUser(function(user, done){
User.findById(id, function(err, user) {
(err)? done(err): done(null, user);
});
});
```