Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all 9099 articles
Browse latest View live

express-validatorでユーザー登録時にバリデーション処理を行う

$
0
0

Node.jsでのオススメ書籍

どうもNode.js入門中のものです。

タイトルとは少しそれますが、
まずは、Node.jsを勉強する上で、非常にオススメの書籍をまずは紹介したいと思います。
それがこちらです。

入門Node.jsプログラミング

上記は翻訳版で、原書はこちらになります。

Get Programming with Node.js

Node.jsでMVCパターンを実装し、フロントはテンプレートエンジンで実装するというフロントエンド強強の方には不満な構成かもしれませんが、
基本的なアプリケーションの作り方と、特にセキュリティ関連のバックエンドの実装を学ぶことができるのでとてもオススメです。

(AmazonのレビューにあるようにGoogle翻訳すれすれの変な日本語が混ざっていますが、Google翻訳に慣れている方なら苦もなく読めると思います。原書の内容が素晴らしいので、よし)

今回はログイン時の実装でバリデーションの実装を行う際に最新バージョンだとはまってしまう箇所があったので、解説したいと思います。

そもそもバリデーションってなに?

「バリデーション」とは、「検証、実証、認可、妥当性」を意味する英単語になります。

例えば、「文書をバリデーション」といった場合には、「記述・入力されたデータが、あらかじめ規定された条件・使用に適合しているか検証・確認する。」ことを表します。
引用:(https://career-picks.com/business-yougo/validation/)

です。今回の実装では、ユーザー登録をする際に、メールアドレスが正しい形式で入力されているか?zipCodeが5ケタで正しく入力されているかをユーザー登録の前にバリデーションします。

express-validatorをインストール

このバリデーションを実装する方法は様々なアプローチがあるそうですが、本書ではexpress-validatorを用いた方法が紹介されています。
インストールします。

npm i express-validator -S

最初に本書にある方法で実装してみます。

main.js
constexpress=require("express"),app=express(),router=express.Router(),expressValidator=require("express-validator");router.use(express.json());router.use(expressValidator());router.post('/users/create',usersController.validate,usersController.create,usersController.redirectView,);
userController.js
"use strict"constUser=require("../models/user")// ----(中略) ----- //module.exports={create:(req,res,next)=>{if(req.skip)next();letuserParams=getUserParams(req.body);User.create(userParams).then(user=>{req.flash("success",`${user.fullName}'s account created successfully!`);res.locals.redirect="/users";res.locals.user=user;next();}).catch(error=>{console.log(`Error saving user: ${error.message}`);res.locals.redirect="/users/new";req.flash("error",`Failed to create user account because: ${error.message}.`);next();});},redirectView:(req,res,next)=>{letredirectPath=res.locals.redirect;if(redirectPath)res.redirect(redirectPath);elsenext();},validate:(req,res,next)=>{req.sanitizeBody("email").normalizeEmail({all_lowercase:true}).trim();req.check("email","Email is invalid").isEmail();req.check("zipCode","Zip code is invalid").notEmpty().isInt().isLength({min:5,max:5}).equals(req.body.zipCode);req.check("password","Password cannot be empty").notEmpty();req.getValidationResult().then(error=>{if(!error.isEmpty()){letmessages=error.array().map(e=>e.msg);req.skip=true;req.flash("error",messages.join(" and "));res.locals.redirect="/users/new";next();}else{next();}});}};

このようなエラーが出ます。

Express Validator Error: expressValidator is not a function

解決方法

expressValidatorは関数として使えません。というエラーです。ググってみたら、v6.0.0から実装が変わっているようです。

(https://translate.google.com/translate?hl=ja&sl=en&tl=ja&u=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F56733975%2Fexpress-validator-error-expressvalidator-is-not-a-function%2F56734897&anno=2)

上記のリンクのように実装しても良いのですが、main.jsの処理が大きくなるので、usersControllerに実装したいと思います。

main.js
-expressValidator=require("express-validator");-router.use(expressValidator());
userController.js
constUser=require('../models/user'),{check,sanitizeBody,validationResult}=require('express-validator'),module.exports={validate:(req,res,next)=>{sanitizeBody('email').normalizeEmail({all_lowercase:true,}).trim();check('email','Email is invalid').isEmail();check('zipCode','Zip code is invalid').notEmpty().isInt().isLength({min:5,max:5,}).equals(req.body.zipCode);check('password','Password cannot be empty').notEmpty();consterrors=validationResult(req);if(!errors.isEmpty()){letmessages=error.array().map(e=>e.msg);req.skip=true;req.flash('error',messages.join(' and '));res.locals.redirect='/users/new';next();}else{next();}},}

userController.jsでexpress-validatorをインストールする形にしました。
sanitizeBodyメソッドでメールを全て小文字にし、空白を無くしてから、
checkメソッドで正しい形式・すでに登録されているアドレスではないかをバリデーションしています。
同様に
同様にzipCode,passwordもcheckしています。notEmpty()は空白ではないかをcheckするメソッドです。

最後にvalidationResultメソッドで、それぞれの形式が正しいか。一致するかを検証し、誤りがあった場合ユーザー作成画面にリダイレクトしています。

まとめ

すごい良い本なので、Node.jsに興味のある方は読んでみてください。
実装方法やパッケージでもっと良いのがあったらコメントいただけると嬉しいです。

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


JavaScriptで書いているWebPackのプロジェクトをTypeScriptに移行・書き換えした

$
0
0

背景

学習がてら開発しているクライアントサイドのみの小さなSPAをJavaScriptで書いていたが、変数に入っているのが何のオブジェクトなのかがわからなくなって混乱してきたので重い腰を上げてTypeScriptに移行することに。

環境

Vue.jsを使ったJavaScript(ES6)のコードをWebPackで固めてクライアントサイドで実行している。
エディタはVSCode

typescriptとts-loaderモジュールのインストール

WebPackや各種モジュールを使うのにすでにnpmやNode.jsやWebPackはインストール済みなので、TypeScriptとそれをWebPackにロードするためのローダーのモジュールをnpmでインストールする。

$ npm i typescript ts-loader

webpack.config.jsの書き換え

既存のwebpack.config.jsファイルのmodule.rulesに.tsファイルのロードと解決に関する設定を追加したところ以下のようになった。

webpack.config.js
module.exports={mode:'development',module:{rules:[{test:/\.css$/,use:["style-loader","css-loader"]},{// .tsファイルをロードする設定を追加test:/\.ts$/,use:'ts-loader'}]},resolve:{alias:{'vue$':'vue/dist/vue.esm.js'},// 拡張子.tsのファイルをjsに変換// (この設定はファイル名の解決に使われるらしい。詳細を後述)extensions:['.ts','.js']},entry:'./src/index.js',output:{path:`${__dirname}/public`,filename:'main.js'},devServer:{contentBase:'./public'}}

ts設定ファイルの追加

以下のようなtsconfig.jsonを追加しました。詳細は後述。

tsconfig.json
{"compilerOptions":{"target":"esnext","module":"commonjs",// "noImplicitAny": true, // 今まで書いたJSのコードで定義した変数がとりあえずそのまま使えるように"esModuleInterop":true,},}

拡張子とソースコードの変更

JavaScriptで書かれたコードはそのままTypeScriptのコードとして通用すると言うことなので、以下のようなprogram.jsをprogram.tsにリネーム。

idnumberのような仮引数に対して"Parameter 'id' implicitly has an 'any' type.ts(7006)
"と怒られた。tsconfig.jsonのnoImplicitAny : trueをコメントアウトすることで解消。

program.js
exportdefaultclassProgram{constructor(id,number,title,startTime,lengthMinute,unlockOffsetMinutes){this.id=id;this.number=number;this.title=title;this.startTime=startTime;this.lengthMinute=lengthMinute;this.unlockOffsetMinutes=unlockOffsetMinutes;this.isSelected=false;}/* 色々な処理が書かれているけど省略 */}

すると、Property 'id' does not exist on type 'Program'.ts(2339)と怒られた。TypeScriptのクラス定義ではコンストラクタ外でプロパティの定義が必要らしい。VSCodeのQuick FixでDeclare propertyすることで解消。

program.ts
exportdefaultclassProgram{id:any;number:any;title:any;startTime:any;lengthMinute:any;unlockOffsetMinutes:any;isSelected:boolean;constructor(id,number,title,startTime,lengthMinute,unlockOffsetMinutes){this.id=id;this.number=number;this.title=title;this.startTime=startTime;this.lengthMinute=lengthMinute;this.unlockOffsetMinutes=unlockOffsetMinutes;this.isSelected=false;}/* 色々な処理が書かれているけど省略 */}

遭遇したエラー

Module not found: Error: Can't resolve './program' in '/Users/rlcl-226/git/gachi-taite-designer-web/src'

webpackが出したっぽいエラー.
./programと言うファイルを探した結果見つからなかったと言っているように見える。webpack.jsのresolve.extensionsの設定をすることで解消。
WebPackがprogram.jsを探しに来た時に、TypeScriptコンパイラがprogram.tsをコンパイルしたものを渡してあげるという設定だろうか?

終わりに

とりあえずTypeScriptへの移行の最初のファイルはこれで完了した。
これとは別のソースファイルをTypeScriptに移行しようとした時、moment-duration-formatでエラーが出て直したりしたが、それは別の記事に書くとする。

JSのモジュールとbabelとwebpackとは何かまとめてみる(初心者向け)

$
0
0

初心者向けにモダンJavaScriptの開発で必要になる
CommonJSとECMAScriptでのモジュールの違い、babelとwebpackの知識に関してまとめておきます。

CommonJSとECMAScript

JavaScriptの歴史的経緯にもなるのですが、
実はJavaScriptには
サーバサイドのNodeJS(CommonJS)とブラウザのJavaScript(ECMAScript)
の大まかに2つの言語仕様があります。1

元々のJavaScriptの欠点としてJavaScript内でモジュール化されたJavaScriptファイルを外部参照するという言語機能がなかったことが起因しています。
(かつてはHTMLのscriptタグでグローバル参照するというやり方が従来だった)
モジュール機能が先に言語仕様として策定されたのがCommonJSでECMAScriptのモジュール機能は後発となります。
したがって、大まかな文法はほぼ一緒なもののモジュール機能の記述に関して、CommonJSとECMAScript間で大きな違いがあります。
なお、ECMAScriptには仕様策定を決めているTC39という委員会があります。

参考:JavaScriptが辿った変遷

CommonJSとECMAScriptのModulesの違い

細かい違いは以下の記事のほうが参考になります

参考:CommonJS と ES6の import/export で迷うなら

CommonJSのモジュール機能

CommonJSでモジュールを外部参照できるようにするためには、主にmodule.exportsを使います。
ここだとabc.jsというファイル名のモジュールを作成します。

abc.js
module.exports=変数

CommonJSでモジュールを参照するためには、requireを使います。
requireには参照するモジュールのファイル名を指定します。
abcにはmodule.exportsした変数そのものが参照できます。

constabc=require('abc')

ECMAScriptのモジュール機能

ECMAScriptでモジュールを外部参照できるようにするためには、主にexportもしくはexport defaultを使います。(exportsでないのに注意)
ここだとdef.jsというファイル名のモジュールを作成します。

abc.js
export変数exportdefault変数

ECMAScriptでモジュールを参照するためには、importを使います。
importには参照するモジュールのファイル名を指定します。

import{変数}from'def'// exports 変数を参照する場合importdeffrom'def'// exports default 変数を参照する場合// import { default as def } from 'def' // こうもかける

ちなみにimport文はトップレベルでないと使えないという制限があります。

// トップレベルimportdeffrom'def'// OKif(条件分岐){// ブロック{}で囲まれている箇所はトップレベルでないimportdeffrom'def'// NG}

dynamic import

Promise形式で外部モジュールを非同期に参照読み込みすることもできます。(dynamic import)
これにより、うまく使いこなせれば読み込みのオーバヘッドをなくすことができます。(並列読み込みによる高速化)
また、こちらに関してはトップレベルでなくても使えます。

import('def').then(module=>{/* 処理 */})

参考:Chrome、Safari、Firefoxで使えるJavaScriptのdynamic import(動的読み込み)

NodeJSからECMAScript Module(ESM)のimport、exportができるようになる(将来的に)

現在、NodeJS側からESMを参照できるようにするという流れでNodeJSチームが頑張って対応しています。2
Plan for New Modules Implementation

NodeJS 13.2.0から--experimental-modulesフラグ無しでESMのimport、exportが使える

ついにNode 13.2.0(2019/11/21リリース)から--experimental-modules無しでESMのimport、exportが使えるようになりました。(Phase4)
拡張子も.jsのままで.mjsにする必要はなく、package.jsonに"type":"module"の設定を追加するだけでESMのimport、exportが使えます。
公式:ECMAScript Modules

package.json
{"type":"module","name":"node13.2","version":"1.0.0","main":"index.js","license":"MIT","dependencies":{"body-parser":"^1.19.0","express":"^4.17.1"}}

ESMのimportを使ってNodeJSのサーバを立ててみます。

app.js
import{defaultasexpress}from'express'import{defaultasbodyParser}from'body-parser'constapp=express()constwrap=(fn)=>(req,res,next)=>fn(req,res,next).catch(err=>{console.error(err)if(!res.headersSent){res.status(500).json({message:'Internal Server Error'})}})process.on('uncaughtException',(err)=>console.error(err))process.on('unhandledRejection',(err)=>console.error(err))process.on('SIGINT',()=>process.exit(1))app.use(express.static('dist'))app.use(bodyParser.urlencoded({extended:true}))app.use(bodyParser.json())app.get('/api',wrap(async(req,res)=>{res.json('hello world!!')}))app.listen(3000,()=>{console.log('Access to http://localhost:3000')})

警告は出ますが、babel無しでESMのモジュールのimportができるようになりました。

$ node app.js 
(node:62642) ExperimentalWarning: The ESM module loader is experimental.
Access to http://localhost:3000

babel

フロントエンドの実装にはブラウザ間でHTML、CSS、JSの実装レベルが異なるという大きな問題があります。
ブラウザの各機能の実装状況はCan I useなどで確認できます。
この問題に関して新しいJS文法を古いJS文法に変換(トランスパイル)して古いブラウザでも使えるようにするためのツールがbabelです。
babelの役割は非常に多く、多数のプラグインとpresetが存在しています。
例えば以下のようなことが実現できます。

Polyfill参考:Babel 7.4.0で非推奨になった@babel/polyfillを使わず、core-js@3で環境構築する
TypeScript参考:Babel 7でTypeScriptをトランスパイルしつつ型チェックをする 〜webpack 4 + Babel 7 + TypeScript + TypeScript EsLint + Prettierの開発環境を構築する〜

babelの設定ファイル

.babelrc, babel.config.js, babel.config.cjs, babel.config.jsonのいずれかのファイルに設定を書くか
webpackの場合、後述のbabel-loaderプラグインのoptionsに指定する方法があります。
(babel実行時に自動に設定ファイルを探しに行く)
主にpluginと複数のpluginをまとめたpresetを指定することがほとんどです。

公式:Configure Babel

例えば.babelrc、babel.config.jsonの場合は次のように書きます。

{"plugins":["@babel/plugin-proposal-optional-chaining"],"presets":[["@babel/preset-env",{"useBuiltIns":"usage","corejs":3,"modules":false}]]}

babel.config.js、babel.config.cjsの場合は次のように書きます。

module.exports=function(api){api.cache(true)// この変換設定関数をキャッシュするconstpresets=[["@babel/preset-env",{"useBuiltIns":"usage","corejs":3,"modules":false}]]constplugins=["@babel/plugin-proposal-optional-chaining"]return{presets,plugins}}

babelの実行

babelのトランスパイルには@babel/core、@babel/cliが必要です。

# package.json作成$ npm init -y# babelのコマンドラインツールのインストール$ npm install @babel/core @babel/cli 
# babelのプラグインのインストール$ npm install @babel/plugin-proposal-optional-chaining
# polyfill系$ npm install @babel/preset-env core-js regenerator-runtime

例えば、次のようなindex.jsを作成します。

index.js
constmessage={hello:'Hello',world:'world!'}console.log(message?.hello+''+message?.world)

.babelrcもしくはbabel.config.jsを作成し、次のコマンドを実行します。

$ babel index.js --out-file out.js

変換後のout.jsは次のようになります。
(ES5文法への変換、optional-chaining文法が変換される)

out.js
varmessage={hello:'Hello',world:'world!'};console.log((message===null||message===void0?void0:message.hello)+''+(message===null||message===void0?void0:message.world));

webpack

webpackは、モダンJavaScriptアプリケーション用の静的モジュールバンドルです。
webpackがアプリケーションを処理するとき、プロジェクトが必要とするすべてのモジュールをマップし、1つ以上のバンドルを生成する依存関係グラフを内部で構築します。
要はwebpackでビルドすることでnode_modules以下の依存関係も含め、1つのJSファイルにまとめることができます。
コアなコンセプトとして以下の機能があります。

  • Entry:ビルド対象のアプリケーションエントリーポイントとなるファイル
  • Output:出力先のフォルダ、ファイル名
  • Loaders:ビルド用ローダー(メインの変換処理を行うための設定)
  • Plugins:ビルドの補助プラグイン
  • Mode:development/productionビルドかの指定
  • Browser Compatibility:ブラウザ最適化

公式:Concept

バージョン4.0.0以降、webpackはプロジェクトをバンドルするための設定ファイルを必ずしも必要としません。
ただし、デフォルトの設定を上書きする際はwebpack.config.jsを作成して設定を記述します。

Entry

エントリポイントは、webpackが内部依​​存関係グラフの構築を開始するために使用するモジュールを示します。
webpackは、エントリポイントが依存する他のモジュールとライブラリを(直接的および間接的に)把握します。
デフォルトでは、その値は./src/index.jsですが、webpack構成でentryプロパティを設定することにより、異なる(または複数のエントリポイント)を指定できます。

webpack.config.js
module.exports={entry:'./path/to/my/entry/file.js'};

Output

outputプロパティは、作成したバンドルを出力する場所とこれらのファイルに名前を付ける方法をwebpackに指示します。
デフォルトでは、メイン出力ファイルの場合は./dist/main.js、その他の生成されたファイルの場合は./distフォルダになります。

設定ファイルにて出力先を変更することも可能です。

webpack.config.js
constpath=require('path');module.exports={entry:'./path/to/my/entry/file.js',output:{path:path.resolve(__dirname,'dist'),// 出力するフォルダ名filename:'my-first-webpack.bundle.js'// 出力するメインファイル名}};

Loader

すぐに使えるwebpackは、JavaScriptファイルとJSONファイルのみを理解します。Loaderを使用すると、webpackは他の種類のファイルを処理し、アプリケーションで使用して依存関係グラフに追加できる有効なモジュールに変換できます。

大まかに言うと、LoaderにはWebpack構成に2つのプロパティがあります。

  1. testプロパティは、変換する対象のファイルを識別します。
  2. useプロパティは、変換を行うために使用するLoaderを指定します。
webpack.config.js
constpath=require('path');module.exports={output:{filename:'my-first-webpack.bundle.js'},module:{rules:[{test:/\.txt$/,use:'raw-loader'}]}};

Plugins

Loaderは特定のタイプのモジュールを変換するために使用されますが、
プラグインを活用して、バンドルの最適化、リソース管理、環境変数の注入などの幅広いタスクを実行できます。
プラグインを使用するには、require()してplugins配列に追加する必要があります。
ほとんどのプラグインはオプションでカスタマイズできます。
プラグインはさまざまな目的のためにconfigで複数回使用できるため、new演算子で呼び出してプラグインのインスタンスを作成する必要があります。

webpack.config.js
constHtmlWebpackPlugin=require('html-webpack-plugin');//installed via npmconstwebpack=require('webpack');//to access built-in pluginsmodule.exports={module:{rules:[{test:/\.txt$/,use:'raw-loader'}]},plugins:[newHtmlWebpackPlugin({template:'./src/index.html'})]};

Mode

modeパラメーターをdevelopment、production、またはnoneに設定することにより、
各環境に対応するwebpackの組み込みの最適化を有効にできます。
デフォルト値はproductionです。

webpack.config.js
module.exports={mode:'production'};

Browser Compatibility

webpackは、ES5準拠のすべてのブラウザーをサポートしています(IE8以下はサポートされていません)。
webpackでは、import()およびrequire.ensure()にPromiseが必要です。
古いブラウザをサポートする場合は、
これらの文法を使用する前にpolyfillをロードする必要があります。

webpackの実行

次のような簡単なReactのプロジェクト構成でビルドしてみます。

├── dist
├── index.html
├── package.json
├── src
│   └── index.tsx
├── tsconfig.json
└── webpack.config.js

webpackのビルドにはwebpackwebpack-clibabel-loaderのパッケージが追加で必要です。
今回はReactのプロジェクトのため、reactreact-dom@babel/preset-reactを追加します。
さらにtypescriptを使うため、typescript@babel/preset-typescript@types/react@types/react-domのパッケージが必要です。
@babel/preset-typescriptは型チェックをしてくれないため、tscで並列で型チェックを行います。
npm-run-allを使うとrun-pコマンドで並列でコマンドを実行することができます。
webpackコマンドでwebpack.config.jsの設定でプロジェクトをビルドすることができます。
package.jsonは次のようになります。

package.json
{"name":"test","version":"1.0.0","license":"MIT","scripts":{"start":"run-p check-types webpack","check-types":"tsc -w","webpack":"webpack --watch"},"devDependencies":{"@babel/cli":"^7.7.4","@babel/core":"^7.7.4","@babel/plugin-proposal-optional-chaining":"^7.7.4","@babel/preset-env":"^7.7.4","@babel/preset-react":"^7.7.4","@babel/preset-typescript":"^7.7.4","@types/react":"^16.9.13","@types/react-dom":"^16.9.4","babel-loader":"^8.0.6","core-js":"^3.4.2","html-webpack-plugin":"^3.2.0","npm-run-all":"^4.1.5","react":"^16.12.0","react-dom":"^16.12.0","regenerator-runtime":"^0.13.3","typescript":"^3.7.2","webpack":"^4.41.2","webpack-cli":"^3.3.10"}}

index.tsxにReactの最小のプログラムを記載します。
Reactの型に関しては次のサイトが参考になります。

参考:TypeScript Deep Dive 日本語版

index.tsx
importReactfrom'react'importReactDOMfrom'react-dom'constmessage={hello:'Hello',world:'world!'}constApp:React.SFC<{message:string}>=(props)=>{return<h1>{props.message}</h1>
}ReactDOM.render(<Appmessage={`${message?.hello}${message?.world}`} />,
  document.getElementById('root')
)

vscodeの場合、Optinal Chainingのエラー表記が出る場合は以下の設定を行うと解消されます
TypeScriptのOptional Chainingは用法用量を守って正しく使え

typescriptのビルド設定をtsconfig.jsonに記載します。
今回はtypescriptのビルドには@babel/preset-typescriptを使うため、型のチェックのみを行います。
"noEmit": trueが重要)

tsconfig.json
{"compilerOptions":{/*トランスパイル後のECMAScriptのバージョン*/"target":"ES2019",/*相対パスではないモジュールはnode_modules配下を検索する*/"moduleResolution":"node",/*今回、トランスパイルはBabelが行うので、`tsc`コマンドでJavaScriptファイルを出力しないようにする*/"noEmit":true,/*厳格な型チェックオプション(noImplicitAny、noImplicitThis、alwaysStrict、strictBindCallApply、strictNullChecks、strictFunctionTypes、strictPropertyInitialization)を有効化する*/"strict":true,/*各ファイルを個々のモジュールとしてトランスパイルする。Babelでは技術的制約で、ネームスペースなどのファイルを跨いだ構文を解釈してトランスパイルできない。このオプションを有効にすれば、BabelでトランスパイルできないTypeScriptの構文を検出して警告を出す*/"isolatedModules":true,/*ESmodules形式以外の、CommonJS形式などのモジュールをdefaultimport形式で読み込める例)constmodule=require('module')->importmodulefrom'module'*/"esModuleInterop":true,/*Reactの場合、JSX文法のチェックを有効にする*/"jsx":"react"},"include":["src/**/*"]}

webpack.config.jsにwebpackのビルド設定を記述します。

webpack.config.js
constHtmlWebpackPlugin=require('html-webpack-plugin')constpath=require('path')module.exports={mode:'development',// 開発モードビルドentry:'./src/index.tsx',// ビルド対象のアプリケーションのエントリーファイルdevtool:"source-map",// ソースマップを出力するための設定、ソースマップファイル(.map)が存在する場合、ビルド前のソースファイルでデバッグができるoutput:{path:path.resolve(__dirname,'dist'),// 出力するフォルダ名(dist)filename:'bundle.js'// 出力するメインファイル名},module:{rules:[{test:/\.ts(x?)$/,// .ts .tsxがbabelのビルド対象exclude:/node_modules/,// 関係のないnode_modulesはビルドに含めないuse:{loader:'babel-loader',// babeloptions:{presets:[// polyfillのpreset['@babel/preset-env',{useBuiltIns:'usage',corejs:3,modules:false// ECMAScript向けビルド}],// reactのpreset'@babel/preset-react',// typescript→javascript変換のpreset'@babel/preset-typescript'],// optional-chaining文法を使うためのプラグインplugins:['@babel/plugin-proposal-optional-chaining']},}}]},plugins:[newHtmlWebpackPlugin({template:'./index.html'})// ビルドしたbundle.jsをindex.htmlに埋め込む]}

index.htmlは以下のようになります。

index.html
<!DOCTYPE html><htmllang="ja"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><metahttp-equiv="X-UA-Compatible"content="ie=edge"><title>タイトル</title></head><body><divid='root'></div><!-- webpackビルドが完了するとHtmlWebpackPluginによりビルド済みのbundle.jsが埋め込まれる --><!-- <script type="text/javascript" src="bundle.js"></script></body> --></body></html>

ビルドには次のコマンドを実行します。
成功するとdistフォルダにbundle.jsが埋め込まれたindex.htmlが生成されます。

$ npm start

webpack is watching the files…

Hash: e532e63e7e9060154c4e
Version: webpack 4.41.2
Time: 1350ms
Built at: 2019-11-25 17:56:00
        Asset       Size  Chunks                   Chunk Names
    bundle.js    1.1 MiB    main  [emitted]        main
bundle.js.map   1.26 MiB    main  [emitted] [dev]  main
   index.html  326 bytes          [emitted]        
Entrypoint main = bundle.js bundle.js.map
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {main}[built]
[./src/index.tsx] 495 bytes {main}[built]
    + 65 hidden modules
Child html-webpack-plugin for"index.html":
     1 asset
    Entrypoint undefined = index.html
    [./node_modules/html-webpack-plugin/lib/loader.js!./index.html] 484 bytes {0}[built]
    [./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {0}[built]
    [./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {0}[built]
        + 1 hidden module
[17:56:01] Found 0 errors. Watching for file changes.


webpackのミドルウェア

webpackのミドルウェアを導入することができれば、さらに開発の効率があがります。
導入方法だけでそれぞれ記事ができてしまうため、ここでは紹介までにしておきます。

  • webpack-dev-derver:開発用のサーバを起動して、その上でwebpackを実行するためのミドルウェア。HMR(Hot Module Replacement)なども設定でき、プログラム修正時にブラウザをリロードすることなくモジュールの再読み込みを行ってくれる。
  • webpack-dev-middleware:バックエンド(NodeJS)でwebpackビルドができるようになるミドルウェア
  • webpack-hot-middleware:バックエンドのwebpackビルド時にHMRしてくれるミドルウェア

Parcel

webpackに関してはビルドの設定の自由度が高い反面、ビルドの設定が難しいという問題があります。
簡易的なプロジェクトであれば、静的バンドラーの一種であるParcelを使うという選択肢もあります。
Parcelはビルド設定ファイルがなく裏側で色々よしなにやってくれる反面、細かいビルド設定ができないという欠点もあります。
本格的なプロジェクトの場合はwebpackをマスターしたほうが後々良いでしょう。

参考:(兎に角)早くプロトタイプを作る技術(初心者向け)


  1. 正確には亜種なスクリプト仕様はいくつかあるのですが一旦置いときます(Google Apps ScriptとかJScriptとか・・・) 

  2. ESMがNodeJS側で参照できるようになれば、npmであらゆるパッケージがバックエンド、フロントエンドの垣根を超えて使えるようになる未来がくるのかも・・・?(さらにエコシステム化が進む) 

Node.js でつくる WASMコンパイラー - Extra1:WASIを使ってWASMを動かす

$
0
0

はじめに

Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニコンパイラー」「ミニインタープリター」とやってきました。そして三部作(?)の最後として、 ミニNode.jsからWASMを生成する小さなコンパイラーに取り組んでいます。

今回の目的

前回で目標としていたNode.js-WASMコンパイラーの最低限の実装が終わりました。今回は生成したWASMをいろいろな環境で動かすべく WASI(WebAssembly System Interface)に対応させたいと思います。

WASI とは

WASIはWebAssemblyをウェブ以外の場所(ブラウザやNode.js以外の環境)で動かせる様にする取り組みです。

WASMのコードを、いろいろなプラアットフォーム上で動かせる様にシステムコールに相当するAPIを標準化する試みです。様々なランタイムが実装されていて、CDNのエッジサーバーや組み込みデバイスで動かす試みもあります。

  • wasmtime ... Rustで作られた、リファレンス的なランタイム環境
  • lucet ... Fastlyが取り組んでいる、CDNエッジ上でWASMを実行することを目指したランタイム
  • WebAssembly Micro Runtime ... 組み込みでも使えることを目指した、軽量ランタイム(JITコンパイラーなし、インタープリターのみ)

WASIを使えば、将来的にCDN上や組み込みデバイス上でWASMを実行できるはずです。ワクワクしますね。

WASIで使える関数

WASIではOSを抽象化して、ファイルやネットワークなどの入出力にアクセスできるようになります。実際にサポートされるAPIはこちらにまとめられています。

これを見ると、「System Interface」と言うだけあってC言語のprintf()やputs()などは存在せず、よりプリミティブな関数がサポートされています。今回のミニWASMコンパイラーの組み込み関数putn()/puts()を実現するために、次の関数を利用することにします。

Hello, WASI

WASI の実行環境

今回はWASIの実行にwasmtimeを使います。ビルドにはRustとcargoが必要です。

wasmtimeのビルド
$ git clone --recurse-submodules https://github.com/bytecodealliance/wasmtime.git
$ cd wasmtime
$ cargo build --release
$ ./target/release/wasmtime --version
0.7.0

文字列の出力

さっそくWASIを使った文字列出力にチャンレンジしてみます。次のWATファイルを用意しました。

hello_wasi.wat
(module
    ;; -- WASIの fd_write()をインポイートするため宣言 -- 
    ;;    fd_write(File Descriptor, *iovs, iovs_len, nwritten)
    ;;      -> Returns number of bytes written
    (import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))

    (memory 1)
    (export "memory" (memory 0))
    (data (i32.const 16) "Hello WASI\n") ;; 'Hello WASI\n' をメモリ上に確保 (offset 16 bytes, length 11 bytes)

    ;; -- メイン関数は _start() としてエクスポート --
    (func $main (export "_start")
        ;; iov (バッファーのアドレスと、長さのセット)をメモリ上に用意
        (i32.store (i32.const 0) (i32.const 16))  ;; バアッファーの先頭アドレス(=offset)
        (i32.store (i32.const 4) (i32.const 11))  ;; バッファーの長さ

        (call $fd_write
            (i32.const 1) ;; ファイルでスクリプタ - 1:stdout
            (i32.const 0) ;; iovのセットへのアドレス
            (i32.const 1) ;; iovのセットの長さ - [buffer, length]のセットの数
            (i32.const 8) ;; *nwritten - 出力されたバイト数を受け取るポインター
        )
        drop ;; 戻り値として出力されたバイト数が帰ってきているので、それを破棄
    )
)
  • fd_write()関数をインポート
  • エントリーポイントとなるメイン関数を _start() という名前でエクスポート
  • 出力する文字列をメモリ上に確保
  • 「文字列バッファーの先頭アドレスと、その長さ」のセットをメモリ上に確保 ... iov
  • 先のiovのアドレスと、そのセット数を指定して、fd_write()を呼び出す

実行結果はこちら

$ wasmtime hello_wasi.wat
Hello WASI

無事出力されました。

WASI対応コンパイラー

これまで作った Node.js-WASMコンパイラー mininode_wasm_08.jsを、WASI向けに改造します。

WASI関数のインポート

いままでは呼び出し側(Node.js)でputn(), puts()の実体を用意したものをWASM内部でインポートしていました。その代わりにWASIのランタイムからfd_write()をインポートします。

// ---- compile simplified tree into WAT ---functioncompile(tree,gctx,lctx){// ... 省略 ...letblock='(module'+LF();// -- builtin func (imports) --block=block+TAB()+';; ---- builtin func imports ---'+LF();// --- normal WASM ---//block = block + TAB() + '(func $putn (import "imports" "imported_putn") (param i32))' + LF();//block = block + TAB() + '(func $puts (import "imports" "imported_puts") (param i32))' + LF();// --- WASI ---block=block+TAB()+'(import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))'+LF();// ... 省略 ...}

メイン関数の宣言

メイン関数のエクスポート宣言部分も_start()に変更します。

// ---- compile simplified tree into WAT ---functioncompile(tree,gctx,lctx){// ... 省略 ...block=block+TAB()+';; ---- export main function  ---'+LF();// --- normal WASM ---//block = block + TAB() + '(export "exported_main" (func $main))' + LF();// --- WASI ---block=block+TAB()+'(export "_start" (func $main))'+LF();// ... 省略 ...}

整数の出力 putn()

インポートしたfd_write()を内部で呼び出して、符号付32ビット整数を表示するputn()関数を作ります。あらかじめWATで記述した別ファイルを用意した関数を用意しておき、コンパイラでWATを生成する際に連結する方式ににします。

putn() は内部で次の処理を行います。

  • (1) 整数値を文字列で表現した時の桁数を算出する
    • この時、マイナス値の場合はマイナス記号分もカウントする
  • (2) 整数値がマイナスの場合は絶対値をとる
  • (3) 一桁ずつ取り出し、1文字のASCIIキャラクターに変換、メモリー上に格納する
    • 例) 1 --> 49 (0x31)
  • (4) 最後に改行文字(`\n')を入れる、マイナス値だったら先頭にマイナス記号を格納する
  • (5) fd_write()を呼び出すためのパラメーターをメモリー領域に準備する
    • パラメーターは、あらかじめメモリー上の決まった位置にダミーの値で確保
    • 毎回値を書き換えて使う
  • (6) fd_write()を呼びだす

これを全てWATで手書きするのはなかなか骨が折れます。そこで、一部をJSで記述して、前回までのコンパイラーを使ってWATを生成することにしました。直接メモリーをいじったり関数を呼び出すところはコンパイラーでサポートしていないので、手書きすることになります。

整数を文字列に変換する部分を _convI32ToString() として抜き出してJSで実装します。担当する処理は上記(1)~(3)の範囲です。

_convI32ToString()
function_convI32ToString(n){letrestValue=n;letisMinus=0;letdummy;if(_isMinus(n)){restValue=-n;isMinus=1;dummy=_storeChar(0,45);// minus mark '-'}letlen=_calcLength(restValue);letidx=len-1;letdigitChar=0;while(idx>=0){digitChar=_getOneDigit(restValue);_storeChar(idx+isMinus,digitChar);restValue=_div10(restValue);idx=idx-1;}returnlen+isMinus;}

実際にはさらに次の内部関数を呼び処理を行っています。
- _calcLength() ... 整数が文字列にした場合に何桁になるかを算出
- _getOneDigit() ... 整数の一の位をASCIIコードに変換
- _div10() ... 整数を1/10にする
- _isMinus() ... 整数がマイナス値かどうかを判定
- _storeChar() ... 1文字分をメモリーに格納するダミー
- 実際にはWASMのメモリーに値を格納する処理に後で置き換える

// calc char length of int32//  NOT support minusfunction_calcLength(n){letrestValue=n;letlen=1;while(restValue>=10){restValue=restValue/10;len=len+1;}returnlen;}// get 1 digit char codefunction_getOneDigit(n){constr=n%10;constc=48+r;// '0' + rreturnc;}// div 10function_div10(n){constd=n/10;// calc as intreturnd;}// --- for node direct ---//let _strBuf = '....................';function_storeChar(idx,charCode){puts(' _storeChar() called. idx, charCode bellow');putn(idx);putn(charCode);/* --- for Node.js direct --- *///let ch = String.fromCharCode(charCode);//_strBuf = _strBuf.slice(0, idx) + ch + _strBuf.slice(idx + 1);return0;}function_isMinus(n){if(n<0){return1;}return0;}

前回のWASMコンパイラー mininode_wasm_08.jsでコンパイルした結果の抜粋はこちらです。実際にはこれを手で修正して利用しています。

watの抜粋
 (func $_calcLength (param $n i32) (result i32)
    (local $restValue i32)
    (local $len i32)
    get_local $n
    set_local $restValue

    i32.const 1
    set_local $len

    loop ;; --begin of while loop--
      get_local $restValue
      i32.const 10
      i32.ge_s
      if
        get_local $restValue
        i32.const 10
        i32.div_s

        set_local $restValue

        get_local $len
        i32.const 1
        i32.add

        set_local $len

        br 1 ;; --jump to head of while loop--
      end ;; end of if-then
    end ;; --end of while loop--
    get_local $len
    return

    i32.const 88
    return
  )

  (func $_div10 (param $n i32) (result i32)
    (local $d i32)
    get_local $n
    i32.const 10
    i32.div_s

    set_local $d

    get_local $d
    return

    i32.const 88
    return
  )

  (func $_convI32ToString (param $n i32) (result i32)
    (local $restValue i32)
    (local $isMinus i32)
    (local $dummy i32)
    (local $len i32)
    (local $idx i32)
    (local $digitChar i32)
    get_local $n
    set_local $restValue

    i32.const 0
    set_local $isMinus

    get_local $n
    call $_isMinus

    if
      i32.const 0
      get_local $n
      i32.sub

      set_local $restValue

      i32.const 1
      set_local $isMinus

      i32.const 0
      i32.const 45
      call $_storeChar

      set_local $dummy

    end

    get_local $restValue
    call $_calcLength

    set_local $len

    get_local $len
    i32.const 1
    i32.sub

    set_local $idx

    i32.const 0
    set_local $digitChar

    loop ;; --begin of while loop--
      get_local $idx
      i32.const 0
      i32.ge_s
      if
        get_local $restValue
        call $_getOneDigit

        set_local $digitChar

        get_local $idx
        get_local $isMinus
        i32.add

        get_local $digitChar
        call $_storeChar

        get_local $restValue
        call $_div10

        set_local $restValue

        get_local $idx
        i32.const 1
        i32.sub

        set_local $idx

        br 1 ;; --jump to head of while loop--
      end ;; end of if-then
    end ;; --end of while loop--
    get_local $len
    get_local $isMinus
    i32.add

    return

  )

この生成した関数を使って、putn()を実現します。

putn()
  (func $putn(param $n i32)
    (local $strLen i32)
    get_local $n
    call $_convI32ToString ;; ret=Lenght
    set_local $strLen

    ;; write tail LF
    i32.const 12 ;; head of string buffer
    get_local $strLen
    i32.add
    i32.const 10 ;; LF
    i32.store8 

    ;; +1 length for tail LF
    get_local $strLen
    i32.const 1
    i32.add
    set_local $strLen

    ;; iov.iov_base 
    i32.const 4
    i32.const 12
    i32.store

    ;; iov.iov_len
    i32.const 8
    get_local $strLen
    i32.store

    ;; $fd_write
    i32.const 1 ;; file_descriptor - 1 for stdout
    i32.const 4 ;; *iovs - The pointer to the iov array, which is stored at memory location 0
    i32.const 1 ;; iovs_len - We're printing 1 string stored in an iov - so one.
    i32.const 0 ;; nwritten - A place in memory to store the number of bytes writen
    call $fd_write

    drop ;; Discard the number of bytes written from the top the stack
  )

文字列の出力 puts()

同様に、固定文字列を表示するputs()関数も作ります。puts() は内部で次の処理を行います。

  • (1) 出力する文字列のアドレスを受け取る
  • (2) 別のメモリー領域に文字列をコピーする
  • (3) 最後に改行文字(`\n')を入れる
  • (4) fd_write()を呼び出すためのパラメーターをメモリー領域に準備する
    • パラメーターは、あらかじめメモリー上の決まった位置にダミーの値で確保
    • 毎回値を書き換えて使う
  • (5) fd_write()を呼びだす

これを全てWATで手書きするのはなかなか骨が折れます。そこで、一部をJSで記述して、前回までのコンパイラーを使ってWATを生成することにしました。直接メモリーをいじったり関数を呼び出すところはコンパイラーでサポートしていないので、手書きすることになります。

今回のputs()の例では「(2)別のメモリー領域に文字列をコピーする」部分をJSファイルで書いてからコンパイラーで生成したものを参考にし、残りは手書きで作りました。

puts()
  (func $puts (param $n i32)
    (local $srcIdx i32)
    (local $destIdx i32)
    (local $len i32)
    (local $c i32)
    get_local $n
    set_local $srcIdx

    i32.const 0
    set_local $destIdx

    i32.const 0
    set_local $len

    get_local $srcIdx
    call $_loadChar

    set_local $c

    loop ;; --begin of while loop--
      get_local $c      
      if
        get_local $destIdx
        get_local $c
        call $_storeChar

        get_local $len
        i32.const 1
        i32.add

        set_local $len

        get_local $srcIdx
        i32.const 1
        i32.add

        set_local $srcIdx

        get_local $destIdx
        i32.const 1
        i32.add

        set_local $destIdx

        get_local $srcIdx
        call $_loadChar
        set_local $c

        ;; check lenght 255
        get_local $destIdx
        i32.const 255
        i32.lt_s
        br_if 1

        ;; br 1 ;; --jump to head of while loop--
      end ;; end of if-then
    end ;; --end of while loop--

    ;;get_local $len
    ;;call $putn

    ;; tail LF
    get_local $destIdx
    i32.const 10 ;; LF
    call $_storeChar

    get_local $len
    i32.const 1
    i32.add
    set_local $len

    ;; iov.iov_base 
    i32.const 4
    i32.const 12
    i32.store

    ;; iov.iov_len
    i32.const 8
    get_local $len
    i32.store


    ;; $fd_write
    i32.const 1 ;; file_descriptor - 1 for stdout
    i32.const 4 ;; *iovs - The pointer to the iov array, which is stored at memory location 0
    i32.const 1 ;; iovs_len - We're printing 1 string stored in an iov - so one.
    i32.const 0 ;; nwritten - A place in memory to store the number of bytes writen
    call $fd_write

    drop ;; Discard the number of bytes written from the top the stack 
  )

WASI対応コンパイラーの拡張

テンプレートの用意

用意したビルトイン関数putn(), puts()はこちらの別ファイルに保存しておき、コンパイラーで読み込んで使います。

テンプレート読み込みモジュール

今回のミニNode.js-WASMコンパイラーでは、最初に作っていた「ミニインタープリター」で動かす、という縛りを設けています。ミニインタープリターではファイルの読み書きを直接はサポートしておらず、外部モジュールとして準備しています。なので今回のテンプレートファイルも外部モジュールを用意してそちらで読み込みます。

// -------------------------// module_wasibuiltin.js - WASM builtin for WASI// - puts()// - putn()// -------------------------'use strict'constfs=require('fs');constprintln=require('./module_println.js');constabort=require('./module_abort.js');constprintWarn=require('./module_printwarn.js');constbuiltinTamplateFile='wasi_builtin_template.watx';// === exports ===// --- parser ----module.exports=wasiBuiltin;functionwasiBuiltin(){constbuiltinFuncs=fs.readFileSync(builtinTamplateFile,'utf-8');//println(builtinFuncs);returnbuiltinFuncs;}

fd_write()呼び出し用のパラメータ領域

fd_write()の呼び出しで使うパラメータをメモリ上に確保しておきます。

  • オフセット位置 0バイト目から、4バイト分 ... 実際に出力したバイト数を受け取るための領域
  • オフセット位置 4バイト目から、4バイト分 ... 出力するバイト列の組の最初のアドレスを格納する領域
  • オフセット位置 8バイト目から、4バイト分 ... 出力するバイト列の組の数
  • オフセット位置 12バイト目から、255バイト分 ... 出力するバイト列を格納する領域
functiongenerateMemoryBlock(){letblock='';block=block+TAB()+'(memory 1)'+LF();block=block+TAB()+'(export "memory" (memory 0))'+LF();block=block+TAB()+'(data (i32.const 0) "\\00\\00\\00\\00") ;; placeholder for nwritten - A place in memory to store the number of bytes written'+LF();block=block+TAB()+'(data (i32.const 4) "\\00\\00\\00\\00") ;; placeholder for iov.iov_base (pointer to start of string)'+LF();block=block+TAB()+'(data (i32.const 8) "\\00\\00\\00\\00") ;; placeholder for iovs_len (length of string)'+LF();block=block+TAB()+'(data (i32.const 12) "hello world\\n")  ;; 4--> iov.iov_base = 12, 4--> iov_len = 8, 12-->"hello ...":len=13'+LF();returnblock;}

この領域をputn(), puts()で利用しています。

テンプレートの連結

コンパイラーでWATファイルを生成する際に、ユーザ定義関数に引き続きテンプレートとして用意しておいたputn(), puts()のWATコードを連結して出力します。

functioncompile(tree,gctx,lctx){// ... 省略 ...// ---- global user_defined functions ---block=block+generateGlobalFunctions(gctx);// ---- builtin function for wasi ---block=block+wasiBuiltin();// --- close all ---block=block+')';returnblock;}

WASI向けのコンパイル&実行

今回作ったコンパイラーはこちらです。

これを使って、これまでのサンプルをコンパイル、wasmtimeを使って実行してみましょう。(wasmtimeはテキスト形式の.wat、バイナリ形式の.wasmの両方を実行することができます)

FizzBuffの例

$ node mininode_wasm_wasi.js sample/fizzbuzz_func.js
$ wasmtime generated.wat
1
2
Fizz
4
Buzz
Fizz
7
... 省略 ...
94
Buzz
Fizz
97
98
Fizz
Buzz
$

WASIランタイム上で、無事FizzBuzzを実行できました!

ここまでのソース

GitHubにソースを上げておきます。

nvmのnodeのバージョンごとにyarnをインストールする方法

$
0
0

これまで、brewでインストールしたyarnを使っていたのだが、nvmでインストールした各Node.jsのバージョンごとにyarnのバージョンを変更したくなった。
調べてみると、npmでyarnをインストールすることで実現可能なことがわかった。

ちなみにyarnの公式にyarnをnpm経由でインストールするのはお勧めできないとの記述があるのだが、開発で必要になったのでメモを残しておく。

注意: npm から Yarn をインストールすることは一般的にはお勧めしません。 Node ベースのパッケージマネージャで Yarn をインストールする場合は、パッケージは署名されておらず、整合性のチェックはベーシックな SHA1 ハッシュのみで行われており、システム全体にまたがるアプリケーションをインストールする場合にはセキュリティリスクとなります。
これらの理由から、使用中の OS に最も適した方法で Yarn をインストールすることを強くお勧めします。

実行環境

macOS 10.14.6
nvm 0.35.1
npm 6.12.1

手順

nvmでNode.jsをインストールして、yarnをインストールする。

$ nvm install 12.13.1
$ nvm use 12.13.1
$ npm install -g yarn

上記を実行するとバージョン12.13.1用のディレクトリにyarnがインストールされる。
yarnをインストールする時に、バージョンは指定しなかったので、現時点で最新の1.19.2がインストールされた。

下記コマンドで確認。

$ which yarn
/Users/hoge/.nvm/versions/node/v12.13.1/bin/yarn
$ yarn -v
1.19.2

次に、nvmで新たにバージョン13.2.0のNode.jsをインストールして、同じくyarnをインストールする。
yarnのインストールの際、npm install hoge@*.*.*のように@の後ろにバージョンを指定することで特定のバージョンをインストールすることができる。

$ nvm install 13.2.0
$ nvm use 13.2.0
$ npm install -g yarn@1.18.0

先ほどインストールしたyarnはそのまま残り、バージョン13.2.0用のディレクトリに新たにyarnがインストールされる。

which yarn
/Users/takuma/.nvm/versions/node/v13.2.0/bin/yarn

また、バージョンを確認すると、先ほど指定した1.18.0がインストールされている。

$ yarn -v
1.18.0

ちなみにbrewのyarnはアンインストールせずに、そのまま残しておいて大丈夫!

【自分用】脆弱性への対処法①

$
0
0

主な脆弱性

(脆弱性への対処法①)
・OSコマンド・インジェクション
・SQLインジェクション
・ディレクトリ・トラバーサル
・セッションハイジャック
脆弱性への対処法②
・クロスサイト・スクリプティング(XSS)
・クロスサイト・リクエストフォージェリー(CSRF)
・HTTPヘッダインジェクション
・クリックジャギング

脆弱性の具体例と対策

OSコマンド・インジェクション

OSコマンド・インジェクションは、ユーザーからデータや数値の入力を受け付けるようなWebサイトなどにおいて、プログラムに与えるパラメータにOSへの命令文を紛れ込ませて不正に操作する攻撃です。
引用:OSコマンドインジェクションの仕組みとその対策

具体的には、以下のような例です。

index.js
'use strict';consthttp=require('http');//他のプログラムを実行することができるモジュール。constcp=require('child_process');constserver=http.createServer((req,res)=>{constpath=req.url;//同期的にシェルコマンドを実行するexecSync関数res.end(cp.execSync('echo '+path));});constport=8000;server.listen(port,()=>{console.info('Listening on '+port);});

以上のファイルができたら、サーバーを起動します。

$ node index.js

サーバーを起動したら、以下のURLにアクセスしてください。
http://localhost:8000/taroimo;ls
スクリーンショット 2019-11-25 15.18.03.png
URLに紛れた、lsというコマンドが実行され、ディレクトリ内のファイルが見えてしまっています。
このようにコマンドが実行できる状態は、メールを送られたり、個人情報が見られたりするなど危険な状態です。

OSコマンド・インジェクションの対処法は、クライアントから受け取ったデータを使ってOSコマンドを実行する処理を含めないことや、コマンドが実行されないようなエスケープ処理をすることです。

SQLインジェクション

SQLインジェクションは、ユーザーが入力したSQLの命令(クエリ)により、データベースが操作されてしまうことです。

例えば、データベースのbooksというテーブルのidが〇〇番から、title, authorのデータを取り出したいときにSQLで以下のように記述します。
id=@idの@idはユーザーが入力する〇〇番を指します。

SELECTtitle,authorFROMbooksWHEREid=@id

この場合@idに、0 OR 1=1を入力したとしたら、

SELECTtitle,authorFROMbooksWHEREid=0OR1=1

となりますが、「WHERE 1=1」とは必ず正(true)であるという意味なので、全てのidに当てはまるデータが取り出されてしまいます。

SQL をアプリケーションから利用する場合、SQL 文のリテラル部分をパラメータ化することが一般的である。パラメータ化された部分を実際の値に展開するとき、リテラルとして文法的に正しく文を生成しないと、パラメータに与えられた値がリテラルの外にはみ出した状態になり、リテラルの後ろに続く文として解釈されることになる。この現象が SQL インジェクションである。
参考:安全なSQLの呼び出し方

これを防ぐためには、パラメータ化する必要があります。
node-postgresモジュールを使った
パラメータ化は以下のようにします。
※ $1には、client.queryの第二引数'3'が入ります。

//node-postgresモジュールの導入const{Client}=require('pg');//データベースの設定constclient=newClient({//データベースの設定を記述する});//データベースへの接続 client.connect();//ここからパラメータ化constquery='SELECT title, author FROM books WHERE id = $1';client.query(query,['3'],(err,result)=>{if(err){console.log(err.stack)}else{console.log(res.rows[0])// {'3'}}})

ディレクトリ・トラバーサル

ディレクトリ・トラバーサルは、意図していないディレクトリ/ファイルの閲覧、操作をされてしまうことです。
これは、ユーザーにファイルを渡すときに、選択的や自動的なファイルの送信を行うことで防ぐことができます。つまり、ユーザーにファイル渡す場合に、条件以外のものを指定できないようにすれば良いのです。

参考:ディレクトリトラバーサルとは?情報漏洩を防ぐ方法

セッションハイジャック

セッションとは、システムにログインしてからログアウトするまでの通信のこと、または通信が確立してから切断されるまでの流れのことを意味します。

セッションハイジャックとは、他人の識別用のID(セッションID)を乗っ取り、なりすましをすることです。

セッションIDとは、WebサーバとWebブラウザで情報を共有する仕組みであるCookieなどを応用し、サーバが初回アクセス時に発行してクライアントが保存するID。以降は通信のたびにクライアント側からセッションIDを申告することで、同時にアクセス中の利用者の識別を行う。利用者自体を継続的に識別するユーザー名などの識別子とは異なり、機械的に生成されて一時的に利用されるもので、一連の通信が終了すると破棄される。同じ利用者が次に通信を開始すると新しいセッションIDが与えられる。
参考:セッションID【SID】

セッションハイジャックをされないようにするためには、セッションIDをハッシュ関数でハッシュ値とします。

ハッシュ関数とは、入力した値から、長さの決まったまったく別の値(ハッシュ値)を生成する関数です。入力した値が1文字でも100文字でも決まった長さのハッシュ値が出力されます。ハッシュ値から元の値を探し出すのは難しいため、会員情報を取り扱うサイトは一般的にパスワードをハッシュ値にしています。会員制度でなくセッションIDで投稿者を判断する2chのような掲示板でも、ハッシュ関数が使われています。

ハッシュ関数のアルゴリズムには次のような種類があります。
・MD5
・bcrypt
・SHA
・RIPEMD

<MD5>

$ echo -n 文字 | md5sum
> 96f5a0cfa940f3a41a2ed3cbd9c55119  -

となります。出力された文字がハッシュ値となります。

htpasswdモジュールを使えば、md5、bcrypt、shaなどのアルゴリズムを使いパスワードをハッシュ値にすることができます。

$ yarn global add htpasswd@2.4.0
> success Installed "htpasswd@2.4.0" with binaries:
      - htpasswd

htpasswdは以下のような文法で使います。

$ htpasswd [options] [passwordfile] username [password]

htpasswd [オプションの指定] [パスワードの保存ファイル] [セッションIDなど] [パスワード]
という意味です。
オプションにはどのアルゴリズムを使うかなどの設定ができます。
ハッシュ化し、指定したファイルの中身をみると、以下のようになっているはずです。

passwordfile
username:$2aUHuh8uUG33jHUBUuHijofoBVUDE3aAQ98B1QNt3RC

このようにセッションIDを使う際は、セッションハイジャックされないように、ハッシュ値にして保存しておくと良いです。

参考:N予備校 プログラミングコース

【自分用】脆弱性への対処法②

$
0
0

主な脆弱性

脆弱性への対処法①
・OSコマンド・インジェクション
・SQLインジェクション
・ディレクトリ・トラバーサル
・セッションハイジャック
(脆弱性への対処法②)
・クロスサイト・スクリプティング(XSS)
・クロスサイト・リクエストフォージェリー(CSRF)
・HTTPヘッダインジェクション
・クリックジャギング

脆弱性の具体例と対策

クロスサイト・スクリプティング(XSS)

XSSとは、動的にHTMLを生成する機能(JavaScriptなどによってHTMLが生成されている機能)において、悪意を持ったユーザーにHTML、JavaScript、CSSの変更がなされてしまうことです。

例えば、掲示板など自分の書いた情報が反映されるアプリがあるとした場合、

<script>window.onload=function(){document.getElementsByTagName('h1')[0].innerHTML='たろいも';}</script>

以上のようなスクリプトを投稿し、仮に掲示板にXSS対策がなされていない場合、
HTMLのh1タグの中身がすべて"たろいも"となってしまいます。

XSSの対処法は、X-XSS-Protectionを設定することです。
Internet Explorer, Chrome, Safariでは、X-XSS-Protectionレスポンスヘッダを設定することができます。これは、クロスサイトスクリプティング (XSS) 攻撃を検出したときに、ページの読み込みを停止するためのものです。
引用:X-XSS-Protection

Node.jsの場合は、Expressフレームワークのhelmetというモジュールを利用すれば簡単に、X-XSS-Protectionを導入できます。

//helmetモジュールconsthelmet=require('helmet')//X-XSS-Protectionの設定app.use(helmet.xssFilter())

参考:XSS Filter

クロスサイト・リクエストフォージェリー(CSRF)

CSRFとは、掲示板や問い合わせフォームなどを処理するWebアプリケーションが、本来拒否すべき他サイトからのリクエストを受信し処理してしまいます。
参考:トレンドマイクロ

例えば、ヤフーニュースに脆弱性があったならば、以下の私の電話番号は〇〇-〇〇-〇〇ですという文が、ボタンを押したらコメント欄に投稿されてしまいます。

<!DOCTYPE html><htmllang="ja"><head><metacharset="UTF-8"><title>偽物の投稿フォーム</title></head><body><h1>偽物の投稿フォーム</h1><formmethod="post"action="https://headlines.yahoo.co.jp/cm/main?d=20191125-00615521-fullcount-base&expand_form"><inputtype="hidden"name="content"value="私の電話番号は〇〇-〇〇-〇〇です"><buttontype="submit">ボタン</button></form></body></html>

スクリーンショット 2019-11-25 20.54.49.png
仮に脆弱性に問題があったならば、以上の「ボタン」が以下の「投稿する」ボタンと繋がっていることになる。
スクリーンショット 2019-11-25 21.05.07.png

CSRFを防ぐためには、ワンタイムトークンを投稿フォームに埋め込み、トークンを消費する形で投稿をさせます。
ワンタイムトークンは一度しか利用できないため、「トークンが発行されてから使用するまで」、外部にトークンが漏れることがなければ勝手にリクエストが処理されることはありません。

HTTPヘッダインジェクション

HTTP通信では、はじめにユーザーが、ブラウザを利用してリクエストを送ります。
リクエストを受け取ったWebサーバーが、ユーザー(が利用するブラウザ)に対してレスポンスを返します。
HTTPヘッダインジェクションは、HTTPレスポンスヘッダの出力処理に対して行われます。具体的には「クッキーが勝手にセットされる」「他のURLへリダイレクトされる」などの問題が発生します。

参照:【HTTPヘッダインジェクション】の仕組みと対策のまとめ記事

以下はHTTPレスポンスの構成情報です。
改行の上がレスポンスヘッダ
改行の下がレスポンスボディ
となります。

HTTP/1.1200OKDate:Wed, 18 Oct 2010 18:21:19 GMTServer:Apache/1.3.12 (Unix) (Red-Hat/Linux)Expires:-1Accept-Ranges:bytesCache-Control:private, max-age=0Content-Type:text/html; charset=UTF-8<-----改行-----><!DOCTYPE html><html itemscope="" itemtype="http://localhost:8000" lang="ja"><head><title>たろいものごはん</title></head>(以下略)

これに対して、例えば以下のURLにアクセスします。

http://localhost:8000?location=%0d%0d<script type=”text/javascript”>alert(“HTTPヘッダにインジェクション”);</script>

?location= の後に %0d%0d とありますが、これは改行を意味します。
つまり、以下のようなHTTPレスポンスとなってしまったということです。

HTTP/1.1200OKDate:Wed, 18 Oct 2010 18:21:19 GMTServer:Apache/1.3.12 (Unix) (Red-Hat/Linux)Expires:-1Accept-Ranges:bytesCache-Control:private, max-age=0Content-Type:text/html; charset=UTF-8Location:<-----改行-----><script type=”text/javascript”>alert(“HTTPヘッダにインジェクション”);</script><!DOCTYPE html><html itemscope="" itemtype="http://localhost:8000" lang="ja"><head><title>たろいものごはん</title></head>(以下略)

レスポンスヘッダのLocationの後に改行がなされ、

<script>がレスポンスボディと認識されてしまっています。
これはHTMLの改ざんなど、危険な状態です。

HTTPヘッダインジェクションの対策としては、
外部からのリクエストURLを、レスポンスヘッダの値へ設定しない。
リクエストURLを、レスポンスヘッダの値に設定する場合は「改行コード( %0d%0d)」をエスケープするなどの方法があります。

クリックジャギング

クリックジャギングとはiframeを使ったボタンの透明化などの設定をすることで、ユーザーの意図しないクリックを誘発することです。
<iframe>(インラインフレーム要素) は、入れ子になった閲覧コンテキストを表現し、現在のHTMLページに他のページを埋め込むことができます。
例えば、googleMapが埋め込んであるサイトはその例です。

これは、HTTPレスポンスヘッダ内のX-Frame-Optionsの設定をすることで防ぐことができます。X-Frame-Optionsは、HTMLにおけるiframeなどの「フレームというHTML内にHTMLを読み込む」機能に対して、どのようなサイトからの読み込みを許可するかという設定です。

//ページをフレーム内に表示することはできません。
X-Frame-Options: deny
//同じサイト内のページのみフレームに表示できる。
X-Frame-Options: sameorigin
//指定したURLのページのみフレームに表示できる。
X-Frame-Options: allow-from https://example.com/

Node.jsでは、Expressのhelmetモジュールを使い、X-Frame-Options:SAMEORIGINの設定をすることができます。

consthelmet=require('helmet');constapp=express();app.use(helmet.frameguard({action:"sameorigin"}));

参考:N予備校 プログラミングコース

Nodejs Send Email Using Nodemailer


最速 install ?! electron のはじめ方

$
0
0

最速 install ?! electron のはじめ方

みなさまこんにちは。ハーツテクノロジーの山崎です。この記事は仕事の中で得たられた知見によって書かれています。

どんな話?

なんか面白そうなので、electronを使ってプログラムを作ってみようと思ってググってみたけど、何をインストールすればいいのかいまひとつ理解できなかったので、理屈はいいから、どこかにサクッと動かすまでの手順だけまとめて書いてないかな?と思ったひと向けの記事です。

electronってなに?」「node.jsってなに?」「JavaScriptってなに?」なひとは対象にしていないです。まぁ、Qiita を読んでいるひとにそんなひとはいないと思っていますが。

あ、Windows 環境で解説します。Mac や Linux なかた、ごめんなさい。

1, フォルダ(作業環境)を作る

好きな名前でフォルダを作成してください。個人的には日付を入れるのが好み。
image.png
image.png

2, フォルダに electronを入れる

npm コマンドを使って electron を install しますが、グローバルにインストールせず、作ったフォルダにローカルインストールします。

2-1, コマンドプロンプトを開いてすぐにカレントディレクトリをそのフォルダに移動

> cd <フォルダパス>

image.png

2-2, node.jsがインストールされていることを確認し、無ければインストール

> node -v

image.png

node.js のインストールの仕方はここでは解説しないので、各自でググってください。

2-3, 本格的なアプリを作るつもりなら先に package.json を作っておく(お試しならば飛ばせる項目です)

> npm init

npmpackage.jsonの使い方に関しても、同じくここでは解説しないのでググってください。

2-4 electronをインストール

npmコマンドで、electronをインストールする。

> npm install electron

electron-install-01.png
package.json 無しで実行したので警告(WARN)がでてます。

node_modules フォルダが作成され、そこに electron > dist > electron.exe が入っていることを確認。
-gオプションを付けてたりすると別の場所に入るので注意。VSCodeでのデバッグ起動も難しくなるかもしれない。-gを使うとどんなライブラリが入っているのか管理が難しくなるので個人的におすすめしないです。使わないほうをおすすめしてます。

image.png

ここにありました。ちなみにダブルクリックして実行してみると、
image.png
こんな感じで、バージョンが表示されたりします。ここでは exe の位置が確認できればおっけー。

3, フォルダにファイルを2つ用意する

新規ファイルで以下の2つのファイルを作成し、エディタで開いて、コピペ&保存終了してください。

  • main-process.js
main-process.js
'use strict'const{app,BrowserWindow}=require('electron')// Electron 本体letmain_gui=nullapp.on('ready',()=>{letwin_option={frame:false,// electron の タイトルバーを消す(必ず -webkit-app-region: drag; とペアで使うこと)width:900,height:700,backgroundColor:'#eee',webPreferences:{experimentalFeatures:true,// backdrop-filter: すりガラス機能を有効にするnodeIntegration:true,// electron v5.0.0 から、明示的に true を指定しないと、require( 'electron' ) に失敗する}}win_option.width=1300// debug 少し 400横に広くするmain_gui=newBrowserWindow(win_option)main_gui.loadURL('file://'+__dirname+'/renderer-process.html')main_gui.webContents.openDevTools()// debug})

コメントの通り、タイトルバーを表示したい場合はframe: false,を消してください。ちなみに、タイトルバーのデザインを変更する手段は、、、、無いみたい。

experimentalFeatures: true,もコメントの通り「磨りガラス機能backdrop-filter」なので普通はいらないかな。

これらのコードは、デバッグ画面の表示なので、リリース時には消すこと。

win_option.width = 1300    // debug 少し 400横に広くする

main_gui.webContents.openDevTools()     // debug
  • renderer-process.html
renderer-process.html
<!DOCTYPE html><html><body><divstyle="-webkit-app-region: drag;">Hello electron world!</div></body></html>

この HTML に <script> ... </script> tag を追加して、javascript のコードを書いていくことになる。

4, 実行(動作確認)

この段階で、フォルダ内は、こんな感じになっているはず。
electron-install-04.png

準備が終わったら、いよいよ実行です。先程確認した electron.exeの引数にJavascript のファイル main-process.js を引数に指定して実行します。
コマンドラインから

> node_modules\electron\dist\electron.exe main-process.js

これで、以下が表示されればおっけー。

electron-install-02.png

タイトルバーを消しているので、終了する(閉じる)のに苦労するかと思う。Hello の div tagをタイトルバーとして機能するように指定-webkit-app-region: drag;しているので、右クリックでメニューが出ます。「閉じる」を選択すると終われます。
electron-install-03.png

おめでとうございます。electron のプログラムが起動しました。あとは、好きなようにコードを改良してください。

ここまで、5分くらい??
たぶん最速。違ってたらごめんなさい。

最近の版では、main-process.js はほとんど触らなくても renderer-process.html 側になんでも書けて実行できちゃうようです。
いい時代になりました。

5, VSCodeからデバッグ実行

ここから先は、さらに極めたいひと向け。

VSCodeのことはここでは説明しない。

[F5]キーでデバッグ実行

.vscode/launch.jsonファイルを以下のように記述すると、[F5]キーでデバッグ実行できるようになる。

launch.json
{// IntelliSense を使用して利用可能な属性を学べます。// 既存の属性の説明をホバーして表示します。// 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387"version":"0.2.0","configurations":[{"type":"node","request":"launch","name":"Electron Main","runtimeExecutable":"${workspaceFolder}/node_modules/.bin/electron","program":"${workspaceFolder}/main-process.js",// <- 修正"skipFiles":["<node_internals>/**"]}]}

image.png

コードを更新してみる

たとえば、このtag <button onclick="require('electron').remote.app.quit()">x</button>を追加すると、ボタンを押すだけで、アプリを終了できるようになります。

renderer-process.html
<!DOCTYPE html><html><body><divstyle="-webkit-app-region: drag;">Hello electron world!</div><buttononclick="require('electron').remote.app.quit()">x</button></body></html>

あとはよしなにー。

以下はここでは解説しないので、別途ググってくだださい。

  • node.jsのインストール方法

  • package.json の作り方(npm の使い方)

    • 使用するライブラリが増えるのがわかっているなら、作っておいたほうが 絶対によい。
  • electronのビルド(exe の作り方)

    • electron の真骨頂。ビルドして exe が作れるんだよね!
  • VSCodeのインストール方法

まとめ

「electron って簡単!!」ってことが理解していただけたと思います!(希望)

みなさまの、快適なプログラミングライフを願いつつ。

Node.jsでのリクエストIDのログ出力(express, log4js, request-context)

$
0
0

概要

Node.jsのexpressベースのWebアプリで、リクエストIDをログ出力します。
今回、log4js、request-contextを使用しました。

request-contextで、リクエスト毎にリクエストIDを保持します。
log4jsのtokensに、リクエストIDをセットして、patternでリクエストIDを出力するようにしています。

参照情報

参考にさせていただいたページは下記です。

注意点

request-contextは、下記の通り、Node.jsのdeprecatedになっているdomainに依存しています。参照先2でも指摘されており、より詳しく記載されています。
request-context

See the Domain Docs for further information on error handling for domains. Note that the domain module is pending deprecation!

サンプルコード

今回は、リクエスト毎に、uuidでリクエストを発行するようにしています。
contextService.setのところでリクエストIDを保持させています。

app.js
constexpress=require('express');constapp=express();constcontextService=require('request-context');constuuid=require('uuid/v4');constlogger=require("./logger").logger();app.use(contextService.middleware('request'));app.use((req,res,next)=>{constrequestId=uuid();contextService.set('request:requestId',requestId);logger.info("Start API.",req.method,req.originalUrl);next();});app.get('/',function(req,res){res.send('hello world');logger.info("End API.");})app.listen(3000)

getRequestIdで、保持しているリクエストIDを取得しています。
layoutのpatternの%x{requestId}がリクエストIDです。

logger.js
constlog4js=require("log4js");constcontextService=require('request-context');constgetRequestId=function(){returncontextService.get('request:requestId');};log4js.configure({appenders:{console:{type:'console',layout:{type:'pattern',pattern:'[%d] [%x{requestId}] %[[%p]%] [%c] %m',tokens:{requestId:getRequestId}}}},categories:{default:{appenders:['console'],level:'info'}}});exports.logger=function(){constlogger=log4js.getLogger('default');returnlogger;};

ログ出力結果

下記のようにリクエスト毎に、リクエストIDが出力されます。

[2019-11-26T11:24:46.451] [ef9b0c23-53f2-4c7b-bce3-0def665fbc08] [INFO] [default] Start API. GET /
[2019-11-26T11:24:46.460] [ef9b0c23-53f2-4c7b-bce3-0def665fbc08] [INFO] [default] End API.
[2019-11-26T11:24:49.588] [4af4ffed-a22f-4b81-806a-1cdcff4b7c22] [INFO] [default] Start API. GET /
[2019-11-26T11:24:49.590] [4af4ffed-a22f-4b81-806a-1cdcff4b7c22] [INFO] [default] End API.
[2019-11-26T11:24:51.226] [360033c9-e6eb-405c-b3a7-6ac9e20b3fa4] [INFO] [default] Start API. GET /
[2019-11-26T11:24:51.227] [360033c9-e6eb-405c-b3a7-6ac9e20b3fa4] [INFO] [default] End API.
[2019-11-26T11:24:53.371] [048a8ce6-1bd8-4514-89df-aa467419e0ce] [INFO] [default] Start API. GET /
[2019-11-26T11:24:53.372] [048a8ce6-1bd8-4514-89df-aa467419e0ce] [INFO] [default] End API.

Node.jsのprocess.argv[i]とは

$
0
0

Node.jsのファイルで以下のような記述がありました。

index.js
'use strict';constnumber=process.argv[2]||0;letsum=0;for(leti=1;i<=number;i++){sum=sum+i;}console.log(sum);

それに対して、いかのような記述をすると、

console
$ node index.js 3
> 7

となります。
つまり、node index.js 3の3の部分が、process.argv[2]に当てはまるということです。
process.argv[0]だったら、node
process.argv[1]だったら、index.js
を意味します。

Node.jsで簡易テストをできるassert.equal()

$
0
0

assert.equal()

assert.equalは、アサーションというnode.jsが提供する簡易テストのモジュールです。

constassert=require('assert');assert.equal(50,50);//OKassert.equal(50,"50");//OKassert.equal(50,70);/*AssertionError: 50 == 70 *///関数と答えの比較もできますfunctionaddition(n){letresult=n+1;returnresult;}//第三引数に、エラーが出た場合のエラー表示の設定をできる。assert.equal(addition(1),3,`1 + 2の答えは3ですが、計算は${additon(1)}でした`)//AssertionError: 1 + 2の答えは3ですが、計算は${additon(1)}でした`

assert.deepEqual()

assert.deepEqualは配列オブジェクトの深い比較まで行ってくれます。
例えば、assert.deepEqual([50], [50]);はOKなのに対して、
assert.equal([50], [50]); ではエラーが出てしまいます。
これは、JavaScriptでは配列やオブジェクトを == 演算子で比較した際、同じオブジェクト同士でないとfalseになってしまうからです。

constassert=require('assert');assert.deepEqual(50,50);//OKassert.deepEqual(50,"50");//OKassert.deepEqual(50,70);/*AssertionError: 50 == 70 */assert.deepEqual([50],[50]);//OKassert.equal([50],[50]);/*AssertionError: [ 50 ] == [ 50 ] */

Node.jsでプロファイリングをする方法

$
0
0

node --prof index.js

--profはプロファイリングと呼ばれる、処理に時間がかかっている様子やどれ位メモリを使っているのかを調べる方法を提供するオプションです。

console
$ node --prof index.js

とすると、index.jsが入っているディレクトリに、

index.jsが入っているディレクトリ
isolate-0x103002a00-v8.log

以上のようなログファイルが生成されます。
しかし、このログファイルは人間が見てもよくわかりません。
そこで、このログファイルを以下のように人間が見やすいファイルにします。

console
$ node --prof-process isolate-0x103002a00-v8.log

すると、コンソールに以下の表示が出ます。

console
>Statistical profiling result from isolate-0x103002a00-v8.log, (382 ticks, 37 unaccounted, 0 excluded).

 [Shared libraries]:
   ticks  total  nonlib   name
     15    3.9%          /usr/lib/system/libsystem_platform.dylib
      3    0.8%          /usr/lib/system/libdyld.dylib
      2    0.5%          /usr/lib/system/libsystem_pthread.dylib
      1    0.3%          /usr/lib/system/libsystem_malloc.dylib
      1    0.3%          /usr/lib/system/libsystem_kernel.dylib

 [JavaScript]:
   ticks  total  nonlib   name
      1    0.3%    0.3%  Script: ~bootstrapInternalLoaders internal/bootstrap/loaders.js:42:35
      1    0.3%    0.3%  LazyCompile: ~realpathSync fs.js:1380:22
      1    0.3%    0.3%  LazyCompile: ~getOptionValue internal/options.js:6:24
      1    0.3%    0.3%  LazyCompile: ~binding internal/bootstrap/loaders.js:77:39
      1    0.3%    0.3%  LazyCompile: ~QuickSort native array.js:530:19
      1    0.3%    0.3%  Builtin: LoadIC_Uninitialized
      1    0.3%    0.3%  Builtin: Construct {1}

 [C++]:
   ticks  total  nonlib   name
(中略)
[Summary]:
   ticks  total  nonlib   name
      7    1.8%    1.9%  JavaScript
    316   82.7%   87.8%  C++
      7    1.8%    1.9%  GC
     22    5.8%          Shared libraries
     37    9.7%          Unaccounted

この[Summary]という部分に注目すると、どの処理に一番時間がかかっているのか調べることができます。
今回の処理では、C++の処理が全体の87.8%なので、C++の処理を改善すれば良いことがわかります。
その他にも[Bottom up (heavy) profile]という部分には時間のかかった処理が書かれています。

ZEIT NowでNode.jsのバージョン指定をする

$
0
0

NowへのデプロイでNodeのバージョンに起因するエラーが起きた

Error: @grpc/grpc-js only works on Node ^8.13.0 || >=10.10.0

上記のエラーが起きて困った。

:bomb:原因

要するに8.13.0 から 10.10.0 の間のバージョンじゃないと動かないよということだと思う。

:star:解決策

じゃあNodeのバージョンをこっちで指定してあげようということになる。

英語の情報しか出てこないが漁っていると、「now.json」で「engine」という項目を指定するみたいな情報が出てくるがこれが罠である。

ZEIT NowのNode.jsバージョン指定は「now.json」ではなく「package.json」で指定する

package.json
"engines":{"node":"10.x"}

これでOK。

ただし、バージョンは何でもかんでも指定出来る訳ではなく提供されているものだけ。

Node.js で無限ループしつつ、一定周期で処理をしたい

$
0
0

メモ。
Node.js で無限ループしつつ、一定周期で処理をしたいという要件があった。
(具体的には SIGTERM シグナルを受けてから1秒毎に標準出力に文字列を出力したい)
上記について Node.js のサイトにずばり書いてあった。

"Infinite Loop" Execution ~ setInterval()

node.js
functionintervalFunc(){console.log('Cant stop me now!');}setInterval(intervalFunc,1500);

この場合、1.5秒毎に intervalFunc()が実行される。
必要に応じて時間や処理内容を変えれば良い。

Node.js v10.17.0 で動作確認済み。


ファイルをセーブしたら、npmスクリプトを走らせる」というライブラリを1行で作る。

$
0
0

「ファイルをセーブしたら、npmスクリプトを走らせる」というライブラリがある日突然欲しくなりました。

onchangeというライブラリが見つかりました。npmライブラリです。

使い方は簡単で、onchangeをグローバルにインストールした後、grobパターンで、監視するファイルとnpmスクリプトを記述するだけです。

npm install -g onchange
onchange 'app/**/*.js' 'test/**/*.js' -- npm test

しかしです...

globパターンを記述するのが地味に面倒い!
ルートフォルダ以下で変更があれば、npmスクリプトを走らせるだけでいいのに...

そこでよりシンプルなライブラリを自作することにしました

作ったのが「save-run」というライブラリ。

名前は、save-runとして、npmに登録してあります。
githubリポジトリはこちら

使い方は簡単で、グローバルにインストールしてからrun-saveコマンドの後にnpmスクリプトを渡すだけ。

npm install -g run-save
run-save npm test

幸せになれました〜〜〜

私は、react-nativeエンジニアなのですが、
run-save npm start
run-save npm test
などに使っています。

実はこのライブラリは1行で実装されているので、興味のある方は読んでみてください。

#!/usr/bin/env node
const{watch}=require('chokidar')const{execSync}=require('child_process')watch('.').on('change',()=>execSync(process.argv.slice(2).join(''),{stdio:'inherit'}))

完!

Node.jsのfsモジュールの使い方

$
0
0

fsモジュールとは

fsモジュールはファイルを扱うためのモジュールで、ファイルから書き出したり、ファイルに書き込んだりするときに役立ちます。
Node.jsがはじめから提供しているモジュールなので、Node.jsのインストールがしてあれば、fsモジュールのインストールの必要はありません。

ファイルの読み込み

constfs=require('fs');//fs.readFileSync(ファイルのパス, 文字コード, コールバック関数)fs.readFileSync('./text.txt',utf-8,(err,data)=>{//dataがファイルの中身、errは読み込み時のエラーif(data){console.log(data);}else{console.log(err);}});

ファイルへの新規書き込み

constfs=require('fs');//fs.writeFileSync(ファイルのパス, 書き込む文字, 文字コード, コールバック関数)fs.writeFileSync('./text.txt',"こんにちは",utf-8,(err)=>{if(err){console.log(err);}});

ファイルへの追加書き込み

constfs=require('fs');//fs.appendFileSync(ファイルのパス, 書き込む文字, 文字コード, コールバック関数)fs.appendFileSync('./text.txt',"こんにちは",utf-8,(err)=>{if(err){console.log(err);}});

ファイルの削除

index.js
constfs=require('fs');//fs.unlink(ファイルのパス, コールバック関数)fs.unlink(process.argv[2],(err)=>{if(err){console.error(err);}else{console.log('削除が終わりました');}});

process.arg[2]はコマンドの第三引数を指すので、以下のようにファイルをコマンドの第三引数に書き、ファイルの実行を行います。

console
$ node index.js ./text.txt

ファイルの作成

constfs=require('fs');//fs.open(新規作成ファイルのパス, 書き込む文字, コールバック関数)fs.open('./text.txt','こんにちは',(err)=>{if(err){console.log(err);}else{console.log('ファイルが作成されました');}});

ストリーム

当初、ストリームを使う理由が分からなかったのですが、ストリームだとデータを必要な分だけ受け取ることができるようになるため、メモリを多く使わずに済むそうです。

参考:Node.js Stream を使いこなす

constfs=require('fs')//読み込みストリームを作成constrs=fs.createReadStream('./text.html');//dataイベントが発生したら(データを受け取ったら)、chunkに'./text.html'のデータが入ります。rs.on('data',(chunk)=>{res.write(chunk)});
普通のファイル読み込みで同じ処理をしてみると、

readFileSyncだと、同期的な読み込みになるため、すごく読み込みが遅いです。
そのため、readFileで非同期的な読み込みをしています。

constfs=require('fs');//fs.readFile(ファイルのパス, コールバック関数)fs.readFile('./text.html',(err,data)=>{//dataがファイルの中身、errは読み込み時のエラーif(data){res.write(data);}else{console.log(err);}});

今回は、./text.htmlファイルも大きくなく、この程度のファイルならばスピードに大きな違いはありませんでした。
ですが、これが大規模な開発になると違いが出てくると思われます。

CircleCIのnode imageでnpm installに失敗する

$
0
0

CircleCI2系で、公式ドキュメントを参照してセットアップしたらnpm installがコケた。
circleci/node:6.17.1のイメージを使用してる環境。

https://circleci.com/docs/ja/2.0/language-javascript/

Error: Cannot find module 'strip-ansi'

こういうエラーでnpm installが停止する。
package-lockの中を見るとstrip-ansiは入ってるし……と思ってたがプロジェクトのパッケージが問題ではなくて、原因としては、公式ドキュメントにある sudoがだめ。

.circleci/config.yml
-run:name:update-npmcommand:'sudonpminstall-gnpm@latest'

npmがsudoでインストールされるとそのあとのnpmコマンドが通らなくなる(別にinstallに限らずnpmコマンド全部落ちる)。
sudoを外すとパーミッションでコケるので、以下のように変更するとよい

.circleci/config.yml
-run:name:set-global-prefixcommand:npm config set prefix '$HOME/.npm-global/'-run:name:update-npmcommand:npm install -g npm@latest

グローバルパッケージのインストール場所をパーミッションのいらないところに変更すると通る。
もしかしたらnodeのバージョンにもよるのかもしれないけど、公式通りにやるとハマるのは困った。

Technology Radar 2019のピックアップ

$
0
0

Technology Radarから気になったものをピックアップし、軽く説明を添えてみました。
社内で共有したところ、反響が良かったのでQiitaにも投稿します。

(自分と似た技術スタックの方に刺さるのではないかと考えています)

ピックアップの観点

「自社の技術スタックとマッチしてるか」「自分の技術スタックとマッチしているか」の観点からピックアップしています。

筆者は現在web系の企業のSREチームに所属しており、業務や趣味で触れる技術/言語としては下記のとおりです。

  • Ruby on Rails
  • Node.js / Vue.js / Nuxt.js
  • AWS + Terraform
  • Go/python/Firebase/GCP

一方で下記技術は興味が無い/専門じゃない等の理由でスルーしていますのでご注意ください。

  • モバイルアプリ系
  • ML系
  • JVM系

Technology Radarとは

要は今年のイケてる技術の紹介です。
4段階で導入のおすすめ度合いが評価されているので、毎年重宝しています。

TECHNOLOGY RADERとはThoughtWorks社が発表している技術トレンド分析の調査結果になります。 年1-2回発表しており、2016年は4月と11月に発表されました。

ユニークな点は、技術トレンドの分析を
Techniques(開発手法) / Tools(ツール) / Platforms (プラットフォーム) / Languages & Frameworks(言語とフレームワーク)
の4分野に分けているところです。
また、評価結果も ADOPT / TRIAL / ASSESS / HOLDの4つに分けているところです。

ADOPT : プロジェクトにマッチするならば、採用を強くおすすめしている。
TRIAL : プロジェクトでリスクを管理できればやる価値はある。
ASSESS: どのような影響をあたえるか理解するために採用するときがある。(今後のために採用するときがある)
HOLD : 採用する場合は慎重に進める必要がある。

引用元:https://allabout-tech.hatenablog.com/entry/2017/02/15/093000

気になった技術

それぞれの領域ごとに概要と所感を記載しています。

Techniques

Container security scanning (ADOPT)

もはやコンテナのセキュリティスキャンは必須の時代です。
CI/CDパイプライン内でスキャンを実施しましょう。

所感:ECRのscan on pushやtrivyで簡単に実現できそうなのでやっていきたい。

Pipelines for infrastructure as code (ADOPT)

ソフトウェアのCI/CDパイプラインによるデプロイが主流になってきました。
Chef/Puppet/Ansible、Packer、Terraform等の登場により、インフラレイヤーもCI/CDを実現することが可能です。
インフラレイヤーもCI/CD化することで、実行元の一元管理や、実行前のエラー検知ができるようになるので、是非やりましょう。

所感:Lint/validationやplan結果の表示もできるのでやりたい。stg/prdのapplyタイミングを踏まえて設計する必要があるのでちょっと大変かも。Dockerfileも現在はCIにかかってないので回すようにしたい。

security policy as code (TRIAL)

セキュリティポリシーをコード化し管理していきましょう。
Open Policy AgentIstio等であれば、ポリシー定義と実施メカニズムを提供しています。

所感:明文化してGit管理を開始したので、適用を強制したり検知する仕組みもあわせて実施していけるといい感じかも。

Tools

Commitizen (ADOPT)

http://commitizen.github.io/cz-cli/
対話形式でGitのコミットメッセージをサポートしてくれるツールです。
コミットの種類とか関連するIssueの有無とかbreaking changeの有無とか聞いてくれていい感じにメッセージを組み立ててくれます。
commitizen

所感:個人のリポジトリはコミットメッセージ英語なので、これを導入することで良さげなリポジトリに見えるようになりそう。

jib (TRIAL)

https://github.com/GoogleContainerTools/jib
Java用のコンテナイメージ作成ツールです。
MavenやGradleに対応しており、DockerfileやDockerデーモンを必要とせずにイメージをビルドします。

所感:使うことはまあ無いだろうけど、デーモン無しにイメージを作成できる技術は気になる。

Trivy (TRIAL)

https://github.com/aquasecurity/trivy
コンテナイメージのセキュリティスキャンツールです。

所感:今ではかなり有名なやつ。使っていきたい。

Twistlock (TRIAL)

https://www.twistlock.com/
Twistlockは、コンテナ環境向けセキュリティ製品です。開発環境から実行環境まで、包括的なセキュリティを提供します。
NIST/CISベンチマークなど、業界のベストプラクティスに沿った対策が可能です。
twistlock

所感:金額次第だが、セキュリティ要件厳しいサービスを運用する際にはいいかも。ただし、融通が利くかどうかは重要。

asdf-vm (ASSESS)

https://asdf-vm.com/#/
複数の言語のバージョンを管理できるコマンドラインツールです。
RVMやnvmのようにバージョンを管理できますが、複数の言語をこのツール1つで管理できるのが特徴です。

所感:ruby以外もバージョン固定して利用するプロジェクトでは良さそう。個人の環境は全部これに乗せ替えたい。

AWSume (ASSESS)

https://github.com/trek10inc/awsume
AssumeRoleをいい感じにやってくれるCLIツール
AWSume

所感:社内のエンジニアには基本的にassum roleして利用するIAMユーザーを配ってるので、これを標準の手順にしてみてもよさそう。(現在、MFAを利用していることもあり、AssumeRoleするユーザーでawscliを使う手順がやや面倒)

Pumba (ASSESS)

https://github.com/alexei-led/pumba
PumbaはDockerのためのchaos testingとネットワークエミュレーションのツールです。
ネットワークをエミュレートし、遅延、パケット損失、帯域幅レート制限などのさまざまなネットワーク障害をシミュレートすることもできます。

所感:chaos testingはやったこと無いので挑戦してみたい。

Platforms

Crux (ASSESS)

https://opencrux.com/
bitemporal graph queryを備えたドキュメントデータベースです。
bitemporalとは、履歴を持ったデータのこと

不変のトランザクションレコードも保持しながら、ビジネスの真の履歴を記録します。これがバイテンポラリティの本質です。開発者およびアプリケーションユーザーとして、時間をかけて効率的にクエリを実行する機能のロックを解除します。 Cruxを使用すると、遡及修正を作成し、履歴データの移行を簡素化し、異常なイベントデータの統合ビューを構築できます。

所感:特許で見たことあるような。履歴を持ち、かつそれを遡及して修正するシステムは本当に辛いので、これがマッチするなら使うのが良さそう。

Hydra (ASSESS)

https://www.ory.sh/hydra/
OAuth2、OpenID connect providerを簡単にホストすることができるOSSです。

所感:micro serviceをk8s上で構築する際に使えそう。認証系の自前メンテは辛いので、こういうものを活用していきたい。

Teleport (ASSESS)

https://gravitational.com/teleport/

『Gravitational Teleportは、SSHまたはKubernetes API経由でLinuxサーバーのクラスタへのアクセスを管理するためのゲートウェイです。従来のOpenSSHの代わりに使用することを目的としています。』

参考記事

所感:中規模以上のk8sを利用している場合に効果を発揮しそう。自前でteleportのクラスタを構築し、ライセンスも必要なプロダクトなので、導入するのはけっこうな大事。GitHubや任意のSSO連携可能なサービスのアカウントで、ロールに基づいたsshができるのはまさに我々が求めていたものである。

Languages & Frameforks

jest-when (TRIAL)

https://www.npmjs.com/package/jest-when
when(fn).calledWith(args).thenReturn(value)のような感じでモック関数の引数に対するレスポンスを定義できるプラグインです。

所感:地味に便利で記述量を少なくできるので良さそう。

NestJS (ASSESS)

https://nestjs.com/
TypeScript製のserver side Node.jsフレームワークです。
GraphQL、Websocket、ORMライブラリなどのプロトコルをサポートしています。

参考記事:Nest.jsは素晴らしい

所感:発想はRailsに近いように思える(レールに乗ることでスタイルが統一され楽に開発できる等)。expressとの2択になりそう。

Paged.js

https://www.pagedmedia.org/paged-js/
HTMLで書籍等の印刷物を作る場合に必要なページカウンターやヘッダー、フッター等を描画できるポリフィルとCSSモジュールを生成してくれるライブラリです。

所感:本を作る機会があれば使ってみたいかも。前職であれば仕様書は謎に文書形式である必要があったので、Markdownで書いた場合でもヘッダーやフッターを頑張ってつけてたから、それにも使えそう。

まとめ

個人的に気になった技術をいくつかピックアップして紹介してみました。

よくあるツール紹介でごめんなさい。
でも、元が英語だから許してね。

Node.js, JavaScript学習まとめ

$
0
0

今回の学習のゴール

  1. Node.jsについて知る
  2. 基本文法を学ぶ
  3. ライブラリを把握する

目次

  1. Node.jsとは
  2. そもそもJavaScriptとは
  3. JavaScriptの基本知識
  4. Node.jsの基本知識
  5. ライブラリの把握
  6. 今後の課題

1. Node.jsとは

スケーラブルなネットワークアプリケーションを構築するために設計された非同期型のイベント駆動のJavaScript環境
Node.js

  • それぞれの意味
    • スケーラブル : 拡張性が高い
    • 非同期 : 各要求(request)の処理が完了するのを待たずに、それ以降の処理を行う方式
    • イベント駆動 : イベントと呼ばれるアプリや端末上で起きた出来事に対して処理を行うプログラムの実行形式
  • 特徴
    • サーバーサイドで使用できる
    • ノンブロッキングI/Oモデルを採用しており、I/Oの処理を待たずに次の処理を始めることができるので、大量のデータ処理が可能
      • ノンブロッキング : ある処理を行いながら、ほかの処理も同時進行で行えること
      • I/O : Input/Outputの略で、入出力の意

2. そもそもJavaScriptとは

  • ブラウザに実行エンジンが搭載されたプログラミング言語
  • Netscape Navigatorというブラウザ向けに開発され、その後Internet Explorer, Firefox, Chromeなどの主要ブラウザに採用された
  • 特徴

    • ブラウザで動作する
    • 実行エンジンの内部で動的にコンパイルが行われるので、コンパイルしなくとも実行できる
    • 動的型付け言語
  • JavaScript - Wikipediaによると

JavaScriptはプロトタイプベースのオブジェクト指向スクリプト言語であるが、クラスなどのクラスベースに見られる機能も取り込んでいる

  • それぞれの意味
    • オブジェクト指向スクリプト言語
      • オブジェクトを組み立てるように表現して、コンピュータに動作をさせ、script(台本、原稿)のようにプログラムを記述できるプログラミング言語
    • プロトタイプベース
      • 全てのオブジェクトがプロトタイプ(試作品)をベースにして作られているもの
      • プロトタイプと呼ばれるテンプレートをコピーして、新しいオブジェクトが作られるイメージ
    • クラスベース
      • 全てのオブジェクトがクラスをベーシにして作られているもの
      • クラスはオブジェクトを作る設計書のことで、クラスそのものの名前、属性、処理の3つの要素を持つオブジェクトをまとめて定義したもの

js.jpg

3. JavaScriptの基本知識

データ型

  • String : 文字列
  • Number : 数
  • Boolean : 真偽値
  • Null : 値が存在しないまたは無効なオブジェクト
  • Undefined : 値を代入していない変数の値
  • Array : 複数の値を格納可能
  • Object : 基本的に何でも格納可能
// 文字列を整数に変換するメソッドparseInt('030',10);// 第2引数には変換の基数, 30が返されるparseInt('hello',10);// NaN(非数  "Not a Number" の略)が返される
// 文字列の操作方法'hello'.charAt(0);// "h"を返す'hello, world'.replace('hello','goodbye');// "goodbye, world"を返す'hello'.toUpperCase();// "HELLO"を返す

演算子

1+1;// 数字を加える'Hello'+'world';// 文字列の結合10-1;// 減算2*3;// 乗算10/2;// 除算
varmyVariable='value';// 代入myVariable==='value';// 等価 値と型が等しいか真偽値で返す 変数myVariableにvalueが代入されている場合は、trueが返される!(myVariable==='value');// 否定 値と型が等しくないか真偽値で返すmyVariable!=='value';_//非等価 値と型が等しくないか真偽値で返すweather==='sunny'&&temperature<25// AND 2つ以上の式を1つに繋げそれぞれの式を個別に評価、全てtrueになった場合その式全体がtrueを返すweather==='sunny'||temperature<25// OR 2つ以上の式を1つに繋げそれぞれの式を個別に評価し、最初にtrueになったところでその式全体をtrueとして返す
x+=5;// x = x + 5;の意 複合代入文という

変数

  • varを用いた変数の宣言
var<変数名>;// 変数の宣言<変数名>='';// 変数に値を割り当て, 変数の値を変更するvar<変数名>='';<変数名>;// 変数の値を取得
  • 変数のスコープは関数単位
functionf(){varnum=123;console.log(num);{varnum=456;console.log(num);}console.log(num);}f();// 実行結果123456456
  • letを用いた変数の宣言
let<変数名>='';// ブロックレベルの変数を宣言
  • 変数のスコープがブロックに限定される
functionf(){letnum=123;console.log(num);{letnum=456;console.log(num);}console.log(num);}f();// 実行結果123456123

定数

constnumber='10';// 定数の宣言 一度宣言された値は変更不可

配列

// 配列を生成する①varperson=newArray();person[0]='たなか';person[1]='なかむら';person[2]='しぶや';// 配列を生成する②varperson=['たなか','なかむら','さいとう']// 配列に要素を追加するperson.push(いとう);

条件文 if

varcolor='red'if(color==='red'){// (条件式)がtrueを返した場合、以下の処理が実行されるalert('好きな色は赤です');// アラートを使って表示}elseif(color===blue){alert('好きな色は青です');}else{// 2つの(条件式)がfalseを返した場合、elseの後の処理が実行されるalert('好きな色は黄色です');}

switchステートメント

varcolor='red'switch(color){case'red':alert('好きな色は赤です');break;// 値がcaseにマッチした時ループを抜けるcase'blue':alert('好きな色は青です');break;// 以下に選択肢を好きなだけ並べることが可能default:alert('選択肢に好きな色がありません');}

ループ

  • カウンター : ループの開始地点で、初期化される値
  • 終了条件 : ループが終了する条件
  • イテレーター : 終了条件を満たすまで、カウンターの値をループごとに少量ずつ増加(減少)させる
// forを使ったループvarsequence=[1,2,4,7,11,16,22];for(vari=0;i<sequence.length;i++){// カウンター変数を宣言 lengthプロパティを使用して配列の長さを取得し、ループを配列の長さと同じ数になったら、繰り返しを終了console.log(sequence[i]);}
// whileを使ったループvarsequence=[1,2,4,7,11,16,22];vari=0;// 初期化処理while(i<sequence.length){console.log(sequence[i]);i++;}
// do...whileを使ったループvarsequence=[1,2,4,7,11,16,22];vari=0;do{console.log(sequence[i]);i++;}while(i<sequence.length)

関数

  • 再利用したい機能をパッケージ化する方法
functionsayHello(){alert('hello');}myFunction();// functionの呼び出し helloのアラートが表示される
functionsum(num1,num2){//関数の定義 関数に複数の引数がある場合はカンマで区切るvarresult=num1+num2;returnresult;}
sum(1, 2);# コンソールで実行すると3が返ってくる

イベント

  • ブラウザの中で起きていることを検出し、その応答としてコードを実行する
  • 動作や操作(以下の例ではクリック)に対して特定の処理を与えるための命令のことをイベントハンドラという
  • ブラウザに組み込まれたJavaScript APIの一部として定義されたもの
varpage=document.selector('html');// 関数を定義し変数に代入page.onclick=function(){// 無名関数は主にイベント発火のレスポンスとして、一連のコードを走らせるだけのような場合に、イベントハンドラとして使われるalert('ページがクリックされた');};

オブジェクト

  • 関連のあるデータと機能をひとまとめにしたモノ
  • 機能はたいてい変数と関数で構成され、オブジェクトの中ではそれぞれプロパティとメソッドと呼ばれる
varobj=newObject();// 空のオブジェクトを作成する方法①varobj={};// 空のオブジェクトを作成する方法②オブジェクトリテラル構文
  • オブジェクトリテラル使用してオブジェクトを生成する例
varperson={name:['たかはし','なかむら'],age:20,gender:'female',greeting:function(){// オブジェクトのメソッドalert('こんにちは、'+this.name[1]+'と申します。'+this.age+'歳です。');// thisは現在のオブジェクトを参照しているので、personを指す}};
#コンソールで実行person#{name:Array(2),age:20,gender:"female",greeting:ƒ}と返ってくるperson.name[1]#"なかむら"と返ってくるperson.greeting()#こんにちはなかむらと申します。20歳です。とアラートが返ってくるperson.age=30;#値を上書きすることができる
// サブ名前空間でオブジェクト生成するときの記載方法name:{first:'たかはし',// name.firstで"たかはし"が返ってくるsecond:'なかむら'// name.secondで"なかむら"が返ってくる}

継承

  • ”親”クラスからの機能を継承する”子供”のオブジェクトクラス (コンストラクタ) の生成方法について
// コンストラクタ内部にプロパティのみを定義functionPerson(first,second,age,gender){this.name={first,last};this.age=age;this.gender=gender;};// メソッドはすべてコンストラクタのプロトタイプとして定義するPerson.prototype.greeting=function(){alert('こんにちは、'+this.name.first+'と申します。'+this.age+'歳です。');};// Personコンストラクタの子であるTeacherコンストラクタを作成functionTeacher(first,second,age,gender,subject){Person.call(this,first,second,age,gender);// call()関数 その他の場所で定義された関数から呼ぶことができるthis.subject=subject;// Teacherだけが持つプロパティを定義}

prototype弄るのは基本しないらしいby先生

JSON(JavaScript Object Notation)

  • JavaScript オブジェクトの構文に従ったテキストベースのフォーマット
  • ウェブアプリケーションでデータを転送する場合に使われる
  • MIME type(メディアタイプ)がapplication/jsonで、「.json」という拡張子の付いたテキストファイルとしてJSON自身を格納することもできる(以下その例)
{"companyName":"Super heroes","homeTown":"Central City","formed":2005,"active":true,"members":[{"name":"Takahashi","age":28,"business description":["labor management","Payroll"]},{"name":"Nakamura","age":35,"business description":["Disclosure","Payment","Sales recording"]}]}
  • このオブジェクトをJavaScriptのプログラムへ読み込む(excellenceという変数に代入したとすると)と、ドットや角括弧を使ってデータへアクセスすることができる ※JSONでは文字列とプロパティ名をシングルクォートではなく、ダブルクォートで括る
excellence.companyNameexcellence['members'][1]['business description'][0]// 2番目のメンバーの1番目の業務内容を参照

Web API

  • Application Programming Interfacesの略
  • 開発者が複雑な機能を簡単に作成できるように、プログラミング言語から提供される構造のこと
  • ブラウザやサイトが動作しているOSの様々な面を操作したり、他のWebサイト、サービスから取得したデータを操作するためのプログラムされた機能である
  • APIのカテゴリ
    • ブラウザAPI : Webブラウザに組込まれているAPIで、ブラウザやコンピュータの環境の情報を取得して複雑な機能を簡単に実装できる(ex. Geolocation API)
      • ブラウザで読み込んだ文書を操作するためのAPI, サーバからデータ取得をするAPI, クライアント側でのデータ保持APIなどがある
    • サードパーティAPI : サードパーティのプラットフォーム(TwitterやFacebook)上に作られた構造で、それらの機能をWebページで利用できるようにする(ex. Twitter API, Google Maps API, YouTube API)

クロージャ

  • 関数とその関数が作られた環境が一体となった特殊なオブジェクトのこと
  • あるコードブロック内で定義された関数などが、そのブロックをスコープとする変数などを参照できる
  • クロージャが用意されていないと、ある関数内で参照できる変数は引数とその関数内で定義されたローカル変数およグローバル変数のみである
  • オブジェクト内部で使用している変数やメソッドを他のプログラムから容易に変更できないようになる(カプセル化)
  • ex. 関数createStopwatchのスコープ内で定義された変数timeと関数の結果が一体となったオブジェクトを変数stopwatchへ代入しているため、変数stopwatchが呼び出される都度、変数timeは0に初期化されることなく、下記のような結果が返ってくる
varcreateStopwatch=function(){vartime=0;returnfunction(){time+=1;console.log(time);};};varstopwatch=createStopwatch();stopwatch();// 1が返ってくるstopwatch();// 2が返ってくるstopwatch();// 3が返ってくる

4. Node.jsの基本知識

Hello, Nodeを出力

hello.js
console.log('Hello, Node')
  • プログラムを実行
$ node hello.js
  • 実行結果
Hello, Node
  • 'use strict';を宣言するとstrict(厳格)モードで実行できる
    • strictモード : javascriptのコードをより厳しくエラーチェックすることができる仕組み
hello.js
'use strict';console.log('Hello, Node')
  • Webサーバとして動作させる場合
hello2.js
varhttp=require("http");// HTTPモジュールの読み込みhttp.createServer(function(request,response){//  HTTPサーバを作成response.writeHead(200,{'Content-Type':'text/plain'});// レスポンスHTTPヘッダーを設定response.end('Hello, Node\n');// レスポンスボディを送信}).listen(8000);// ポート8000でリクエストを行う//  サーバにアクセスするためのURLを出力 console.log('Server running at http://127.0.0.1:8000/');
  • プログラムを実行
$ node hello.js    // ブラウザで"http://localhost:8000"にアクセス Hello, Nodeと表示される

非同期処理

  • 処理を実行したら結果を待たずに他の処理を実行できる(複数の処理を平行して実行できる)
    • 同期処理は、上から下へ1行ずつ順番にプログラムが実行されていく(サーバーへアクセスをして値を取得する間プログラムはストップしている)、
  • JavaScriptでは基本的に非同期APIが使用される
// 操作が完了する前に次の処理を実行する 以下は "Second, First"と出力されるsetTime(function(){console.log('First');},3000);// 処理に3秒間かかるconsole.log('Second');
  • Node.js ではPromiseという仕組みを使って非同期を実現
    • Promiseとは非同期処理を実現するために用意されたオブジェクト
varpromise=newPromise(function(resolve,reject){// Promiseオブジェクトを変数に代入 引数にはresolve, rejectsetTimeout(function(){resolve('hoge');// 引数に返したい結果となる値を指定},3000);});promise.then(function(value){// then()の中の関数の引数valueにPromiseの結果が格納されいるconsole.log(value);// 3秒待ってhogeが返されることが約束されている});console.log(promise);// [object Promise]が返される

実行結果

[object Promise]
"hoge"

5. ライブラリの把握

  • axios
    • HTTP通信を簡単に行うことができるJavascriptのライブラリ
  • request
    • 標準のhttpライブラリを使うより簡単で理解しやすい記述でHTTP通信を行うことができるライブラリ
  • Moment.js
    • JavaScriptで日付を扱うためのライブラリ
    • 日時の加算減算や2つの日付の差の計算などができる
  • facebook/jest
    • JavaScriptのテストフレームワーク

6. 今後の課題

  • npm(Node.jsのパッケージを管理するもの)を学習する際に、Node.jsについての理解を深める

参照

Viewing all 9099 articles
Browse latest View live