よく使われるHTTPステータスコードをNode.jsの実例で勉強する

#はじめに
簡単なアプリを作りながらNode.jsと基本的なサーバーサイドプログラミングについて勉強していました。

また、ステータスコードの扱いについて本で読んでいると、こういう場合にはこのステータスコードを返せばいいよと書かれてあったのですが、実際のコードでそれをどう表現すればいいのか本だけではイマイチ理解できませんでした。

今回はNode.jsの復習を兼ねて手を動かしながら実例を作ってみました。

#対象のステータスコード
- `200`: OK
- `301`: Moved Permanently
- `303`: See Other
- `400`: Bad Request
- `401`: Unauthorized
- `404`: Not Found

これ以外のステータスコードについてはまた別の記事でやるかも...?
#例題
このような勤怠表を作ります。（見た目素のHTMLですが。。。）
basic認証でのログイン機能と、ログアウト、出勤退勤ボタンを押すと、ユーザ名と勤怠のログが生成される簡単なWebアプリです。
<img width="456" alt="スクリーンショット 2020-03-29 19.09.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/564587/ffaeeb61-d680-c4cc-a17b-dfd83dae1222.png">
簡単に機能を下記にまとめました。

|パス|メソッド|機能|
|----|----|-----|
|/top|GET|勤怠表の表示
|/top/kintai|POST|勤怠表への勤怠情報の追加|
|/logout|GET|ログアウト機能|
|/old-url|GET|古いURLなので/topへリダイレクトさせる|
#準備
依存モジュールはこのようになっております。

```json:package.json
{
  "dependencies": {
    "http": "^0.0.1-security",
    "http-auth": "^4.1.2",
    "pug": "^2.0.4"
  }
}
```
basic認証には`http-auth`, テンプレートエンジンは`pug`を使用します。
次に、node.jsの雛形を記述します。

```javascript:index.js
'use strict';
const http = require('http');
const auth = require('http-auth');
const pug = require('pug');
const basic = auth.basic({
    realm: 'Enter username and password',
    file: './password'
});

const server = http.createServer(basic.check((req, res) => {
    switch (req.url) {
    default:
        break;
    }
}));

const port = 8000;
server.listen(port, () => {
    console.info('Listening on ' + port);
});
```
ちなみにこのコードはこのままでは動きません。
また、認証に使用するユーザ名とパスワードはこのようなテキストファイルに格納しておきます。

```:password
guest1:2222
guest2:3333
```
#ステータスコード : 200 OK
このステータスコードはリクエストが正常に完了したことを表します。GET,PUT,POSTメソッドで正常なリクエストが来たらまずこれでいいのではないでしょうか。
今回の例では`GET`メソッドで`/top`にリクエストが来たらステータスコード`200`とHTMLファイルを返します。

```javascript:index.js
'use strict';
const http = require('http');
const auth = require('http-auth');
const pug = require('pug');
const basic = auth.basic({
    realm: 'Enter username and password',
    file: './password'
});

//勤怠履歴
var history = [];

const server = 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;
    }
}));

const port = 8000;
server.listen(port, () => {
    console.info('Listening on ' + port);
});

```
`res.writeHead`でHTTPレスポンスヘッダに200のステータスコードを書き込んでいます。
また、`res.end`でpugテンプレートからHTMLに変換したものをレスポンスボディに書き込んでいます。
pugテンプレートは以下のようになります。

```pug:views/index.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に書き加えます。

```diff: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を書いています。
これでプログラムを再起動して、別のターミナルから

```sh
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）へリダイレクトを行います。
このコードを追加したものが以下の差分になります。

```diff:index.js
             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が帰ってくると思います。

```shell
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を返してくれるのでログアウト処理部分だけ対応することにします。

```diff:index.js
     case '/old-url' :
         handleMovedPermanently(req, res);
         break;
+    case '/logout' :
+        handleUnauthorized(req, res);
+        break;
     default:
         break;
     }
 }));
```

```diff:index.js
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に追加します。

```views/index.pug
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は見つかりません"　という記述をレスポンスボディに追加しています。

```diff:index.js
     case '/logout' :
         handleUnauthorized(req, res);
         break;
     default:
+        handleNotFound(req, res);
         break;
```

```diff:index.js
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で実装します。  

コードは以下の通りとなります。  

```diff:index.js
             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は以下の通りとなります。

```views/index.pug
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'）をレスポンスヘッダに記述します。出勤ボタンを押されると、見かけ上はページの移動が行われていないように見え、そのページ上で次々とログが出てくるはずです。

#最後に
この例題では他のステータスコードの具体例を思いつかなかったので、他のステータスコードはまた別の記事でやるかもしれません。
自分の学習ログ兼ねているので読みづらい文章だったかもしれませんがここまで読んでいただきありがとうございました。