「Webサービスを(少しずつ)構築する」 第3回
両者の特徴や長所短所についてはここでは省略する (各自で調べておいて下さい)。
(本章は管理者が行う作業)
サーバ(CentOSなどRedHat系のLinux)上で新たな アプリケーション(などのパッケージ)をインストールする時は、 yum コマンド(パッケージマネージャ)を (管理者権限で)使うことになる。
一般的には、
sudo yum install mysql
でいいのだが、ここでは、MySQLとほぼ互換の(かつ、Oracleの 配下に入っていない)MariaDB を使うことにした (ので、その過程も参考のため記しておく)。
このファイルに書くべき内容として、 様々なサイトにサンプルが掲載されているが、 それらは古い可能性があるので、 現時点で有効な指定内容を生成させるようにしよう。
MariaDBのオフィシャルサイトから、repo生成ページを見つけ出して右図のように選択したものをコピペで入力した。
その上で、
sudo yum install MariaDB-devel MariaDB-client MariaDB-server –enablerepo=MariaDB
サーバー クライアント 開発用ツール (個別のパッケージになっている)の3つ(と、依存関係で自動的にインストールされるものも含めて)をインストールしている。
runlevel # => N 3 のような値を返すだろう
# この右側の 3 が現在のランレベル
chkconfig -a
# または
chkconfig --list mysql
sudo /etc/init.d/mysql start
# これで起動される
netstat -a | grep mysql
# ポート3306(サービス名 mysql)
自動起動:
ディレクトリ /etc/init.d/ に mysql というファイルが置かれていれば、この先、mysql(またはMariaDB)サーバは 自動の対象になっている筈なので、特に何もしなくていい。 * 現在、サーバが動作しているかの確認は以下のようなコマンドで(一般ユーザでも)できる。
ps aux | grep mysql
netstat -an | grep 3306
ただし、Unix/Linuxのroot権限でmysqlクライアントに入ると、mysqlでのrootとしての操作は可能になる。
sudo mysql
以下のように対処しよう。
sudo vi /etc/sysconfig/iptables
で編集し、以下のような行を適切な場所に挿入(似たような行を、キー操作 yy p
で複製してから編集すればいい)。
-A INPUT -m state --state NEW -m tcp -p tcp --dport 3306 -j ACCEPT
編集できたら、サービス iptables を再起動する。
sudo /etc/init.d/iptables restart
データベースの作成と、権限委譲(mysqlの管理者)
CREATE DATABASE db名 ;
# のあと、権限委譲を行う。
GRANT ALL PRIVILEGES ON db名.* TO ユーザ名@localhost IDENTIFIED BY 'パスワード' WITH GRANT OPTION ;
GRANT ALL PRIVILEGES ON db名.* TO ユーザ名@% IDENTIFIED BY 'パスワード' ;
# 同じサーバ機上のクライアントからの接続のための
パスワードと、
# それ以外(の全部、を表すためにワイルドカード文字として
# % で表現している。シェルやコマンドプロンプトの
# * に相当するもの)のためのパスワード、
# と2回に分けて入れる必要があるようだ。
パスワード:
データベースごとに、アクセス権設定ができます。
参考になるサイトが多数ある(たとえばここは 「node.js ORM mysql」でググると上のほうに現れる)ので参照してみて下さい。
以下のインストール作業は、アプリケーションディレクトリで行うことを前提にしている
(ので –save オプションをつけている)。
npm install -g sequelize-cli
npm install --save sequelize
次節後半 sequelize db:create あたりで発覚すると思うが、
mysql2 ライブラリも必要になるだろう(これも手動インストール)。
npm install mysql2
Sequeilzeの設定(コマンドを使って)
初期化:アプリケーションディレクトリで
sequelize init
config/config.json に、データベースアクセスのための情報を以下のような書式で設定する。
"host": ... ,
"database": ... ,
"username": ... ,
"password": ... ,
...
部分に必要な情報を文字列として( ""
で囲んで)書き込む。上記の4項目は(名称から推察できる筈だが) 前節の4項目に(この順で)対応している。
"dialect": mysql,
ここはこのままでいい。それに加えて、
"operatorsAliases": false
データベース生成
sequeilize db:create
ただし、ゼミのサーバでは(あるいは大抵の組織の運用形態で)、CREATE DATABASE は、DBサーバ管理者の権限(または サーバOSのroot権限)で行うことが求められるので、
モデル生成
sequelize model:create --underscored ^
--name memo ^
--attributes "memo:string, date:date"
上記のようなコマンド(行末のキャレット ^
は継続行を表している)を入れると、
Sequelize CLI [Node: 10.13.0, CLI: 5.3.0, ORM: 4.41.2]
New model was created at [パス名]
New migration was created at [パス名]
のようなメッセージが出力され、2つのディレクトリが生成される(詳細はそこに置かれたファイルをざっと読んでみて下さい)。
データベースへの反映(マイグレート)
sequelize db:migrate
Sequelize CLI [Node: 10.13.0, CLI: 5.3.0, ORM: 4.41.2]
Loaded configuration file "config\config.json".
Using environment "development".
== 20181122234537-create-memo: migrating =======
== 20181122234537-create-memo: migrated (0.211s)
このとき、DBサーバ上では、前項で定義したモデルに従って CREATE TABLE(SQL文)が実行されている。
Sequelizeを使うコード
方針の概略:
現時点では1つのサーバアプリケーションの中で、 このDBサーバへのアクセスが発生する route が 1つだけ(routes/memo)。
なので、下記のコードの変更はいずれも routes/memo.js の中で行えばいいだろう。
アプリケーションが多機能化して、複数の route で DBサーバを使うようならば、
App.js (またはそこから読み込まれるソース・ファイル)に、下記のコードを移し、必要に応じて モジュール相互間でのデータの受け渡しを行う必要が 出てくるだろう。
初期化:
let db = require('../models/index')
// このファイルはモデル生成に際して
// 作成された index.js
// (routes/index.js とは別)
// プログラムの中ではsequelize の大元になる
// オブジェクトを生成する。
//
)するか
行ごと削除してもいい
(この後の項目も同様)。App.js から requireする時と、 (ディレクトリの)1階層下の routes/memo.js からrequireする時とで、
ファイルの指示の仕方が変わってくる(起点が違うので)ことに留意されたい。
* この例では routes が起点なので ../
から始まる。
データ抽出:
function showdb(res) {
db.memo.findAll({}).then((docs)=>{
res.render('memo', {list:docs})})
}
db
を起点に、
モデル名 memo (の名のついた属性、の値として
設定されているオブジェクト)を経て、findAll
(NeDBでは find
だったがメソッド名はライブラリによってこの程度違う)を呼ぶ。findAll
が掴んで来る抽出結果(オブジェクトの配列の形)に対してコールバックを呼ぶための書き方として、
(Sequelizeでは)thenメソッド(次項でも述べる)があるので、
これをメソッドチェーンの最後の段で呼ぶ。SELECT * FROM memos;
のようなSQL文が送られることになるだろう。データ追加:(router.post()関数の中に)
db.memo.create({
date: new Date().toLocaleDateString(),
memo: req.body.memo
}).then((created)=>{
console.log(created)
})
INSERT INTO memos (...) VALUES (...);
のようなSQL文が送られることになるだろう。並び替え:
データを指定順序で表示させるために、データ列を受け取ってからJavaScript側でソートするのではなく、
SQLの ORDER BY 句で並び順を指定したほうが効率的。
findAll({order: [['id','ASC']]})
[]
で囲んである。id
というフィールドは Sequelizeが自動的に生成するレコード番号に相当するもの。生成順に大きくなるので、これで並び替えても機能的にはOKだろう。date
フィールドで整列させてもいい。Latin1
(要するに半角英数字等からなるASCII文字セットのことと思っていい)に設定されている。
そのことは、作ったデータベースやテーブルについて、
SHOW CREATE DATABASE DB名 ;
SHOW CREATE TABLE memos ;
のように入れてみると確認できる (それぞれについて DEFAULT CHARACTER SET が UTF8 ではなく LATIN1 になっていれば 日本語文字を受理しない)。
設定ファイル(おそらく /etc/my.conf)に、以下のような行を追加する(システム管理者)。
[mysqld]
...
character-set-server=utf8 #mysqldセクションの末尾に追加
前項1.の方法がすぐに使えない場合は、データベース生成時に
CREATED DATABASE DB名 DEFAULT CHARACTER SET utf8 ;
で作成する。
すでに作成済のデータベースやテーブルについては、
ALTER DATABASE DB名 DEFAULT CHARACTER SET utf8 ;
ALTER TABLE memos CONVERT TO CHARACTER SET utf8 ;
のように変更すればいい。
なお、こういった問題については、
適切な検索 でヒントが見つかるだろう。
今回は 「AWS RDSのMySQLの文字コードをutf-8に変更」、 「mysqlで文字コードをutf8にセットする」 を参考にした。
入力結果が画面にすぐに反映されない問題
(と、結果表示画面のURLの問題)
INSERT INTO
の直後に続けて SELECT *
(いずれもSQL文)を発行した場合に、
先のデータ挿入が出力に反映されないようだ。間を置いてリクエストを出したり、間に COMMIT の動作を明示的に挟んだり、といった解決策もあるだろうが、
ここでは、もう1つ、入力を実行した直後の画面 (でアドレスバーに表示される)のURLが、 POSTメソッドでデータを受理するAPIのアドレスのまま 残っているという問題も解決してしまおう。
routes/memo.js の route.post(‘/add’) で、 データをデータベースサーバに書き込む処理が終了したら、結果表示を自前で行わずに、代わりに
res.redirect('/memo')
で最初のURLに飛ばしてしまうことにする。
と、現時点で route/memo.js は以下のようなコードになる。
var express = require('express');
var router = express.Router();
let db = require('../models/index');
// Sequelize and mysql version
router.get('/', (req, res, next)=> {
db.memo.findAll({order: [['id','DESC']]}).then((docs)=>{
res.render('memo', {list:docs})})
});
router.post('/add',(req,res)=>{
// console.log(req.body.memo)
db.memo.create({
date: new Date().toLocaleDateString(),
memo: req.body.memo
}).then((created)=>{
console.log(`new data ${created.dataValues.memo}`)
})
res.redirect('/memo')
})
module.exports = router;
アクセスできない問題
ところが、大学のキャンパス内にあるPC上でアプリケーションを動作させた時は、 学内LANのフィルタリングによって特定のサービス(HTTP SSH等)に向けた通信以外は遮断されてしまうので、
学外のデータベース(たとえばポート3306)への通信ができない。