言語
日本語
English

Caution

お使いのブラウザはJavaScriptが無効になっております。
当サイトでは検索などの処理にJavaScriptを使用しています。
より快適にご利用頂くため、JavaScriptを有効にしたうえで当サイトを閲覧することをお勧めいたします。

Node.js辞典

  1. トップページ
  2. Node.js辞典
  3. http.createServer()(HTTPサーバー)

http.createServer()(HTTPサーバー)

Node.js標準の『http』モジュールを使うと、外部ライブラリなしにHTTPサーバーを作成できます。http.createServer()でサーバーを生成し、listen()でポートを開いてリクエストを待ち受けます。

http モジュールの読み込み

httpモジュールはNode.js標準ライブラリです。インストール不要でrequire()で読み込めます。

var http = require('http');

主要なオブジェクト・メソッド一覧

メソッド / プロパティ概要
http.createServer(callback)HTTPサーバーを作成します。コールバック(リスナー)はリクエストのたびに呼ばれます。
server.listen(port, callback)指定したポートでリクエストの待ち受けを開始します。
server.close(callback)新規リクエストの受付を停止します。既存のコネクションはそのまま処理されます。
req.urlリクエストURLのパス部分(例: /about)です。
req.methodHTTPメソッド(GETPOSTPUTDELETEなど)です。
req.headersリクエストヘッダーをキーと値のオブジェクトで保持します。
res.writeHead(statusCode, headers)レスポンスのステータスコードとヘッダーを送信します。
res.write(data)レスポンスボディの一部を送信します。
res.end(data)レスポンスボディの送信を完了します。省略せずに必ず呼ぶ必要があります。
res.setHeader(name, value)レスポンスヘッダーを個別に設定します。
res.statusCodeレスポンスのHTTPステータスコードを設定します(デフォルト: 200)。

最初の HTTP サーバー

最もシンプルなHTTPサーバーの例です。どのURLへのリクエストに対しても同じレスポンスを返します。

simple_server.js
var http = require('http');

// createServer()のコールバックがリクエストリスナー
// req: IncomingMessage(リクエスト情報)
// res: ServerResponse(レスポンス操作)
var server = http.createServer(function(req, res) {
    // ステータスコード200とContent-Typeヘッダーを送信する
    res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });

    // レスポンスボディを送信して完了する
    res.end('KOFファイター、参上!\n');
});

// ポート3000でリクエストを待ち受ける
server.listen(3000, function() {
    console.log('サーバーが起動しました: http://localhost:3000/');
});
node simple_server.js
サーバーが起動しました: http://localhost:3000/

別のターミナルからcurlでアクセスして確認します。

curl http://localhost:3000/
KOFファイター、参上!

req.url・req.method によるルーティング

リクエストオブジェクト(req)のurlmethodを使って、URLとHTTPメソッドに応じてレスポンスを切り替えられます。

routing_server.js
var http = require('http');

// ファイターデータ(KOFキャラクター)
var fighters = [
    { id: 1, name: '八神庵',         style: '八神流古武術' },
    { id: 2, name: '草薙京',         style: '草薙流古武術' },
    { id: 3, name: 'テリー・ボガード', style: 'パワーウェイブ' },
    { id: 4, name: '不知火舞',        style: '不知火流忍術' },
    { id: 5, name: 'リョウ・サカザキ', style: '極限流空手' },
];

var server = http.createServer(function(req, res) {
    var url = req.url;
    var method = req.method;

    console.log(method + ' ' + url);  // アクセスログ

    // GETメソッドかどうか確認する
    if (method !== 'GET') {
        res.writeHead(405, { 'Content-Type': 'text/plain; charset=utf-8' });
        res.end('405 Method Not Allowed');
        return;
    }

    if (url === '/') {
        // トップページ
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        res.end('<h1>KOF ファイター名鑑</h1><p><a href="/fighters">ファイター一覧</a></p>');

    } else if (url === '/fighters') {
        // ファイター一覧をJSONで返す
        res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
        res.end(JSON.stringify(fighters, null, 2));

    } else if (url.startsWith('/fighters/')) {
        // /fighters/1 のような個別ルート
        var id = parseInt(url.replace('/fighters/', ''), 10);
        var fighter = fighters.find(function(f) { return f.id === id; });

        if (fighter) {
            res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
            res.end(JSON.stringify(fighter, null, 2));
        } else {
            res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
            res.end('404 Not Found');
        }

    } else {
        res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
        res.end('404 Not Found');
    }
});

server.listen(3000, function() {
    console.log('サーバーが起動しました: http://localhost:3000/');
});
node routing_server.js
サーバーが起動しました: http://localhost:3000/

別のターミナルからアクセスして確認します。

curl http://localhost:3000/fighters
[
  { "id": 1, "name": "八神庵", "style": "八神流古武術" },
  ...
]
curl http://localhost:3000/fighters/3
{
  "id": 3,
  "name": "テリー・ボガード",
  "style": "パワーウェイブ"
}

req.headers — リクエストヘッダーの取得

リクエストヘッダーはreq.headersオブジェクトから取得できます。ヘッダー名はすべて小文字で格納されています。

headers_server.js
var http = require('http');

var server = http.createServer(function(req, res) {
    // リクエストヘッダーを確認する
    var userAgent = req.headers['user-agent'] || '不明';
    var contentType = req.headers['content-type'] || '未指定';
    var acceptLang = req.headers['accept-language'] || '未指定';

    console.log('User-Agent:', userAgent);
    console.log('Content-Type:', contentType);
    console.log('Accept-Language:', acceptLang);
    console.log('全ヘッダー:', req.headers);

    res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
    res.end(JSON.stringify({
        method: req.method,
        url: req.url,
        userAgent: userAgent,
        acceptLanguage: acceptLang,
    }, null, 2));
});

server.listen(3000, function() {
    console.log('サーバーが起動しました: http://localhost:3000/');
});
curl -H "Accept-Language: ja,en" http://localhost:3000/
{
  "method": "GET",
  "url": "/",
  "userAgent": "curl/8.4.0",
  "acceptLanguage": "ja,en"
}

POST リクエストのボディを受け取る

POSTリクエストのボディはストリームとして流れてきます。dataイベントでチャンクを受け取り、endイベントで結合して処理します。

post_server.js
var http = require('http');

var server = http.createServer(function(req, res) {
    if (req.method === 'POST' && req.url === '/battle') {
        var body = '';

        // dataイベントでリクエストボディを受け取る
        req.on('data', function(chunk) {
            body += chunk.toString('utf8');
            // ボディが大きすぎる場合は拒否する(DoS対策の例)
            if (body.length > 1024) {
                res.writeHead(413, { 'Content-Type': 'text/plain' });
                res.end('Request Entity Too Large');
                req.destroy();
            }
        });

        // endイベントでボディの受信完了
        req.on('end', function() {
            var data;
            try {
                data = JSON.parse(body);
            } catch (e) {
                res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
                res.end('400 Bad Request: JSONパースエラー');
                return;
            }

            console.log('受信データ:', data);

            // レスポンスを返す
            res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
            res.end(JSON.stringify({
                status: 'ok',
                message: data.fighter + ' が参戦しました!',
            }));
        });

    } else {
        res.writeHead(404, { 'Content-Type': 'text/plain' });
        res.end('Not Found');
    }
});

server.listen(3000, function() {
    console.log('サーバーが起動しました: http://localhost:3000/');
});
curl -X POST -H "Content-Type: application/json" -d '{"fighter":"八神庵"}' http://localhost:3000/battle
{"status":"ok","message":"八神庵 が参戦しました!"}

概要

httpモジュールはNode.js標準ライブラリのため、インストールなしにHTTPサーバーを構築できます。ルーティングやボディパースなどの機能はすべて自前で実装する必要があるため、実際のWebアプリケーション開発では『Express』などのフレームワークが使われることが多いです。

ただし、httpモジュールの仕組みを理解しておくことで、フレームワークが内部で何を行っているかを把握しやすくなります。req.urlreq.methodreq.headersによるリクエスト解析と、res.writeHead()res.end()によるレスポンス送信がHTTPサーバーの基本構造です。

POSTリクエストのボディはストリームとして届くため、dataイベントとendイベントを組み合わせて受け取ります。大量のデータが送り込まれるDoS攻撃への対策として、ボディサイズの上限チェックを入れておくことが一般的です。

よくあるミス

res.end() を呼び忘れてクライアントがレスポンス待ちのまま固まる

res.end()はレスポンスの送信完了をクライアントに伝えるメソッドです。呼ばないとHTTP接続が閉じられず、クライアント(ブラウザやcurl)はレスポンスの終端を受け取れないまま待ち続けます。

NGの例(res.end()がない):

var http = require('http');

var server = http.createServer(function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.write('KOFファイター、参上!\n');
    // res.end() を呼んでいない — クライアントは接続を閉じられず待ち続ける
});

server.listen(3000, function() {
    console.log('サーバーが起動しました: http://localhost:3000/');
});

OKの例(res.end()でレスポンスを完了させる):

var http = require('http');

var server = http.createServer(function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.end('KOFファイター、参上!\n'); // レスポンスを完了させる
});

server.listen(3000, function() {
    console.log('サーバーが起動しました: http://localhost:3000/');
});

EADDRINUSE — ポートがすでに使用中のエラー

同じポートで別のNode.jsプロセスがすでに起動している場合、EADDRINUSEエラーが発生してサーバーを起動できません。

node simple_server.js
Error: listen EADDRINUSE: address already in use :::3000

ポートを使用しているプロセスのPIDを確認して停止します。macOS・Linuxではlsofまたはfuserコマンドを使います。

lsof -i :3000
COMMAND   PID  USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
node     1234  user   22u  IPv6  12345      0t0  TCP *:hbci (LISTEN)
kill 1234

fuserを使う場合は次のように実行します。

fuser 3000/tcp
3000/tcp:             1234

PIDを確認したらkillコマンドでプロセスを停止し、サーバーを再起動します。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。