20190627のNode.jsに関する記事は12件です。

nodejs request のチートシート

request とは?

書式

request(options, function (error, response, body) {
  // リクエスト結果の処理
});

他の書式もあるけど、optionでいろいろ変えられる書式がすき:heart:

http リクエストを投げるモジュール。
options の設定により、様々なリクエストが投げられる。
公式 もあった ← 知らずにハマったこと数知れず:sweat_smile:

ネストが深くなるのがいやなので、いつもpromiseしてます。これもついでにチートシート

function httpRequest(options) {
  return new Promise((resolve, reject) => {
    request(options, function (error, response, body) {
      let ret = {
        headers: response.headers,
        body: body,
        error: error,
        href: response.request.uri.href,
      }

      if (error) {
        reject(ret);
      } else {
        resolve(ret);
      }
    });
  });
}

Promise.resolve()
  .then(function(value) {
    var header = {
    }
    var data = {
    }
    var options = {
    }
    return httpRequest(options); // リクエスト1回目

  })
  .then(function(value){
    // リクエスト結果の処理
    return httpRequest(options); // リクエスト2回目
  })
  .then(function(value){
    // リクエスト結果の処理
  })
  .catch(function(reason) {
    // エラー、rejectしたときの処理
  });

チートシート

  var jar = request.jar(); // cookieを使うとき

  var header = {
//    'Content-type': 'application/x-www-form-urlencoded', // form のときつけてくれる
//    'Content-type': 'application/json',  // jsonのときつけてくれる
    Authorization: 'Bearer beabea', // Basic 認証はここに書いてはいけない
  }
  var data = {
    honya: 'honya data',
  }

  var options = {
    url: 'http://example.com',
    method: 'GET', 
    header: header,
    followAllRedirects: true, // 3xxが返ってきたとき、リダイレクトする。
    jar: jar,  // cookieを使うとき
    auth: [ // Basic認証するとき
      user: 'username',
      password: 'password',
    ],
    qs: data, // GET のとき query stringをurlにつけてくれる
    form: data, // form-urlencoded で送るとき。Content-type ヘッダもつけてくれる
    json: data, // jsonで送るとき。Content-type ヘッダもつけてくれる
  }

ハマるたびに追加していきます。

めんどくさいときは、機能を探す

わーめんどくさーいと思ったときはゴリゴリ書かずに、機能を探しましょう

今までのハマり

  • jar を知らず、ヘッダからset-cookieを取り出して、次のリクエストに入れてた。(動く)
  • basic認証するとき、Authorizatioinヘッダにbase64した値を設定してた。(動かない)
  • getパラメータをhttp_build_queryみたいな関数を作って、urlにつけてた。(動く)
  • followAllRedirects を知らず、3xx が返ったときは、またリクエストしてた。(動く)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Visual Studio Code(SSH経由)でIBMi上のNode.jsファイルを編集・デバッグする

目的

Visual Studio Codeを使い、IBMi上のnode.jsファイルを編集・デバッグします。

検証環境

  • ローカルPC(Windows10)
    • VSCode 1.35.1
  • IBMi
    • Node.js 10.15.3
    • SSHで接続出来ている事

ローカルPC側準備

  • VSCodeからIBMiにSSH接続するための拡張機能(SSH FS)をインストールします。
    01.PNG

  • SSHの設定をします。
    Ctrl+Shift+Pでコマンドパレットを開き、SSH FSと入力し候補を出します。
    Create a SSH SF configurationを選択します。
    Nameは適当な名前を入力し、Saveボタンを押します。
    01.PNG
    画面が遷移しますので、SSH接続の設定をします。(私は鍵認証方式で接続しました)
    設定が終わりましたら、SAVEボタンを押してください。

  • SSHで接続します。
    Ctrl+Shift+Pでコマンドパレットを開き、SSH FSと入力し候補を出します。
    Connect as Workspace folderを選択します。
    先ほど入力した設定が候補で出てきますので、クリックします。
    01.PNG
    接続が成功すると、以下のようにフォルダ/ファイルの一覧が表示されます。
    01.PNG
    これだけでも素晴らしいですね。

デバッグの方法

  • 作業フォルダの作成 ここでの作業はVSCodeでは出来ないので、5250のQP2TERMかSSH接続したターミナルから作業を行います。(以下、ターミナル) ※検証のため、expressをインストールしました。
mkdir debugtest
cd debugtest
npm init -y
npm install -save express

VSCodeに戻り、最新表示を押します。
01.PNG

  • デバッグ用ソースの準備を準備します。
server.js
const express = require("express");
const app = express();

app.get('/hello', (req, res) => {
    const message = 'Hello IBMi Node.js!!';

    res.send(message);
});

app.listen(8080);
  • デバッグ用package.jsonの編集します。

XXX.XXX.XXX.XXXはIBMiのIPアドレスです。

  "scripts": {
    "start": "node --inspect=xxx.xxx.xxx.xxx:9229 server.js"
  },
  • デバッグ用の.vscode/launch.jsonの作成し、編集します。

メニューから「デバッグ」→「構成を開く」→「構成の選択」でnode.jsを選びます。
.vscode/launch.jsonファイルが作成されるので、以下のように編集します。

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "attach",
      "name": "デバッグの開始",
      "address": "XXX.XXX.XXX.XXX",
      "port": 9229
    }
  ]
}
  • ターミナルからプログラムを起動します。
npm start

Debugger listening on ws:XXX.XXX.XXX.XXX:9229・・・
とメッセージが表示されることを確認してください。

  • VSCoodeからデバッグをとる。

メニューから「デバッグ」→「デバッグの開始」を選択します。

画面左下に、「読み込み済みスクリプト」が表示されますので、server.jsを選択します。

01.PNG

ここから、ブレークポイントを置くとデバッグが出来るようになります。

02.PNG

メモ:VSCode公式のRemote SSHでは繋がりませんでした・・・

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

【Google Cloud / Node.js】Cloud SchedulerをAPI経由で使ってみる

はじめに

Firebase をバックエンドに使ったWebアプリの開発にあたり
「定期実行されるcronサービスを使いたい」
「クライアントアプリからジョブを設定したい(API使いたい)」
と思い、調べたところ Google Cloud PlatformCloud Scheduler が良さげさったので使ってみました。

cronサービスは他にもいくつかありますが、Cloud Schedulerの特徴としては

  • 無料枠がある(1アカウントあたり3タスク)
  • APIが提供されており、HTTP経由でジョブの設定、変更が可能である
  • GCPの他のサービスとの親和性が高い※

などかなと思います。

※ Cloud Functions for Firebaseであれば、Cloud Schedulerの機能をラップしたメソッドを使って定期実行ができるそうです。参考: Firebase Cloud Functionsの定期実行が、それ単体で簡単にできるようになった! - Qiita

本記事は、Node.jsでCloud SchedulerジョブをAPIを使って設定し、 Cloud Functions for Firebase を実行する方法の備忘録です。

事前準備

GCPに登録

Cloud Schedulerは Google Cloud Platform が提供するサービスです。無料使用枠はありますが使用には登録が必要なため、予め登録しておきましょう。
登録完了後、APIを使うためのプロジェクトを作成してください。

APIを有効化

APIを使用するため、 コンソールのAPIライブラリからCloud Schedulerを検索しAPIを有効化しておきます。
今回はCloud FunctionsもAPI経由で実行するため、Cloud Functions APIも有効化しましょう。

Google Cloud API認証用の環境設定

Google Cloud APIの使用にはOAuth認証が必須です。
事前にGCPのサービスアカウントと、秘密鍵を含むJSONファイルを作成し、それを用いて認証を行います。
こちらを参考に、サービスアカウントの作成〜環境変数の設定までを実施します。

Cloud Pub/Sub の設定

Cloud Schedulerでジョブを作成する際、定期実行するジョブの種類(ターゲット)を以下の選択肢から設定する必要があります。

今回はCloud Scheduler → Cloud Pub/Sub → Cloud Functions の順で実行するので、予めPub/Subの設定と実行される関数を準備しておきます。
まず、最終的に実行されるCloud Functionsの関数を functions.pubsub で作成します。トピック名は後ほど設定します。

const functions = require('firebase-functions');

exports.handlePubSubTrigger = functions
  // トリガーとなるPub/Subのトピックを指定する
  .pubsub.topic('[トピック名]')
  // Pub/SubからPUSHがあったときの処理
  .onPublish(async (event, context) => {
    const pubsubMessage = event.data;
    console.log(Buffer.from(pubsubMessage, 'base64').toString());
  });

次に、前段で作成した関数をトリガーするPub/Subのトピックおよびサブスクリプションを作成します。作成は Google Cloud SDK からCLIで作成できるほか、GCPのコンソール画面からも作成可能です。こちらを参照しつつ

  1. トピック作成後、トピック名を実行する関数に設定
  2. サブスクリプションのエンドポイントURLに実行する関数のURLを指定

を行います。

APIを使ってみる

事前準備が完了したら、実際にCloud Scheduler APIを使ってジョブを作成してみます。
なお、Google Cloud API リクエストの実装方法として
1. 単純にHTTPリクエストを発行する
2. クライアントライブラリ( Google APIs Node.js Client )を使う

の2パターンあり、今回は使い勝手のいい後者を採用します。
ジョブ作成するCloud Functionsの関数は以下になります。プロセスとしては、

  1. GCPのOAuth認証
  2. Cloud Schedulerジョブの新規作成(APIのリクエスト)

の2つです。

const functions = require('firebase-functions');
const { google } = require('googleapis');

exports.createSchedulerJob = functions.https.onRequest(
  async (req, res) => {
    // ①GCPのOAuth認証
    const client = await google.auth.getClient({
      scopes: ['https://www.googleapis.com/auth/cloud-platform'],
    });

    // ②ジョブの新規作成
    const projectId = '[PROJECT_ID]'; // プロジェクトID
    const locationId = '[LOCATION_ID]'; // ロケーション名(Cloud Functionsのデフォルトはus-central1)
    const topicName = '[TOPIC_NAME]'; // トリガーするPub/Subのトピック名
    const jobName = '[JOB_NAME]'; //作成するCronジョブ名

    const request = {
      parent: `projects/${projectId}/locations/${locationId}`,
      resource: {
        name: `projects/${projectId}/locations/${locationId}/jobs/${jobName}`,
        schedule: '0 7 * * 1-5', // 定期実行する時刻 今回は月~金のAM7:00に設定
        pubsubTarget: {
          topicName: `projects/${projectId}/topics/${topicName}`,
          data:  Buffer.from('Cron triggered.').toString('base64'),
        },
      },
      auth: client,
    };
    // APIリクエスト
    cloudScheduler.projects.locations.jobs.create(request, (err, response) => {
      if (err) {
        console.error(err);
        return;
      }
      return response.data;
    });
  },
);

① GCPのOAuth認証

まず、クライアントライブラリのauth.getClient()メソッドを使ってOAuth認証を行います。

このとき、scopesの設定が必要になります。
スコープとはアプリケーションに許可するAPI操作の権限の範囲で、これを認証時に明記する必要があります。
各APIに必要なスコープはAPIのドキュメントにAuthorization Scopesとして記載されています。今回はCloud Schedulerに必要なhttps://www.googleapis.com/auth/cloud-platformを指定します。

const client = await google.auth.getClient({
  scopes: ['https://www.googleapis.com/auth/cloud-platform'],
});

補足ですが、auth.getClient()は開発環境において前段で準備した秘密鍵のJSONファイルをよしなに参照してくれるので、コード内で認証ファイルをインポートするなどの必要はありません。クライアントライブラリを使うのにはこうした利点があります。

② Cloud Schedulerジョブの新規作成

OAuth認証後、Cloud Schedulerのジョブを作成します。ジョブの作成には projects.locations.jobs.create を使います。

リクエストとして以下の項目を設定します。

  • parent …ジョブを作成するプロジェクト。
  • resource.name …ジョブの名前。projects/~で始まるパスの形で設定。
  • resource.schedule … ジョブの設定時刻。 unix-cron 文字列形式で設定。
  • resource.pubsubTarget … トリガーするPub/Subトピックの設定。topicNamedataを必須で設定する。dataはBase64エンコードする。
  • auth … 前段で取得した認証情報

なお、resource.pubsubTarget の箇所はトリガーするターゲットに応じてプロパティが変わるので注意してください。

const projectId = '[PROJECT_ID]'; // プロジェクトID
const locationId = '[LOCATION_ID]'; // ロケーション名(Cloud Functionsのデフォルトはus-central1)
const topicName = '[TOPIC_NAME]'; // トリガーするPub/Subのトピック名
const jobName = '[JOB_NAME]'; //作成するCronジョブ名

const request = {
  parent: `projects/${projectId}/locations/${locationId}`,
  resource: {
    name: `projects/${projectId}/locations/${locationId}/jobs/${jobName}`,
    schedule: '0 7 * * 1-5', // 定期実行する時刻 今回は月~金のAM7:00に設定
    pubsubTarget: {
      topicName: `projects/${projectId}/topics/${topicName}`,
      data:  Buffer.from('Cron triggered.').toString('base64'),
    },
  },
  auth: client,
};
// APIリクエスト
cloudScheduler.projects.locations.jobs.create(request, (err, response) => {
  if (err) {
    console.error(err);
    return;
  }
  return response.data;
});
},

無事ジョブが作成されると、レスポンスで作成されたジョブの情報が返却されます。

③ ジョブの変更

一度作成したジョブを変更する際は projects.locations.jobs.patch を使います。
以下の例では、先程作成したジョブの設定時刻を7時から8時に変更します。

const client = await google.auth.getClient({
  scopes: ['https://www.googleapis.com/auth/cloud-platform'],
});
const projectId = '[PROJECT_ID]'; // プロジェクトID
const locationId = '[LOCATION_ID]'; // ロケーション名(Cloud Functionsのデフォルトはus-central1)
const jobName = '[JOB_NAME]'; // 更新するCronジョブ名

const request = {
  name: `projects/${projectId}/locations/${locationId}/jobs/${jobName}`,
  updateMask: 'schedule',
  resource: {
    schedule: '0 8 * * 1-5', // AM8:00に設定変更
  },
  auth: client,
};

cloudScheduler.projects.locations.jobs.patch(request, (err, response) => {
  if (err) {
    console.error(err);
    return;
  }
  return response.data;
});

作成時と異なるのはpubsubTargetが不要となる点と、リクエストにupdateMaskを指定する点です。
updateMaskはジョブのどのフィールドを更新するかを指定するパラメータです。設定しないと更新されないので必ず設定します。

おわりに

API経由でcronジョブをスケジューリングするような記事があまりなかったので、参考になれば幸いです。
内容に誤りや改善点があればコメントいただけるとありがたいです!

参考記事

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

Jestの結果がおかしくなったら--clearCacheしよう。

ある日の出来事...

.vueでレイアウトちょっと変えただけだけどいちおうテストしとこうっと。

やぁ!

yarn run test:unit

結果はどうかな?

pre.png

...なんかめちゃめちゃやんけ!!

Uncovered Lineの表示もソースコードと全然合っていません。

こんな時は慌てず騒がず(騒いだけど...)キャッシュをクリアしましょう。

うりゃ〜!

yarn run test:unit --clearCache
$ vue-cli-service test:unit --clearCache
Cleared /var/folders/2l/gq85xz3j06x5y3rzsmlnq8vr00172j/T/jest_uvl

みたいになります。

再びテストします!やぁ!!

yarn run test:unit

aft.png

ばっちりですね。

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

[ Mac ] nodeのあれこれ

  • nodebrewで入れたnodeの確認
MacBook-Pro-2: ~/mu$ nodebrew ls
v9.11.2
v12.4.0

current: v9.11.2
  • nodeのバージョンの変更
$ nodebrew use v12.4.0
use v12.4.0
  • バージョンの確認
$ node -v

あれ・・・変わってない。

  • 環境変数の反映 変更した後、これでパスを通さないと反映しないよ!
$ source ~/.bash_profile
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Mac】nodeのあれこれ

  • nodebrewで入れたnodeの確認
MacBook-Pro-2: ~/mu$ nodebrew ls
v9.11.2
v12.4.0

current: v9.11.2
  • nodeのバージョンの変更
$ nodebrew use v12.4.0
use v12.4.0
  • バージョンの確認
$ node -v

あれ・・・変わってない。

  • 環境変数の反映 変更した後、これでパスを通さないと反映しないよ!
$ source ~/.bash_profile
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Mac】Node.js のあれこれ

  • nodebrewで入れたnodeの確認
MacBook-Pro-2: ~/mu$ nodebrew ls
v9.11.2
v12.4.0

current: v9.11.2
  • nodeのバージョンの変更
$ nodebrew use v12.4.0
use v12.4.0
  • バージョンの確認
$ node -v

あれ・・・変わってない。

  • 環境変数の反映 変更した後、これでパスを通さないと反映しないよ!
$ source ~/.bash_profile
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

eslint prettier husky を使った簡単環境構築

環境を構築するメリット

eslint

  • 一定のコーディングルールを儲けることができる為、コードレビュー負担の削減や可読性の向上を期待できます。
  • プラグインを設定することで即時エラーを確認することができる為、開発スピードを向上できます。

prettier

  • コードフォーマット機能を備えており、コードフォーマットの統一による可読性の向上を期待できます。
  • インデント等を気にせずプログラミングが可能な為、開発スピードを向上できます。

husky

  • コミット時に追加ファイルをeslintprettierによる自動修正が可能。
  • 自動修正できない場合は、コミット時にエラーがある場合はエラー内容が出力されコミットができない。
  • 定めた条件をクリアしたものだけがコミットできる為、レビュアーの負担を軽減できます。

eslint prettier husky で作る簡単環境構築

導入する際の、基本的な構築手順を記載します。
導入後に、必要に応じてルールの追加やプラグインの追加も可能です。

1. eslintをinstall

npm install eslint  --save-dev

2. eslintの初期化

./node_modules/eslint/bin/eslint.js init

いくつか質問されるので、好みやプロジェクトに合わせて選択する。
→ eslintの設定ファイル .eslintrc(ファイル形式選択可能) が作成される。
※ style guideについてはいくつか選択肢がありますが、迷ったら最初は厳しすぎない Standard がオススメです。

3. prettierをinstall

npm install prettier eslint-config-prettier eslint-plugin-prettier --save-dev

4. eslintrcファイルにprettierの設定を追加

extends

  "extends": [
    "plugin:prettier/recommended"
  ]

extends(配列)に設定を追記します。

rule

  "rules": {
    "prettier/prettier": [
      "error",
      {
        "singleQuote": true,
        "semi": false
      }
    ]
  }

シングルクォートとセミコロンの設定を追加してます。
ここに追加したいルールを追加することでカスタマイズ可能です。

カスタマイズ例

  "rules": {
    "prettier/prettier": [
      "error",
      {
        "singleQuote": true,
        "semi": false
      }
    ],
    "no-var": "error",
    "prefer-const": "error",
    "object-shorthand": "error",
    "prefer-arrow-callback": "error"
  }

5. husky lint-staged をinstall

npm install husky lint-staged  --save-dev

6. huskyの設定追加

package.json に追加

{
  "lint-staged": {
    "*.js": [
      "eslint --fix",
      "git add"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  }
}

"scripts": {...},などと同階層に追加します。

おまけ. VSCode (Visual Studio Code) で保存時自動修正

  1. プロジェクトルート配下に、.vscodeディレクトリを作成
  2. .vscode 配下に下記ファイルを配置

setting.json

{
  "eslint.nodePath": "./node_modules/eslint/bin/eslint.js",
  "eslint.autoFixOnSave": true
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

eslint prettier huskey を使った簡単環境構築

環境を構築するメリット

eslint

  • 一定のコーディングルールを儲けることができる為、コードレビュー負担の削減や可読性の向上を期待できます。
  • プラグインを設定することで即時エラーを確認することができる為、開発スピードを向上できます。

prettier

  • コードフォーマット機能を備えており、コードフォーマットの統一による可読性の向上を期待できます。
  • インデント等を気にせずプログラミングが可能な為、開発スピードを向上できます。

huskey

  • コミット時に追加ファイルをeslintprettierによる自動修正が可能。
  • 自動修正できない場合は、コミット時にエラーがある場合はエラー内容が出力されコミットができない。
  • 定めた条件をクリアしたものだけがコミットできる為、レビュアーの負担を軽減できます。

eslint prettier huskey で作る簡単環境構築

導入する際の、基本的な構築手順を記載します。
導入後に、必要に応じてルールの追加やプラグインの追加も可能です。

1. eslintをinstall

npm install eslint  --save-dev

2. eslintの初期化

./node_modules/eslint/bin/eslint.js init

いくつか質問されるので、好みやプロジェクトに合わせて選択する。
→ eslintの設定ファイル .eslintrc(ファイル形式選択可能) が作成される。
※ style guideについてはいくつか選択肢がありますが、迷ったら最初は厳しすぎない Standard がオススメです。

3. prettierをinstall

npm install prettier eslint-config-prettier eslint-plugin-prettier --save-dev

4. eslintrcファイルにprettierの設定を追加

extends

  "extends": [
    "plugin:prettier/recommended"
  ]

extends(配列)に設定を追記します。

rule

  "rules": {
    "prettier/prettier": [
      "error",
      {
        "singleQuote": true,
        "semi": false
      }
    ]
  }

シングルクォートとセミコロンの設定を追加してます。
ここに追加したいルールを追加することでカスタマイズ可能です。

カスタマイズ例

  "rules": {
    "prettier/prettier": [
      "error",
      {
        "singleQuote": true,
        "semi": false
      }
    ],
    "no-var": "error",
    "prefer-const": "error",
    "object-shorthand": "error",
    "prefer-arrow-callback": "error"
  }

5. huskey lint-staged をinstall

npm install husky lint-staged  --save-dev

6. huskeyの設定追加

package.json に追加

{
  "lint-staged": {
    "*.js": [
      "eslint --fix",
      "git add"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  }
}

"scripts": {...},などと同階層に追加します。

おまけ. VSCode (Visual Studio Code) で保存時自動修正

  1. プロジェクトルート配下に、.vscodeディレクトリを作成
  2. .vscode 配下に下記ファイルを配置

setting.json

{
  "eslint.nodePath": "./node_modules/eslint/bin/eslint.js",
  "eslint.autoFixOnSave": true
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Expressはミドルウェアで開発しよう!

概要

仕事でExpressアプリを開発しています。
動作を保証する指標として、テストコードを書いていますが、Expressのジェネレーターが出力してくれる部分(app.jsやserver.js)まで、毎度毎度テストコードを書きたくない思いでいっぱいです。

Expressはミドルウェアの開発に合っているように感じます。(理由は後ほど)
なので、これからはExpressアプリを作るのではなく、Expressミドルウェアを作るようにしましょう!

この投稿で叶えたい理想像

まず前提として、現状のプロダクトを作っていく考え方として次が挙げられます。

  • 1つ1つのプロダクトのサイズを小さく
  • 外から持ってこれる場合は外から持ってくる
  • 設定・環境のコード化
  • 共有して使えるようにする

これらを踏まえて、次のような状態を作っていきたいです。

1. 複数のルーター(アプリ)がサーバーの構成を共有

理想像_1.jpg

2. 複数のサーバーがルーター(アプリ)を共有

理想図_2.jpg

広めたい考え方

上の理想像を叶える上で、次のようなことを大事にしたいです。

1. アプリを主語に

Express上ではルーターですが、アプリケーションの個別設定が主語になるような構成にします。

2. サーバーは環境でしかない

サーバーにはメインの処理は書かれていません。
メインの処理が書かれているのはアプリケーションです。
そんなアプリケーションの動作する場所こそがサーバーという環境です。
考え方.jpg

3. アプリが環境を選ぶ

環境に合わせてアプリを作るのではなく、アプリに合わせて環境を選べるように作ります。

Expressミドルウェア

Expressアプリの開発でミドルウェア単位での開発が合っていると考える理由を挙げていきます。

1. Expressの思想と合っている

Expressは「基礎的な Web アプリケーション機能をシンプルな階層」で表現できるように作られています。
ミドルウェアはこの1つ1つの階層を表現するために作られ、使われます。

そして、Expressミドルウェアの使用にあるように、「Express アプリケーションは基本的に一連のミドルウェア関数呼び出し」です。
Expressでは、 リクエストレスポンスサイクル を進めていくことで、アプリケーションの処理が実行されます。その リクエストレスポンスサイクル 内でのアプリケーションの処理が、 一連のミドルウェア関数呼び出し になります。
そのため、Expressアプリケーションの処理単位はミドルウェアで表現するのが、Expressの思想と合っていると考えています。

2. requestオブジェクトとresponseオブジェクトを共有している

ミドルウェア単位での開発を行うことで、クローズドな環境で開発を行うことができます。
その一方で、全ての requestオブジェクトresponseオブジェクト で同じプロパティを参照します。
その後の処理に引き継ぎたい内容は responseオブジェクト へ送り、それ以外はクローズドな環境で安全に開発のようなことができます。

3. ミドルウェアの開発者と利用者で共通認識を持ちやすい

開発者は、Expressのミドルウェアという範囲内で開発を行います。
利用者は、Expressのミドルウェアであることは理解して使います。
互いにExpressミドルウェアであるというルールの中にいるのは、静的型付けが主流となっている流れに合っていると思います。

開発

開発は TypeScript で行いました。
実処理コードは、サーバー部・ルーター部ともに /src/ 内にあります。
VSCodeでの設定ファイルは、walk8243/qiita-express-middlewareにあります。

サーバー部

実際に開発したコードは、 walk8243/express-middleware-server にあります。

ポイント

  • server.ts でクラスター化の設定
  • app.ts でアプリケーションの基本設定
  • router.ts でアプリ側のルーターを指定
  • npm-scriptspostinstall にビルドを設定
  • package.jsonbin で起動スクリプトを設定
  • クラスター数ポート番号ルーターの場所 をアプリ側から設定できる
    • 環境変数 から設定できる
    • package.jsonconfig から設定できる(環境変数で指定されていない場合)

ルーター部(アプリケーション)

実際に開発したコードは、 walk8243/express-middleware-router にあります。

ポイント

まとめ

この投稿のまとめは以下になります。

  • アプリを主語に
  • サーバー部は後付け

まとめ.gif

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

JavaScriptで自然順ソート

「JavaScriptで自然順ソート」と検索したときに、下のサイトが検索として出てきたのだが、ライブラリとしてはどうも使いにくかった。
https://gist.github.com/think49/660141

mozillaでもsortの比較関数の与え方はあるが、自然順ソートの例があるわけではない。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

日本語で検索してもあまり出てこなかったけれど、ライブラリ自体は見つけたので使い方を日本語で書いておく。
https://github.com/bubkoo/natsort

<script src="https://cdn.jsdelivr.net/npm/natsort@latest/index.min.js"></script>

node.js用のnpmもある

https://www.npmjs.com/package/natsort

nodeの場合はrequireをするがその他は同じ使い方になる。

var natsort = require('natsort');

数字と文字列が混ざっている一次元配列

数字と文字列が混ざっていても、整列される。sort()だけでは、変な並びになる。

var array=['10', 9, 2, '1', '4']
array.sort(natsort())
//["1", 2, "4", 9, "10"]
array.sort()
//["1", "10", 2, "4", 9]

数字だけの一次元配列

sort()だけでも数字の大きさを比較するのかと思ったが、辞書順に並んだ。

var array=[10, 9, 2, 1, 4]
array.sort(natsort())
// [1, 2, 4, 9, 10]
array.sort()
//[1, 10, 2, 4, 9]

多次元配列のソート

単純に自然順ソートをすると一列目の内容で整列される。列番号を指定して整列する場合も書いている。

var multi_array=[
[10,6,8],
[2,11,15],
[4,12,13],
];
multi_array.sort(natsort())
//[2, 11, 15]
//[4, 12, 13]
//[10, 6, 8]

multi_array.sort()
//[10, 6, 8]
//[2, 11, 15]
//[4, 12, 13]

var sorter = natsort();
multi_array.sort(function(a, b) {
  return sorter(a[1], b[1]);
});
//[10, 6, 8]
//[2, 11, 15]
//[4, 12, 13]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LINE BOT + ObnizでIoTアルペジエーターを作ってみた

これはなに?

Botに対して、LINEメッセージでChord名を送信すると、そのChordの構成音を返してくれます。
同時に、そのChordをObnizがアルペジオで演奏してくれます。

動作の流れ

  1. ユーザはChord名を送信する(ここではC Minor・Seventh) image.png
  2. BotはChordの構成音を返す image.png
  3. ObnizがChordをアルペジオする image.png

動作の様子

構成図

image.png

使用したもの

ソースコード

  • node.jsでローカル起動し、ngrokでトンネリングします。
  • LINE BotのWebhook URLにngrokのURLを指定してください。
  • LINE BotのToken, SecretおよびObniz IDはjsファイルと同じ階層に置いたsecret.jsファイルに保持しています。
server.js
'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');
const fs = require('fs');
const { promisify } = require('util');
const Obniz = require('obniz');
const PORT = 3000;
const SECRET_PATH = "secret.json";

const keyHzMapping = {
  'C': 261.626,
  'C#': 277.183,
  'Db': 277.183,
  'D': 293.665,
  'D#': 311.127,
  'Eb': 311.127,
  'E': 329.628,
  'F': 349.228,
  'F#': 369.994,
  'Gb': 369.994,
  'G': 391.995,
  'G#': 415.305,
  'Ab': 415.305,
  'A': 440,
  'A#': 466.164,
  'Bb': 466.164,
  'B': 493.883
};

async function main() {
  const { channelSecret, channelAccessToken, obnizId } = JSON.parse(await promisify(fs.readFile)(SECRET_PATH, 'utf-8'));
  const config = {
    channelSecret: channelSecret,
    channelAccessToken: channelAccessToken
  };
  let speaker;
  const obniz = new Obniz(obnizId);
  obniz.onconnect = async function () {
    speaker = obniz.wired("Speaker", { signal: 0, gnd: 1 })
    const id = setInterval(() => {
      const tones = queue.shift();
      if (tones) {
        playArpeggio(tones);
      }
    }, 3200);
  }
  const queue = [];
  async function playArpeggio(tones) {
    let hrzs = tones.map(tone => keyHzMapping[tone]);
    const duration = 600 / hrzs.length;
    hrzs.sort();
    await playSpeaker(hrzs[hrzs.length - 1], duration);
    for (let i = 0; i < 5; i++) {
      for (let hrz of hrzs) {
        await playSpeaker(hrz, duration);
      }
    }
    speaker.stop();
  }
  async function queueArpeggio(tones) {
    queue.push(tones);
  }
  async function playSpeaker(hertz, duration) {
    speaker.play(hertz);
    await obniz.wait(duration);
  }

  const app = express();
  app.get('/', (req, res) => res.send('Hello LINE BOT!'));
  app.post('/webhook', line.middleware(config), (req, res) => {
    console.log(req.body.events);
    // 接続確認用
    if (req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff') {
      res.send('Hello LINE BOT!(POST)');
      console.log('疎通確認用');
      return;
    }
    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
  });

  const client = new line.Client(config);
  const waitingMessage = "調べています…";
  const chordDoesNotExistMessage = "そのコードは存在しません";
  async function handleEvent(event) {
    if (event.type !== 'message' || event.message.type !== 'text') {
      return Promise.resolve(null);
    }
    // 非同期処理に入る前にプッシュメッセージ
    pushMessage(event.source.userId, waitingMessage);
    const tones = await getComposition(event.message.text);
    if (tones) {
      queueArpeggio(tones.split(','));
    }
    return client.replyMessage(event.replyToken, {
      type: 'text',
      text: tones || chordDoesNotExistMessage
    });
  }

  const pushMessage = async (userId, message) => {
    await client.pushMessage(userId, {
      type: 'text',
      text: message,
    });
  }

  const chordApiUrl = 'https://api.uberchord.com/v1/chords/';
  const getComposition = async (chordName) => {
    const res = await axios.get(chordApiUrl + encodeURIComponent(chordName));
    return res.data.length ? res.data[0].tones : "";
  }

  app.listen(PORT);
  console.log(`Server running at ${PORT}`);
}

main();

工夫したところ

Chordが鳴っている最中にLINEで新たなChordを送信しても、割り込みが起きないようにしています。前の音が最後まで鳴ってから、次の音が鳴り始めます。
キューを用意してあり、送信されたChordは構成音に分解後、エンキューされます。
スピーカーを操作するfunctionは、3200msに1度Chordの構成音をデキューします。次の音を取り出すまでの3200msの間、そのChordをアルペジオし続けます。
これにより、Chordが途切れないようメッセージを送信し続けることで、曲を演奏しているかのように音を再生し続けることが可能になります。

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