0
0

Node.jsまとめ

Last updated at Posted at 2024-03-21

Node.js まとめ

参考

リスト

  • Node.js本家

  • Node.js日本ユーザグループ

  • npm

    • Nodeのパッケージマネージャ
    # Nodeで作成する前に実行してpacakge.jsonを作成しておく
    npm init
    # npm でパッケージを追加
    npm install [package name] --save
    npm install [package name] --save-dev
    
    # どこかから持ってきた環境でpacakge.jsonがあれば以下で必要なパッケージがインストールされる
    npm install
    
  • NodeSchool

    • NodeでNodeを学習
  • Express

    • NodeのデファクトのMVCフレームワーク
    • RouteとMiddleware
    • MSの情報
  • Socket.io

    • ソケット接続ライブラリ

メモ

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 を返す
  • 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"
} ]
  • VisualStudioCode

    • 実行は三角ボタンで(自動的に--inspect-brkを追加して実行される)
    • ソースを参照してブレイクポイントを設定できる。
    • ステップ実行も可能
    • 最下部の入力欄に変数を入力することで中身の確認が可能

// デバッグで、設定ボタンからlaunch.jsonを作成する。以下を設定。
{
    "version": "0.2.0",
    "configurations": [{
        "type": "node",
        "request": "launch",
        "name": "プログラムの起動",
        "program": "${workspaceFolder}\\bin\\www" // これはpackage.jsonを参照して追加される
    }]
}

express

認証: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 の設定

    // 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);
    });
});
```
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0