1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

node.jsによるサーバの基本構造

1
Last updated at Posted at 2024-06-30

node.jsによるサーバの基本構造

ここでは、JavaScript埋め込みのHTMLファイルとNode.jsサーバを別々に扱い、HTML上からの入力に基づいたPOSTメソッドのリクエストに対応サーバの基本を理解する。これは非常に重要なので、GETメソッドを受け取る単純なサーバの構造と、このサーバの構造は必ず理解しておいて欲しい。

HTMLファイルはブラウザで直接開くか、別のローカルサーバから開くことを想定する。
Node.jsサーバは、APIサーバとして http://localhost:3000 で動作する。
fetch は、JavaScriptからWebサーバへリクエストを送るための命令である。

HTMLファイル
  ↓ fetch
Node.js API Server

この構成では、ブラウザから見るとHTMLの読み込み元とNode.js APIのアクセス先が異なる場合がある。

そのため、Node.js側でCORSを明示的に許可する。


1. ファイル構成

次の2つのファイルを作成する。

cors-post-test/
  server.js
  index.html
ファイル 役割
server.js Node.jsのAPIサーバ
index.html ブラウザ側のHTML画面

2. server.js

server.js を作成し、次のコードを書く。

const http = require('http');

function setCorsHeaders(res) {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
}

function readRequestBody(req) {
  return new Promise((resolve, reject) => {
    let body = '';

    req.on('data', chunk => {
      body += chunk;
    });

    req.on('end', () => {
      resolve(body);
    });

    req.on('error', error => {
      reject(error);
    });
  });
}

function sendJson(res, statusCode, obj) {
  setCorsHeaders(res);

  res.writeHead(statusCode, {
    'Content-Type': 'application/json; charset=utf-8'
  });

  res.end(JSON.stringify(obj, null, 2));
}

const server = http.createServer(async (req, res) => {
  setCorsHeaders(res);

  if (req.method === 'OPTIONS') {
    res.writeHead(204);
    res.end();
    return;
  }

  if (req.method === 'GET' && req.url === '/') {
    sendJson(res, 200, {
      status: 'ok',
      message: 'Node.js API server is running.'
    });
    return;
  }

  if (req.method === 'POST' && req.url === '/submit') {
    try {
      const body = await readRequestBody(req);
      const data = JSON.parse(body);

      console.log('Received JSON:', data);

      sendJson(res, 200, {
        status: 'ok',
        message: `Hello, ${data.name}! Your age is ${data.age}.`,
        received: data
      });

    } catch (error) {
      sendJson(res, 400, {
        status: 'error',
        message: 'Invalid JSON',
        detail: error.toString()
      });
    }

    return;
  }

  sendJson(res, 404, {
    status: 'error',
    message: 'Not Found'
  });
});

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000/');
});

3. index.html

index.html を作成し、次のコードを書く。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>CORS POST Test</title>
  <style>
    body {
      font-family: sans-serif;
      margin: 30px;
      background: #f5f5f5;
    }

    .panel {
      background: white;
      padding: 20px;
      border-radius: 8px;
      width: 420px;
      box-shadow: 0 2px 6px rgba(0,0,0,0.15);
    }

    label {
      display: block;
      margin-top: 12px;
      font-weight: bold;
    }

    input {
      width: 100%;
      box-sizing: border-box;
      margin-top: 5px;
      padding: 8px;
      font-size: 14px;
    }

    button {
      margin-top: 15px;
      padding: 10px 16px;
      font-size: 15px;
      cursor: pointer;
    }

    pre {
      background: #222;
      color: #eee;
      padding: 12px;
      border-radius: 6px;
      overflow-x: auto;
      white-space: pre-wrap;
      min-height: 80px;
    }

    #status {
      margin-top: 15px;
      padding: 10px;
      border-radius: 6px;
      background: #ddd;
      font-weight: bold;
    }

    .waiting {
      background: #dddddd !important;
    }

    .sending {
      background: #90caf9 !important;
    }

    .success {
      background: #a5d6a7 !important;
    }

    .error {
      background: #ef9a9a !important;
    }
  </style>
</head>
<body>
  <h1>CORS POST Test</h1>

  <p>
    このHTMLファイルから、Node.js APIサーバ
    <code>http://localhost:3000/submit</code>
    へPOST通信する。
  </p>

  <div class="panel">
    <form id="submitForm">
      <label for="name">Name</label>
      <input type="text" id="name" value="Taro">

      <label for="age">Age</label>
      <input type="number" id="age" value="20">

      <button type="submit">送信</button>
    </form>

    <div id="status" class="waiting">状態:待機中</div>

    <h2>サーバからの応答</h2>
    <pre id="result">まだ送信していません。</pre>
  </div>

  <script>
    const form = document.getElementById('submitForm');
    const statusBox = document.getElementById('status');
    const result = document.getElementById('result');

    function setStatus(text, className) {
      statusBox.textContent = '状態:' + text;
      statusBox.className = className;
    }

    form.addEventListener('submit', async event => {
      event.preventDefault();

      const data = {
        name: document.getElementById('name').value,
        age: document.getElementById('age').value
      };

      try {
        setStatus('送信中', 'sending');
        result.textContent = '';

        const response = await fetch('http://localhost:3000/submit', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(data)
        });

        const responseData = await response.json();

        if (response.ok) {
          setStatus('完了', 'success');
        } else {
          setStatus('エラー', 'error');
        }

        result.textContent = JSON.stringify(responseData, null, 2);

      } catch (error) {
        setStatus('通信エラー', 'error');
        result.textContent = error.toString();
      }
    });
  </script>
</body>
</html>

4. 実行方法

4.1 Node.jsサーバを起動する

コマンドプロンプトで作業フォルダに移動する。

cd cors-post-test

Node.jsサーバを起動する。

node server.js

次のように表示されれば成功である。

Server running at http://localhost:3000/

4.2 サーバの起動確認

ブラウザで次のURLを開く。

http://localhost:3000/

次のようなJSONが表示されれば、Node.jsサーバは起動している。

{
  "status": "ok",
  "message": "Node.js API server is running."
}

4.3 HTMLファイルを開く

index.html をブラウザで開く。

たとえば、HTMLを直接開いた場合は、URLは次のようになる。

file:///C:/.../cors-post-test/index.html

このHTML画面から、Node.jsサーバの次のURLへPOST通信する。

http://localhost:3000/submit

5. curlでPOSTを確認する

ブラウザを使わずに、コマンドプロンプトからPOST通信を確認することもできる。

Node.jsサーバを起動した状態で、別のコマンドプロンプトを開き、次のコマンドを実行する。

curl -v -H "Content-Type: application/json" -d "{\"name\":\"Taro\",\"age\":30}" http://localhost:3000/submit

成功すると、次のようなJSONが表示される。

{
  "status": "ok",
  "message": "Hello, Taro! Your age is 30.",
  "received": {
    "name": "Taro",
    "age": 30
  }
}

6. CORSの設定

この教材では、HTMLファイルとNode.js APIサーバを別のものとして扱う。

たとえば、HTMLファイルを直接開くと、HTMLの読み込み元は次のようになる。

file:///C:/.../index.html

一方、Node.js APIサーバは次のURLで動作している。

http://localhost:3000/submit

このように、HTMLの読み込み元とAPIの送信先が異なる場合、ブラウザは安全のために通信を制限することがある。

これがCORSである。


6.1 CORS許可ヘッダ

Node.js側では、次の関数でCORSを許可している。

function setCorsHeaders(res) {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
}

それぞれの意味は次の通りである。

ヘッダ 意味
Access-Control-Allow-Origin どのオリジンからのアクセスを許可するか
Access-Control-Allow-Methods どのHTTPメソッドを許可するか
Access-Control-Allow-Headers どのリクエストヘッダを許可するか

今回の教材では、学習用に次のようにしている。

res.setHeader('Access-Control-Allow-Origin', '*');

* は、どのオリジンからのアクセスも許可するという意味である。

実際のサービスでは、必要なオリジンだけを許可する方が望ましい。


6.2 OPTIONSリクエスト

ブラウザは、POSTでJSONを送る前に、事前確認として OPTIONS リクエストを送ることがある。

これをプリフライトリクエストという。

今回のNode.jsサーバでは、次の部分で OPTIONS に対応している。

if (req.method === 'OPTIONS') {
  res.writeHead(204);
  res.end();
  return;
}

204 は、レスポンス本文はないが、処理は成功したことを表すステータスコードである。


7. プログラムの流れ

今回の通信の流れは、次の通りである。

HTML
  ↓ fetch
Node.js API Server
  ↓ JSON
HTML

より詳しく書くと、次のようになる。

フォームに入力する
  ↓
送信ボタンを押す
  ↓
JavaScriptがfetchを実行する
  ↓
JSONをPOSTする
  ↓
Node.jsがPOSTデータを受け取る
  ↓
Node.jsがJSONを解析する
  ↓
Node.jsがJSONレスポンスを返す
  ↓
ブラウザがJSONを受け取る
  ↓
画面に結果を表示する

8. よくあるエラー

8.1 Node.jsサーバを起動していない

index.html だけを開いても、Node.jsサーバが起動していなければ通信できない。

先に次を実行する。

node server.js

8.2 ポート3000が使われている

すでに別のNode.jsサーバが起動している可能性がある。

その場合は、起動中のサーバを停止する。

コマンドプロンプトで次を押す。

Ctrl + C

8.3 URLが違う

HTML側では、次のURLへ送信している。

fetch('http://localhost:3000/submit', {

Node.js側では、次のURLを処理している。

if (req.method === 'POST' && req.url === '/submit') {

この2つが対応している必要がある。

HTML側 Node.js側
http://localhost:3000/submit /submit

8.4 CORSエラーが出る

CORSエラーが出る場合は、Node.js側で次のヘッダが設定されているか確認する。

res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

また、OPTIONS リクエストに対応しているか確認する。

if (req.method === 'OPTIONS') {
  res.writeHead(204);
  res.end();
  return;
}

8.5 JSONの形式が間違っている

Node.js側では、次の部分でJSONを解析している。

const data = JSON.parse(body);

送られてきたデータがJSON形式でない場合、エラーになる。

今回のHTMLでは、次のようにJSONへ変換している。

body: JSON.stringify(data)

9. まとめ

この教材では、HTMLファイルからNode.js APIサーバへPOST通信する方法を学習した。

重要な点は次の通りである。

HTML
  ↓ fetch
Node.js API Server
  ↓ JSON
HTML

Node.js側では、CORSを許可するために次の処理を入れた。

res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

また、プリフライトリクエストに対応するために、OPTIONS メソッドを処理した。

if (req.method === 'OPTIONS') {
  res.writeHead(204);
  res.end();
  return;
}

この構成を理解しておくと、ローカルHTML、Node.js、GAS Web APIなど、異なる場所で動くWebコンテンツ同士の通信を理解しやすくなる。

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?