概要
Node.jsでEJS実行環境と静的ファイルをそのまま返すwebサーバーを作ってみた
方針
目標はApache(Nginx)+PHPのような環境をNode.js(Express)+EJSで構築すること
- 拡張子.html .ejsはEJSテンプレートとしてレンダリングして返す
- それ以外のファイルはそのまま返す
インストールしたパッケージ
{
"dependencies": {
"body-parser": "^1.20.0",
"ejs": "^3.1.8",
"express": "^4.18.1"
}
}
参考
以下の記事を参考というかほとんどこれをベースにカスタマイズしました
拡張子htmlをejsとして実行する方法
EJS内で直接モジュールをrequireできないらしいので、server.jsでModuleを追加できるようにしました
VSCode
VSCodeでフォルダを開いたときに自動的にサーバーが開始するようにすると大変便利です。
tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "node webserver",
"type": "shell",
"command": "node server.js",
"runOptions": {
"runOn": "folderOpen"
}
}
]
}
また、設定すると右クリックで該当するローカルサーバーのURLでプレビューできる拡張機能を入れるとさらに便利です。
コード
server.js
const serverlib = require('./serverlib.js');
const WebRoot = './public';
serverlib.WebRoot = WebRoot;
serverlib.RenderExt = '';
// EJSで利用するモジュールの設定
const fs = require('fs');
const path = require('path');
serverlib.Modules = { fs: fs, path: path };
const app = require('express')();
// .htmlをEJSでレンダーさせる設定
app.set('view engine', 'ejs');
app.engine('html',require('ejs').renderFile);
// expressでpostデータを受け取る3行のおまじない
const bodyparser = require('body-parser')
app.use(bodyparser.urlencoded({extended: true}))
app.use(bodyparser.json())
app.get( serverlib.getReg , ( request, response) => serverlib.SendFile(request, response) );
app.post( serverlib.postReg , ( request, response) => serverlib.SendFile(request, response) );
app.listen(3000);
serverlib.js
const url = require('url');
const path = require('path');
const fs = require('fs');
(() => {
let WebRoot = '';
let RenderExt = '';
let Modules = {};
const funcs = {
indexServe: (request, response) => // /でアクセスした場合index.htmlを表示
render(request, response, 'index.html'),
defaultServe: (request, response) => // ファイルをそのまま表示
FileSend(fileExist(request), response),
renderHtml: (request, response) => // .html
render(request, response),
};
// 許可する静的ファイルの拡張子
const AllowExtents = ['.js', '.css', '.jpg', '.png', '.gif', '.mp3', '.ogg', '.avi', '.mov', '.mpeg4', '.flv'];
// 動的な処理をするファイルの拡張子と処理内容
const fileExtentsFunc = {
'/': funcs.indexServe,
'.html': funcs.renderHtml,
'.ejs': funcs.renderHtml,
};
// パスをテストするための正規表現を作成 post用
const postReg = createReg(fileExtentsFunc);
// fileExtentsFuncにAllowExtentsを統合
AllowExtents.forEach(e => fileExtentsFunc[e] = funcs.defaultServe);
// パスをテストするための正規表現を作成 get用
const getReg = createReg(fileExtentsFunc);
function createReg(obj) {
return new RegExp('(' +
Object.keys(obj) // キーを配列に変換
.sort((a, b) => b.length - a.length) // キーを文字数が多い順にソート
.map(e => e.replace(/\./g, '\\.')) // . を \\.に置換
.join('|') + // | を区切りとして一つの文字列へ
')$');
}
const FileSendeOpt = {
dotfiles: 'deny'
};
// 静的ファイルの送信
function FileSend(filename, response) {
if (filename === null) resError(response, 404);
else {
response.sendFile(path.join(__dirname, filename), FileSendeOpt,
function (err) {
if (err) resError(response, 404);
});
}
}
// テンプレートファイルがあればレンダーして送信
// なければ静的ファイルを送信
function render(request, response) {
//const fpath = fileExist(request, addName + RenderExt);
const fpath = fileExist(request);
//リクエスト用の連想配列とモジュール用の連想配列を結合
const renderObj = Object.assign({ __FILE__: path.join(__dirname, fpath), request: request}, Modules);
if (fpath === null) {
resError(response, 404);
} else {
//レンダリング
response.render(path.join(__dirname, fpath), renderObj);
}
/*
if (fpath !== null) response.render(path.join(__dirname, fpath)
, { get: request.query, post: request.body });
else FileSend(fileExist(request, addName), response);
*/
}
// ファイルがない=null ある=パス を返す
function fileExist(request, addExt = '') {
const path = url.parse(request.url, true).pathname + addExt;
if (path.includes('/..')) return null;
const rpath = WebRoot + path;
try {
fs.statSync(rpath);
return rpath;
} catch (error) {
return null;
}
}
function resError(response, type) {
response.sendStatus(type);
}
// exportsにプロパティ登録・外部に公開
Object.defineProperties(exports, {
getReg: {
value: getReg,
enumerable: true,
},
postReg: {
value: postReg,
enumerable: true,
},
SendFile: {
value: (request, response) => fileExtentsFunc[request.params[0]](request, response),
enumerable: true,
},
WebRoot: {
set: (r) => WebRoot = r,
enumerable: true,
},
RenderExt: {
set: (r) => RenderExt = r,
enumerable: true,
},
//EJSで使うモジュールをserver.jsで設定できるようにする。
Modules: {
set: (r) => Modules = r,
enumerable: true,
}
});
Object.freeze(exports);
})();