Quantcast
Channel: galife
Viewing all 392 articles
Browse latest View live

ESLint ルール 一覧 (日本語)

$
0
0

ESLint ルール を一通り日本語訳して一覧化してみました。 記載はできるだけ「デフォルトがどのような状態か」という内容で記載しています。

なお、最新情報および詳細情報は ESLint - Rulesを参照してください。

エラーになりやすい

no-cond-assign条件処理内で代入を行わないこと
no-consoleconsole の使用をしないこと
no-constant-condition条件判定文が定数のみの判定になっていないこと
no-control-regex正規表現内で制御文字を使用しないこと
no-debuggerdebugger の使用しないこと
no-dupe-argsfunction 内で重複して変数宣言を行わないこと
no-dupe-keysオブジェクト内で重複するキーを指定しないこと
no-duplicate-case重複するcase文を作らないこと
no-empty-character-class正規表現内で空文字クラスの使用しないこと
no-empty空ブロックを作らないこと
no-ex-assigncatch文内で例外変数に再代入を行わないこと
no-extra-boolean-cast不要なboolean型キャストを行わないこと
no-extra-parens不要な括弧を記述しないこと
no-extra-semi不要なセミコロンの記述しないこと
no-func-assignfunction を再定義しないこと
no-inner-declarationsネストしたブロック内で変数および関数の宣言を行わないこと
no-invalid-regexpRegExpコンストラクタに不正な正規表現文字列を指定しないこと
no-irregular-whitespace規格外の空白文字の使用しないこと
no-obj-calls組み込みオブジェクトを関数のように使用しないこと
no-prototype-builtinsオブジェクトインスタンスから Object.prototype の組み込み関数呼び出しを行わないこと
no-regex-spaces正規表現において複数スペースを使用しないこと
no-sparse-arrays配列においてまばらな値設定を行わないこと
no-template-curly-in-string通常文字列中においてテンプレートリテラルを使用しないこと
no-unexpected-multiline紛らわしい複数行コード記述を行わないこと
no-unreachable到達不可能なコードを記述しないこと
no-unsafe-finallyfinally句内において制御文を使用しないこと
no-unsafe-negation関係演算子の左側において否定演算子の使用を行わないこと
use-isnanNaNチェックにはisNaN()を利用すること
valid-jsdocコメントはJSDoc形式で記述すること
valid-typeoftypeof利用時は正しい型名文字列と比較すること

ベストプラクティス

accessor-pairsgetter とsetter はペアで作成すること
array-callback-return配列の組み込み関数のコールバックでは必ずreturnを記述すること
block-scoped-var宣言した変数は宣言したブロックスコープ内で利用すること
class-methods-use-thisクラスメソッド中においてthisが使われていること
complexity循環的複雑度が規定値(デフォルト20)以内であること
consistent-returnreturnする型は常に一致させるか全く使わないこと
curly波括弧の省略を行わないこと
default-caseswitch文では必ずdefaultケースを作成すること
dot-location改行前にドットを記述すること
dot-notationオブジェクトプロパティのアクセスは角括弧ではなくドットでアクセスすること
eqeqeq型を含めた比較演算子を利用すること
guard-for-infor in ループではループしているオブジェクト自身のプロパティかどうかのチェックを行うこと
no-alertalert, prompt, confirm を使わないこと
no-callerarguments.caller, arguments.callee を使用しないこと
no-case-declarationscase, default 節の中で 語彙的宣言(let, const, function, class) を行わないこと
no-div-regex正規表現内の構文で使われる文字列はスラッシュでエスケープすること
no-else-returnreturn を含む if文中 に不要な else 文を作らないこと
no-empty-function空関数定義を行わないこと
no-empty-pattern値の入らない分割代入を記述しないこと
no-eq-nullnull または undefined と比較するときは厳密な比較演算子を利用すること
no-evaleval を使わないこと
no-extend-nativeネイティブオブジェクトを拡張しないこと
no-extra-bind不必要な bind() を行わないこと (アロー関数への適用、thisを含まない関数への適用を禁止)
no-extra-label不必要なラベル記述を行わないこと
no-fallthroughswitch文で下のcaseに流れ落ちるコードを作らないこと
no-floating-decimal少数を利用するときは数字であることが分かるように表記すること
no-global-assignグローバルオブジェクトや読み取り専用オブジェクトに対する代入を行わないこと
no-implicit-coercion型変換のショートハンドは利用しないこと
no-implicit-globalsグローバルスコープで関数や変数の定義を行わないこと
no-implied-eval暗黙的 eval() を使用しないこと (setTimeout, setInterval, execScriptにコード文字列を渡さないこと)
no-invalid-thisクラスまたはクラスのように扱うオブジェクトの外側で this を使用しないこと
no-iterator_iterator_拡張メソッドは利用しないこと
no-labelsラベル文は使用しないこと
no-lone-blocks不必要なコードブロックを作らないこと
no-loop-funcループ内においてブロック変数を利用する関数定義を行わないこと
no-magic-numbersマジックナンバーを使用しないこと
no-multi-spacesインデント以外で複数スペースを使用しないこと
no-multi-str複数行文字列定義を行わないこと
no-new-funcFunction のコンストラクタに文字列を渡さないこと
no-new-wrappersプリミティブオブジェクトのラッパークラス(String, Number, Boolean)をインスタンス化しないこと
no-new変数代入を行わないインスタンス生成をしないこと
no-octal-escape文字列において8進数エスケープは行わないこと
no-octal8進数表記を使用しないこと
no-param-reassign関数の引数に対して関数内で値を再定義(代入)しないこと
no-proto__proto__ を使用しないこと
no-redeclare同じ変数名を再定義しないこと
no-restricted-properties指定したオブジェクトのプロパティを使用しないこと
no-return-assignreturn 宣言時に変数代入を行わないこと
no-return-awaitasync function で return await の記載はしないこと
no-script-urlJavaScript URL (javascript:で始まるコード) を使用しないこと
no-self-assign自分自身に再代入しないこと
no-self-compare自分自身との比較は行わないこと
no-sequencesカンマ演算子を使用しないこと
no-throw-literalthrowでリテラルを返さないこと
no-unmodified-loop-conditionループ変数はブロック内で変更を行うこと
no-unused-expressions意味のない式は記述しないこと
no-unused-labelsどこからも使われないラベルを作らないこと
no-useless-call無駄な call(), apply() を行わないこと
no-useless-concat無駄な文字列結合は行わないこと
no-useless-escape無駄なエスケープは行わないこと
no-useless-return無駄な return は書かないこと
no-voidundefined を得るために void を使わないこと
no-warning-comments特定文字列(todo, fixme, xxx)を含むコメントは除去されていること
no-withwith 文は使わないこと
radixparseInt() を使用するときは基数を指定すること
require-await非同期関数では async 文 が含まれていること
vars-on-top変数定義は該当スコープの先頭でまとめて行うこと
wrap-iifeIIFE (immediately invoked function expression) を使用するときは丸括弧で囲むこと
yodaヨーダ記法で書かないこと

strictモード

strictuse strict を記述すること

変数

init-declarations変数定義時に初期化を行うこと
no-catch-shadowcatch句の外側で定義された変数をcatch句のエラー変数にしないこと
no-delete-var変数宣言したものは delete しないこと
no-label-var同一スコープ内において変数とラベルに同じ名使わないこと
no-restricted-globalsevet など特定のグローバル変数に値代入を行わないこと
no-shadow-restricted-names予約済みグローバル変数(NaN, Infinity, undefined など)に代入を行わないこと
no-shadow外側のスコープで定義された変数を隠すような変数定義を行わないこと
no-undef-init変数定義時に undefined で初期を行わないこと
no-undef未定義の変数は利用しないこと
no-undefinedundefined を使用しないこと
no-unused-vars未使用の定義(変数、関数)は削除すること
no-use-before-define定義より前に使用しないこと

Node.js と CommonJS

callback-returncallback を利用する場合は return するコードを含めること
global-requirerequire() はそのモジュールの一番最初に記述すること
handle-callback-errcallback パターンを記述する際、エラー処理を記載すること
no-mixed-requiresrequire() で定義する変数とその他の変数は分けて宣言すること
no-new-requirerequire() したオブジェクトを直接 new しないこと
no-path-concat__dirname, __filename を直接文字列結合して利用しないこと
no-process-envprocess.env は使わないこと
no-process-exitprocess.exit() は使用しないこと
no-restricted-modules指定したモジュールを使用しないこと
no-sync非同期メソッドがある場合、同期メソッドは使用しないこと

表記法

array-bracket-spacing配列を示す角括弧の直ぐ内側に空白を作らないこと
block-spacing単一行ブロックを使うときはブロックのすぐ内側に空白を入れること
brace-styleプレーススタイルは One true brace style (1tbs) で記述すること
camelcase変数名はキャメルケースで記述すること
capitalized-commentsコメントの先頭は大文字で記載すること
comma-dangleオブジェクトまたは配列の最後の要素の後にカンマを書かないこと
comma-spacingカンマ前には空白を入れず、カンマ後には空白を入れること
comma-style配列、オブジェクト、変数宣言においてカンマは末尾に付けること
computed-property-spacing算出プロパティの角括弧内に空白を入れないこと
consistent-thisthis を置き換えて使うときは that に置き換えて使うこと
eol-lastファイルの最後には空行を入れること
func-call-spacing関数を呼び出す際、関数名と丸括弧の間に空白を入れないこと
func-name-matching関数を使用する際は定義済みの名前を使用すること
func-names無名関数は作らないこと
func-style関数を定義する際は変数代入による定義(function expression)を行うこと
id-blacklistブラックリストとして定義された名前は利用しないこと
id-length変数、プロパティ、関数の名称は2文字以上で定義すること
id-match指定された正規表現に適合する変数名を定義すること
indentインデントは4つの空白文字で行うこと
jsx-quotesjsx中の属性はダブルクゥオートで括って記述すること
key-spacingオブジェクトリテラルにおいてキーとバリューの間に空白を入れること
keyword-spacingJavaScript キーワード の前後には空白を入れること
line-comment-positionラインコメントはコードの真上に記述すること
linebreak-style改行は LF (line feed) になっていること
lines-around-commentブロックコメントの前には空行を入れること
lines-around-directiveディレクティブ宣言の後には空行を入れること
max-depthネストできるブロック深さは4つ以内とすること
max-len1行80文字以内とすること
max-lines1ファイル300行以内とすること
max-nested-callbacksネストできるコールバックは10段以内とすること
max-params引数のパラメータ数は3個以内とすること
max-statements-per-line1行1構文とすること
max-statements1関数は10構文以内で記述すること
multiline-ternary3項演算子は複数行に分けて記述すること
new-capnew するオブジェクトは大文字始まりのオブジェクトとなっていること
new-parensnew で引数なしオブジェクト生成するときは引数なしでも丸括弧を記述すること
newline-after-var変数宣言の直後には空行を入れること
newline-before-returnreturn の前には空行を入れること
newline-per-chained-callメソッドチェーンを利用する場合、チェーン毎に改行すること
no-array-constructorArray クラスを使った配列値を指定した初期化を行わないこと
no-bitwiseビット演算子は利用しないこと
no-continuecontinue は使わないこと
no-inline-commentsコードと同じ行にコメントを記述しないこと
no-lonely-ifelse ブロック内に単独の if文 を記述しないこと
no-mixed-operators複数の演算子グループを括弧なしで混在して記述しないこと
no-mixed-spaces-and-tabsインデントにタブと空白を混在させないこと
no-multiple-empty-lines3行以上の連続した空行を作らないこと
no-negated-conditionelse句を持つ if文 は 否定構文 で記述しないこと
no-nested-ternary3項演算子をネストさせないこと
no-new-objectObjectクラスのコンストラクタは利用しないこと
no-plusplus単項演算子(++, --) は利用しないこと
no-restricted-syntaxfunction宣言やwith句など指定された記述方法は行わないこと
no-tabsタブを使わないこと
no-ternary3項演算子を使わないこと
no-trailing-spaces行末に不要な空白を残さないこと
no-underscore-dangleアンダースコアで始まる変数を作らないこと
no-unneeded-ternary変数初期化で無駄な3項演算子を使わないこと
no-whitespace-before-propertyドット演算子または角括弧を使って1行内でオブジェクトアクセスするときは空白を入れないこと
object-curly-newlineオブジェクト定義を行う波括弧は必ず改行して記述すること
object-curly-spacing波括弧を使って1行でオブジェクト定義する際、波括弧の前後に空白を入れないこと
object-property-newline波括弧を使ってオブジェクト定義をする際、プロパティ毎に改行すること
one-var-declaration-per-line定数定義は定数毎に改行すること
one-varブロックスコープ内では1度の宣言で必要な変数宣言を行うこと
operator-assignmentショートハンドが使える場合はショートハンドで記載すること
operator-linebreak式を記述する際、演算子は行末または行頭のどちらかに統一して記述すること
padded-blocksブロックスコープの最初と最後に空行を入れること
quote-propsオブジェクトのリテラルプロパティは常にクォートで囲むこと
quotes文字列はダブルクォートで記述すること
require-jsdocJSDocコメントを記述すること
semi-spacingセミコロンの前と後に空白を入れること
semi文末にはセミコロンを記述すること
sort-keysオブジェクトプロパティはアルファベット順に並べて記述すること
sort-vars同一ブロック内の変数宣言はアルファベット順に並べて記述すること
space-before-blocksブロック開始前には空白を入れること
space-before-function-paren関数宣言の丸括弧前には空白を入れること
space-in-parens丸括弧の内側には空白を入れるまたは入れない
space-infix-ops中置演算子の前後には空白を入れること
space-unary-ops文字列の単項演算子後には空白を入れ、記号の単項演算子の前または後ろには空白を入れないこと
spaced-commentコメントの先頭には空白を入れること
unicode-bomユニコードのBOMは入れないこと
wrap-regexリテラルの正規表現は丸括弧で囲って使用すること

ECMAScript 6

arrow-body-styleアロー関数の波括弧は必要に応じて記載すること
arrow-parensアロー関数の引数部分には丸括弧を記述すること
arrow-spacingアロー関数の矢印前後には空白を入れること
constructor-super継承しているクラスのコンストラクタではsuper()を呼び出しており、継承していないクラスのコンストラクタではsuper()を呼び出していないこと
generator-star-spacingジェネレータ関数を示す * の前には空白を入れ、後には空白を入れないこと
no-class-assign定義したクラス名を変数として再利用する場合、同一行にコメントすること
no-confusing-arrow1行でアロー関数を定義する時は関数ブロックが分かるよう丸括弧で括ること
no-const-assignconst 定義された変数の再定義を行わないこと
no-dupe-class-membersクラスメンバーの名前は重複させないこと
no-duplicate-imports単一モジュールから import する際、必要な import は1行で書ききること
no-new-symbolSymbolオブジェクトを new で生成しないこと
no-restricted-imports指定されたモジュールの import は行わないこと
no-this-before-supersuper() を呼び出す前に this または super を使わないこと
no-useless-computed-key不要な算術プロパティ表記は行わないこと
no-useless-constructor何もしないコンストラクタ または 親クラスのコンストラクタを呼び出すだけのコンストラクタ は記述しないこと
no-useless-renameimport, export, 分割代入において変数名を変更しないこと
no-varvar (メソットスコープ変数) は使わず let または const (ブロックスコープ変数) を使うこと
object-shorthandオブジェクト定義時にショートハンドが利用できる場合は利用すること
prefer-arrow-callbackコールバックにはアロー関数を利用すること
prefer-const再代入を行わない変数は const を利用すること
prefer-numeric-literalsparseInt() を文字列と基数を指定して利用しないこと
prefer-rest-paramsarguments の代わりに 残余引数 を利用すること
prefer-spreadFunction.prototype.apply() の代わりに スプレッド演算子 が利用すること
prefer-template文字列結合の代わりにテンプレートリテラルが利用すること
require-yieldジェネレータ関数には yield を含めること
rest-spread-spacing残余引数とスプレッド演算子を記述するときはその演算子と表記の間に空白を入れないこと
sort-importsimport はアルファベット順にソートすること
symbol-descriptionSymbolを生成するときは名称指定すること
template-curly-spacingテンプレートリテラルで利用する波括弧の内側には空白を入れないこと
yield-star-spacingyield* の * の前には空白を入れず後には空白を入れること

参考記事


Node.js + Express で WebAPI を作る

$
0
0

Node.js + Express で WebAPI を作成してみます。 最近だと モバイルアプリ や SPA (Single Page Appliction) などで利用されるような機能を実装するイメージです。 あまり難しいことはせず、単純な GETPOSTの例でまとめました。

概要

画面を返さず JSON を返却する WebAPI を作成してみます。 今回は 単純な GETPOSTを行うサンプルです。 具体的なパスは以下のようなものを作る予定です。

  • GET: /api/user/:id
  • POST: /api/user

上記の POST URLにリクエストしたときの動きのイメージは以下のようなものです。

実装

今回は以下のミドルウェアを利用するのであらかじめ npm install <PACKAGE> --saveでインストールしておきます。

  • express
  • body-parser

app.js

それでは実装に…といっても単純なものなので、何はともあれ実装の全体像は以下に載せます。 ハイライト下部分について、詳しくは後ほど説明していきます。


var express = require("express");
var bodyParser = require("body-parser");

// express application
var app = express();

// add body-parser
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// set routing.
app.use("/api/", (function () {
var router = express.Router();

// GET: /api/user/:id
router.get("/user/:id", (request, response) => {
var user = {
id: request.params.id,
name: "tanaka",
role: "group1"
}
response.json(user);
});

// POST: /api/user
router.post("/user", (request, response) => {
var body = request.body;
if (!body.name || !body.role) {
return response.json(false);
}
var user = {
id: 0,
name: body.name,
role: body.role
};
response.json(true);
});

return router;
})());

// start web applicaiton.
app.listen(3000);

GET: /api/user/:id

L.16-23

コロン「:」 を利用することで URL 中 にパラメータを埋め込むことができます。 埋め込まれたパラメータの取り出しは request.params.パラメータ名でできます。 このパラメータ指定にはいくつかオプション設定(?, +, *, regexp)が使えるのでそれぞれサンプルを以下に載せます。

ルート・パス設定リクエスト例
/api/user/:id
/api/user-
/api/user/-
/api/user/123id = "123"
/api/user/abcid = "abc"
/api/user/abc/def-
/api/user/:id?
/api/userid = undefined
/api/user/id = undefined
/api/user/123id = "123"
/api/user/abcid = "abc"
/api/user/abc/def-
/api/user/:id+
/api/user-
/api/user/-
/api/user/123id = "3"
/api/user/abcid = "c"
/api/user/abc/def-
/api/user/:id*
/api/user-
/api/user/-
/api/user/123id = "123"
/api/user/abcid = "abc"
/api/user/abc/defid = "abc"
/api/user/:id(\\d+)
/api/user-
/api/user/-
/api/user/123id = "123"
/api/user/abc-
/api/user/abc/def-

POST: /api/user

L.8-9 および L.26-37

POSTの場合、リクエストボディー に JSON を入れて情報を伝達するのが通常かと思います。 リクエストボディーに埋め込まれた JSON を取り出す場合 body-parserを使うと便利です。 body-parserミドルウェアの設定および登録は L8-9 に記載の通りです。 この登録をしておけば、body-parserにはリクエストボディーに埋め込まれた文字列を JSONオブジェクト に変換して request.bodyへセットしてくれます。

テスト

GETPOSTの両方をテストしてみます。 作成した Webアプリケーション を実行した状態で localhost:3000に向かってリクエストを投げてみます。

リクエスト生成するツールはいろいろありますが…私は Fiddler の Composer をよく使うので、 ここでも Fiddler を利用してリクエストを投げています。 Fiddler を利用しているのでリクエストサンプルの User-Agent は Fiddler となてっていますがテストには関係ないので無視で良いです。

以下は Fiddler の Composer でリクエストを投げている画面のキャプチャです。

GET: /api/user/:id

リクエスト


GET http://localhost:3000/api/user/123 HTTP/1.1
User-Agent: Fiddler
Host: localhost:3000
Content-Length: 0

レスポンス


HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 44
ETag: W/"2c-yyXPiEPxROPhaVRftHE4bQ"
Date: Sun, 12 Feb 2017 08:48:35 GMT
Connection: keep-alive

{"id":"123","name":"tanaka","role":"group1"}

POST: /api/user

リクエスト


POST http://localhost:3000/api/user HTTP/1.1
User-Agent: Fiddler
Host: localhost:3000
Content-Length: 35
Content-Type: application/json

{"name":"tanaka", "role": "group1"}

レスポンス


HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 4
ETag: W/"4-sya1BisvDmkEaBBxdTTLCQ"
Date: Sun, 12 Feb 2017 08:49:12 GMT
Connection: keep-alive

true

参考記事

Node.js + Express で ASP.NET ライクな ルーティング を行う ミドルウェア

$
0
0

Node.js + Express で Webアプリケーション開発 を行うとき困ったのがルーティング。 そこで ASP.NET MVC のような実装ができる Express 向け ミドルウェア "express-junction"を作成しました。

概要

"express-junction"は Express上 で動くミドルウェアです。

「開発時に一定ルール」を設けることで URLルーティング を /コントローラー名/アクション名へ自動で割り振られるようにできます。

「開発時の一定ルール」とは具体的に以下の2点です。

  • Controller の配置場所は "/controllers" フォルダ配下
  • View テンプレート の配置場所は "/views/{コントローラー名}" フォルダ配下

詳しい実装はこの後の 使い方で解説します。

インストール


npm install express-junction

依存関係に "express"が入っているので、 上記をいきなり実行することで "express"含めてまとめてインストールできます。

使い方

簡単な実装例を以下で見ていきます。

ディレクトリ、ファイル

ディレクトリ構成は以下の通りです。 作成するのは 3ファイル のみです。 ディレクトリ構成とファイル保存場所が決まっている点が要注意です。 コントローラー名、アクション名は自由に設定できます。


%PROJECT_ROOT%
│ app.js
│ package.json

├─controllers
│ home.js

├─node_modules
│ ├─express
│ ├─express-junction
│ └─express-session

└─views
└─home
index.ejs

/app.js


var express = require("express");
var session = require("express-session");
var junction = require("express-junction");

// Expressインスタンス作成
var app = express();

// テンプレートエンジンの設定
app.set("views", "./views");
app.set("view engine", "ejs");

// ミドルウエアの設定
app.use("/public", express.static("public"));
app.use(session({
secret: "YOUR SECRET KEY IS HERE",
resave: false,
saveUninitialized: true,
name: "sid"
}));
app.use(junction());
app.use("/", junction.router());

// サーバー開始
app.listen(3000);

"express-junction"は "express-session"を使用していることを前提としているので、"express-session"ミドルウェアよりあとに設定します。

/controllers/home.js


var {Controller, Action, allow, deny} = require("express-junction");

// Controller の生成
var controller = new Controller("home");

// GET: /home/index
controller.add(new Action(
"GET",
"index",
function (request, response) {
return this.view({ title: "/home/index" });
}
));

// Controller のエクスポート
module.exports = controller;

コントローラー、アクションともにインスタンス生成時に各種設定を行います。

Controllerインスタンス生成時の引数は以下の通りです。

name
stringコントローラー名を指定します。 URLの一部になります。

Actionインスタンス生成時の引数は以下の通りです。

method
string HTTPリクエストメソッドを指定します。
name
stringアクション名を指定します。 URLの一部になります。
[rules]
Validator[]オプションでアクセス制御を指定できます。 指定しなければすべてのユーザーに対してアクセス許可になります。
process
function(Request, Response)アクションが呼ばれたときに実行する処理を記述します。 Viewテンプレートでレスポンスする場合、this.view()を使用します。 JSONでレスポンスする場合、this.json()を使用します。 リダイレクトは this.redirect()です。

/views/home/index.ejs


<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<meta charset="utf-8">
</head>
<body>
<h1>Hello World</h1>
<p>This is a sample page.</p>
</body>
</html>

何の変哲もない ejs ファイル…。 コントローラーで this.view()が実行された際に呼び出されます。

公開鍵基盤 (PKI) 構築 に関する まとめ

$
0
0

最近の Webサービス の流れとして HTTPS対応 が当たり前になってきたので、SSL/TLS化 を考えようと思ったのですが… 公開鍵関連はよく分からなくなるので、概要だけを本記事まとめました。

この記事では 公開鍵基盤 において環境構築に関する全体像をざっくりとつかみます。 その上で、具体的な手順は個別に関連記事へのリンクを掲載しているのでそちらを参照すると理解しやすくなっていると思います。

証明書ファイル形式

証明書ファイルは「 [規格] × [エンコード] 」の組み合わせで決まるようです。 以下に載せる拡張子は「エンコード形式」ベースで記載しているので、中身(規格)については考慮していません。 エンコード方法の違いなので、基本は相互に変換可能です。

拡張子説明
.derDER (Distinguished Encoding Rules) で符号化された証明書。 ASN.1 で定義された BER (Basic Encoding Rules) と互換するバイナリ形式。
.pemBase64 で符号化された証明書。 -----BEGIN CERTIFICATE----------END CERTIFICATE-----で囲まれたテキスト形式。
.cerWindows において 証明書を示すコンテナ としての拡張子。 実体として中身は pem か der 。

CA構築 と ルート証明書登録

CAサーバー構築からクライアントにルート証明書登録するまでの概略です。

  1. CAサーバー構築

    • CAサーバー構築時には以下のような公開鍵と秘密鍵のペアを作成します。

      公開鍵
      ca_cert.pem
      秘密鍵
      ca_key.pem
    • 秘密鍵作成時、 CN には FQDN を指定します。

  2. クライアントにルート証明書登録

    • 公開鍵 ca_cert.pem の拡張子を書き換えて ca_cert.cer にして登録します。

関連記事

サーバー証明書要求作成 と 署名

CAサーバー構築が完了している前提で、SSL/TLS化 (HTTPS化) したいサーバーで 証明書要求(CSR) を作成するところから署名された公開鍵を登録するところまでの概略です。

  1. サーバー側で証明書要求 (CSR) 作成
    • SSL/TLS化したいサーバーで以下のような証明書要求(CSR) と 秘密鍵を作成します。

      証明書要求 (CSR)
      server_csr.pem
      秘密鍵
      server_key.pem
  2. CAサーバー側で証明書要求(CSR) に署名
    • 受領した証明書要求(CSR)に署名して公開鍵を作成します。
      公開鍵
      server_cert.pem
  3. サーバー側に公開鍵登録
    • 受領した署名済み公開鍵をWebサーバーに登録します。

関連記事

参考記事

IIS に 登録した 署名済みサーバー証明書 を バックアップ / リストア する方法

$
0
0

Windowsサーバー で稼働中のWebアプリケーションを別の Windowsサーバー へ移す場合、証明書も移す必要があります。 ここでは証明書をバックアップして復元する手順をまとめます。

署名済みサーバー証明書のバックアップ

以下に載せる手順で行うバックアップでは「署名済み公開鍵」と「秘密鍵」の両方を含んだファイルを出力できます。

  1. IIS の管理コンソール を開く

  2. 「サーバー証明書」をダブルクリックして開く

  3. バックアップしたい「署名済みサーバー証明書」を選択

  4. 操作にある「エクスポート」を選択

  5. 出力先、パスワードを設定して「署名済み公開鍵」と「秘密鍵」をバックアップ

署名済みサーバー証明書のリストア

上記バックアップ方法でバックアップされたファイルから証明書の復元を行います。

  1. IIS の 管理コンソール を開く

  2. 「サーバー証明書」をダブルクリックして開く

  3. 操作パネルの「インポート」を選択

  4. バックアップしていたファイル、パスワードを指定して「OK」を選択

ファイル形式について

IISで管理される署名済みサーバー証明書のバックアップファイルは、「拡張子が pfx であること」と「中身がバイナリ形式」であったことから、「 PKCS#12 に従った DERエンコーディング ファイル」です。 OpenSSL と相互に鍵交換する場合、上記内容に即しているか注意します。

参考記事

Node.js + Express で HTTPS サーバー を 作る

$
0
0

Node.js + Express の環境で HTTPS 通信できるようにする方法を整理しました。 あまり複雑な要素は含めずできるだけシンプルなサンプルコードを掲載します。

HTTPS サーバー 構築

Windows の IIS で自己署名入り証明書 をエクスポートすると PKCS#12 の証明書 (拡張子 pfx のファイル) になっているので、 今回は pfx を指定してHTTPSサーバーを構築してみます。

IIS で 自己署名入り証明書 を作成、エクスポートする方法は以下の「自己署名証明書の作成」を参照してください。


var express = require("express");
var fs = require("fs");
var https = require("https");

var app = express();

var options = {
pfx: fs.readFileSync("./private/localhost.pfx"),
passphrase: "Passw0rd"
};

app.get("/", (request, response)=>{
response.writeHead(200);
response.end("Hello World.");
});

https.createServer(options, app).listen(443);

httpsサーバー構築する場合、署名済み公開鍵と秘密鍵をオプションで設定してあげる必要があります。 上記のコードは 署名済み公開鍵と秘密鍵 の両方を含む pfx ファイル を指定しています。

http で良ければ app.listen()とできるところですが…、 https の場合、 https.createServer()に 証明書情報 と Express のインスタンス を渡すことで実現します。 ポート番号に 443を指定しておくことも注意です。

上記コードを実行して https://localhost/へ接続すると "Hello World."の応答が表示されることが確認できます。

HTTP と HTTPS を共存させた サーバー 構築

上記の HTTPSサーバー構築 に加えて http 用のサーバーを同時に生成、実行することで共存させることができます。 追加するコードはハイライトしている部分です。


var express = require("express");
var fs = require("fs");
var http = require("http");
var https = require("https");

var app = express();

var options = {
pfx: fs.readFileSync("./private/localhost.pfx"),
passphrase: "Passw0rd"
};

app.get("/", (request, response)=>{
response.writeHead(200);
response.end("Hello World.");
});

http.createServer(app).listen(80);
https.createServer(options, app).listen(443);

[おまけ] 自己署名証明書の作成

OpenSSL を利用して自己署名証明書作成はよく見かけるので… ここでは IIS を使った Windows の開発環境で簡単に自己署名証明書を作成する方法を見てみます。

  1. IIS の 管理コンソール を開く

  2. 「サーバー証明書」をダブルクリックして開く

  3. 操作ペインの「自己署名入り証明書の作成」を選択

  4. フレンドり名を指定して「OK」を選択

  5. 作成した「自己署名入り証明書」が選択されているので、操作ペインの「エクスポート」を選択

  6. 出力先、パスワードを指定して「OK」を選択

参考記事

Node.js + Express で HTTP から HTTPS へ 自動リダイレクト させる方法

$
0
0

Node.js + Exprss で HTTPSサーバー 構築した際、http でアクセスしてきた通信を https 強制リダイレクトさせる方法を考えました。 といっても難しいことはせずできるだけ単純な実装にしています。

自動リダイレクト の サンプルコード


var express = require("express");
var fs = require("fs");
var http = require("http");
var https = require("https");

var app = express();

var options = {
pfx: fs.readFileSync("./private/localhost.pfx"),
passphrase: "Passw0rd"
};

app.get("/", function (request, response) {
response.writeHead(200);
response.end("Hello World.");
});

http.createServer((express()).all("*", function (request, response) {
response.redirect(`https://${request.hostname}${request.url}`);
})).listen(80);
https.createServer(options, app).listen(443);

httpへのアクセスはすべてリダイレクトしたいので Express の 別インスタンス を準備してすべて https へリダイレクトするよう設定しています。 単純ですが抜けもれなくすべてリダイレクトできるのでこの方法が便利かな、と思います。

Node.js + Express の URLルーティング

$
0
0

Node.js + Express において URLルーティング 、特に名前付きパラメーターをどのように指定するかまとめました。

基本

基本のルーティング指定方法は以下の通りです。

構文


app.METHOD(PATH, CALLBACK);
METHOD
HTTPリクエストメソッドを小文字で指定します。例:get, post, put, delete
PATH
URLルーティングを指定します。
CALLBACK
URLルーティングが一致したときに実行する処理を指定します。

たとえば、ルートパス /へのルーティングを指定する場合、以下のように指定します。


app.get("/", function (request, response) {
// GET: /
response.send("Hello World");
});

名前付き パラメーター

コロン ( : ) のプレフィックスで始まる文字列はパラメータとして認識されます。 パラメータ文字列は次のパスセグメントまでをパラメータ文字列として認識します。 パラメータ文字列として利用可能な文字列はアルファベット大文字小文字、数字、アンダースコア [A-Za-z0-9_]になります。

名前付きパラメーターは request.params.PARAMETER_NAMEで値を取り出すことができます。

ルート定義リクエスト例
/api/user/:id
/api/user-
/api/user/-
/api/user/123id = "123"
/api/user/abcid = "abc"
/api/user/abc/def-

オプション

サフィックスに クエスチョンマーク ?を付けることで、パラメーターがオプションでることを指定できます。

ルート定義リクエスト例
/api/user/:id?
/api/userid = undefined
/api/user/id = undefined
/api/user/123id = "123"
/api/user/abcid = "abc"
/api/user/abc/def-

0 以上

サフィックスに アスタリスク *を付けることで、0 以上のパラメーターと一致させることができます。

ルート定義リクエスト例
/api/user/:id*
/api/user-
/api/user/-
/api/user/123id = "123"
/api/user/abcid = "abc"
/api/user/abc/defid = "abc"

1 以上

サフィックスに プラス +を付けることで、1 以上のパラメーターと一致させることができるハズ…ですが、 実際試してみると動作的にはサフィックスにプラス +があってもなくても通常の動作と変わらない動作でした。

ルート定義リクエスト例
/api/user/:id+
/api/user-
/api/user/-
/api/user/123id = "3"
/api/user/abcid = "c"
/api/user/abc/def-

カスタムマッチ パラメーター

名前付きパラメーターに対して正規表現で条件指定することができます。 以下の例では数値だけを受け付けるカスタムマッチパラメーターを設定しています。

ルート定義リクエスト例
/api/user/:id(\\d+)
/api/user-
/api/user/-
/api/user/123id = "123"
/api/user/abc-
/api/user/abc/def-

名前なし パラメーター

グループマッチさせる正規表現は名前なしパラメーターとして指定できます。 グループマッチ (.*)の指定は アスタリスク *の省略表記も使えます。 無名なため rquest.paramsで値を取り出すことはできません。

ルート定義リクエスト例
/api/user/(\\d+)
/api/user-
/api/user/-
/api/user/123-
/api/user/abc-
/api/user/abc/def-

参考記事


Node.js + Express で 検索画面 を作る

$
0
0

Node.js + Express + MongoDB を使った「検索」機能のサンプルを作ってみます。 今回はサンプルとして「店舗検索」を行うような機能を実装してみます。

概要

店舗検索として、検索条件入力で入力した条件にマッチする店舗一覧が表示されるような機能を作ります。 検索結果画面には検索結果総数、ページネーション(ページング)を実装します。

以下、基本設計…とまではいきませんが、本記事で作ろうとしている機能の概要をまとめます。

画面

「検索条件入力」画面と「検索結果出力」画面の2画面を作成します。 以下には画面ごとに作成する項目もあわせて記載しています。

  • 検索条件入力 (/search/index.ejs)

    • 検索テキスト
    • 検索ボタン
  • 検索結果出力 (/search/result-list.ejs)

    • 検索テキスト
    • 検索ボタン
    • 検索結果総数
    • 検索結果リスト
      • 店舗名
      • 店舗所在地
      • 店舗電話番号
    • ページネーション

データベース

以下のようなドキュメントスキーマを想定します。

database

test
collection

shops
document

shop = {
name: { type: string },
location: { type: string },
tel: { type: string }
}

何はともあれ試すにはデータが必要なので、以下のコードをコピペして MongoDB に追加しておきます。


db.shops.insert({name: "abc shop1", location: "shibuya", tel: "0120-111-1111"});
db.shops.insert({name: "bcd shop2", location: "shinagawa", tel: "0120-111-2222"});
db.shops.insert({name: "cde shop3", location: "shinjuku", tel: "0120-111-3333"});
db.shops.insert({name: "def shop4", location: "ikebukuro", tel: "0120-111-4444"});
db.shops.insert({name: "efg shop5", location: "akihabara", tel: "0120-111-5555"});

実装

それでは、Node.js + Express + MongoDB を使って実装を行っていきます。 作成するファイルおよび実装は以下の通りです。 ファイルごとに実装の内容を見ていきます。

/app.js


var express = require("express");
var bodyParser = require("body-parser");
var MongoClient = require("mongodb").MongoClient;

// expressインスタンス生成
var app = express();

// テンプレートエンジンの設定
app.set("views", "./views");
app.set("view engine", "ejs");

// ミドルウエアの設定
app.use("/public", express.static("public"));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// ルーティング設定
app.use("/shop", (function () {
var router = express.Router();

// GET: /shop/search
router.get("/search", function (request, response) {
var URL = "mongodb://localhost:27017/test";
var MAX_ITEMS_PER_PAGE = 1;
var query = request.query.q;
var page =request.query.pg ? parseInt(request.query.pg) : 1;

// 検索クエリがない場合、初期表示
if (!query) {
return response.render("./search/index.ejs");
}

// 検索クエリがある場合、データベースを検索して結果リストを表示
MongoClient.connect(URL).then((db) => {
return Promise.all([
// 検索総ヒット数
db.collection("shops").find({
name: new RegExp(`.*${query}.*`)
}).count(),
// 現在ページに表示する内容
db.collection("shops").find({
name: new RegExp(`.*${query}.*`)
}).skip((page - 1) * MAX_ITEMS_PER_PAGE).limit(MAX_ITEMS_PER_PAGE).toArray()
]);
}).then((results) => {
// ビューへ渡すデータを整形
var data = {
count: results[0],
list: results[1],
pagination: {
max: Math.ceil(results[0] / MAX_ITEMS_PER_PAGE),
current: page,
isFirst: page === 1,
isLast: page === Math.ceil(results[0] / MAX_ITEMS_PER_PAGE)
},
query: query
};
// ビューを表示
return response.render("./search/result-list.ejs", data);
}).catch((reason) => {
// エラー処理
console.log(reason);
});

return router;
})());

// サーバー起動
app.listen(3000);

メインとなる処理です。 Node.js、Express、MongoDB の基本的な処理についてはここでは割愛します。

L.29-31: 検索クエリの有無はクエリパラメーター qに値が設定されているかどうかで判定しています。 値が設定されていなければ初期表示を行います。 全件策したいところですが…データが多くなった時に応答時間が長くなりそうなので止めておきます。。

L35-44: データベースから取得する情報は「検索結果総数」と「現在ページに表示する内容」の2種類です。 それぞれのデータは非同期で取得するので Promise.all()を使ってすべての処理が完了するのを保証します。

L47-57: ビューへ渡すデータを整形します。 ページネーション(ページング)に必要な情報はここで整形しておきます。 ページネーションを作るにはループで作成するので、「ページ総数 (max)」と「現在ページ番号 (current)」は最低でも準備しておきます。

/views/search/index.ejs


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>search</title>
</head>
<body>
<h1>検索</h1>
<form action="/shop/search" method="GET">
<input type="text" id="q" name="q" />
<input type="submit" value="検索" />
</form>
</body>
</html>

検索条件入力画面の初期表示ページ。 結果はないので検索条件を入力するだけの単純なページです。 検索条件クエリ―の有無で表示ページを出しわけしているので、フォームのメソッドは GETとしています。

/views/search/result-list.ejs


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>search</title>
<style>
.result-item { border: 1px solid #666; margin: 0.5em 1em; }
.pagination { list-style: none; display: flex; }
.pagination li { display: inline-block; width: 2em; }
</style>
</head>
<body>
<h1>検索</h1>
<form action="/shop/search" method="GET">
<input type="text" id="q" name="q" value="<%= query %>"/>
<input type="submit" value="検索" />
</form>
<p>結果:<%= count %>件</p>
<p>
<% for(var i = 0; i < list.length; i++) {%>
<%- include("result-item.ejs", list[i]); %>
<% }%>
</p>
<p>
<ul class="pagination">
<% for (var i = 1; i <= pagination.max; i++) {%>
<% if (i !== pagination.current) {%>
<li><a href="/shop/search?q=<%= query %>&pg=<%= i %>"><%= i %></a></li>
<% } else {%>
<li><%= i %></li>
<% }}%>
</ul>
</p>
</body>
</html>

検索結果表示画面。 リストの外枠だけを記述して個別検索結果は includeで読み込むようにします。

L18: 検索結果総数の表示は countに保持しているのでそれを表示します。

L20-22: 検索結果リストを forでループしながら includeに値を入れて表示を行います。 具体的な表示内容はこの後の result-item.ejsを確認してください。 includeを利用するときは <%- include("VIEW_FILEPATH") %>と記述する点に注意です。

L26-31: ページネーション(ページング)の表示です。 表示する「ページ総数」と「現在ページ」はサーバー側 (app.js) で算出しているので、それらを利用して表示を行います。

/views/search/result-item.ejs


<div>
<table class="result-item">
<tr>
<th>name</th>
<td><%= name%></td>
</tr>
<tr>
<th>location</th>
<td><%= location%></td>
</tr>
<tr>
<th>tel</th>
<td><%= tel%></td>
</tr>
</table>
</div>

検索結果の各項目を定義しています。 MongoDB から取得した値がそのまま流れてきているので、表示したいデータを埋め込んでいきます。

テスト

上記の実装がすべて終われば動作するはずなので、動作確認を行います。

  • 検索条件入力

  • 検索結果出力

スタイルの実装を最低限としたので、最初の 概要 のイメージとは少しデザインが異なりますが…機能としては必要なものが盛り込まれているかと思います。

今回の記事は参考になったでしょうか? 一般的な検索ページの作成をテーマに Node.js + Express でどのように実装するかをまとめてみました。 本記事(検索ページ作成)のポイントは以下の通りです。

  • 検索リクエストは GET
  • 検索結果画面には「検索入力」、「検索結果総数」、「検索結果」、「ページネーション」を表示
  • MongoDB への複数リクエストは Promise.all()で非同期ですべて取得
  • 「ページネーション」に必要な値はサーバー側で準備

Node.js + Express で CSRF対策 を行う 方法

$
0
0

Node.js + Express 環境において CSRF対策 を行う方法について今回はまとめていきます。 ちなみに、本記事で CSRF対策 として利用するミドルウエアは csrfです。 他の記事で csurfを利用したものがあったので参考に実装してみたのですが… csurfは一度発行したトークンが再利用できた点がセキュリティ的に怪しい動作なので、使うのを止めました。。

CSRFとは

詳しい内容は別記事がかけそうなくらいなので、ここでは概要だけおさらいします。

CSRF (cross-site request forgeries) は 掲示板や問い合わせフォームなどを処理するWebアプリケーションが、 本来拒否すべき他サイトからのリクエストを受信し処理してしまう脆弱性。

主な対策はこの記事でも取り上げる「正規ページであることを示すトークンを検証する」方法になります。

対策すべき画面は「データ作成・更新・削除を行う画面」です。 例えば上記であげている「掲示板の書き込み」や「問い合わせフォームの送信」、「ユーザー登録」、「パスワード変更」などが対象になります。

csrf ミドルウェア の 使い方

csrfの使い方として、大まかな流れを以下に記載します。


var Tokens = new require("csrf");
var tokens = new Tokens();
var secret = tokens.secretSync(); // secret は セッション に保存
var token = tokens.create(secret); // token は フォーム に返却
if (tokens.verify(secret, token) === false) {
throw new Error("invalid token");
}

L1-2
まずは Tokens インスタンス を生成します。 生成した tokens を利用して実際に利用する 秘密文字 (secret) や トークン (token) の生成 および それらの検証(verify) を行います。

L3
秘密文字を生成します。 秘密文字の生成はユーザー毎(セッション毎)に生成します。 また、一度検証を行って通過させた秘密文字とトークンの組み合わせは破棄して新規に生成しなおす方が望ましいです(=セッションで固定化すると悪意あるユーザーにバレる危険が増すため)。 生成した秘密文字は必ずセッションに保存するようにし、クライアントに返却は行いません。 ちなみに秘密文字は非同期生成できるので、実装できるなら非同期 ( tokens.secret(callback)または Promise ) で実装した方が良いかと思います。 ただし、この記事では分かりやすさを優先させるため同期で記載します。

L4
先に生成した秘密文字を利用してトークンを生成します。 生成したトークンはフォームに埋め込むかクッキーに埋め込んでクライアントに返しておき、クライアントからサーバーへ処理依頼をするときにあわせてトークンをサーバーへ戻してもらいます。

L5
tokens.verify(secret, token)で 秘密文字 と トークン の検証を行います。

csrf ミドルウェア を使った 実装例

Node.js + Express を使って CSRF対策 を行う実装例を以下に載せます…が、すべてのコードを載せると長くなるので一部抜粋です。。 以下の実装例では 秘密文字をセッション に、トークンをクッキーに 保存するよう実装しています。 以下のコードには出てきませんが、セッションとクッキーを利用するので express-sessionおよび cookie-parserを事前に設定しておく必要があります。


var router = require("express").Router();
var Tokens = require("csrf");
var tokens = new Tokens();

// …中略 (extract, validate, commit 処理) …

// GET: /shop/regist/input
router.get("/regist/input", function (request, response) {
// 新規に 秘密文字 と トークン を生成
var secret = tokens.secretSync();
var token = tokens.create(secret);

// 秘密文字はセッションに保存
request.session._csrf = secret;

// トークンはクッキーに保存
response.cookie("_csrf", token);

// 入力画面の表示
response.render("./shop/regist/input.ejs");
});

// POST: /shop/regist/complete
router.post("/regist/complete", function (request, response) {
// 秘密文字 と トークン を取得
var secret = request.session._csrf;
var token = request.cookies._csrf;

// 秘密文字 と トークン の組み合わせが正しいか検証
if (tokens.verify(secret, token) === false) {
throw new Error("Invalid Token");
}

// 入力データを取得
var data = extract(request);

// 入力データの検証
if (validate(data) === false) {
return response.render("./shop/regist/input.ejs", data);
}

// 登録処理
commit(data);

// 使用済み 秘密文字 と トークン の無効化
delete request.session._csrf;
response.clearCookie("_csrf");

// 完了画面へリダイレクト
response.redirect("/shop/regist/complete");
});

module.exports = router;

L10, 11
入力画面が最初に表示されたタイミングで新規秘密文字とトークンを生成します。

L14
生成した秘密文字はセッションに保存します。

L17
生成したトークンはクッキーに保存します。 クッキーでなければクライアントのフォームへ返してもよいです。 WebAPI を考えるとクッキーへ返すのが良いきがしますが…。

L26, 27
完了処理では秘密文字をセッションから取り出し、トークンはクッキーから取り出します。

L30-32
取り出した秘密文字とトークンを検証します。 検証して適切な組み合わせでない場合、エラーを発生させて以後の処理を停止します。

L46,47
一度使った秘密文字とトークンの組み合わせは完了時に初期化します。 初期化しておかないと再利用される懸念が出てきてしまいます。

今回のサンプルは参考になったでしょうか? CSRF対策でポイントは以下になります。

  • 対策を行う処理はデータ作成・変更・削除を行う画面
  • 入力画面の表示時に新規秘密文字と新規トークンを生成
  • 作成・変更・削除の実処理が呼び出されたとき秘密文字とトークンの組み合わせを検証
  • 一度使った秘密文字とトークンは初期化

JavaScript で 二重送信防止 する 方法

$
0
0

jQuery を使って フォームのサブミット および 通常ボタン 、 アンカー の二重送信(多重実行、多重送信)を防止するサンプルコードを作成しました。

サンプルコード

以下に「サブミットに対する多重送信防止」と「ボタンおよびアンカーに対する多重送信防止」のサンプルを載せますが、違いは対象としているイベント ( submitclickか) だけで基本的なコードは同じです。

サブミット に対して 多重送信防止


(function () {
$("form").on("submit", function onsubmit (event) {
$(this).off("submit", onsubmit).on("submit", false);
});
})();

ボタン および アンカー に対して 多重送信防止


(function () {
$("input[type='button']").on("click", function onclick (event) {
$(this).off("click", onclick).on("click", false);
});
})();

解説

サブミットの場合は submitイベントを、ボタンおよびアンカーの場合は clickイベントを2度目以降呼ばれたときに無効化することで多重送信防止を実現します。 イベント動作を無効化するには onの第2引数に falseを設定することで実現できます。 falseを設定すると jQuery の内部では stopPropagation() (バブルアップの停止) と preventDefault() (デフォルトイベントのキャンセル) が呼び出されるので該当動作を無効化することができます。

余談ですが…イベントハンドラーに関数名を指定したくなかった(=無名関数にしたかった)ので、 jQuery.off()の引数に arguments.calleeを指定していました。 実はこの arguments.calleeMDN - arguments.calleeを見ると「 "use strict"モードで使用禁止」との警告表示がありました。 なので、なくなく関数名を指定することにして上記のサンプルコードに至ります…。

単一フォーム に 複数サブミット を配置して 遷移先 を切り替える 方法

$
0
0

jQuery を使って 単一フォーム内に複数ボタンを配置して押されたボタンに応じて遷移先を変えるようにするサンプルコードを作成しました。

サンプルコード

HTML と JavaScript は分けて記載します。

confirm.html


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>確認</title>
</head>
<body>
<h1>確認</h1>
<form id="form" method="POST" action="">
<div>
<span>会社名:</span>
<input type="text" id="name" name="name" value="<%= name%>" readonly />
</div>
<div>
<input type="submit" id="btn-back" value="戻る" />
<input type="button" id="btn-regist" value="登録" />
</div>
</form>
<script type="text/javascript" src="/public/third_party/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="/public/scripts/shop/regist/confirm.js"></script>
</body>
</html>

confirm.js


(function () {
// 「戻る」ボタン押下時に呼び出されます。
var btnBack_onclick = function (event) {
$("#form").attr("action", "/shop/regist/input").submit();
};

// 「登録」ボタン押下時に呼び出されます。
var btnRegist_onclick = function (event) {
$("#form").attr("action", "/shop/regist/complete").submit();
};

// ドキュメント読み込み完了時に呼び出されます。
var document_onready = function (event) {
$("#btn-back").on("click", btnBack_onclick);
$("#btn-regist").on("click", btnRegist_onclick).focus();
};

$(document).ready(document_onready);
})();

解説

confirm.html

HTMLの form 内には複数の buttonを配置しています。

formについて、遷移先は押下するボタンに応じて書き換えるため form[@action]は空欄にしています。

それぞれのボタンについて、個別に識別する必要があるため、id属性を指定しています。 ここで指定した idは次の confirm.jsスクリプトで利用します。 なお、フォーム内にフォーカスがあるとき Enterキー押下した際のデフォルト動作を指定したい場合、 デフォルト動作にしたいボタンを サブミット ( input[@type="submit"] ) とし、その他はすべて通常ボタン ( input[@type="button"] ) とすることで実現できます。

confirm.js

それぞれのボタンに対して押下されたときのイベント( clickイベント )を設定します。 ボタン押下されたとき、formaction属性を変更してから submitを行います。

ちなみに今回は jQuery なので 一連の動作をメソッドチェーンで記述してみました。

いかがでしょうか、参考になったでしょうか? 単一フォームに複数サブミットで遷移先を切り替える方法を書いた記事はいくつか見受けますが… デフォルト動作に関してはなかなか見かけないので参考になったかと思います。

Node.js + Express で 登録画面 を作る

$
0
0

よくある「登録機能」を作るときの ポイント および 具体的な実装方法 をまとめてみました。 個別な課題は調べることができますが、「登録機能を作る」という観点でまとめているので、どのようなことを気を付けなければならないかがこの記事で理解できると思います。 この記事でざっくりとしたイメージを持っていただいたうえで、個別の詳しい内容を調べるとより理解が深まると思います。

概要

店舗情報を登録する機能を実装してみます。 画面上は見た通りのテキストボックスとボタンしかありませんが、登録機能では以下の4点がポイントになります。 ポイントのうち3つ(CSRF対策、1フォーム複数サブミット、2重送信防止)については個別記事もありますので以下の「関連記事」をご参照ください。

  • CSRF対策
  • 1フォームに複数サブミット(複数遷移先)を設定
  • 2重送信防止
  • 再送信防止

画面

データ登録する画面は「入力」、「確認」、「完了」の3画面構成になっています。 実装詳細はあとで見ていくので、まずは作ろうとしているもののざっくりとした画面イメージです。

  • 入力 (/shop/regist/input)

  • 確認 (/shop/regist/confirm)

  • 完了 (/shop/regist/complete)

データベース

以下のようなドキュメントスキーマを想定します。 データ登録する画面なので、特に初期データは準備しません。
database

test
collection

shops
document

shop = {
name: { type: string },
location: { type: string },
tel: { type: string }
}

実装

登録画面の具体的な実装のサンプルコードを以下に載せます。 以下のリストにはありませんが、別途 jQuery も含まれていますのでご注意ください。

/app.js


var express = require("express");
var cookie = require("cookie-parser");
var session = require("express-session");
var bodyParser = require("body-parser");

// Expressインスタンスを生成
var app = express();

// テンプレートエンジンの設定
app.set("views", "./views");
app.set("view engine", "ejs");

// ミドルウエアの設定
app.use("/public", express.static("public"));
app.use(cookie());
app.use(session({ secret: "YOUR SECRET SALT", resave: true, saveUninitialized: true }));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// ルーティングの設定
app.use("/shop", require("./router.js"));

// サーバー起動
app.listen(3000);

L2-4
セッションおよびクッキーの利用、フォームからのポストがあるので cookie-parserexpress-sessionbody-parserを利用します。

L21
今回はルーティング処理が複数存在して煩雑になるので、別モジュール( router.js)に掃き出します。

/router.js


var router = require("express").Router();
var co = require("co");
var MongoClient = require("mongodb").MongoClient;
var Tokens = require("csrf");
var tokens = new Tokens();

/**
* リクエストボディーからデータを抽出
*/
var extract = function (request) {
return {
name: request.body.name,
location: request.body.location,
tel: request.body.tel,
_csrf: request.body._csrf
};
};

/**
* リクエストデータを検証
*/
var validate = function (data) {
var errors = data.errors = [];

if (!data.name) {
errors[errors.length] = "会社名を指定してください。";
}

if (!data.location) {
errors[errors.length] = "所在地を指定してください。";
}

if (!data.tel) {
errors[errors.length] = "電話番号を指定してください。";
}

return errors.length === 0;
};

/**
* リクエストデータを登録
*/
var commit = function (data, callback) {
const URL = "mongodb://localhost:27017/test";

return co(function* () {
var db = yield MongoClient.connect(URL);
var collection = db.collection("shops");
var result = yield collection.updateOne(
{ name: { $eq: data.name } },
{ $set: data },
{ upsert: true },
(error, result) => {
db.close();
callback && callback();
});
}).catch((reason) => {
console.error(JSON.stringify(reason));
});
};

/**
* GET: /shop/regist/input
*/
router.get("/regist/input", function (request, response) {
// 新規に 秘密文字 と トークン を生成
var secret = tokens.secretSync();
var token = tokens.create(secret);

// 秘密文字はセッションに保存
request.session._csrf = secret;

// トークンはクッキーに保存
response.cookie("_csrf", token);

// 入力画面の表示
response.render("./shop/regist/input.ejs");
});

/**
* POST: /shop/regist/input
*/
router.post("/regist/input", function (request, response) {
// 入力データを取得
var data = extract(request);

// 入力画面の再表示
response.render("./shop/regist/input.ejs", data);
});

/**
* POST: /shop/regist/confirm
*/
router.post("/regist/confirm", function (request, response) {
// 入力データを取得
var data = extract(request);

// 入力データの検証
if (validate(data) === false) {
return response.render("./shop/regist/input.ejs", data);
}

response.render("./shop/regist/confirm.ejs", data);
});

/**
* POST: /shop/regist/complete
*/
router.post("/regist/complete", function (request, response) {
// 秘密文字 と トークン を取得
var secret = request.session._csrf;
var token = request.cookies._csrf;

// 秘密文字 と トークン の組み合わせが正しいか検証
if (tokens.verify(secret, token) === false) {
throw new Error("Invalid Token");
}

// 入力データを取得
var data = extract(request);

// 入力データの検証
if (validate(data) === false) {
return response.render("./shop/regist/input.ejs", data);
}

// 登録処理
commit(data).then(() => {
// 使用済み 秘密文字 と トークン の無効化
delete request.session._csrf;
response.clearCookie("_csrf");

// 完了画面へリダイレクト
response.redirect("/shop/regist/complete");
});
});

/**
* GET: /shop/regist/complete
*/
router.get("/regist/complete", function (request, response) {
response.render("./shop/regist/complete.ejs");
});

module.exports = router;

L67-68,71,74
入力画面が初回呼び出しされたとき、CSRF対策として 秘密文字 と トークン を新規発行します。 秘密文字はセッションへ保存し、トークンはクライアントのクッキーに保存します。

L111-112,115-117
確定画面が呼び出されたとき、実際にデータ登録処理を行いますが、この確定処理が正しく呼び出されているかを検証します。 入力画面が呼び出されたときに準備した 秘密文字 と トークン をそれぞれ取り出して組み合わせが正しいか検証を行います。 組み合わせに問題があればエラーとして返してしまいます。

L130-131
確定画面が呼び出され、データ処理も正常に終わると、まずは CSRF対策 として準備していた 秘密文字 と トークン を破棄して無効化します。 これにより同じ 秘密文字 と トークン の組み合わせが2度以上使えなくなります。 秘密文字 と トークン の再利用を許可しないことで、トークンが漏洩した場合の対策になります。

L134
確定画面が呼び出され、データ処理も正常に終わると、完了画面へ リダイレクトさせます。 リダイレクトさせることで完了画面で F5 を押下したときに確定処理が再実行されることを防止します。

/public/scripts/shop/regist/confirm.js


(function () {
// 「戻る」ボタン押下時に呼び出されます。
var btnBack_onclick = function (event) {
var $form = $("#form");
$form.attr("action", "/shop/regist/input");
$form.submit();
};

// 「登録」ボタン押下時に呼び出されます。
var btnRegist_onclick = function (event) {
var $form = $("#form");
$form.attr("action", "/shop/regist/complete");
$form.submit();
};

// ドキュメント読み込み完了時に呼び出されます。
var document_onready = function (event) {
$("#btn-back").on("click", btnBack_onclick);
$("#btn-regist").on("click", btnRegist_onclick).focus();
};

$(document).ready(document_onready);
})();

「確認」画面には1つのフォーム内に「戻る」ボタンと「確定」ボタンがあります。 どちらを押したかによって遷移先が異なるため、それぞれのボタン押下時に formaction属性を書き換えて遷移先を切り替えます。

L19
起動時に「確定」ボタンへフォーカスを当てておくことで、この画面へ遷移した直後に Enterキー 押下で次の画面へ進めるようにしておきます。 ちょっとした配慮ですが…前画面が入力画面でキーボード操作をしているので対応しておくのが望ましいと思います。

/public/scripts/shop/regist/protect-double-submit.js


(function () {
"use strict"

var onsubmit = function (event) {
$("form").off("submit", onsubmit).on("submit", false);
};

$("form").on("submit", onsubmit);
})();

2重送信防止スクリプトです。 フォームで一度サブミットが押されると2度目以降のサブミットが無効になるよう実装しています。

/views/shop/regist/input.ejs


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登録</title>
<style>
.alert {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}
</style>
</head>
<body>
<h1>登録</h1>
<% if (locals.errors) { %>
<ul class="alert">
<% for (var i = 0; i < errors.length; i++) { %>
<li><%= errors[i]%></li>
<% } %>
</ul>
<% } %>
<form method="POST" action="/shop/regist/confirm">
<div>
<label for="name">会社名:</label>
<input type="text" id="name" name="name" value="<%= locals.name ? name : ""%>"/>
</div>
<div>
<label for="location">所在地:</label>
<input type="text" id="location" name="location" value="<%= locals.location ? location : ""%>"/>
</div>
<div>
<label for="tel">電話:</label>
<input type="text" id="tel" name="tel" value="<%= locals.tel ? tel : ""%>" />
</div>
<div>
<input type="submit" value="確認" />
</div>
</form>
<script type="text/javascript" src="/public/third_party/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="/public/scripts/shop/regist/protect-double-submit.js"></script>
</body>
</html>

L16-22
入力画面の初回表示でエラーが出ることはありませんが、入力したデータを検証したとき問題があった場合にエラー内容をこの部分に表示します。 ejdへは errorsオブジェクトを渡すようにしていますが、存在有無をチェックする際は errorsとせず locals.errosで判定します。 単純な errorsだとオブジェクトがないと怒られてしまいます…。。

L26,30,34
入力値を検証した後、戻ってくる際、入力済みの値があれば復元します。 上記と同じく localsのプロパティとしてアクセスするようにします。

/views/shop/regist/confirm.ejs


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>確認</title>
<style>
input[readonly] {
background-color: #eee;
color: #555;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<h1>確認</h1>
<form id="form" method="POST" action="">
<div>
<span>会社名:</span>
<input type="text" id="name" name="name" value="<%= name%>" readonly />
</div>
<div>
<span>所在地:</span>
<input type="text" id="location" name="location" value="<%= location%>" readonly />
</div>
<div>
<span>電話:</span>
<input type="text" id="tel" name="tel" value="<%= tel%>" readonly />
</div>
<div>
<input type="button" id="btn-back" value="戻る" />
<input type="submit" id="btn-regist" value="登録" />
</div>
</form>
<script type="text/javascript" src="/public/third_party/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="/public/scripts/shop/regist/confirm.js"></script>
<script type="text/javascript" src="/public/scripts/shop/regist/protect-double-submit.js"></script>
</body>
</html>

L34-36
単一JavaScriptは読み込まれた順に実行されるので、スクリプトの読み込み順が重要です。 すべての基本となる jQuery () が一番最初、 次にフォーム内に複数ボタン配置して遷移先を変更するスクリプト( confirm.js)、 最後に2重送信防止のスクリプト ( protect-double-submit.js ) を読み込みます。

/views/shop/regist/complete.ejs


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>完了</title>
</head>
<body>
<h1>完了</h1>
<p>登録が完了しました。</p>
</body>
</html>

「完了」画面は単純なHTMLなので、残念ながらあまり説明することはありません…。

テスト

上記までの実装が終わったらテスト実行してみます。 以下では「テスト」と呼ばれるような内容ではなく単純に正常系の動作を確認してみます。

  1. 入力画面

  2. 確認画面

  3. 完了画面

今回はデータ登録を行う画面の基本的な実装をテーマに Node.js + Express での実装をまとめてみました。 本記事(データ登録)におけるポイントは以下の通りです。

  • CSRF対策
    • csrfミドルウェア を利用
    • 登録完了したら CSRF 秘密文字 と トークン を破棄
  • 同一フォーム内で複数遷移先へPOSTはJavaScriptで実装
  • 2重送信防止は JavaScript で実装
  • 完了画面はリダイレクト表示させることで F5 対策

関連記事

他システム連携 の 設計

$
0
0

他システム連携 とか 外部システム連携 とか言いますが…要するに自システム以外のシステムとデータ連携を行う場合に気を付けておきたいことをこの記事でまとめました。

概要

他システム連携といえば以下のイメージかと思います。 この記事全体を通して基本的には 接続先側の立場で記載します。 立場は読みづらくなりそうだから決めているだけなので、実際は接続元も接続先も同じ観点を気にする必要があります。

このようなシステム間連携の設計をするとき、気にしたい点の概要をまとめると以下のようになります。

設計…とは少しずれるかもしれませんが、実際に接続することを考えると準備が必要なこととして、以下のようなものもあります。

以下ではそれぞれの観点について詳細に見ていきます。

連携方法

連携方法と言えば、大まかには以下の3種類ではないでしょうか。 接続方法 (プロトコル) という観点で詳細を見ていきます。

リソース共有

データベースへ直接アクセスさせてデータ取得またはデータ更新を行います。

接続方法は各データベースクライアントの独自プロトコルになるかと思います。

参照のみの場合はビューを作成して公開します。更新する場合は実テーブル公開になります。 どちらにしても生データに対して直接作用されるのでリアルタイム性が高い点がメリットです。 反対にテーブル構造の変更=インターフェース変更になるため、全く異なるベンダーとの接続だと影響が大きくなる可能性が高くなります。 また、更新を許可している場合はどのようなデータが来るか分からないので不正データが紛れたときに障害の切り分けが難しくなると思います。

ちなみに…データベース監査が入っている場合はアクセスするテーブルの許可設定が必要になる点に注意です。

【確認観点】

  • 接続元IPアドレス
  • 接続先IPアドレス
  • 接続先ポート番号
  • 接続先URL (スキーマ名含む)
  • 接続時に使うユーザーID/パスワード
  • 穴あけ設定
    • ファイアウォール
    • データベース監査

アプリケーション連携

個人的には「システム連携」と言えばこの方法が一般的な感覚です。

互いに取り決めたURLへアクセスしてもらうことで連携を行います。 通信方法としては Socket 、 WebAPI といった感じでしょうか。 Web / AP で動くアプリを開発する必要があります。 アプリ開発をするので、データベースを非公開にすることができ、接続先の内部が変更されたとしても Web / AP 上で動くアプリで変更内容を吸収することができる点がメリットです。 デメリットは追加アプリ開発の工数が必要な点でしょうか。

大量データ通信には向かない(通信中にずっと Web/AP サーバーのリソースを消費し続けるのはあまり良くない設計)ので、通信データ量が多い場合はこの次のファイル連携で圧縮含めて検討した方が良いと思います。

【確認観点】

  • 接続元IPアドレス
  • 接続先IPアドレス
  • 接続先ポート番号
  • 接続先URL
  • リクエストクエリパラメーター
  • リクエストボディー
  • 接続時に使うユーザーID/パスワード または 証明書
  • 穴あけ設定
    • ファイアウォール
    • Webサーバー

ファイル連携

定期的に出力するファイルで連携を行います。 アプリケーション連携と同様にデータベースを非公開にできます。 また、大量データを通信する場合は圧縮することができるので帯域使用量を減らせる効果が期待できます。 難点はリアルタイム性に欠ける点でしょうか… 定期実行されるバッチでデータ生成して連携することになるので、定期実行の頻度に依存します。

【確認観点】

  • 接続元IPアドレス
  • 接続先IPアドレス
  • 接続先ポート番号
  • 接続先URL(ファイル命名ルール含む)
  • 接続時に使うユーザーID/パスワード または 証明書
  • ファイル圧縮する場合
    • 圧縮ファイル名
    • 圧縮ファイル内のフォルダ/ファイル構造
  • 穴あけ設定
    • ファイアウォール
    • ファイルサーバー

連携内容

接続したときやり取りするデータのうち、

DB (テーブル)

基本的にはテーブル定義書に記載する内容と同程度の内容を確認する必要があります。 具体的には以下のような観点かと思います。

【確認観点】

  • テーブル物理名
  • カラム物理名
  • データ型
  • Not NULL
  • 主キー
  • ユニークキー
  • ER図

tar / zip

大量のデータをやり取りする場合はこの形式になるかと思います。 実態として中身はファイルなので各ファイルにおける確認観点の詳細は該当の節を確認してください。

【確認観点】

  • フォルダ / ファイル構造

CSV / TSV

まとまった集計データをやり取りする連携だと CSV / TSV 形式になると思います。 集計データなので基本はファイル連携(バッチ処理による連携)となることが多いと思います。

【確認観点】

  • ヘッダー有無
  • カラムごとのフォーマット ( 0 や null の扱い含む )
  • ダブルクォート (") 有無

XML

構造化された形式で読み取りやすいですが…ファイルサイズがすぐ大きくなってしまうのが難点です。 サーバー同士の通信で通信内容も軽量であればこの形式が適切かと思います。

【確認観点】

  • DTD または XML Schema

JSON

最近だと JSON 形式も増えてきている気がします。 できることは XML と似ていますが XML に比較してファイルサイズを小さくできる点がメリットです。

【確認観点】

  • JSON Schema

バイナリ

ローレベルな通信だとバイナリ形式になる気がします。 「先頭○バイトから○バイトは××を示すデータ。」といった形式です。 データ量を小さくかつ読み取りも早くできるのが特徴。 半面、実装はとても面倒…。 実装が面倒すぎて個人的にはあまり使いたくないです。。

【確認観点】

  • ファイルフォーマット (バイト位置とデータの意味)

その他

上記までに含まれていないが気にした方が良いことをツラツラとあげていきます。

接続タイミング / 接続頻度

外部システム連携をどの程度利用するかも重要な決め事です。 接続で使われるサーバーリソースやネットワーク帯域が増えると既存のシステムにも影響が出てくるので、時間帯や頻度、通信量を予測しておきます。

【確認観点】

  • 接続してくる時間帯
  • 接続頻度
  • 同時接続数
  • 1回の接続レイテンシー

文字コード

やり取りするデータの文字コードは重要です。 きちんと決められていないと読み取ってもらえません。 最近は UTF-8 を使うことが多いとは思いますが、システムが古いと Shift-JIS なんとこともあります。 「こうだろう」と決めてかからず、必ず確認します。

順序保証

連続するデータ、リストデータの場合、順序保証しているかどうかを取り決めておきます。

リカバリ

どちらかというと接続元が考えることかもしれません。 接続に失敗したときどのように復帰させるかを考えます。 以下の確認観点リストの最後にもある通り「接続先が出力するデータに誤りがある」ことも考えられるので、リカバリを考えると受け取ったデータのログを残さず直接取り込むのは良くない設計かと思います。

【確認観点】

  • 接続が開始できない
  • 接続できたが途中で切断された
  • データ受信できたが破損していた
  • データ受信して取り込めたが、実はデータに間違いがあった

スタブ / ドライバ

設計が終わって開発が始まると必要になってくるのが スタブ / ドライバ です。 これだけでも1本記事がかけそうなので、ここでは簡単にだけ触れます。

スタブ

接続元側を開発するとき、接続先を仮置きで準備します。 この仮置きした接続先をスタブと呼びます。 仮置きなのであまり作りこまず単純な応答を行うものを作成します。

ドライバ

接続先側を開発するとき、接続元を仮置きで準備します。 この仮置きした接続元をドライバと呼びます。 ドライバを作るといったことはあまりないような気がします。 作らなくても直接DBをのぞいてみたり、Fiddler でリクエストを模倣したり、直接ファイルをのぞいたりはできます。

かなり長い記事になってしまいました…。 参考になったでしょうか? 最近は単独システムで完結しなくなってきたので、他システム連携するときが必ず来ます。 そんな時にこの記事の観点が役に立てばと思います。

参考記事

他システム連携 の テスト

$
0
0

他システム連携 (外部システム連携) を行う際、いきなり結合を行うのではなく順を追って連携レベルをあげていきます。 ここではそれぞれの段階でどのような確認を行うかをまとめました。

概要

一般に「開発」と言えば「ウォーターフォール開発」が基本になるかと思います。 図に示すと以下のようなものになります。

他システム連携のテストも通常の「ウォーターフォール開発」と同様に徐々に結合レベルをあげるようにテスト実施します。

以下ではそれぞれのテストでどのようなことを確認するか詳細に見ていきます。

単体テスト(規約確認)

リソース共有(データベース共有)の場合はいきなり結合テスト(疎通確認)になりますが、それ以外の接続方法(アプリケーション連携、ファイル連携)ではファイル交換による規約確認を最初に行います。 ウォーターフォール開発で言うところの単体テストに相当する確認です。 この時点で環境含めた通しでの性能テストは実施できませんが、ファイル連携だと最大サイズを交換することで疑似的な性能テストが実施できます。

対向システムとのファイル交換をするにあたり、 「実際にアプリを動かして出力できること」 または 「ファイルを受け取ってアプリが取込できる状態であること」 が必要なので、実装として単体テストまで完了していることが実施条件になります。

他システム連携を行う場合は共通する話題かもしれませんが… 「ファイル交換をどのように行うか」や「障害管理をどのように行うか」についてもあらかじめ決めておくと良いでしょう。

【確認観点】

  • 必須項目のみのデータ
  • 任意項目含めたすべてのデータ
  • 0、null、undefinedを含めたデータ
  • 最大値、最大長を含めたデータ
  • ファイル連携の場合は最大ファイルサイズのデータ

結合テスト(疎通確認)

対向システムと自システムを接続して環境面の設定ミスがないことを確認します。 確認の主眼はシステム設定面なのでできるだけ単純な正常系シナリオを選定して実施します。

ここからは対向システムの接続がありますので、対向システムベンダーの開発状況、環境構築状況の影響も受けます。 対向システムがテスト環境を用意できないケースも考えられます。 どうしても準備できそうになければテストスケジュールを見直す必要があるので、あらかじめ対向システムベンダーと日程調整を行っておく方が無難です。。

【確認観点】

  • アクセス先が間違っていないか
  • 穴あけ設定は正しくできているか
    • ファイアウォール
    • ロードバランサ
    • Webサーバー
  • 認証情報は正しく設定されているか
  • 暗号化および復号化に必要な設定が正しくされているか
  • フォルダは間違っていないか

システムテスト

規約確認と疎通確認が終われば実際にテスト環境同士で接続してシステムテストを行います。 テスト環境同士なので一般的な境界値テストや異常系テスト、性能テストを必要に応じて実施します。

【確認観点】

  • 正常系シナリオ
    • 受け取ったデータが正しい項目に表示されるか
  • 異常系シナリオ
    • 接続できなかった場合
    • 接続中に切断した場合
  • 性能テスト
    • 想定される最大データ量

今回の記事は参考になったでしょうか? 「フェーズ」という視点で分割して「他システム連携のテスト」をまとめましたが、 各テストでは「PMBOKの知識エリア」に応じた観点で必要事項の洗い出しを行うのがポイントです。 全体的にはどちらかというと経験則でしか書き起こせていないですが…少しでも参考になればと思います。


Node.js で 環境変数 を 設定 / 取得 する 方法

$
0
0

Node.js を実行しようとすると環境変数を指定、利用したいケースがあると思います。 今回は Node.js で環境変数を利用する方法 についてまとめてみました。

環境変数 を 設定する

構文


[ENV_VAR1=ENV_VAL1 [ENV_VAR2=ENV_VAL2 ...]] node FILE_NAME

解説

nodeコマンド実行前に設定したい環境変数をスペース区切りで指定することで設定できます。 あらかじめ実行環境の環境変数として定義してあるものも利用できるので、nodeを実行前に setで定義することもできます(サンプルには記載)。

サンプル


USER_ID=123456 USER_KEY=J4rhsP7n node app.js

set USER_ID=123456
set USER_KEY=J4rhsP7n
node app.js

環境変数 を 取得する

構文


process.env.ENV_VAR1

解説

基本的には process.envオブジェクトのプロパティとしてアクセスすることで利用できます。

サンプル


console.log(JSON.stringify(process.env));

定義済み 環境変数

実行環境依存の環境変数

実行環境で既に定義済みの環境変数はもちろん使えます。 Windows で定義済みの環境変数は以下の記事にまとめているので参照してください。

Node.js 定義済みの環境変数

Node.js として実行時にすぐ使える独自定義済みのものをここでは取り上げます。

変数名説明
NODE_PATH';'で分割されたディレクトリパスリストを指定して、モジュール検索先を指定します。
NODE_DISABLE_COLORS'1'を設定すると REPL での色利用を無効化します。
NODE_ICU_DATAICU (Intel オブジェクト) データ へのパスを指定します。
NODE_REPL_HISTORY永続させる REPL履歴ファイル のパスを指定します。

Node.js で慣例的に使われる環境変数

モジュールによってはデフォルトで定義済みとなる、 Node.js で慣例的に使われる環境変数をここでは取り上げます。

変数名説明
NODE_ENV'production'または 'development'を指定することで、該当フェーズに応じた動作に変わります。
NODE_DEBUG'http'や 'http,net'のようにデバッグ出力したいモジュール名をカンマ区切りで指定します。

参考記事

Visual Studio Code に 環境変数 設定 を追加 する 方法

$
0
0

Node.js で環境変数を 設定 / 取得 する方法については 別記事があるのでそちらをご参照ください。 ここでは、VSCode に対して 環境変数を 設定 / 取得 する方法についてのみまとめました。

環境変数設定を追加する

F5実行したとき呼び出される launch.jsonに追記することで実現します。 以下に launch.jsonへ追記する手順を載せます。

  1. ビューバーの「デバッグ」を選択

  2. 「歯車」アイコンを選択

  3. launch.jsonenvプロパティを追加

    今回は envプロパティ に "SAMPLE_MESSAGE": "Hello World"を追加します。


    {
    // 使用できる Node.js デバッグ属性を学ぶために、インテリセンスを使用してください。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
    {
    "type": "node",
    "request": "launch",
    "name": "プログラムの起動",
    "program": "${file}",
    "env": {
    "SAMPLE_MESSAGE": "Hello World"
    }
    }
    ]
    }
  4. launch.jsonを保存

一度 launch.jsonを設定すると以下の場所へファイルが作成されます。 2度目以降は上記手順で launch.jsonを開いてもよいですし、以下のパスにある launch.jsonを直接開いても同じです。


%PROJECT_ROOT%

└─.vscode
launch.json

追加した環境変数を取得する

F5で デバッグ実行 すると以下のスクリプトで追加した SAMPLE_MESSAGE環境変数を呼び出すことができます。


process.env.SAMPLE_MESSAGE

今回の記事は参考になったでしょうか?

Visual Studio Code に環境変数を設定するポイントは「 launch.jsonenvプロパティを設定すること」です。 そして launch.json%PROJECT_ROOT%\.vscode\launch.jsonにあります。

この記事が参考になればと思います。

Visual Studio Code で jasmine の spec を デバッグ実行する 方法

$
0
0

Visual Studio Code で Node.js アプリの単体テストを jasmine を使って作成していて、 どこでエラーになっているか分かりづらかったのでバグ取りするのにデバッグ実行できる方法を調べたのでここにまとめておきます。

開発環境 の 準備

Node.js で jasmineを使う場合、 jasmineというパッケージを global インストールして使いますが、 Visual Studio Code と組み合わせて使うときは jasmine-nodeを使った方が簡単に扱えます。 デバッグ実行できるようにするためには プロジェクトローカル へ jasmine-nodeのインストールを行います。 以下ではその具体的な手順および設定を記載します。

  1. jasmine-nodeを追加。


    npm install jasmine-node --save-dev

  2. launch.jsonを追加。

    1. ビューバーの「デバッグ」を選択

    2. 「歯車」アイコンを選択

  3. launch.jsonに jasmine 起動コマンド を追加。

    /.vscode/launch.json


    {
    "version": "0.2.0",
    "configurations": [
    {
    "type": "node",
    "request": "launch",
    "name": "jasmine-node debugging",
    "program": "${workspaceRoot}/node_modules/jasmine-node/lib/jasmine-node/cli.js",
    "args": [
    "./spec",
    "--color"
    ],
    "cwd": "${workspaceRoot}"
    }
    ]
    }

    "program"
    プロジェクトローカルにある jasmine-node の cli.js を呼び出せるよう設定しています。

    "args"
    テストケースファイル ***spec.jsが保存されているパスを プロジェクトルート からの相対パスで指定しています。 テストケースを保存するパスを testにするのであればこの値を ./testに修正します。

以上で環境準備は完了です。 F5でデバッグ実行すれば jasmine のデバッグ実行 が可能です。

実装, テスト の準備

ここでは上で準備した環境に対して実際のソースコードおよびテストコードを作成していきます。

ディレクトリ構成


%PROJECT_ROOT%

│ package.json

├─source
│ index.js

└─spec
index.spec.js

ソースコード

今回は長方形の面積を求めるモジュールを作成します。

./souce/index.js


module.exports = function (a, b) {
return a * b;
};

テストコード

Node.js において requireは実行している JavaScriptファイル からの相対パスを指定するので、ここでは "../source/index.js"を指定してテストしたいモジュールを呼び出しています。

./spec/index.spec.js


var square = require("../source/index.js");

describe("A 'square' module", function () {
it("calculates the area of a 2 x 3 rectangle to be 6", function () {
expect(square(2, 3)).toBe(6);
});
});

デバッグ実行

ブレークポイントを指定した後、通常のデバッグ実行と同じように F5でデバッグできます。 きちんんと jasmine の spec 中で中断されているのが分かります。

今回の記事は参考になったでしょうか? Visual Studio Code において jasmine の デバッグ実行 を行うポイントは以下の2点になります。

  • jasmine-nodeをローカルインストールして利用する
  • デバッグ起動する設定は ./vscode/launch.jsonに記述する

参考記事

Node.js で HTTP/HTTPS リクエスト を行う方法

$
0
0

Node.js で HTTP / HTTPS の GETリクエスト や POSTリクエスト をする方法をまとめます。 Node.js 標準機能で実装もできますが…無駄なコードが多くなってしまうので requestモジュール を利用して実装するのが簡単です。 今回は requestモジュール を利用して GETリクエスト または POSTリクエスト を行うサンプルコードを載せます。

サンプルコード

GETリクエスト、POSTリクエストを行う簡単なサンプルコードを以下に記載します。

GETリクエスト

リクエストで http://localhost:3000/api/test?testkey=testvalue&hoge=hogeを行う例です。 httpsへアクセスする場合はそのまま URL を https で記載すれば動作します。


var webclient = require("request");

webclient.get({
url: "http://localhost:3000/api/test",
qs: {
testkey: "testvalue",
hoge: "hoge"
}
}, function (error, response, body) {
console.log(body);
});

POSTリクエスト

リクエストで http://localhost:3000/api/testPOSTを行い、そのリクエストボディーに JSON文字列 を指定する例です。


var webclient = require("request");

webclient.post({
url: "http://localhost:3000/api/test",
headers: {
"content-type": "application/json"
},
body: JSON.stringify({foo: "bar"})
}, function (error, response, body){
console.log(body);
});

API仕様

request(option, callback)

HTTPリクエスト または HTTPSリクエスト を行います。

option

Type: object

HTTP/HTTPSリクエストの設定を行います。 よく使いそうな設定項目だけを以下に取り上げます。

パラメーター説明
uri || urlstring必須。リクエスト先のURLを指定します。
methodstringリクエストメソッドを指定します。デフォルト "GET"
headersobjectリクエストヘッダーを指定します。
qsobjectリクエスト先のURLへ付与するクエリ文字列を指定します。
bodyBuffer, String, ReadStreamリクエストボディーを指定します。JSONを指定する場合は文字列化して指定します。
formobject値が設定された場合、content-type: application/x-www-form-urlencodedでリクエストを行います。
formDataobject値が設定された場合、 content-type: multipart/form-dataでリクエストを行います。
multipartarray値が設定された場合、 multipart/relatedでリクエストを行います。
preambleCRLFbooleanmultipart/form-dataリクエストの境界の手前に CRLF改行 を挿入します。
postambleCRLFbooleanmultipart/form-dataリクエストの境界の後ろに CRLF改行 を挿入します。
jsonbooleanbodyに JSON文字列 を指定している場合、リクエストヘッダーに content-type: application/jsonを付与します。 また、レスポンスボディーは JSONオブジェクト として パース を行います。

callback

Type: Function(Error error, http.IncomingMessage response, string / Buffer body)

レスポンスを受け取った際に呼び出されます。

参考記事

Node.js で FTP / FTPS 通信 を行う 方法

$
0
0

外部連携を行う際、HTTP / HTTPS の次に使いそうなのが FTP / FTPS 通信と思います。 今回は Node.js で FTP / FTPS 通信を行うサンプルコードを作成しました。 素のまま使うとコードが面倒になるので ftpモジュールを使った通信方法をまとめています。

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

とりあえず、利用にあたって ftpモジュールのインストールを行っておきます。


npm install ftp --save

サンプルコード

ファイルダウンロード

FTPでダウンロード

ftp://192.168.86.135:21/test/sample.txtをダウンロードするサンプルコードを以下に載せます。


var fs = require("fs");
var Client = require("ftp");
var client = new Client();

client.connect({
host: "192.168.86.135",
port: 21,
user: "ftpuser",
password: "ftpuser"
});

client.on("ready", () => {
client.get("test/sample.txt", (error, stream) => {
if (error) {
throw error;
}
stream.once('close', function () { client.end(); });
stream.pipe(fs.createWriteStream(`./recieved/sample.local-copy_${(new Date()).getTime()}.txt`));
});
});

L5-10
接続先のサーバー、ユーザー、パスワードを設定します。

L12
接続確立すると readyイベントが発生するので、後続操作は readyイベントのコールバックに指定します。

L13
必要なファイルを client.get(filename, callback)にてダウンロードします。 ディレクトリ変更が必要であれば client.cwd()を利用して移動しておきます。

FTPSでダウンロード

上記とほぼコードは同じです。 差分があるところをハイライトしました。


process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";

var fs = require("fs");
var Client = require("ftp");
var client = new Client();

client.connect({
host: "192.168.86.135",
port: 21,
secure: true,
user: "ftpuser",
password: "ftpuser"
});

client.on("ready", () => {
client.get("test/sample.txt", (error, stream) => {
if (error) {
throw error;
}
stream.once('close', function () { client.end(); });
stream.pipe(fs.createWriteStream(`./recieved/sample.local-copy_${(new Date()).getTime()}.txt`));
});
});

L1
FTPサーバーがオレオレ証明書を利用している場合、証明書エラーが発生します。 こうした場合はエラーを無視するオプション NODE_TLS_REJECT_UNAUTHORIZEDを設定しておきます。

L10
FTPSアクセスする場合は secure属性を指定します。

ファイルのアップロード

コードはダウンロードとほぼ変わりません。 アップロード時は putを使う点が異なります。

FTPでアップロード


var fs = require("fs");
var Client = require("ftp");
var client = new Client();

client.connect({
host: "192.168.86.135",
port: 21,
user: "ftpuser",
password: "ftpuser"
});

client.on("ready", () => {
client.put("src.txt", "dist.txt", (error) => {
if (error) {
throw error;
}
client.end();
});
});

FTPSでアップロード


process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";

var fs = require("fs");
var Client = require("ftp");
var client = new Client();

client.connect({
host: "192.168.86.135",
port: 21,
secure: true,
user: "ftpuser",
password: "ftpuser"
});

client.on("ready", () => {
client.put("src.txt", "dist.txt", (error) => {
if (error) {
throw error;
}
client.end();
});
});

ftp モジュール API

readyイベント

接続と認証が終わったとき呼び出されます。

ftp.connect( config )

FTPサーバーへ接続します。

config

Type: object

サーバー接続に必要な設定を指定します。 指定できる設定プロパティは以下の通りです。

プロパティ名デフォルト説明
hoststring"localhost"FTPサーバーのホスト名またはIPアドレスを指定します。
portinteger21FTPサーバーのポート番号を指定します。
secureboolean / "control" / "implicit"false暗号化通信の設定を行います。
true
制御通信およびデータ通信の両方で暗号化通信を行います。
"control"
制御通信のみ暗号化通信を行います。
"implicit"
暗黙的暗号化制御接続を行います。(通常 990 ポートを利用します。最近ではあまり利用されません。)
secureOptionsobject(none)tls.connect()メソッドに渡すオプションを指定します。
userstring"anonymous"認証に使うユーザー名を指定します。
passwordstring"anonymous@"認証に使うパスワードを指定します。
connTimeoutinteger10000制御接続確立まで何ミリ秒まで待つかを指定します。
pasvTimeoutinteger10000PASVデータ接続で何ミリ秒まで待つかを指定します。
keepaliveinteger10000接続維持のために"dummy"(NOOP)コマンドを送信する間隔(ミリ秒)を指定します。

ftp.cwd( path, callback )

カレントディレクトリを pathに変更します。

path

Type: string

移動先のパスを バックスラッシュ "/"区切りで指定します。

callback

Type: function(Error error , string currentDir)

currentDirはサーバー応答に含まれている場合に設定されます。

ftp.get( path, callback )

サーバーから指定したパスにあるデータを受信します。

path

Type: string

移動先のパスを バックスラッシュ "/"区切りで指定します。

callback

Type: function(Error error , ReadableStream stream)

streamは受信したファイルのストリームが渡されます。

ftp.put( src, dis, callback )

サーバーからファイルを取得します。

src

Type: ReadableStream / Buffer / string

送信したいファイルのストリームまたはローカルファイルパスを指定します。

dist

Type: string

送信先のパスを バックスラッシュ "/"区切りで指定します。

callback

Type: function ( Error error )

送信完了したら呼び出されます。

今回の記事は参考になったでしょうか? Node.js で FTP/FTPS通信 を行うポイントは以下になります。 今回の記事も参考になれば幸いです。

  • FTP/FTPS には ftpモジュール を利用する
  • 接続は client.connect()
  • ディレクトリ変更は client.cwd()
  • ファイルダウンロードは client.get()
  • ファイルアップロードは client.put()
  • FTPS を利用する場合は client.connect()secure: trueを指定する
  • オレオレ証明書を利用する場合は process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";を記述する

参考記事

Viewing all 392 articles
Browse latest View live