Node.jsで静的ファイルを表示する

今回はhtmlファイルなどの静的ファイルをNode.jsで表示する方法について記載します。調べたところ「Express」や「http-server」などのライブラリを使う方法は多く見つかりましたが、今回はそれらを使わない方法になります。

前提・環境

今回は以下のバージョンでnodeのバージョンで動かしています。

$ node -v
v15.3.0

また、Mac環境で動かしています。他のバージョン・環境でも動くと思いますが、動かない場合はこれらの違いにご注意ください。

コード全体

詳細な説明は別途記載しますが、まずは作成したコード全体を記載しておきます。

なお、TypeScriptで作成していますがコンパイル後のJavaScriptのコードも最後に記載しておきます。

内容

作成したコードの全体が以下です。

import path = require("path");
import * as fs from "fs";
import * as http from "http";

type Request = http.IncomingMessage | { url: string };
type Response = http.ServerResponse;

const hostname = "127.0.0.1";
const port = 3000;
const dirPublic = "./server";
const indexFile = "index.html";

const service = function (req: Request, res: Response) {
  const pathname = req.url;
  const root = path.resolve(dirPublic);
  const file = path.join(root, pathname);

  fs.stat(file, (err, stat) => {
    if (err) {
      console.log("err", err);
      res.write("err");
      res.end();
      return;
    }

    if (stat.isDirectory()) {
      service({ url: `${req.url}/${indexFile}` }, res);
    }

    if (stat.isFile()) {
      const stream = fs.createReadStream(file);
      stream.pipe(res);
    }
  });
};

const listener = function (req: Request, res: Response) {
  service(req, res);
};

const server = http.createServer(listener);
server.listen(port, hostname);

全体の流れ(概要)

インポート(import)

インポート部分です。必要なものをインポートしています。

import path = require("path");
import * as fs from "fs";
import * as http from "http";

型定義

型の定義部分です。この後のコードの見やすさのために記載しています。趣味の範疇なので、これは無くとも問題ないです。

type Request = http.IncomingMessage | { url: string };
type Response = http.ServerResponse;

定数定義

定数の定義部分です。直接該当箇所に書いても良いですが、分かりやすさのために切り出しています。

const hostname = "127.0.0.1";
const port = 3000;
const dirPublic = "./server";
const indexFile = "index.html";

これらの値は私が用意した環境の内容になっています。環境的に異なる部分は適宜読み替えてください。

サービス関数

今回のメイン部分です。ファイルを読み込んで画面に表示するようにレスポンスします。詳細は後述します。

const service = function (req: Request, res: Response) {
 ...
};

リスナー関数

リスナーの定義部分です。サービスメソッドを呼び出しています。

const listener = function (req: Request, res: Response) {
  service(req, res);
};

今回はサービスを呼び出しているだけなので、あまり意味がないですが、別の処理・分岐などを追加するときに備えてサービスとリスナーという形で分けています。

HTTPサーバ起動

HTTPサーバの起動部分です。この部分でサーバが起動し、指定したホスト名・ポート番号でリクエストを待機します。

const server = http.createServer(listener);
server.listen(port, hostname);

指定しているホスト名やポート番号は最初の方で定数定義したものになります。

サービス詳細

今回のメイン部分です。

関数定義(引数)

関数の定義部分です。引数はHTTPサーバのリクエストの情報、レスポンスの情報が渡ってきます。

const service = function (req: Request, res: Response) {

ファイルパス取得

リクエストの情報を元にサーバ上のファイルパスを作成しています。リクエストの「url」プロパティにリクエストのパスの情報が入っているので、それとルートフォルダのパスを連結しているだけです。

  const pathname = req.url;
  const root = path.resolve(dirPublic);
  const file = path.join(root, pathname);

今回はシンプルな内容にしていますが、実際はファイル名が日本語を含む場合など、条件によっては、もう少し複雑な処理が必要になるかなと思います。

ファイル情報取得

ファイルの情報の処理です。fsのstatメソッドで先ほど作成したファイルパスのファイルの情報を取得しています。読み込んだ情報を元にこの後の処理を実施します。

  fs.stat(file, (err, stat) => {
    ...
  });

エラー処理

エラーの場合の処理です。これは指定したパス(ファイルやディレクトリ)が見つからなかった場合も含みます。ログと画面にerrの文字だけ出力して終了しています。

    if (err) {
      console.log("err", err);
      res.write("err");
      res.end();
      return;
    }

エラーのコードを見て分岐を入れることもできますが、今回はシンプルな形にしています。

実際はファイルが見つからなかった場合は404のHTTPステータスコードを設定、それ以外のエラー(致命的なエラーなど)は500系のHTTPステータスコードを設定した方が良いかなと思います。

フォルダの場合

対象のパスがフォルダの場合の処理です。

    if (stat.isDirectory()) {
      service({ url: `${req.url}/${indexFile}` }, res);
    }

フォルダの中に「index.html」がある場合はそれを表示したいので、元のパスにファイル名を連結して再度サービスメソッドを呼び出しています。

この処理は個人的に欲しかったので作成しましたが、「index.html」を表示する必要がなければ無くとも問題ないです。

ファイルの場合

対象のパスがファイルの場合の処理です。fsのcreateReadStreamメソッドを使ってストリームを生成して、それをレスポンスにパイプ(pipe)しています。

    if (stat.isFile()) {
      const stream = fs.createReadStream(file);
      stream.pipe(res);
    }

このパイプ処理を入れることでファイルの内容がレスポンスされて画面に表示されます。

コンパイル結果(JavaScript)

今回のコードをコンパイルした結果のJavaScriptコードの内容です。

"use strict";
exports.__esModule = true;
var path = require("path");
var fs = require("fs");
var http = require("http");
var hostname = "127.0.0.1";
var port = 3000;
var dirPublic = "./server";
var indexFile = "index.html";
var service = function (req, res) {
    var pathname = req.url;
    var root = path.resolve(dirPublic);
    var file = path.join(root, pathname);
    fs.stat(file, function (err, stat) {
        if (err) {
            console.log("err", err);
            res.write("err");
            res.end();
            return;
        }
        if (stat.isDirectory()) {
            service({ url: "".concat(req.url, "/").concat(indexFile) }, res);
        }
        if (stat.isFile()) {
            var stream = fs.createReadStream(file);
            stream.pipe(res);
        }
    });
};
var listener = function (req, res) {
    service(req, res);
};
var server = http.createServer(listener);
server.listen(port, hostname);

TypeScriptを使っていない場合は上記を参考にしてもらえればと思います。

実行

作成したコードを実際に実行して確認してみます。

なお、確認のため以下のようなhtmlファイルを該当のフォルダに置いています。

<!DOCTYPE html>
<html>

<head>
    <title>static page title</title>
</head>

<body>
    static page
</body>

</html>

起動

作成したコードを起動します。

TypeScriptでコードを書いているので、先にコンパイルしています。

$ tsc server/index.ts
$
$ node server/index.js

ここではシンプルな方法を取っていますが、詳しい人は別の方法でも大丈夫です。

表示

起動したので以下のURLにアクセスしてみます。

http://localhost:3000/

以下の通り無事に画面が表示されました。

作成物

今回のコードは以下に置いてあります。

https://github.com/masaki-code/node/tree/main/static

コメント

タイトルとURLをコピーしました