Express 4 のログ出力とフォームの処理について調べてみました。
コードはこちら。
https://github.com/hoshi-takanori/express-sample
ログ出力
Express 4 ではログ出力のパッケージは独立して morgan という名前になったようなので、まずこれをインストールします。
{
...
"dependencies": {
"express": "*",
"morgan": "*",
"jade": "*"
}
}
$ npm install
使い方はこんな感じ。
var express = require('express');
var morgan = require('morgan');
var app = express();
...
app.use(morgan({ format: 'dev', immediate: true }));
app.use(express.static(__dirname + '/public'));
...
ブラウザからアクセスすると、ログが標準出力に(format: 'dev' を指定したので、色付きで)出力されます。
$ node app
GET / - - ms - -
GET /css/style.css - - ms - -
GET /favicon.ico - - ms - -
app.use(morgan()); は複数書くこともでき、書いたものはすべて実行されます。
また、書く場所も重要で、app.use(express.static()); の後ろに書くと、static が実行された場合にはログは出力されません。
ログのフォーマット
morgan() の引数に文字列を渡すか、format: を指定することで、ログのフォーマットを指定することができます。
まず、デフォルトの表示。
app.use(morgan());
//app.use(morgan('default'));
//app.use(morgan({ format: 'default' }));
127.0.0.1 - - [Tue, 24 Jun 2014 12:34:56 GMT] "GET / HTTP/1.1" 200 205 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:30.0) Gecko/20100101 Firefox/30.0"
127.0.0.1 - - [Tue, 24 Jun 2014 12:34:56 GMT] "GET /css/style.css HTTP/1.1" 200 111 "http://localhost:3000/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:30.0) Gecko/20100101 Firefox/30.0"
次に、短いやつ。
app.use(morgan('short'));
//app.use(morgan({ format: 'short' }));
127.0.0.1 - GET / HTTP/1.0 200 205 - 33.145 ms
127.0.0.1 - GET /css/style.css HTTP/1.0 200 111 - 1.900 ms
他にもいろいろな表示方法が可能で、自分でフォーマットを定義することもできます。
また、immediate: に true を指定するかどうかで、表示の内容やタイミングが変わります。
immediate: に true を指定すると、リクエストに基づいて(処理の前に)ログが出力されるのに対して、immediate: に true を指定しない場合は、レスポンスに基づいて(リクエストに対する処理が完了した後に)ログが出力されます。
つまり、immediate: true の場合は処理の結果(ステータスコードや、実行時間など)を反映できなかったり、immediate: true ならリクエストがあった場合には必ず出力されるけど、immediate: true でなければ処理中に何かあったら表示されない可能性がある(?)といった違いがあります。
...
app.use(morgan('short'));
app.use(morgan({ format: 'dev', immediate: true }));
...
app.get('/', function (req, res) {
console.log('processing...');
res.send('Hello, World!');
});
...
GET / - - ms - -
processing...
127.0.0.1 - GET / HTTP/1.0 200 13 - 6.657 ms
動作の切り替えと出力先の変更
開発中とデプロイ時(production モード)で動作を切り替えるには次のようにします。
...
if (app.get('env') == 'production') {
app.use(morgan());
} else {
//app.use(morgan('short'));
app.use(morgan({ format: 'dev', immediate: true }));
}
...
#!/bin/sh
export NODE_ENV=production
node app
また、開発時は標準出力、デプロイ時にはファイルにログを出力、なんてこともできます。
...
if (app.get('env') == 'production') {
var fs = require('fs');
var stream = fs.createWriteStream(__dirname + '/log.txt', { flags: 'a' });
app.use(morgan({ stream: stream }));
} else {
...
}
...
さらに、フィルター関数を指定することなどもできるようです。
フォームの処理 (GET)
適当なフォームを作ってみます。まずは GET。
app.get() の関数の中で req.param() を使うとパラメータが取れます。簡単。
...
app.get('/', function (req, res) {
var username = req.param('username');
res.render('index', { title: 'Express Sample', username: username });
});
...
...
h1= title
if username
p Welcome, #{username}.
else
p What's your name?
form(method='get', action='/')
input(type='text', name='username')
input(type='submit', value='OK')
フォームの処理 (POST)
次に、POST。POST のパラメータを取得するには body-parser が必要です。
{
...
"dependencies": {
"express": "*",
"morgan": "*",
"body-parser": "*",
"jade": "*"
}
}
var express = require('express');
var morgan = require('morgan');
var bodyParser = require('body-parser');
var app = express();
...
app.use(bodyParser.urlencoded({ extended: false }));
...
そして、app.post() で POST 処理用の関数を登録します。
...
app.post('/login', function (req, res) {
var username = req.param('username');
var password = req.param('password');
if (username == password) {
res.render('index', { title: 'Express Sample', username: username });
} else {
res.render('index', { title: 'Express Sample', error: 'Unknown username or password.' });
}
});
...
...
h1= title
if username
p Welcome, #{username}.
else
if error
p= error
else
p What's your name?
form(method='post', action='/login')
input(type='text', name='username')
input(type='password', name='password')
input(type='submit', value='OK')
リダイレクト
ログイン成功時には、app.post() で直接結果を返すのではなく、いったん app.get() にリダイレクトすると良いかもしれません。
...
app.post('/login', function (req, res) {
var username = req.param('username');
var password = req.param('password');
if (username == password) {
res.redirect('/?username=' + encodeURIComponent(username));
} else {
res.render('index', { title: 'Express Sample', error: 'Unknown username or password.' });
}
});
...
もっとも、username のような情報は URL に埋め込むよりも、クッキーに保存(そのまま保存するのはセキュリティ的に問題がありますが)したり、セッションを使ったりすべきでしょう。
クッキー
クッキーの値を取得するには cookie-parser が必要です。
{
...
"dependencies": {
"express": "*",
"morgan": "*",
"body-parser": "*",
"cookie-parser": "*",
"jade": "*"
}
}
var express = require('express');
var morgan = require('morgan');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var app = express();
...
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
//app.use(cookieParser('cookie secret'));
...
'cookie secret' は signed cookie というものを使う場合に、任意の文字列を指定します。(signed cookie はクッキーの値の改ざんを防ぐもので、クッキーの値を隠すものではありません。)
そして、app.get() と app.post() でクッキーの処理を行います。
...
app.get('/', function (req, res) {
var username = req.cookies.username;
//var username = req.signedCookies.username;
res.render('index', { title: 'Express Sample', username: username });
});
app.post('/login', function (req, res) {
var username = req.param('username');
var password = req.param('password');
if (username == password) {
res.cookie('username', username);
//res.cookie('username', username, { signed: true });
res.redirect('/');
} else {
res.render('index', { title: 'Express Sample', error: 'Unknown username or password.' });
}
});
app.post('/logout', function (req, res) {
res.clearCookie('username');
res.redirect('/');
});
...
...
h1= title
if username
p Welcome, #{username}.
form(method='post', action='/logout')
input(type='submit', value='Logout')
else
if error
p= error
else
p Please login.
form(method='post', action='/login')
input(type='text', name='username', placeholder='Username')
br
input(type='password', name='password', placeholder='Password')
br
input(type='submit', value='Login')
セキュリティやプライバシーを考えると、クッキーに生の値を埋め込むのではなく、セッションを使うべきでしょう。
セッション
セッションを利用するには express-session が必要です。
{
...
"dependencies": {
"express": "*",
"morgan": "*",
"body-parser": "*",
"cookie-parser": "*",
"express-session": "*",
"jade": "*"
}
}
var express = require('express');
var morgan = require('morgan');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var app = express();
...
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(session({
secret: process.env.SESSION_SECRET || 'session secret',
resave: false,
saveUninitialized: false
}));
...
'session secret' はセッション用のクッキーの値の改ざんを防ぐものです。デプロイ時には秘密の値にできるように、環境変数を参照しています。
そして、app.get() と app.post() のクッキーの処理を、セッションを使って書き換えます。
...
app.get('/', function (req, res) {
var username = req.session.username;
res.render('index', { title: 'Express Sample', username: username });
});
app.post('/login', function (req, res) {
var username = req.param('username');
var password = req.param('password');
if (username == password) {
req.session.username = username;
res.redirect('/');
} else {
res.render('index', { title: 'Express Sample', error: 'Unknown username or password.' });
}
});
app.post('/logout', function (req, res) {
req.session.destroy();
res.clearCookie('connect.sid', { path: '/' });
res.redirect('/');
});
...
セッションの永続化とログインの処理もやりたいんだけど、何が何やら。
リンク
https://github.com/expressjs/morgan
https://github.com/expressjs/body-parser
https://github.com/expressjs/cookie-parser
https://github.com/expressjs/session