9
3

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.

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

Last updated at Posted at 2020-04-02

はじめに

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

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

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

対象のステータスコード

  • 200: OK
  • 301: Moved Permanently
  • 303: See Other
  • 400: Bad Request
  • 401: Unauthorized
  • 404: Not Found

これ以外のステータスコードについてはまた別の記事でやるかも...?

例題

このような勤怠表を作ります。(見た目素のHTMLですが。。。)
basic認証でのログイン機能と、ログアウト、出勤退勤ボタンを押すと、ユーザ名と勤怠のログが生成される簡単なWebアプリです。
スクリーンショット 2020-03-29 19.09.00.png
簡単に機能を下記にまとめました。

パス メソッド 機能
/top GET 勤怠表の表示
/top/kintai POST 勤怠表への勤怠情報の追加
/logout GET ログアウト機能
/old-url GET 古いURLなので/topへリダイレクトさせる

準備

依存モジュールはこのようになっております。

package.json
{
  "dependencies": {
    "http": "^0.0.1-security",
    "http-auth": "^4.1.2",
    "pug": "^2.0.4"
  }
}

basic認証にはhttp-auth, テンプレートエンジンはpugを使用します。
次に、node.jsの雛形を記述します。

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ファイルを返します。

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テンプレートは以下のようになります。

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にhistoryuser.nameを渡していますが、historyは現段階では使用していません。

ステータスコード : 400 Bad Request

このステータスコードはリクエストの構文やパラメータが間違っている時に返します。
例えば、ユーザの入力からパスワードの設定を行う時に指定した要件を満たしていなかった場合など400を返します。
またリクエストのメソッドが指定されたURLには実装されていないときなどもこのコードを返します。
リクエストのメソッドが指定されたURLには実装されていない場合の処理をindex.jsに書き加えます。

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

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が帰ってくると思います。

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

index.js
     case '/old-url' :
         handleMovedPermanently(req, res);
         break;
+    case '/logout' :
+        handleUnauthorized(req, res);
+        break;
     default:
         break;
     }
 }));
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は見つかりません" という記述をレスポンスボディに追加しています。

index.js
     case '/logout' :
         handleUnauthorized(req, res);
         break;
     default:
+        handleNotFound(req, res);
         break;
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で実装します。

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

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

最後に

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

9
3
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
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?