125
124

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-06-24

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

125
124
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
125
124

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?