HTTPSテスト環境の選定

前回、HTTPSテスト環境について説明してきました。証明書の作成には、mkcert を用いるとして、HTTPSサーバーには、下記のようなものがあり、複数の選択肢があることがわかりました。

  • Node.jsサーバー
  • http-server
  • Express
  • Vite

この中で、本サイトのテスト環境では、下記の観点から Express サーバーを採用しています。

  • 自由度の高さ
  • ヘッダーも自由に設定可能
  • 今後の拡張性

今回の記事では、実際にコマンドを発行し、その構築手順についてみていきたいと思います。

テスト環境構築には、おおまかに下記の作業が必要になります。

  • ローカル認証局ツール( mkcert )の導入
  • 作業用ターミナルの起動
  • プロジェクトの作成
  • HTTPS 証明書の作成
  • HTTPS サーバー( Express )の導入
  • server.js の作成
  • 公開用サイトの作成
  • server.js の起動
  • ブラウザでの動作確認

それでは、順番に、各作業内容について、細かくみていきたいと思います。


ローカル認証局ツール( mkcert )の導入

mkcert (公式サイト)は、Filippo Valsorda(FiloSottile)さんによって開発されたツールです。ローカル開発環境で信頼できるSSL証明書を簡単に作成することが可能です。

項目 内容
開発者 Filippo Valsorda(FiloSottile)
最新リリース v1.4.4(2022年4月26日)
リリース履歴 2020 年以降も継続的に更新
ライセンス 無料のオープンソース(OSS)

無料で、HTTPS テスト環境を作成するためには、欠かすことのできないツールです。

開発者の方、関係者の方には、この場をお借りして、深くお礼を申し上げたいと思います。

mkcert の導入手順は下記のとおりとなります。

  1. ターミナル(管理者)の起動
  2. mkcert の ID 確認
  3. mkcert の導入
  4. ターミナル(管理者)の再起動
  5. mkcert の確認
  6. 自己認証局の作成

以降、順番に実際の作業内容を説明します。


1. ターミナル(管理者)の起動

Windowsの場合:

「ターミナル(管理者)」アプリのウインドウから、コマンド入力により作業を行っていきます。

  • スタートボタンを右クリック
  • 「ターミナル(管理者)」をクリック
  • 起動許可を求めるポップアップウインドウが表示されたら、「はい」を押下

👉 これからの作業はすべて起動された「ターミナル(管理者)」のウインドウに入力します。


2. mkcert の ID 確認

winget コマンドで、mkcert の ID を確認する

以下を入力:

winget search mkcert

👉 mkcert の ID が表示されます。


3. mkcert の導入

「2. mkcert の ID 確認」で表示された ID (FiloSottile.mkcertの部分)を指定して mkcert を導入します。

以下を入力:

winget install FiloSottile.mkcert

補足: ※ パッケージID(FiloSottile.mkcertの部分)は変更される可能性があるため、winget search で必ず確認して表示された値に合わせてください


4. ターミナル(管理者)の再起動

mkcertのパスを有効にする為、「ターミナル(管理者)」アプリを再起動します。

Windowsの場合:

「ターミナル(管理者)」アプリを終了した後、

  • スタートボタンを右クリック
  • 「ターミナル(管理者)」をクリック
  • 起動許可を求めるポップアップウインドウが表示されたら、「はい」を押下

👉 これからの作業はすべて起動された「ターミナル(管理者)」のウインドウに入力します。

注)再起動ではなく、ターミナルのウインドウで新しいタブを開いても大丈夫です。


5. mkcert の確認

以下を入力:

mkcert --version

👉 バージョンが表示されれば、導入完了です。


6. 自己認証局の作成

自己認証局を登録します。

以下を入力:

mkcert -install

👉 ルート証明書が作成され、Windowsの信頼済みに登録されます。

注)セキュリティ警告のポップアップ画面が表示されたら、「はい」を選択します。


作業用ターミナルの起動

Windowsの場合:

以降の作業は管理者ではない「ターミナル」アプリのウインドウから、コマンド入力により作業を行っていきます。 事故防止の為、「ターミナル(管理者)」のアプリを閉じ、下記の作業で新たに、「ターミナル」を起動してください。

  • スタートボタンを右クリック
  • 「ターミナル」をクリック
  • 起動許可を求めるポップアップウインドウが表示されたら、「はい」を押下

👉 これからの作業はすべて起動された「ターミナル」のウインドウに入力します。


プロジェクトの作成

今回の記事では、ユーザー名を test とし、プロジェクト用のフォルダをユーザーフォルダーの配下に、serverTest として作成します。

作成したフォルダは、Node.js のプロジェクトとして初期化します。

以下を入力:

mkdir serverTest
cd serverTest
npm init -y 


HTTPS 証明書作成

HTTPS 証明書を作成します。

以下を入力:

mkcert localhost 127.0.0.1 ::1

これには、下記の意味が含まれます。各指定値アドレスでのアクセスに対応します。

指定値 意味
localhost ローカルホスト名(ブラウザで https://localhost を使うため)
127.0.0.1 IPv4 のローカルループバックアドレス
::1 IPv6 のローカルループバックアドレス

👉 下記のファイルが作成されれば、成功です。

  • localhost+2-key.pem
  • localhost+2.pem

注)ファイル名( localhost+2 の部分)は、若干異なる場合があります。以降の作業は、作成された実際のファイル名に合わせてください。


HTTPS サーバー( Express )の導入

Express.js (公式サイト) は TJ Holowaychuk さんによって開発された Node.js 環境下で動作するツールです。自由度が高く、本格的なサーバー運用も可能な為、将来的な拡張性も高いです。

項目 内容
開発者 TJ Holowaychuk(後に StrongLoop → IBM → OpenJS Foundation)
初回リリース 2010年11月16日
現在の管理 OpenJS Foundation
ライセンス MIT License

本ツールがなければ、HTTPSの開発環境の構築も容易に行うことができませんでした。 開発者の方、関係者の方には、この場をお借りして、深くお礼を申し上げたいと思います。

Express の導入手順

npm コマンドを使って、Express を導入します。

以下を入力:

npm install express

👉 この例では、通常(dependencies)環境に express を導入していますが、テスト用、開発用にしか使用しない場合、「npm install -D express」コマンドで、開発用(devDependencies)環境に導入しても問題ありません。


server.js の作成

Express サーバーの定義ファイル server.js を JavaScript で作成します。

👉 ここでは、Windows標 準の「メモ帳」アプリを使用します。

以下を入力:

notepad server.js

メモ帳にて、以下を入力して保存:

※ 使用しているモジュール形式に応じて選択してください。.js ファイルの場合、一般的には package.json の type プロパティが "module" であれば ES Modules、"commonjs" または type の指定がなければ CommonJS として扱われます。以下のコードの主な違いは、モジュールの読み込み方法(require / import)です。

【 CommonJS の場合】

const https = require("https");
const fs = require("fs");
const express = require("express");

const app = express();

// COOP / COEP ヘッダー
app.use((req, res, next) => {
  res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
  res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
  next();
});

// 静的ファイル
app.use(express.static("public"));

// HTTPS サーバー起動
https.createServer(
  {
    key: fs.readFileSync("localhost+2-key.pem"),
    cert: fs.readFileSync("localhost+2.pem")
  },
  app
).listen(3000, () => {
  console.log("HTTPS server running at https://localhost:3000");
});

【 ES Modules の場合】

import https from "https";
import fs from "fs";
import express from "express";

const app = express();

// COOP / COEP ヘッダー
app.use((req, res, next) => {
  res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
  res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
  next();
});

// 静的ファイル
app.use(express.static("public"));

// HTTPS サーバー起動
https.createServer(
  {
    key: fs.readFileSync("localhost+2-key.pem"),
    cert: fs.readFileSync("localhost+2.pem")
  },
  app
).listen(3000, () => {
  console.log("HTTPS server running at https://localhost:3000");
});

本コードでは、下記の3点の定義を行っています。

  • COOP / COEP ヘッダーの指定

今回構築するプロジェクトでは、OPFS を使用している為、app.use((req, res, next) => {}) を使って、全てのアクセスに対して、COOP / COEP の http ヘッダーを送り返すようにしています。

※ 詳細については、「データ保存技術/OPFSとは①」を参照してください。

  • 静的ファイルの公開フォルダーの指定

app.use(express.static("public")) を使って、 public フォルダーをルートディレクトリとして指定しています。

  • HTTPS サーバー起動時の指定

https.createServer() を使って、作成した HTTPS 証明書 を使用するように指定しています。また、.listen() により、ポート 3000 番で待ち受けを行うよう指定しています。

listen() では、第2パラメーターに IP アドレスもしくは、ホスト名を指定することで、特定の IP アドレスもしくは、ホスト名に限定した待ち受けも可能です。


公開用サイトの作成

今回は、「データ保存技術/OPFS の動作確認(非同期操作)」で使用したサンプルプログラムをサイトの公開用として利用することにします。

サイトのルートフォルダとなる public を作成し、移動します。

以下を入力:

mkdir public
cd public

index.html の作成

以下を入力:

notepad index.html

メモ帳にて、以下を入力して保存:

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>簡易 OPFS ファイラー (非同期版)</title>
  <link rel="stylesheet" href="./style.css">
  <script defer type="module" src="./script.js"></script>
</head>
<body>
  <input id='input' type="file">
  <button id='copyBtn'>コピー</button>
  <button id='deleteBtn'>削除</button>
  <div id='allArea'>
    <div id='fileList'></div>
    <iframe id='viewArea'></iframe>
  </div>
  <div id='status'></div>
</body>
</html>

style.css の作成

以下を入力:

notepad style.css

メモ帳にて、以下を入力して保存:

body {
  padding: 1rem;
  background-color: rgb(253, 191, 76);
}
button {
  width: 5rem;
}
.selected {
  background-color: rgb(167, 167, 167);
}
#input {
  display: none;
}
#allArea {
  margin-top: 1rem;
  display: flex;
  gap: 10px;
  height: 70vh;
}
#fileList {
  border: solid 1px;
  background-color: white;
  width: 30%;
  box-shadow: 5px 5px 10px rgba(0,0,0,0.3);
  border-radius: 5px;
  font-size: small;
  overflow: auto;
}
.fileItem {
  cursor: pointer;
  border-bottom: solid 1px;
  padding: 0.5rem;
} 
#viewArea {
  border: solid 1px;
  background-color: white;
  width: 70%;
  box-shadow: 5px 5px 10px rgba(0,0,0,0.3);
  border-radius: 5px;
  overflow: auto;
}
#status {
  font-weight: bold;
  color: red;
  margin-top: 1rem;
}

script.js の作成

以下を入力:

notepad script.js

メモ帳にて、以下を入力して保存:

const fileList = document.getElementById("fileList");
const viewArea = document.getElementById("viewArea");
const status = document.getElementById("status");
let currentObjectURL = null;

// OPFS のルートディレクトリを取得
const root = await navigator.storage.getDirectory();

// ファイル一覧項目の作成
function createFileItem(handle) {
  const fileItem = document.createElement("div");
  fileItem.id = handle.name;
  fileItem.textContent = `📄 ${handle.name}`;
  fileItem.classList.add("fileItem");

  // ファイル一覧項目クリック時の処理
  fileItem.addEventListener("click", async (e) => {
    const file = await handle.getFile(); // OPFS 上のファイルの取得

    // 取得したファイルを iframe に表示
    if (currentObjectURL) URL.revokeObjectURL(currentObjectURL);
    currentObjectURL = URL.createObjectURL(file);
    viewArea.src = currentObjectURL;

    // 既にハイライト表示のものがあれば解除し、選択したファイルをハイライト表示
    const selected = document.querySelector(".selected");
    if (selected) {
      selected.classList.remove("selected");
    }
    e.target.classList.add("selected");

    status.textContent = `選択しました: ${handle.name}`;
  });
  return fileItem; // 作成したファイル一覧項目を返す
}

// 現在の OPFS のファイル一覧を取得し、画面に表示
for await (const handle of root.values()) {
  const fileItem = createFileItem(handle);
  fileList.appendChild(fileItem);
}

// コピーボタン押下時に、ファイル選択ポップアップを表示
document.getElementById("copyBtn").addEventListener("click", (e) => {
  document.getElementById("input").click();
});

// ファイル選択ポップアップでファイルが選択された時の処理
document.getElementById("input").addEventListener("change", async (e) => {
  const file = e.target.files[0];
  e.target.value = ""; // 次回、同じファイルを選択できるようにする

  if (!file) return; // ファイルが選択されなかった場合は何もしない

  let fileHandle;

  try {
    fileHandle = await root.getFileHandle(file.name); // OPFS 上に同一名のファイルがあるか確認

    // 同一名のファイルが既に存在する場合、上書きするか確認する
    if (!confirm(`${file.name}」は既に存在します。\n上書きしてもよろしいですか?`)) return;
  }
  catch (err) {
    if (err.name === "NotFoundError") {
      // 同一名のファイルが無い場合、新規作成する
      fileHandle = await root.getFileHandle(file.name, { create: true });

      const fileItem = createFileItem(fileHandle);
      fileList.appendChild(fileItem);
    } else {
      throw err;
    }
  }
  
  // OPFS ファイルに書き込み
  const writable = await fileHandle.createWritable(); // 書き込み用のストリームを作成
  await writable.write(file); // ユーザーファイルの内容を OPFS のファイルに書き込む
  await writable.close(); // 書き込み用のストリームを閉じる

  status.textContent = `ファイルをコピーしました: ${fileHandle.name}`;
});

// 削除ボタン押下時に、選択中のファイルを削除
document.getElementById("deleteBtn").addEventListener("click", async (e) => {
  const selected = document.querySelector(".selected");
  if (!selected) return; // 選択中のファイルが無い場合は何もしない

  await root.removeEntry(selected.id); // ファイルの削除

  selected.remove();

  // 不要になった Object URL を解放
  if (currentObjectURL) {
    URL.revokeObjectURL(currentObjectURL);
    currentObjectURL = null;
  }
  viewArea.src = "";

  status.textContent = `削除しました: ${selected.id}`;
});


Express サーバーの起動

ここまでの作業で、HTTPS でのサイト公開に必要なファイルが一通り用意されました。

プロジェクトフォルダー内のファイル構造を下記に示します。

C:\users\test\serverTest
│  localhost+2-key.pem        → HTTPS 証明書(キー)
│  localhost+2.pem            → HTTPS 証明書
│  package-lock.json          → Node.js のファイル
│  package.json               → Node.js のファイル
│  server.js                  → 作成する Express サーバーの定義ファイル
│  
├─node_modules                → Node.js のフォルダー
│  │  .package-lock.json
│  │  
│  ├─.bin
│  │  acorn
│  :      (以降省略)
│ 
└─public                      → https公開ルート
        index.html
        script.js
        style.css

ここまでの準備が完了したら、Node.js コマンドで、Expressサーバーを起動します。

node server.js


ブラウザによる動作確認

ブラウザを起動し、URL 欄に 下記を入力

https://localhost:3000/

表示結果

(図1)HTTPSサイト画面1

動作結果

(図2)HTTPSサイト画面2


👉 テスト対象となるプロジェクトの index.html が表示され、OPFS も問題なく動作することが確認できます。

※ 使い方については、「データ保存技術/OPFS の動作確認(非同期操作)」を参照してください。


まとめ

  • テスト用の HTTPS 環境は、サーバー環境無しでローカル環境に構築可能
  • mkcert + Express により、柔軟な環境構築が可能
  • Express の導入や稼働には、Node.js プロジェクトの環境が必要
  • Express では、Node.js のプロジェクト毎に異なるサーバー運用も可能