前回の記事
自己署名証明書(オレオレ証明書)を使ってHTTPSサーバーをNode.jsで立ち上げる(windows10) その1 (attacktube.com)
HTTPSサーバーを立ち上げる。
前回作ったコードをより細かくする。
HTTPSサーバーを立ち上げるコード「https.js」を次に示す。
現地時間を取得するために自作モジュールLocalTime.jsを使用している。
環境
Windows10(64bit)
nvm(Node Version Manager)を使用する。
nvmはnodeとnpmのバージョンの切り替えができる。
node.jsはアップデートが頻繁に行われるため、バージョン管理ツールを使うと都合が良いことが多い。
「特定のプロジェクトに必要なNode.jsのバージョンを簡単にインストールして使用することができる。」や「プロジェクト毎にNode.jsのバージョンの切り替えが簡単になる。」等のメリットがある。
nvm 1.1.10
node v18.15.0
npm 9.5.0
(function() {
// 自作モジュールLocalTime.jsをrequireを使ってインポートして、定数localTimeに格納する。
const localTime = require("./LocalTime.js");
// LocalTimeの言語を日本語に設定
localTime.SetLang("JP");
// LocalTimeのタイムゾーンを東京(TYO)に設定
localTime.SetTimeZone("TYO");
// Node.jsのhttpsモジュールをrequireを使ってインポートして、定数httpsに格納する。
const https = require("https");
// Node.jsのfsモジュールをrequireを使ってインポートして、定数httpsに格納する。
// fsモジュールはファイルシステムへのアクセスを提供するためのモジュールである。
const fs = require('fs');
const port = 443;//port番号
const hostname = 'localhost'; // ホスト名またはドメイン名
const options = {
//秘密鍵
key: fs.readFileSync('server-key.pem'),
//自己署名証明書(オレオレ証明書)
cert: fs.readFileSync('server-crt.pem')
};
// httpsオブジェクトからcreateServerメソッドを実行する。
// createServerメソッドは、引数としてリクエストを処理するコールバック関数を受け取る。
// このコールバック関数は、リクエストが発生した際に実行され、リクエストオブジェクト(req)とレスポンスオブジェクト(res)を受け取る
// つまり、このコールバック関数は、https://localhost:3000にアクセスしたときに実行される。
// このコールバック関数内で、適切なレスポンス(res)を生成してクライアントに送信する処理を実装する。
// リクエストオブジェクト(req)は、http.IncomingMessageオブジェクトのインスタンスである。
// リクエストオブジェクト(req)は、HTTPリクエスト(リクエストヘッダーやボディ)のデータを読み取るためのデータを読み取るためのReadableストリームです。
// レスポンスオブジェクト(res)は、http.ServerResponseオブジェクトのインスタンスである。
// レスポンスオブジェクト(res)は、Writableストリームとして機能する。
// レスポンスオブジェクト(res)は、クライアントに対してデータを送信するためのストリームとして扱われる。
// レスポンスオブジェクト(res)はhttp.ServerResponseオブジェクトのインスタンスであり、HTTPレスポンスを生成および送信するための
// Writableストリームです。レスポンスオブジェクト(res)に対して書き込まれたデータはクライアントに送信されます。
// writeメソッドやendメソッドを使用してレスポンスボディを書き込むことができます。
// レスポンスオブジェクト(res)のボディに書き込むにはwriteメソッドを使う。
// レスポンスオブジェクト(res)の送信を完了するためにはendメソッドを使う。
const server = https.createServer(options, (req, res) => {
console.log(
`Executed. \n` +
`${localTime.GetLocalTimeString()} \n` +
`${req.method} \n` +
`${req.url} \n` +
`${req.headers["user-agent"]}`
);
const url = `https://${req.headers.host}`;
// URL解析
// req.urlは相対URLが入る。
// urlはベースurl(相対URLを解決するための基準となるURL)が入る。
const myURL = new URL(req.url, url);
const whitespace = 2;
const result = [];
const names = [];
let stringLengthMax = 0;
let buf = ' ';
let blanks = '';
console.log("");
result.push(myURL.href);
result.push(myURL.origin);
result.push(myURL.protocol);
result.push(myURL.username);
result.push(myURL.password);
result.push(myURL.host);
result.push(myURL.port);
result.push(myURL.hostname);
result.push(myURL.pathname);
result.push(myURL.hash);
result.push(myURL.search);
names.push("href");
names.push("origin");
names.push("protocol");
names.push("username");
names.push("password");
names.push("host");
names.push("port");
names.push("hostname");
names.push("pathname");
names.push("hash");
names.push("search");
// names配列の中の文字列で最大長をstringLengthMaxに格納する。
names.forEach(t => {
if (t.length > stringLengthMax) stringLengthMax = t.length;
});
// bufに格納した空の文字列を「stringLengthMax + whitespace」回数分のコピーを含む新しい文字列blanksを作る。
blanks = buf.repeat(stringLengthMax + whitespace);
// URLの解析結果を出す。
names.forEach((t, u) => (console.log(t + ':' + blanks.slice(t.length) + result[u])));
console.log("");
console.log("req.headers = ");
console.log(req.headers);
console.log("");
let str = [];
str.push("method:");
str.push(req.method);
str.push("\n");
str.push("req.url:");
str.push(req.url);
str.push("\n");
if (myURL.pathname === "/") {
// ドキュメントルート(/)がリクエストさた場合
res.writeHead(200, {
'Content-Type': 'text/plain;charset=utf-8'
});
if (req.method === "POST") {
let i = 0
// data受信イベントの発生時に断片データ(chunk)を取得する。
req.on('data', chunk => {
console.log(localTime.GetLocalTimeString() + " i = " + i + ", chunk = " + chunk + "");
i += 1;
str.push(chunk);
});
// 受信完了(end)イベント発生時
//「end」イベントは、レスポンスのデータの読み取りが完了した場合に発生する。
req.on('end', function() {
str.push("\n");
//レスポンスデータをchunk分割する。
res.write(str.join(""));
res.end("Hello World! " + req.method + "\n");
});
} else if (req.method === "GET") {
// URLSearchParamsに含まれる値はforEach()メソッドで取り出し可能である。
myURL.searchParams.forEach(function(value, key) {
str.push(key + " = " + value + "\n");
console.log(localTime.GetLocalTimeString() + ", " + key + " = " + value);
});
// レスポンスデータをchunk分割する。
res.write(str.join(""));
res.end("Hello World! " + req.method + "\n");
} else {
let i = 0
// data受信イベントの発生時に断片データ(chunk)を取得する。
req.on('data', chunk => {
console.log(localTime.GetLocalTimeString() + " i = " + i + ", chunk = " + chunk + "");
i += 1;
str.push(chunk);
});
// 受信完了(end)イベント発生時
// 「end」イベントは、レスポンスのデータの読み取りが完了した場合に発生する。
req.on('end', function() {
str.push("\n");
//レスポンスデータをchunk分割する。
res.write(str.join(""));
res.end("Hi! " + req.method);
});
}
} else {
// ドキュメントルート(/)がリクエストされない場合はメソッドに関係なくここを通る。
res.writeHead(404, {
'Content-Type': 'text/plain;charset=utf-8'
});
// レスポンスデータをchunk分割する。
res.write(str.join(""));
res.end("404 Not Found");
}
});
// serverインスタンスオブジェクトからlistenメソッドを実行する。
// httpsサーバーが指定されたポート(443)でリクエストを受け付けるようになる。
server.listen(port, hostname, () => {
console.log(localTime.GetLocalTimeString() + ` Server running at https://${hostname}:${port}`);
});
})();
http.jsを実行して、firefoxで「https://localhost」にアクセスすると次のようになる。
c:\node\https_server>node https.js
[TYO] 2023-05-27(Sat) 20:03:57.637 Server running at https://localhost:443
Executed.
[TYO] 2023-05-27(Sat) 20:04:01.409
GET
/
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0
href: https://localhost/
origin: https://localhost
protocol: https:
username:
password:
host: localhost
port:
hostname: localhost
pathname: /
hash:
search:
req.headers =
{
host: 'localhost',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'accept-language': 'ja,en-US;q=0.7,en;q=0.3',
'accept-encoding': 'gzip, deflate, br',
connection: 'keep-alive',
cookie: '_ga=GA1.1.1823735261.1646346341',
'upgrade-insecure-requests': '1',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'none',
'sec-fetch-user': '?1'
}