Edited at

Express 4 のログ出力とフォームの処理

More than 5 years have passed since last update.

Express 4 のログ出力とフォームの処理について調べてみました。

コードはこちら。

https://github.com/hoshi-takanori/express-sample


ログ出力

Express 4 ではログ出力のパッケージは独立して morgan という名前になったようなので、まずこれをインストールします。


package.json

{

...
"dependencies": {
"express": "*",
"morgan": "*",
"jade": "*"
}
}

$ npm install

使い方はこんな感じ。


app.js

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.js

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.js

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.js

...

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 モード)で動作を切り替えるには次のようにします。


app.js

...

if (app.get('env') == 'production') {
app.use(morgan());
} else {
//app.use(morgan('short'));
app.use(morgan({ format: 'dev', immediate: true }));
}

...



run.sh

#!/bin/sh

export NODE_ENV=production

node app


また、開発時は標準出力、デプロイ時にはファイルにログを出力、なんてこともできます。


app.js

...

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.js

...

app.get('/', function (req, res) {
var username = req.param('username');
res.render('index', { title: 'Express Sample', username: username });
});

...



view/index.jade

  ...

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 が必要です。


package.json

{

...
"dependencies": {
"express": "*",
"morgan": "*",
"body-parser": "*",
"jade": "*"
}
}


app.js

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.js

...

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.' });
}
});

...



view/index.jade

  ...

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.js

...

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 が必要です。


package.json

{

...
"dependencies": {
"express": "*",
"morgan": "*",
"body-parser": "*",
"cookie-parser": "*",
"jade": "*"
}
}


app.js

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.js

...

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('/');
});

...



view/index.jade

  ...

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 が必要です。


package.json

{

...
"dependencies": {
"express": "*",
"morgan": "*",
"body-parser": "*",
"cookie-parser": "*",
"express-session": "*",
"jade": "*"
}
}


app.js

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.js

...

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