はじめに
簡単なアプリを作りながらNode.jsと基本的なサーバーサイドプログラミングについて勉強していました。
また、ステータスコードの扱いについて本で読んでいると、こういう場合にはこのステータスコードを返せばいいよと書かれてあったのですが、実際のコードでそれをどう表現すればいいのか本だけではイマイチ理解できませんでした。
今回はNode.jsの復習を兼ねて手を動かしながら実例を作ってみました。
対象のステータスコード
200
: OK301
: Moved Permanently303
: See Other400
: Bad Request401
: Unauthorized404
: Not Found
これ以外のステータスコードについてはまた別の記事でやるかも...?
例題
このような勤怠表を作ります。(見た目素のHTMLですが。。。)
basic認証でのログイン機能と、ログアウト、出勤退勤ボタンを押すと、ユーザ名と勤怠のログが生成される簡単なWebアプリです。
簡単に機能を下記にまとめました。
パス | メソッド | 機能 |
---|---|---|
/top | GET | 勤怠表の表示 |
/top/kintai | POST | 勤怠表への勤怠情報の追加 |
/logout | GET | ログアウト機能 |
/old-url | GET | 古いURLなので/topへリダイレクトさせる |
準備
依存モジュールはこのようになっております。
{"dependencies":{"http":"^0.0.1-security","http-auth":"^4.1.2","pug":"^2.0.4"}}
basic認証にはhttp-auth
, テンプレートエンジンはpug
を使用します。
次に、node.jsの雛形を記述します。
'use strict';consthttp=require('http');constauth=require('http-auth');constpug=require('pug');constbasic=auth.basic({realm:'Enter username and password',file:'./password'});constserver=http.createServer(basic.check((req,res)=>{switch(req.url){default:break;}}));constport=8000;server.listen(port,()=>{console.info('Listening on '+port);});
ちなみにこのコードはこのままでは動きません。
また、認証に使用するユーザ名とパスワードはこのようなテキストファイルに格納しておきます。
guest1:2222
guest2:3333
ステータスコード : 200 OK
このステータスコードはリクエストが正常に完了したことを表します。GET,PUT,POSTメソッドで正常なリクエストが来たらまずこれでいいのではないでしょうか。
今回の例ではGET
メソッドで/top
にリクエストが来たらステータスコード200
とHTMLファイルを返します。
'use strict';consthttp=require('http');constauth=require('http-auth');constpug=require('pug');constbasic=auth.basic({realm:'Enter username and password',file:'./password'});//勤怠履歴varhistory=[];constserver=http.createServer(basic.check((req,res)=>{switch(req.url){case'/top':if(req.method==='GET'){res.writeHead(200,{'Content-Type':'text/html; charset=utf-8'});res.end(pug.renderFile('./views/index.pug',{history:history,user:req.user}));}break;default:break;}}));constport=8000;server.listen(port,()=>{console.info('Listening on '+port);});
res.writeHead
でHTTPレスポンスヘッダに200のステータスコードを書き込んでいます。
また、res.end
でpugテンプレートからHTMLに変換したものをレスポンスボディに書き込んでいます。
pugテンプレートは以下のようになります。
doctype html
html(lang="ja")
head
meta(charset="utf-8")
title 勤怠表
body
h1 勤怠表
h2 あなたは #{user}
node index.js
でプログラムを実行し、localhost:8000/top
をブラウザで開くとページが見れます。
この時点でログインするとユーザ名だけが表示されるページができました。
index.jsでpugにhistory
とuser.name
を渡していますが、history
は現段階では使用していません。
ステータスコード : 400 Bad Request
このステータスコードはリクエストの構文やパラメータが間違っている時に返します。
例えば、ユーザの入力からパスワードの設定を行う時に指定した要件を満たしていなかった場合など400
を返します。
またリクエストのメソッドが指定されたURLには実装されていないときなどもこのコードを返します。
リクエストのメソッドが指定されたURLには実装されていない場合の処理をindex.jsに書き加えます。
case '/top':
if(req.method === 'GET'){
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
});
res.end(pug.renderFile('./views/index2.pug', {
history: history,
user: req.user
}));
}
+ else{
+ handleBadRequest(req, res);
+ }
break;
default:
break;
}
}));
+function handleBadRequest(req, res) {
+ res.writeHead(400, {
+ 'Content-Type': 'text/plain; charset=utf-8'
+ });
+ const message = "未対応のリクエストです。";
+ res.end('status code :' + res.statusCode + " " + message);
+}
+
const port = 8000;
この例ではtop/にリクエストが来たものの、メソッドがGET以外ならばhandleBadRequest関数でレスポンスヘッダに400を書いています。
これでプログラムを再起動して、別のターミナルから
curl -X POST -u guest1:2222 -d"hoge=huga" localhost:8000/top
などと実装されていないPOSTメソッドでリクエストを送ると、
status code :400 未対応のリクエストです。
と帰ってくるはずです。
ステータスコード : 301 Moved Permanently
こちらはリクエストで指定したURLが新しいURLに移行されている時に返します。古いURLにアクセスされると新しいURLにリダイレクトされます。新しいURLはレスポンスヘッダのLocationに絶対URLとして格納します。
ここでは古いURL('/old-url')にアクセスがくると新しいURL(/top)へリダイレクトを行います。
このコードを追加したものが以下の差分になります。
handleBadRequest(req, res);
}
+ case '/old-url' :
+ handleMovedPermanently(req, res);
+ break
default:
break;
}
}));function handleBadRequest(req, res) {
res.writeHead(400, {
'Content-Type': 'text/plain; charset=utf-8'
});
const message = "未対応のリクエストです。";
res.end('status code :' + res.statusCode + " " + message);
}+function handleMovedPermanently(req, res) {
+ res.writeHead(301, {
+ 'Content-Type': 'text/html; charset=utf-8',
+ 'Location' : '/top'
+ });
+ res.end('<!DOCTYPE html><html lang="jp"><body>' +
+ '<h1>新しいURLに移動しました</h1>' +
+ '<a href="/top">新しいほう</h1>' +
+ '</body></html>');
+}
const port = 8000;
server.listen(port, () => {
console.info('Listening on ' + port);
Chromeでlocalhost:8000/old-url
を開くと/top
にリダイレクトされます。
一方curlではレスポンスボディに新しいURLに移動した旨を知らせるHTMLが帰ってくると思います。
curl -X GET -u guest1:2222 localhost:8000/old-url
<!DOCTYPE html><html lang="jp"><body><h1>新しいURLに移動しました</h1><a href="/top">新しいほう</h1></body></html>%
ステータスコード : 401 Unauthorized
このステータスコードは認証に失敗したときに返します。
またユーザがログアウトするときはこのステータスコードを返せばOKです。
この例では、server
がbasic認証が通らなかったら自動的に401を返してくれるのでログアウト処理部分だけ対応することにします。
case '/old-url' :
handleMovedPermanently(req, res);
break;
+ case '/logout' :
+ handleUnauthorized(req, res);
+ break;
default:
break;
}
}));
function handleMovedPermanently(req, res) {
res.writeHead(301, {
'Content-Type': 'text/html; charset=utf-8',
'Location' : '/top'
});
res.end('<!DOCTYPE html><html lang="jp"><body>' +
'<h1>新しいURLに移動しました</h1>' +
'<a href="/top">新しいほう</h1>' +
'</body></html>');
}+function handleUnauthorized(req, res) {
+ res.writeHead(401, {
+ 'Content-Type': 'text/html; charset=utf-8'
+ });
+ res.end('<!DOCTYPE html><html lang="jp"><body>' +
+ '<h1>ログアウトしました</h1>' +
+ '<a href="/top">ログイン</h1>' +
+ '</body></html>');
+}
次にログアウトボタンをviewに追加します。
doctype html
html(lang="ja")
head
meta(charset="utf-8")
title 勤怠表
body
h1 勤怠表
h2 あなたは #{user}
a(href="/logout") ログアウト
ブラウザでlocalhost:8000/top
を開き、ログアウトを押すと、ログアウトした旨のHMLが表示され、ログインを押すとまたユーザ名とパスワードが要求されます。
ステータスコード : 404 Not Found
このステータスコードは指定したリソースが存在しない場合に返します。
またこのときのレスポンスボディには理由を記述します。
"status code :404 指定したURLは見つかりません" という記述をレスポンスボディに追加しています。
case '/logout' :
handleUnauthorized(req, res);
break;
default:
+ handleNotFound(req, res);
break;
function handleUnauthorized(req, res) {
res.writeHead(401, {
'Content-Type': 'text/html; charset=utf-8'
});
res.end('<!DOCTYPE html><html lang="jp"><body>' +
'<h1>ログアウトしました</h1>' +
'<a href="/top">ログイン</h1>' +
'</body></html>');
}+function handleNotFound(req, res){
+ res.writeHead(404, {
+ 'Content-Type': 'text/plain; charset=utf-8'
+ });
+ const message = "指定したURLが見つかりません。";
+ res.end('status code :' + res.statusCode + " " +message);
+}
ブラウザでlocalhost:8000/hoge
などと存在しないURLを開くと、ページが存在しない旨が表示されます。
ステータスコード : 303 See Other
このステータスコードはリクエストに対する処理結果が別のURLで取得できる時に返します。例えばブラウザからPOSTで何かリソースを操作するリクエストの処理結果やそれを反映した結果をGETで取得する時に使います。
ここの例では、出勤ボタンを押すと、出勤
のログを出し、退勤ボタンを押すと、退勤
のログをユーザ名と一緒に流す機能をPOSTで実装します。
コードは以下の通りとなります。
handleBadRequest(req, res);
}
break;
+ case '/top/kintai':
+ let data = [];
+ if(req.method === 'POST'){
+ req.on('data', (chunk) => {
+ data.push(chunk);
+ }).on('end', () => {
+ data = data.toString();
+ const decoded = decodeURIComponent(data);
+ let kintai = decoded ? decoded.split('kintai-button=')[1] : '';
+ kintai = req.user + ':' + kintai
+ history.unshift(kintai);
+ res.writeHead(303, {
+ 'Location': '/top'
+ });
+ res.end();
+ })
+ }
+ else{
+ handleBadRequest(req, res);
+ }
case '/old-url' :
handleMovedPermanently(req, res);
break;
viewは以下の通りとなります。
doctype html
html(lang="ja")
head
meta(charset="utf-8")
title 勤怠表
body
h1 勤怠表
h2 あなたは #{user}
a(href="/logout") ログアウト
form(method="post" action="/top/kintai")
button(type="submit" name="kintai-button" value="出勤") 出勤
button(type="submit" name="kintai-button" value="退勤") 退勤
each element in history
div.history
<hr>
p #{element}
<hr>
コードの説明をします。
まず、localhost:8000/top
をブラウザで開くと出勤ボタンと退勤ボタンが出現します。
出勤ボタンを押すとkintai-button="出勤"
、退勤ボタンを押すとkintai-button="退勤"
という情報(厳密には違いますが)が、index.jsのcase 'top/kintai':
以下で処理されることになります。data
にはURLエンコーディングされた文字列が入ってくるので、これをdecodeすることによって人間が読める文字列をdecoded
に取り出します。ここから、decoded
の中(例;kintai-button="退勤"
)から=の右部分("退勤"
)だけを取り出したものとユーザネームをつなげたものをkintai
に格納します。そしてログ全体を格納する配列であるhistory
に前から追加していきます。なぜ普通のpush(後ろから追加)しないのかというと、新しいものをログの上部分に表示したかったからです。
最後に303のステータスコードと、リダイレクト先を指定('Location':'/top')をレスポンスヘッダに記述します。出勤ボタンを押されると、見かけ上はページの移動が行われていないように見え、そのページ上で次々とログが出てくるはずです。
最後に
この例題では他のステータスコードの具体例を思いつかなかったので、他のステータスコードはまた別の記事でやるかもしれません。
自分の学習ログ兼ねているので読みづらい文章だったかもしれませんがここまで読んでいただきありがとうございました。