20190507のNode.jsに関する記事は15件です。

TypeORMで環境ごとに接続するDBを切り分ける

目的

  • Typeormを使って、環境ごとに接続するDBを切り分けます。
  • 補足のところに書きましたが、もっといい方法があると思っているので、もしあれば教えていただきたいです。

環境

  • Node: v10.15.2
    • nodeenvを利用し、プロジェクトごとにバージョンを指定することがおすすめです。

利用するパッケージ

typeorm

使用方法

1. packageをinstall

今回はyarnでinstallします。

$ yarn add --dev typeorm

2. 設定ファイルを作成(任意)

環境ごとに orconfig.{環境名}.json を作成します。

  • orconfig.develop.json


{
  "type": "mysql",
  "host": "YOUR_DB_HOST",
  "port": 3306,
  "username": "YOUR_DB_USERNAME",
  "password": "YOUR_DB_PASSWORD",
  "database":"YOUR_DB_PASSWORD",
    // migrationfileで世代管理するためには、ここをfalseにする必要があります。
    "synchronize": false,
    "logging": false,
    "entities": [
      "src/entities/*.ts"
    ],
    "migrations": [
      "src/migrations/*.ts"
    ],
    "subscribers": [
      "src/subscribers/*.ts"
    ],
    "cli": {
      "entitiesDir": "src/entities",
      "migrationsDir": "src/migrations",
      "subscribersDir": "src/subscribers"
    }
}

  • orconfig.production.json


{
  "type": "mysql",
  "host": "YOUR_DB_HOST",
  "port": 3306,
  "username": "YOUR_DB_USERNAME",
  "password": "YOUR_DB_PASSWORD",
  "database":"YOUR_DB_PASSWORD",
    // migrationfileで世代管理するためには、ここをfalseにする必要があります。
    "synchronize": false,
    "logging": false,
    "entities": [
      "src/entities/*.ts"
    ],
    "migrations": [
      "src/migrations/*.ts"
    ],
    "subscribers": [
      "src/subscribers/*.ts"
    ],
    "cli": {
      "entitiesDir": "src/entities",
      "migrationsDir": "src/migrations",
      "subscribersDir": "src/subscribers"
    }
}

3. package.jsonの修正(script追記)

環境ごとのyarnスクリプトを実行し、ExpressプロジェクトにENV_SETTINGS環境変数を渡します。

{
  "scripts": {
    "dev": "tsc && ts-node-dev src/index.ts",
    "start:local": "yarn migration:run && tsc && ts-node src/index.ts",
    "start:develop": "yarn migration:run:develop &&tsc && ENV_SETTINGS=\"develop\" ts-node src/index.ts",
    "start:production": "yarn migration:run:production && tsc && ENV_SETTINGS=\"production\" ts-node src/index.ts",
    "//": "use -n option. it defines migration file name. example: yarn migration -n createTableUser",
    "migration": "ts-node ./node_modules/.bin/typeorm migration:generate -f ormconfig.local.json",
    "migration:run": "ts-node ./node_modules/.bin/typeorm  migration:run -f ormconfig.local.json",
    "migration:run:develop": "ts-node ./node_modules/.bin/typeorm  migration:run -f ormconfig.develop.json",
    "migration:run:production": "ts-node ./node_modules/.bin/typeorm  migration:run -f ormconfig.production.json",
    "migration:revert": "ts-node ./node_modules/.bin/typeorm migration:revert -f ormconfig.local.json",
    "migration:revert:develop": "ts-node ./node_modules/.bin/typeorm migration:revert -f ormconfig.develop.json",
    "migration:revert:production": "ts-node ./node_modules/.bin/typeorm migration:revert -f ormconfig.production.json"
  }
}

4. 環境変数によって読み込むconfigファイルを切り分ける

  • index.ts
import {createConnection} from "typeorm";
import App from './app';

// 環境変数にセットされているENV_SETTINGSを格納する
var env: string = (process.env.ENV_SETTINGS) ? process.env.ENV_SETTINGS : "local";
// 環境ごとにconfigファイルを読み込む
export const connectOption = require(`../ormconfig.${env}.json`);

createConnection(connectOption)
  .then(async connection => {
    const app = new App();
    app.start();
  })
  .catch(error => console.log(error));

(補足)やりたかったこと

  • ormconfigの設定ファイルはormconfig.tsのみにして、各値をconfig/{環境}.tsにまとめ、export/importで行いたかったのですが、migration:runの時にエラーが発生し、断念しております。 (https://typeorm.io/#/using-ormconfig)

before

├── config
│   ├── develop.ts
│   ├── production.ts
│   └── undefined.ts
├── ormconfig.develop.json
├── ormconfig.local.json
├── ormconfig.production.json
├── package.json
├── src
│   ├── app.ts
│   ├── index.ts

after

├── config
│   ├── develop.ts
│   ├── production.ts
│   └── undefined.ts
├── ormconfig.ts
├── package.json
├── src
│   ├── app.ts
│   ├── index.ts
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React 開発環境構築

はじめに

この記事では、Reactの開発環境構築手順について、説明していきたいと思います。
構築の流れとして

  1. Node.jsのインストール
  2. パッケージマネージャーYarnのインストール
  3. creat-react-appのインストール
  4. 構築した環境でHello Worldを表示させてみる

1. Node.jsのインストール

まずは、下記のURLをクリックします。
https://nodejs.org/ja/

URLを開くとLTS版と最新版とあります。
簡単に説明すると、LTS版(Long Time Supportの略)は長期的サポートが受けれるもの
一方、最新版はサポート期間が短いが、最新の物を利用できるものです。

この記事ではLTS版をダウンロードして進めていきます。

nodeをダウンロードして、インストールできたら、ターミナルで下記のコマンドを入力して、Nodeがインストールしているかを確認します。Nodeのバージョンが表示されていれば大丈夫です。

$ node -v
v10.15.3

2. Yarnをインストール

nodeのパッケージマネージャであるYarnをインストールしていきます。
npmというパッケージマネージャがNodeをインストールした時点でありますが、npmよりもYarnの方がより高速で信頼度の高いものになっているので、Yarnをインストールしていきます。
Yarnをインストールする場合は、ターミナルで下記のコマンドを実行してください。

$npm install --global yarn
/usr/local/bin/yarn -> /usr/local/lib/node_modules/yarn/bin/yarn.js
/usr/local/bin/yarnpkg -> /usr/local/lib/node_modules/yarn/bin/yarn.js
+ yarn@1.15.2
updated 1 package in 1.698s

Yarnがインストールされているかは下記のコマンドを実行して、バージョンが表示されていれば大丈夫です。

$yarn --version
1.15.2

3. creat-react-appのインストール

従来のreactを用いた開発では、Babelやwebpackなど様々なパッケージをマニュアルでインストールする必要がったため、ものすごく手間がかかっていました。ですが、creat-react-appをインストールすることで、これらの問題が解消でき、簡単に必要なパッケージをインストールすることができます。
下記のコマンドを実行することでインストールできます。

$yarn global add creat-react-app

4. 構築した環境でHello Worldを表示させてみる

ターミナルから下記のコマンドを実行して、アプリケーションを作成します。
作成する場所は任意で構いません。

$create-react-app helloworld

下記のようなメッセージが表示されていればOKです。

Initialized a git repository.

Success! Created helloworld at /Users/******/helloworld

作成したプロジェクト配下に移動し、下記のstartコマンドを実行します。

$cd helloworld
$yarn start

実行後、ブラウザが起動し、以下の画面が表示されていればOKです。
スクリーンショット 2019-04-29 12.28.38.png

ここまでできたら、helloworld/src配下にあるApp.jsを開きましょう。
App.jsを開くと以下のようなコードが書かれていると思います。

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

App.jsの内容を全て消して、以下のようにソースを書いてみましょう。

import React from 'react';

class App extends React.Component{
  render()
  {
    return (
    <div>
      <h1>Hello World</h1>
    </div>
    );
  }
}

export default App;

修正してブラウザに以下のように表示されていればOKです。
スクリーンショット 2019-04-29 12.49.03.png

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

install nodebrew, node and yarn

今さらですが、nodebrew, node (npm), yarn のインストール手順をまとめておきます。

mac で zsh が前提です。
bash の人は .zshrc => .bashrc に読み換えるなどお願いします。

その前に

私の環境がちょっと汚れていたので掃除から始めます。

まず、インストールされていた yarn, node をアンインストール。

$ brew uninstall yarn
$ brew uninstall node

以前にインストールした nodebrew も、一旦、アンインストール。

$ brew uninstall nodebrew

.zshrc から nodebrew に関する環境変数の設定も消しておきました。

export PATH=$HOME/.nodebrew/current/bin:$PATH
export NODEBREW_ROOT=...

環境変数を削除したら source しておきます。

$ source ~/.zshrc

/usr/local/var/nodebrew ディレクトリも残っていたら消します。

$ rm -rf /usr/local/var/nodebrew

brew update もしておきます。

$ brew update

インストール

nodebrew のインストール

node は homebrew ではなく nodebrew で管理することにしました。

$ brew install nodebrew

nodebrew ディレクトリのセットアップをします。

$ nodebrew setup

環境変数に NODEBREW_ROOT が設定されていなければ、$HOME/.nodebrew がセットアップされるはずです。

.zshrc に PATH の設定を追記しておきます。

export PATH=$HOME/.nodebrew/current/bin:$PATH

パスを通すために source しておきます。

$ source ~/.zshrc

node のインストール

続いて Node.js をインストールします。

$ nodebrew install-binary stable
$ nodebrew use stable
use v12.1.0
$ nodebrew list
v12.1.0

current: v12.1.0

$ node -v
v12.1.0

npm や npx もインストールされているはず。

$ npm -v
6.9.0

$ npx -v
6.9.0

yarn のインストール

Yarn は homebrew でインストールします。

$ brew install yarn --ignore-dependencies

yarn は node に依存しているため homebrew で node がインストールされていないと依存関係のエラーになります。

しかし node は nodebrew で管理しているので homebrew ではインストールしません。

そこで --ignore-dependencies オプションを指定します。

以前は --without-node オプションを指定していたようですが、現在は無効になっているみたいです。

$ yarn -v
1.15.2

以上でインストール完了です。

参考

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

JWTライブラリの危機的な脆弱性について調べた

JWT 脆弱性 で検索すると 【翻訳】JSON Web Tokenライブラリの危機的な脆弱性 って記事(4年ぐらい前の記事)が出てきて読んでいたのですが、よくわからなかったので手を動かしながら調べてみました。
※ 翻訳の元記事はこちら

node-jsonwebtoken, pyjwt, namshi/jose, php-jwt, jsjwtを非対称の鍵(RS256, RS384, RS512, ES256, ES384, ES512)で使っている場合、最新のバージョンに更新してください。

とのことなので、バージョン更新で直ったようなので修正を読んでいきます。
また、 js を利用して開発する機会が多いので node-jsonwebtoken の修正を確認してみます。

確認したコードは下記にあります。
https://github.com/OshiroSeiya/jsonwebtoken-audit-check

脆弱性の概要を知る

none アルゴリズムの話は飛ばします。元記事の内容を見たほうが良いです。

RS256 アルゴリズムを利用して JWT を発行している場合に攻撃者が HS256 アルゴリズムを利用して発行した JWT のチェックが通ってしまう問題があります。
RS256 は JWT を発行するのは秘密鍵を用いて発行し、チェックには公開鍵を利用します。
一報 HS256 は JWT を発行する時とチェックに利用する鍵が同じになります。

認証側のチェックが下記のようになっていた場合
※ TOKEN: 発行されたJWT
※ KEY: はチェックの際に利用する鍵です

jwt.verify(TOKEN, KEY)

利用するアルゴリズムが TOKEN の方に入っているためチェックの際に RS256 で検証するべきなのか HS256 で検証するべきなのか判断します。
RS256 でTOKENを発行する仕組みになっている場合、攻撃者が HS256 のアルゴリズムで公開鍵(利用用途的に公開されているので誰でも取得可能)を用いて TOKEN を発行するとチェックする際に HS256 を用いてチェックされ KEY は同じ公開鍵を利用するためチェックが通ってしまうことになり、改ざんが可能になるということみたいです。

対応されたライブラリのPRなどを見る

修正は下記のようです。
https://github.com/auth0/node-jsonwebtoken/pull/69
https://github.com/auth0/node-jsonwebtoken/commit/7017e74db9b194448ff488b3e16468ada60c4ee5
https://github.com/auth0/node-jsonwebtoken/pull/71

この修正が入る以前と以後のライブラリを利用して挙動の確認とどういう対策が行われたのかを確認してきます。

修正前: 4.1.0 (https://github.com/auth0/node-jsonwebtoken/tree/b69d441c6e5e4b2efaafde682b4b9670ac3bcb51)
修正後: 4.2.2 (https://github.com/auth0/node-jsonwebtoken/tree/e46ca6634447cf6a5b7f08298aa2f2450b8df704)

修正前のバージョンで問題が起きることを確認する

4.2.0で追加されたコードは下記でした。
https://github.com/auth0/node-jsonwebtoken/pull/69/files

  if (!options.algorithms) {
    options.algorithms = ~secretOrPublicKey.toString().indexOf('BEGIN CERTIFICATE') ?
                        [ 'RS256','RS384','RS512','ES256','ES384','ES512' ] :
                        [ 'HS256','HS384','HS512' ];
  }
  var header = jws.decode(jwtString).header;
  if (!~options.algorithms.indexOf(header.alg)) {
    return done(new JsonWebTokenError('invalid signature'));
  }

要するに jwt.verify メソッドを利用する時の secretOrPublicKey の値に BEGIN CERTIFICATE が含まれる場合は [ 'RS256','RS384','RS512','ES256','ES384','ES512' ] のみ利用できるようになるということですね。
※ 4.2.1で BEGIN PUBLIC KEY が含まれる場合の処理も追加されていますがやりたいことは同じなので飛ばします。
※ 4.2.2で BEGIN RSA PUBLIC KEY が含まれる場合の処理が追加されていますがやりたいことは同じなので飛ばします。

4.2.0で追加されたテストを見ます。
https://github.com/auth0/node-jsonwebtoken/pull/69/files#diff-f676fb748f383a87d0f55bec4f266023

テストコードを書きながらためしたほうが僕は理解しやすいので下記のようにコードを書きました。
※ 公開鍵と秘密鍵はテストで利用されていたものと同じものを利用しています。
https://github.com/OshiroSeiya/jsonwebtoken-audit-check/blob/master/src/4.1.0/index.test.js

const fs = require('fs');
const path = require('path');
const jwt = require('jsonwebtoken');

// 公開鍵
const PUB = fs.readFileSync(path.join(__dirname, 'pub.pem'), 'utf8');
// 秘密鍵
const PRIV = fs.readFileSync(path.join(__dirname, 'priv.pem'), 'utf8');
// payload
const payload = {
  foo: "bar"
};

test('サーバー側で署名した TOKEN がチェックを通過し payload が取得できる', () => {
  // サーバー側で秘密鍵を利用し、アルゴリズムを RS256 で署名した TOKEN
  const RS256_TOKEN = jwt.sign(payload, PRIV, {algorithm: 'RS256'});

  expect(jwt.verify(RS256_TOKEN, PUB)).toEqual(payload);
});

test('攻撃者が署名した TOKEN がチェックを通過し payload が取得できる', () => {
  // 攻撃者がRS256の公開鍵を利用し、アルゴリズムを HS256 で署名したTOKEN
  // ※HS256はdefaultのアルゴリズムですがわかりやすいように設定
  const HS256_TOKEN = jwt.sign(payload, PUB, {algorithm: 'HS256'});

  expect(jwt.verify(HS256_TOKEN, PUB)).toEqual(payload);
});
PASS  ./index.test.js
  √ サーバー側で署名した TOKEN がチェックを通過し payload が取得できる (9ms)
  √ 攻撃者が署名した TOKEN がチェックを通過し payload が取得できる (1ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        4.022s
Ran all test suites.

しっかりと攻撃者が署名した TOKEN がチェックを通過し payload が取得することができています。

修正後のバージョンで問題が起きないことを確認する

先程作ったテストを 4.2.2 に node-jsonwebtoken をアップデートしてテストを実行してみます。

 FAIL  ./index.test.js
  √ サーバー側で署名した TOKEN がチェックを通過し payload が取得できる (9ms)
  × 攻撃者が署名した TOKEN がチェックを通過し payload が取得できる (4ms)

  ● 攻撃者が署名した TOKEN がチェックを通過し payload が取得できる

    JsonWebTokenError: invalid signature

      24 |   const HS256_TOKEN = jwt.sign(payload, PUB, {algorithm: 'HS256'});
      25 | 
    > 26 |   expect(jwt.verify(HS256_TOKEN, PUB)).toEqual(payload);
         |              ^
      27 | });
      28 | 

      at Object.<anonymous>.module.exports.verify (node_modules/jsonwebtoken/index.js:141:17)
      at Object.verify (index.test.js:26:14)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        3.199s
Ran all test suites.
npm ERR! Test failed.  See above for more details.

結果、攻撃者が署名した TOKEN はしっかりとエラーになっていることが確認できました。

まとめ

記載されていた通り、 node-jsonwebtoken の新しいバージョンであれば問題は起きなくなっていました。
ライブラリで対応(利用できるアルゴリズムを絞る)されてはいますが、仕様的には今後も起きる可能性はなくはないと思われます。
ライブラリを利用するときは対策されているか確認してみると良さそうです。

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

node-gyp@3.8.0がtar@2.0.0に依存している問題の解決方法

Angularで何かを作ろうとするとき、ng new project-nameして新しいプロジェクトを作成するわけですが、するとnpmが脆弱性のあるパッケージがあるからnpm audit fixして修正するか、それが無理だったらnpm auditして原因を確認して手動で修正してね、と言ってくることがあります。

調べてみるとnode-gyp@3.8.0tar@2.0.0に依存していて、tarは4.4.2より前だと脆弱性があるんですね。

解決策をググってみても、どうもピンと来る解決策をズバリ書いている記事に出会わない...

そこで結論をズバリ書きますね。

npm install node-gyp -g

こうすると、node-gyp@4.0.0がグローバルに入るので、これで問題が解決します。

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

Node.jsによるSPA(SinglePageApplication)プログラミングを最適化とWebPack等のモジュールバンドラを使わない開発方法

1. Node.jsによるWebシステム開発

 1.1 Node.jsの特徴

 Node.jsは処理時間の短いアクセス要求を大量に捌くことに適した言語です。その特性上、SPA(SinglePageAppliation)で発生するような、非同期による頻繁なアクセスと相性が良いのです。最も適しているのは、フロントエンドとDBの間を、最低限のデータ処理で仲介するという用途です。

 逆にHTMLページを丸ごと返すのが主目的であるのなら、別言語を選択した方が幸せになれるでしょう。シングルスレッドが前提のNode.jsでは、一回の負担が大きい、HTMLデータを毎回構築する用途には向いていないのです。

 Node.jsの特徴を最大限に生かすための基本的な考え方は、WebAPIを作ることです。フロントエンドからの要求を、最低限の処理で捌いていくWebAPIを作ること、それに特化するのが最も効果的なのです。

 1.2 TypeScriptの利用

 Node.jsは言語ではありません。JavaScriptという言語をブラウザ以外の環境で動かすためのフレームワークです。そして、最終的にJavaScriptになってさえしまえば、JavaScriptの亜種であるAltJSでプログラミングすることも出来るのです。AltJSで最も無難な選択肢はTypeScriptです。TypeScriptの特徴は、JavaScriptに静的型付けが追加された部分が大きいのですが、この機能は単純にエラーチェックが強化されただけに止まりません。エディタと連携して、様々な入力補完が行えるのです。事実上、Node.jsはTypeScriptを利用するためのフレームワークだと言っても過言ではありません。

 さらにTypeScriptには複数のファイルを統合したり、ファイルの更新を監視して自動コンパイルする機能があります。さらに各ディレクトリごとに設定ファイルであるtsconfig.jsonを設置して、複数の設定ファイルを統合するためのtsconfig.jsonを作れば、設定を枝分かれさせてプロジェクトを作ることも可能です。このあたりの機能を使うと、WebPackのようなモジュールバンドラを使わずに、バックエンドとフロントエンドのトランスコンパイルをコマンド一発で行うことも可能です。実はとんでもなく多機能なのですが、あまり活用されていないのが現状です。

 1.3 TypeScriptのみで行うプロジェクト管理

 TypeScriptにはreferencesという、他のtsconfig.jsonを参照する機能があります。これを使うことによって、トランスコンパイルが必要なものをまとめて管理できます。使用例としては以下のような形です。

/
├ local_module
│ └ active-module-framework (自作Node.js用フレームワーク)
│  └ tsconfig.json
├ src
│  ├ app (バックエンドのメイン処理)
│   │ └ tsconfig.json
│  ├ public 
│   │ ├ main(フロントエンドのメイン処理)
│   │ │ └ tsconfig.json
│   │ └ jsw(自作フロントエンドフレームワーク)
│   │   └ tsconfig.json
├ tsconfig.json

 私の作っているWebシステムのディレクトリ構成です。tsconfig.jsonが5つあり、それぞれが以下のようになっています。

/tsconfig.json
{
    "references": [
        {"path": "./src/app"},
        {"path": "./src/public/main"}
    ],
    "exclude": ["."]
}
/local_module/active-module-framework/tsconfig.json
{
    "compilerOptions": {
        "target": "es2017",
        "module": "commonjs",
        "rootDir": "./src/",
        "outDir":"./dist/",
        "declaration": true,
        "sourceMap": true,
        "composite": true
    },
    "exclude": ["node_modules","resource","dist"]
}
/src/app/tsconfig.json
{
    "compilerOptions": {
        "target": "es2017",
        "module": "commonjs",
        "outDir": "../../dist/app/",
        "declaration": false,
        "sourceMap": true,
    },
    "references": [{
        "path": "../../local_modules/active-module-framework"
    }]
}
/src/public/main/tsconfig.json
{
    "compilerOptions": {
        "sourceMap": true,
        "target": "es5",
        "module": "amd",
        "composite": true,
        "outFile": "../../../dist/public/js/index.js",
        "moduleResolution": "node",
        "incremental": true,
        "lib": [
            "es2017",
            "dom"
        ]
    },
    "references": [{
            "path": "../jsw"
        }
    ]
}
/src/public/jsw/tsconfig.json
{
    "compilerOptions": {
        "sourceMap": true,
        "target": "es5",
        "module": "amd",
        "composite": true,
        "declaration": true,
        "outFile": "../../../dist/public/js/jsw.js",
        "rootDir": ".",
        "moduleResolution":"node",
        "lib": [
            "es2017",
            "dom"
        ]
    }
}

 トップのtsconfig.jsonに参照設定のみを記述し、実際は各ディレクトリtsconfig.jsonでトランスコンパイルを行います。フロントエンドとバックエンドのtsconfig.jsonが混在していますが、なんの問題はありません。以下のコマンド一つで、全てトランスコンパイルが行われます。

tsc -b

更新ファイルを自動コンパイルするなら以下のコマンドです。

tsc -b -w

 tscはreferencesの依存関係を追跡し、必要なものから自動的にトランスコンパイルしていきます。フロントエンドとバックエンドが同時に処理されるので楽ちんです。この機能を利用した場合、tsからjsファイルを生成のみならWebPackのようなモジュールバンドラは不要です。

 1.4 フロントエンドとバックエンドの通信

 ここが肝になる部分です。私は以下のようなデータ形式にしました。セッションキー、命令とパラメータのセットを一つのパッケージにしてやりとりします。命令は複数を同時にやりとりするために配列の形になっています。

  • フロントエンド -> バックエンド
interface AdapterFormat {
    globalHash: string      //ブラウザ共通セッションキー
    sessionHash: string     //タブ用セッションキー
    functions:{             //命令格納用
        function: string    //命令
        params: any[]       //パラメータ
    }[]
}
  • バックエンド -> フロントエンド
export interface AdapterResultFormat {
    globalHash: string  //ブラウザ共通セッションキー
    sessionHash: string //タブ用セッションキー
    results: {          //結果データ
        value: {[keys:string]:any}
        error: string
    }[]
}

これをやりとりするためのコードを自作フレームワークを用いた場合

  • フロントエンド側
const adapter = new JSW.Adapter()
const result = await adapter.exec('TestModule.add',10,20)
console.log(result)
  • バックエンド側
export class TestModule extends amf.Module {
    async JS_add(a:number,b:number) {
        return a+b
    }
}

 Node.js用のWebAPIを簡単に作成するためのフレームワークを作成した結果です。非同期の恐怖やコールバック地獄は存在しません。

2 フレームワークにしてしまう

 2.1 作ったもの

  • フロントエンドフレームワーク

    TypeScript/JavaScriptでウインドウシステムを実現するフレームワーク
    JSW

  • バックエンドフレームワーク

    Node.jsで最小限の記述でフロントエンドと通信するためのフレームワーク
    AMF

 今回紹介した内容を使って、フロントエンドとバックエンドのフレームワークをそれぞれ作成しています。バックエンドの方は、npmに登録してあるので、

npm i active-module-framework

でインストール可能です。

 2.2 最小限のコードで掲示板サンプル

 ブラウザ内に仮想ウインドウを表示して、入力したデータをバックエンドに送信、それをフロントエンドに戻す、掲示板もどきのサンプルです。

ソースコード

image.png

  • バックエンド側追加コード
TestModule.ts
import * as amf from 'active-module-framework'

export class TestModule extends amf.Module {
    //プログラム起動時に一回だけ呼ばれる
    static async onCreateModule(): Promise<boolean> {
        //ローカルDB(SQLite)の初期設定
        const localDB = amf.Module.getLocalDB()
        const result = await localDB.run(
            'CREATE TABLE IF NOT EXISTS ModuleTest (id integer primary key,name text,msg text)').
                catch(()=>{return null})
        return result!=null;
    }
    //頭に「JS_」を付けると、フロントエンドと通信するためのメソッドとなる
    async JS_output(name:string,msg:string){
        const localDB = amf.Module.getLocalDB()
        const result = await localDB.run("insert into ModuleTest values(null,?,?)", name, msg).
            catch(() => { return null })
        return result !== null
    }
    async JS_get(id?){
        if(id == null)
            id = 0
        const localDB = amf.Module.getLocalDB()
        return await localDB.all("select * from ModuleTest where id > ? order by id",id)
    }
}
  • フロントエンド側追加コード
index.ts
///<reference path="../../../dist/public/js/jsw.d.ts"/>

//ページ読み込み時に実行する処理を設定
addEventListener("DOMContentLoaded", Main)

function Main(){
    let lastId = 0  //取得データの最終ID保存用

    //バックエンドからメッセージを取り出し表示する
    const load = async()=>{
        //指定ID以降のデータをawaitで待機して受け取る
        const result = await adapter.exec('TestModule.get', lastId)
        if(result){
            const client = mainWindow.getClient()
            //受け取ったデータを表示
            for(const value of result){
                client.innerText += `${value.name}:${value.msg}\n`
                lastId = value.id
            }
            //スクロールを最終位置へ設定
            client.scrollTop = client.scrollHeight
        }
    }

    //通信用アダプターの作成
    const adapter = new JSW.Adapter()

    //メインウインドウの作成
    const mainWindow = new JSW.FrameWindow()
    mainWindow.setTitle('メインウインドウ')
    mainWindow.getClient().style.overflow = 'auto'
    mainWindow.setSize(640,480)

    //ユーザ入力用フォームの作成
    const inputWindow = new JSW.TableFormView({frame:true})
    inputWindow.setOrderTop(true)           //ウインドウを常に最上位に
    inputWindow.setTitle('入力ウインドウ')
    inputWindow.setPos()
    //フォームにアイテムを追加
    inputWindow.addItem({ type: 'textbox',name:'name',label:'名前',value:'太郎'})
    inputWindow.addItem({ type: 'textbox',name: 'msg', label: 'メッセージ' })
    inputWindow.addItem({ type: 'submit', name:'submit',label: '送信',
        events:{click:async()=>{
            //送信ボタンクリック時のイベント処理
            const p = inputWindow.getParams()   //フォームパラメータの取得
            const button = inputWindow.getItem('submit') as HTMLButtonElement
            if ((p.msg as string).length){
                button.innerText = '送信中'
                const result = await adapter.exec('TestModule.output',p.name,p.msg)
                if(result)
                    button.innerText = '送信成功!'
            }
            load()  //再ロード
        }} })

    load()  //メッセージのロード
}

 バックエンドは比較的規模が小さくて済むのですが、フロントエンドはUIを付けると、さすがにコード量が少々増えてしまいます。index.tsはサンプル用に作っているのでこんな形ですが、まともにシステム開発するときは、各ウインドウクラスを継承して作っていく形になります。このあたりはWin32API時代の開発手法に似ています。

3. これから

 Webがらみのシステムは、開発手法の流行り廃りが激しいのが特徴です。時々その中の有名どころを触ってみるのですが、私はいまいち馴染めません。それぞれの設計思想が、自分の思い描いているものと大幅に乖離しているのが原因です。結局、自分で作らなければ自分の欲しいフレームワークは手に入らないのです。ということで、今後もオレオレフレームワークの開発を続けていきたいと思っています。ちなみに私が何か新しいものを作るごとに、フレームワークの機能が追加されていきます。

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

gulp4再入門 gulpfileの分割とnodeモジュールの利用

先に結論だけ

私のポートフォリオサイトのメンテナンスを行い、タスクランナーをgulp4へ更新しました。その結果、gulpfileが8行になりました。

/gulpfile.js/index.js
"use strict";

const revision = require("gulptask-revision")("./dist/");
exports.revision = revision;

const { s3_deploy, s3_staging } = require("./awsPublish");
exports.s3_deploy = s3_deploy;
exports.s3_staging = s3_staging;

gulp4は以前のバージョンと比べ、gulpfileの管理工数を減らすことができます。

はじめに

2018年12月にgulp4が正式リリースされました。

Version 4.0 Now Default

gulp4をしばらく使ってみた結果、gulpfileの管理が簡略化できました。この記事はその手法を共有するためのものです。

この記事が想定する読者

この記事はgulp3以前を利用しているユーザーを想定しています。
そのため、gulpのインストールやタスクの構築などは記事の対象としません。

マイグレーションガイド

gulp3を利用している方は、まずgulp4への移行を行います。
公式ドキュメント Quick Start

こちらの記事でgulp3から4へのマイグレーションの方法が詳しく解説されています。
Gulp4がリリースされたのでgulpfile.js をアップデートした

gulp4の利点

gulp3以前には、

  • gulpfileの肥大化問題
  • gulpプラグインの更新問題

という2つの問題があり、これがgulpfileの管理を困難にしていました。

gulp4へ移行することによって、この2つの問題を解消できます。

前提 : gulp4におけるタスクとは

上記2つの問題を解決するための前提として、gulp4におけるタスクの構造を理解しておく必要があります。この構造が、2つの問題を解くことに繋がります。

もっとも単純な形のgulpタスクを、公式ドキュメントから引用します。

function simpleTask(cb) {
  ...
  cb();
}

gulp4におけるタスクは関数です。処理後に、第一引数に渡されたコールバック関数を実行して処理完了を通知することでタスクとなります。

gulpのタスクは非同期処理を行うことを前提としています。そのため、タスクには処理が完了したことを通知する方法を備えている必要があります。

  • streamをreturnする。
  • Promiseをreturnする。
  • async関数にする。
  • 処理の完了後にコールバック関数を実行する。

などの形にすることで、関数はタスクとして機能します。

gulp.task()について

gulp3までで使われていたgulp.task()関数は非推奨になりました。この関数はgulpfileの互換性維持のために用意されたもので、新規での利用は推奨されません。

gulpfileの肥大化問題

gulp3以前では、gulpfile.jsは巨大な単一ファイルとして扱われてきました。数百行から時には千行を超えるファイルの管理は難しいものでした。

gulp4ではこのgulpfileの肥大化を抑制できます。

パブリックタスク / プライベートタスク

公式ドキュメントから、タスクの作成のサンプルを引用します。

gulpfile.js
const { series } = require('gulp');

function clean(cb) {
  ...
  cb();
}

function build(cb) {
  ...
  cb();
}

exports.build = build;
exports.default = series(clean, build);

exportされていない関数がプライベートタスク、exportされている関数がパブリックタスクになります。

これらのタスクをターミナルから実行してみます。

$ gulp clean
Task never defined: clean

$ gulp build
Starting 'build'...

$ gulp
Starting 'default'...

exportしていないcleanタスクは実行できませんでした。プライベートタスクは定義されたjsファイルの外からは呼び出すことができません。

プライベートタスクを利用することで、タスク製作者は外部から呼び出すべきタスクを明示できます。gulp3では、すべてのタスクが外部から呼び出し可能でした。そのため、タスクを細分化してメンテナンスを簡単にしようとすると、利用者がどのタスクを使っていいのかわからなくなるという問題がありました。プライベートタスクはこうした問題を解決します。

exportsとは

exportsはnode.jsにおけるmodule.exportsへのショートカットです。named exportをより短く記述するための機能です。

node.js Modules

node.js modules exports

つまりgulp4におけるパブリックタスクとは「module.exportsされ、非同期処理の完了を通知する関数」と言えます。

タスクファイルの分割

gulp4におけるパブリックタスクは exportされた関数です。そのため、gulpfile.jsの外部に記述された関数もrequireで読み込めばタスクとして利用できます。つまりgulpfile.jsが分割できます。

例としてtaskA.jsgulpfile.jsの2つのファイルがあるとします。いずれのファイルもpackage.jsonと同じディレクトリにあるとします。

taskA.js
const { series } = require('gulp');

function taskA1(cb) {
  ...
  cb();
}
function taskA2(cb) {
  ...
  cb();
}

exports.taskA = series(taskA1, taskA2);
gulpfile.js
const { taskA } = require("./taskA");
exports.taskA = taskA;

gulpfile.jsはtaskA.jsからタスクを読み込み、再度exportしてパブリックタスクにしています。この状態でターミナルから$ gulp taskAとタスクを実行できます。
また、gulpfile.jsとターミナルの両方からプライベートタスクtaskA1およびtaskA2は呼び出せません。

分割ファイルの配置方法

gulpでは、コード分割の際のファイルの配置方法に関する仕組みを提供しています。
公式ドキュメント

package.json
/ gulpfile.js
  ┠ index.js
  ┗ taskA.js

直感的に理解しにくいのですが、gulpfile.jsという名前のディレクトリを配置し、その中にindex.jsを配置します。するとgulpは./gulpfile.js/index.jsを従来のgulpfile.jsと同様にルートファイルとして認識します。

このルールに従わないファイルのrequireも現状では問題なく機能します。しかし、関連するファイルが分散してしまうのを避けるため、こうしたルールにしたがってファイルを配置することをオススメします。

ファイル分割の恩恵

gulpfile.jsは肥大化しやすいファイルです。これを分割することでメンテナンス性が向上します。また、プライベートタスクをファイル分割と併用することで、親ファイルからアクセス可能なタスクを制限できます。

タスクのモジュール化

タスクを記述したjsファイルをnodeモジュール化し、そのモジュールをgulpfileに読み込むことも可能です。

例として、このようなパッケージを作成してGitHubにpushします。名前はsample-taskとします。

package.json
{
  "name": "sample-task",
  "main": "./index.js",
  ...
  "dependencies": {
    "gulp": "^4.0.1",
    "gulp-rev": "^9.0.0", // <- タスク内で利用するgulpプラグインをdependenciesに追加
    ...
  },
}
index.js
"use strict";

const { series, src, dest } = require("gulp");
const rev = require("gulp-rev");
const path = require("path");
const distPath = path.resolve("./dist/");

function taskA(){
  return src(...)
    .pipe(...)
    .pipe(dest(...);
};

function taskB(){
  return src(...)
    .pipe(...)
    .pipe(dest(...);
};

exports.sampleTask = series(taskA, taskB);

別のプロジェクトから、sample-taskモジュールをnpmでインストールします。

$ npm install https://github.com/[GitHubのユーザーID]/sample-task.git

sample-taskモジュールはgulpfile.jsから読み込めます。

gulpfile.js
const { sampleTask } = require("sample-task");
exports.sampleTask = sampleTask;

requireで読み込んだタスクは以下のように利用します。

  • 再度exportすることでバプリックタスクとして利用する。
  • seriesやparallelに組み込み、プライベートタスクとして利用する。

モジュール化されたタスクは、自身のpackage.jsonで依存するプラグインを管理できます。タスクを読み込む側では、依存プラグインの管理の必要はありません。

タスクモジュールに引数を与える

ファイル分割 / モジュール化したタスクに、タスク実行時に変数を与えることはできません。子タスクに変数を与えたい場合「変数を受け取りタスクを返す関数」を使います。

index.js
"use strict";

const { series, src, dest } = require("gulp");

module.exports = (arg1, arg2, arg3) => {
  function taskA(){
    return src(arg1)
      .pipe(...)
      .pipe(dest(arg2);
  };

  function taskB(){
    return src(arg1)
      .pipe(...)
      .pipe(dest(arg3);
  };

  return series(taskA, taskB);
}

このモジュールをgulpfile.jsから読み込みます。

gulpfile.js
const sampleTask = require("./index")("./path/to/src", "./path/to/dist1", "./path/to/dist2");
exports.sampleTask = sampleTask;

require(モジュールのパスかID)(引数)の形で変数を渡すことができます。

タスクモジュールの例

Webサイト用のgulpfileでよく利用するタスクを切り出し、モジュール化してみました。皆様がモジュール化を行う場合の参考資料としてご利用ください。

gulptask-revision

GitHub リポジトリ

gulp-revを利用して、出力されたファイルにリビジョンを振る一連のタスクをモジュール化したものです。

$ npm install https://github.com/MasatoMakino/gulptask-revision.git -D

でインストールして

gulpfile.js
const rev = require("gulptask-revision")("変換するディレクトリのパス");

と読み込んで利用します。

gulptask-imagemin

GitHub リポジトリ

gulp-imageminを利用して、画像ファイルの最適化を行います。
gulp-image-resizeが別途imagemagickのインストールを必要とします。macOSの場合、Homebrew経由でインストールが可能です。

brew install imagemagick

参考記事

macにImageMagickをインストールし、convertコマンドで画像を縮小する。

モジュールの利用方法は

$ npm install https://github.com/MasatoMakino/gulptask-imagemin.git -D

でインストールして

gulpfile.js
const images = require("gulptask-imagemin")("画像ソースのディレクトリ", "出力先ディレクトリ");

と読み込んで利用します。
watchのタスクとしても動作します。

gulpプラグインの更新問題

gulpの維持管理を難しくするもうひとつの要因として、gulpプラグインの更新問題があります。依存するプラグインの更新が途絶えた場合、プラグインをフォークして自力でメンテナンスするか、そのタスクをgulpから切り離すかの選択を迫られます。

gulpというレイヤーが増える分、依存するモジュールは増え、更新停止のリスクは増加します。ならば最初からnodeモジュールを直接実行したほうがメンテナンスのリスクは少なくなります。

nodeモジュールがそのまま走る

gulp4のタスクは前述の通りただの関数です。そのためタスク内でnodeモジュールがそのまま走ります。

公式ドキュメントからサンプルを引用します。

gulpfile.js
const { rollup } = require('rollup');

// Rollup's promise API works great in an `async` task
exports.default = async function() {
  const bundle = await rollup.rollup({
    input: 'src/index.js'
  });

  return bundle.write({
    file: 'output/bundle.js',
    format: 'iife'
  });
}

rollup.jsのバンドル処理はasync/awaitに対応しています。そのため、タスクの関数をasyncにすることで簡単に取り込めます。

const del = require('delete');

exports.default = function(cb) {
  // Use the `delete` module directly, instead of using gulp-rimraf
  del(['output/*.js'], cb);
}

async/awaitに対応せず、非同期処理の完了後にコールバックを呼ぶタイプのモジュールもあります。その場合タスクの引数のコールバックをそのままモジュール側の関数に渡してしまうことで取り込めます。

公式ドキュメントでも、ファイル変換を伴わない処理ではgulpプラグインよりもnodeモジュールの使用を推奨しています。

Plugins should always transform files. Use a (non-plugin) Node module or library for any other operations.

依存するモジュールの量と層を減らすことで、プラグインの更新停止リスクは減少します。

個人的な感想

破壊的変更

gulp4は3以前と比べるとまったくの別物です。これだけ大規模かつ破壊的な変更をしながらgulpプラグインの互換性がほぼ維持されていることに驚きました。

gulp4のオススメ度

個人的なgulp4への移行のオススメ度を、状況別にまとめると

  • gulp3を利用している : 文句なしにオススメです。
  • すでに他のツールでワークフローができ上がっている : 既存のプロジェクトに導入する必要はありません。
  • 新規のプロジェクトに導入する : 部分的な導入も可能なので検討の価値ありです。

となります。

タスクモジュールの粒度

タスクをモジュール化した場合、対象とする作業はプラグインよりも巨大になり、その分汎用性は低くなります。タスクをどの程度の粒度で切り出すべきなのかは今後の課題です。

タスクモジュールの副作用

モジュール化されたタスクは、以前のgulpプラグイン同様に依存レイヤーを増やすことになります。更新停止のリスクや、破壊的更新による影響の増大がモジュール化の副作用として考えられます。

参考記事

JavaScriptのモジュールシステムの歴史と現状

exports / requireとそのほかのモジュールシステムの歴史がまとめられています。gulpタスクの構造を理解することができました。

JavaScriptをやっていると npm/yarn/gulp/grunt/webpack など、たくさんのツールがあって混乱したので、それぞれの役割と違いをざっくりとまとめた

現状のgulpの立ち位置を、そのほかのツールと比較して再確認できる素晴らしい記事です。

以上、ありがとうございました。

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

Node.js v11.15.0リリースノート

https://nodejs.org/ja/blog/release/v11.15.0/

deps

  • add s390 asm rules for OpenSSL-1.1.1
    • s390 asmルールを追加?

src

  • add .code and SSL specific error properties
    • .codeとSSL特有のエラープロパティを追加

tls

  • add --tls-min-v1.2 CLI switch
    • CLIを切り替えるための--tls-min-v1.2オプションを追加
  • supported shared openssl 1.1.0
    • openssl1.1.0をサポート?
  • revert default max to TLSv1.2
    • デフォルトのTLSの最大をv1.3からv1.2に戻した
      • 下記TLSv1.3サポート時のミスの修正?
  • revert change to invalid protocol error type
    • 下記ERR_TLS_INVALID_PROTOCOL_METHODの実装でエラーの型の指定が誤っていたため修正?
  • support TLSv1.3
    • TLSv1.3をサポート
  • add code for ERR_TLS_INVALID_PROTOCOL_METHOD
    • ERR_TLS_INVALID_PROTOCOL_METHODのコードを追加
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js v11.14.0リリースノート

https://nodejs.org/ja/blog/release/v11.14.0/

child_process

  • doc deprecate ChildProcess._channel
    • ChildProcess._channelが非推奨であることを記載
      • 代わりにChildProcess.channelを使用する

deps

  • update nghttp2 to 1.37.0
    • nghttp2を1.34.0から1.37.0に更新

dns

  • make dns.promises enumerable
    • dns.promisesをenumerableに変更
  • remove dns.promises experimental warning
    • dns.promisesの警告を削除

fs

  • remove experimental warning for fs.promises
    • fs.promisesの警告を削除

stream

  • make Symbol.asyncIterator support stable
    • Symbol.asyncIteratorの状態をExperimentalからStable

worker

  • use copy of process.env
    • 各workerのprocess.envはそのworkerを作成したworkerからコピーされるようになった
      • これまではOSがストアしたものを共有していた?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js v11.13.0リリースノート

https://nodejs.org/ja/blog/release/v11.13.0/

crypto

  • Allow deriving public from private keys
    • 秘密鍵から公開鍵が作成できるようになった

events

  • Added a once function to use EventEmitter with promises
    • EventEmmiterでPromiseを使うために、once()を追加

tty

  • Added a hasColors method to WriteStream
    • WriteStreamにhashColors()を追加
  • Added NO_COLOR and FORCE_COLOR support
    • 環境変数にNO_COLORFORCE_COLORを追加

v8

  • Added v8.getHeapSnapshot and v8.writeHeapSnapshot to generate snapshots in the format used by tools such as Chrome DevTools
    • v8.getHeapSnapshot()v8.writeHeapSnapshot()を追加
      • Chrome DevTools等で使用されている形式のスナップショットを生成するため

worker

  • Added worker.moveMessagePortToContext. This enables using MessagePorts in different vm.Contexts, aiding with the isolation that the vm module seeks to provide
    • worker.moveMessagePortToContext()を追加
      • 異なるvm.ContextsでMessagePortsが使用可能になる?

C++ API

  • AddPromiseHook is now deprecated. This API was added to fill a use case that is served by async_hooks, since that has Promise support
    • AddPromiseHookが非推奨になった
  • Added a Stop API to shut down Node.js while it is running
    • 実行中のNode.jsを終了するためのStop APIをついか
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js v11.12.0リリースノート

https://nodejs.org/ja/blog/release/v11.12.0/

bootstrap

  • Add experimental --frozen-intrinsics flag
    • --frozen-intrinsicsオプションが試験的に追加された
      • 配列やオブジェクトの中身も変更不能になるdeep freezeが使用可能になる?

build

  • Enable v8's siphash for hash seed creation
    • hashのシード生成にv8のsiphashが利用可能になった
      • HashWick(hash関数の結果が予測できてしまう脆弱性)への対応

deps

  • Upgrade openssl to 1.1.1b
    • opensslが1.1.1aから1.1.1bに更新された

process

  • Make process[Symbol.toStringTag] writable again
    • process[Symbol.toStringTag]を書き込み可能に戻した

repl

  • Add util.inspect.replDefaults to customize the writer
    • writerをカスタマイズするためにutil.inspect.replDefaultsを追加

report

  • Rename triggerReport() to writeReport()
    • triggerReport()をwriteReport()に名前変更
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js v11.11.0リリースノート

https://nodejs.org/ja/blog/release/v11.11.0/

n-api

  • Implement date object
    • Dateオブジェクトが使用可能になった

util:

  • Add compact depth mode for util.inspect()
    • util.inspect()で、文字列にする配列やオブジェクトの要素の深さを指定できるようになった

worker:

  • Improve integration with native addons
    • 複数のNode.jsインスタンスからアドオンを読み込めるようになった?
  • MessagePort.prototype.onmessage takes arguments closer to the Web specification now
    • onmessage()の引数がWebの仕様に近いものになった?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js v11.10.1リリースノート

https://nodejs.org/ja/blog/release/v11.10.1/

http

  • Further prevention of "Slowloris" attacks on HTTP and HTTPS connections by consistently applying the receive timeout set by server.headersTimeout to connections in keep-alive mode. (CVE-2019-5737 / Matteo Collina)
    • スローロリス攻撃対策のために、server.headersTimeoutでキープアライブモードでの接続にタイムアウトを設定できるようになった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Passport.js の基本的な利用方法 (Node.js & Express)

はじめに

本記事の目的

  • Node.jsで一般的に利用される認証ミドルである Passport.js の動作を理解すること
  • 基礎を理解するため、公式サイトのサンプルコードに一切手を加えずにコードを補完し、最低限動くようにしたので、その方法を共有する

前提環境

ソフトウェア バージョン
Node.js v10.15.3
express 4.16.4
Passport 0.4.0
  • Express-generator、およびテンプレートエンジンは Pug を利用する。
  • 動作確認のため、データベースは利用せず、ソースコードにユーザ名・パスワードをハードコーディングする。

公式サイト、および利用するサンプルコード

日本語サイト:https://knimon-software.github.io/www.passportjs.org/guide/
オリジナルサイト:http://www.passportjs.org/

  • ユーザ名&パスワードでの認証のサンプルコードを用いる。

ストラテジーの設定

node.js
var passport = require('passport')
  , LocalStrategy = require('passport-local').Strategy;

passport.use(new LocalStrategy(
  function(username, password, done) {
    User.findOne({ username: username }, function (err, user) {
      if (err) { return done(err); }
      if (!user) {
        return done(null, false, { message: 'ユーザーIDが正しくありません。' });
      }
      if (!user.validPassword(password)) {
        return done(null, false, { message: 'パスワードが正しくありません。' });
      }
      return done(null, user);
    });
  }
));

ルーティング

node.js
app.post('/login',
  passport.authenticate('local', { successRedirect: '/',
                                   failureRedirect: '/login',
                                   failureFlash: true })
);

セッション管理に関する設定

node.js
passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

手順1. 必要なモジュールのインストール

  • まず、関連するモジュールをいくつかインストールする必要がある。
$ npm install passport
$ npm install passport-local
$ npm install express-session
$ npm install connect-flash

手順2. 設定に関するコード補完

  • まずはモジュールのRequire
app.js
var session = require('express-session');
var flash = require('connect-flash');
  • 続いて、Session, Flashに関しても初期化を行う。
  • 記載箇所は、ルーティングの直前でいいだろう。
app.js
// Session, Flashの初期化
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: false
}));
app.use(passport.initialize());
app.use(flash());
  • ログイン画面(login.pug)を用意するため、ルーティングを追記する。
app.js
// Passport authenticate handling
app.get('/login', (req, res) => { res.render('login'); });

手順3. ユーザデータの読み出しモジュールを作成する

  • 動作確認のため、サンプルユーザを2名入れておく。
  • サンプルコード側で、findOne, findById という関数名が指定されているため、合わせていく。
User.js
// Sample Users
var records = [
    { id: 1, username: 'andre', password: 'secret', displayName: 'Andre', emails: [{ value: 'andre@example.com'}]},
    { id: 2, username: 'bill' , password: 'asdfgh', displayName: 'Bill' , emails: [{ value: 'bill@example.com'}]}
  ];

exports.findById = function(id, callback) {
    process.nextTick(function() {
        var idx = id - 1;
        if (records[idx]) {
        callback(null, records[idx]);
        } else {
        callback(new Error('User ' + id + ' does not exist.'));
        }
    });
};

exports.findOne = function( username, callback) {
    process.nextTick(function() {
        for (var i=0, len=records.length; i<len; i++) {
            var record = records[i];
            // ユーザがレコードに存在した場合
            if (record.username === username.username) {
                var user = {
                    id : record.username,
                    password : record.password,
                    validPassword(givenPwd) {
                        if (this.password === givenPwd) {
                            return true;
                        } else {
                            return false;
                        }
                    }
                }
                return callback(null, user);
            }
        }
        // ユーザが合致せずForループから抜け出した場合
        return callback(null, null);
    });
};
  • app.js 側で、本モジュールを呼び出すコードを追記する。
app.js
var User = require('./User.js');

手順4. 表示部分(view)を記載する

  • Passport公式サイトのサンプルフォームに忠実に沿っている。
  • エラー時のFlashメッセージを表示するため、Flashメッセージが存在する場合にはメッセージを表示する条件を入れている。
login.pug
extends layout

block content
  h1= title
  p Welcome to #{title}

  if message
    h2= message

  form(action="/login" method="post")
    div
      label ユーザーID:
      input(type="text" name="username")
    div
      label パスワード:
      input(type="passpord" name="password")
    div
      input(type="submit" value="ログイン")
  • Login.pugへのルーティング部分
index.js
router.get('/login', (req, res, next) => {
  res.render('login', { title: 'Passport Login Test', message: req.flash('error')});
});

以上となります。

最後に

  • Node.js初心者のため、指摘ございましたらぜひご連絡をお願いします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon Alexa+AWS Lambda(Node.js)でスクレイピングして詰まったところ。

まとまった休みが取れたので、以前から興味のあったAmazon Alexaにチャレンジしましたが、思いのほか躓いてしまったので記録しておきます。
やったことは公式のサンプルをなぞっただけですので、詳しい手順はそちらを参照して頂けたらと思います。

やったこと

スマートスピーカーのEcho dotに問い合わせを行うと、Alexaスキルがその回答をWEBスクレイピングをして答えてくれる。

私のスペック

今回のチャレンジにあたっての私のスペックです。
プログラミング歴は19年くらいありますが、どれもこれも初めて。

  • Amazon Alexa:はじめて
  • AWS Lambda:はじめて
  • Node.js:はじめて
  • Java Script:はじめて

Amazon Alexa について

Alexa公式 動画シリーズ「Alexa道場」が親切丁寧なので、アカウントの作成〜スキル作成までを一通り悩まずにできました。
動画の本数が多いので「うお」と思いますが、1本の動画が5分程なので自分のテンポで進められます。

AWS Lambda について

当初、上記で作成する「alexa developer console」内でスクレイピングまでやろうと試行錯誤していましたが、どうもブラウザのJavaScriptでできるようなDom操作が、Node.jsの標準ライブラリではできないようなので、スクレイピングを簡単にするために、やむなくAWS Lambdaを使う事にしました。

これにあたり、「alexa developer console」のカスタムスキルのリクエスト先をAWS Lambdaに変更する必要ができたので、公式のカスタムスキルをAWS Lambda関数としてホスティングするを参考に実施しました。

ハマり① 移植

「alexa developer console」で実装したものを、「AWS Lambda関数」に移植しないといけませんが、サンプルコードがまあまあ違うので、移植先のサンプルコードを理解しないといけません。
インテント名を手掛かりにすると、移植しやすいかと思います。

ハマり② AlexaスキルとAWS Lambdaの関連付け

「AlexaスキルのスキルID」と「AWS Lambda関数のARN」を交換しないといけません。
確かに一緒のページには書いてあるのですが、一方がサンプルの記事の中にさらっと書いてあったので見落としていました。

ハマり③ タイムアウト

AWS Lambda関数のデフォルトのタイムアウト値は3秒です。
最初は3秒以内で動いていたのですが、HTTPSでWEBページを取得する処理を追加したり、スクレイピングの処理を追加したりするうちに、いつの間にか処理時間が延びていた事に気づかず、ロジックを見て困っていました。
処理を追加してエラーが発生したらタイムアウトも疑ってみてください。
AWS Lambdaのマネジメントコンソールにて、タイムアウト値の変更が可能です。
https://aws.amazon.com/jp/blogs/news/aws-lambda-timeout-15min/

Node.js について

JavaScriptならなんとかなるかな。と思ってNode.jsについてロクに調べもせずに取り掛かってしまいました。

ハマり① ノンブロッキングIO

HTMLの取得を待たずにパース処理が動いてしまいます。
標準ライブラリに含まれるhttpを使って通信を行うと、結果を待たずに先に進んでしまいました。(で、解析失敗)
これは、「ノンブロッキングIO」と言うNode.jsの仕様であり、メインスレッド1本で複数のリクエストを裁くタイプのサーバーの特徴だそうです。

とはいえ、HTMLの取得を待ってパースを行う必要があるので調べた結果、Promiseという非同期処理を制御(直列実行・並列実行)できるものがある事がわかった為、下記記事を参考に実装を一部やり直しました。

お気楽 Node.js 超入門
いまさら聞けないNode.jsの基礎知識とnpm、Gulpのインストール
Node.jsのhttpsモジュールを用いた通信処理をPromiseで書き直して解読してみた

ハマり② スクレイピング

Node.jsでスクレイピングするならこれが本命(たぶん)」を参考に(と言うかコピペ)させていただいて、HTMLの取得とパースが簡単にできました。
が、HTMLのパースで使うcheerioというライブラリは、パース結果をjQueryオブジェクトで返すとのこと。
jQueryについても知らない為、ハマったと言うか、JavaScriptを舐めてた為に理解するものが増えてしまい、時間がかかってしまいました。
HTMLと下記記事を何回も交互に見ながら、なんとか取りたい情報を取得する事に成功しました。

【jQuery入門】jQueryの基本的な書き方・文法
スペースを含んだクラス名を jquery で指定できない

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