Just do IT

思うは招く

Node.jsでサーバーを立ててウェブサイトを表示する方法

Node.jsの勉強でちょっとつまづいたのでメモ。

課題:Node.jsでCSS、画像、JSなどが読み込まれない

Node.jsでウェブサーバーを立ててウェブサイトを表示したいが、HTMLだけ表示されてCSSや画像ファイルが読み込まれない。

結論

CSSやJS、画像(JPG、SVGPNG等)を読み込む実装をする必要がある。

背景

作ったウェブサイトをテスト的に表示させたかった。まぁ普通にChromeを使えばいいのだが、JavaScriptやNode.jsを勉強中ということもあり、「Node.jsでウェブサーバー立ててみっかー」の勢いで試してみたらつまづいた。Express等のフレームワークを使うほどの規模ではないと判断し直書きで実装することに。 ※Expressフレームワークを使えばデフォルトでcssなど静的ファイルを読み込める

方法

こちらの記事を参考にさせていただいた。感謝。

Node.jsでExpressを使わずシンプルなhttpサービスを作成してみる

Node.jsバージョン

v10.14.2

実装コード

index.jsファイルを作成し、以下のコードを記述。

var http = require('http');
var fs = require('fs');

function getType(_url) {
  var types = {
    '.html': 'text/html',
    '.css': 'text/css',
    '.js': 'text/javascript',
    '.png': 'image/png',
    '.gif': 'image/gif',
    '.svg': 'image/svg+xml'
  }
  for (var key in types) {
    if (_url.endsWith(key)) {
      return types[key];
    }
  }
  return 'text/plain';
}

var server = http.createServer((req, res) => {
  var url = 'public' + (req.url.endsWith('/') ? req.url + 'index.html' : req.url);
  if (fs.existsSync(url)) {
    fs.readFile(url, (err, data) => {
      if (!err) {
        res.writeHead(200, {'Content-Type': getType(url)});
        res.end(data);
      } else {
        res.statusCode = 500;
        res.end();
      }
    });
  } else {
    res.statusCode = 404;
    res.end();
  }
});

var port = 8000;
server.listen(port, () => {
  console.log(`Server listening on ${port}`);
});

HTML、CSS、jpg、pngsvgファイルなどは適当に用意し、publicディレクトリを作ってぶちこんでおく。 ディレクトリ構成はこんな感じ。

- index.js
- public
    - index.html
    - css
    - img 

何をしているか

http、fsモジュールを読み込む。なお練習なので変数定義もconstなどではなくvarで。

var http = require('http');
var fs = require('fs');

.html.cssなどの拡張子で終わる場合に返すコンテンツタイプをそれぞれ定義。

function getType(_url) {
  var types = {
    '.html': 'text/html',
    '.css': 'text/css',
    '.js': 'text/javascript',
    '.png': 'image/png',
    '.gif': 'image/gif',
    '.svg': 'image/svg+xml'
  }
  for (var key in types) {
    if (_url.endsWith(key)) {
      return types[key];
    }
  }
  return 'text/plain';
}

for...in命令を使用し、キーの形式でパスが終了していたら、そのキーに定義されている値をコンテンツタイプとして返す。たとえば、上記の配列を使用し、レプルを立ち上げて以下のような処理を記述すると、動きがイメージしやすくなる。

for (var key in types) { console.log(`${key} = ${types[key]}`)}

これを実行すると以下の処理が返ってくる。

.html = text/html
.css = text/css
.js = text/javascript
.png = image/png
.gif = image/gif
.svg = image/svg+xml

次に、サーバーが起動したときの設定。

var server = http.createServer((req, res) => {
  var url = 'public' + (req.url.endsWith('/') ? req.url + 'index.html' : req.url);
  if (fs.existsSync(url)) {
    fs.readFile(url, (err, data) => {
      if (!err) {
        res.writeHead(200, {'Content-Type': getType(url)});
        res.end(data);
      } else {
        res.statusCode = 500;
        res.end();
      }
    });
  } else {
    res.statusCode = 404;
    res.end();
  }
});

ひとつずつ見ていく。

var url = 'public' + (req.url.endsWith('/') ? req.url + 'index.html' : req.url);

これは、リクエストしたURLの末尾が/で終わっていたら、req.urlの末尾にindex.htmlを追記して表示する設定。つまり、本当はhttp://localhost:8000/にアクセスしようとしてるんだけど、/で終わっているのでhttp://localhost:8000/index.htmlが返ってくる。Expressとか使ってたらデフォルトになってる設定。

if (fs.existsSync(url)) {
    fs.readFile(url, (err, data) => {
      if (!err) {
        res.writeHead(200, {'Content-Type': getType(url)});
        res.end(data);
      } else {
        res.statusCode = 500;
        res.end();
      }
    });

リクエストしたファイルパスの形式に応じて、上記で定義したそれぞれのコンテンツタイプとステータスコード200を返す。res.end(data)をして終了。それ以外はとりあえず500番を返しておく。

ステータスコード500:Internal Server Error、内部サーバエラー(ソースコードの不具合とか)

ポート番号はNode.jsデフォルトだと3000のはずだが、私の環境では8000に設定。 サーバーが起動したらコンソールにServer listening on {ポート番号}と表示される。

var port = 8000;
server.listen(port, () => {
  console.log(`Server listening on ${port}`);

すべて実装できたら、node index.jsでサーバーを起動してみる。 ウェブサイトが表示でき、CSSや画像ファイルも読み込まれたことが確認できた。