おことわり

  • 本ページで記載することは特記のない限り w0s.jp および blog.w0s.jp ドメインで公開しているコンテンツに関する情報である。その他のサブドメインは状況が異なる部分も多い。
  • 当サイトは Web 技術の実験場としての役割も兼ねており、一時的なものも含めて細かい改修は度々実施しているが、本ドキュメントは常に追従できるとは限らない。

歴史

2001年2月

前身のサイトを開設。COOL ONLINE(web.archive.org)の無料会員枠を利用(容量20MB)。地域コミュニティを謳っており、登録の際にまず都市を選択、それによってサブドメインが振り分けられた(e.g. tokyo.cool.ne.jp)。

2001年6月

掲載写真の増大により容量面で厳しくなり、goo フリーホームページ(web.archive.org)に移転(容量50MB)。URL は http://users.goo.ne.jp/${userId} 形式だが、IP アドレスのドメインにリダイレクトされる(ブラウザのアドレスバーには IP アドレスの数字が表示される)というすごい仕様だった。

2002年3月

ネットワーク利用技術研究会(NURS)(www.nurs.or.jp)に移転。レンタルサーバーではなく研究・実験目的のサーバーであり、Telnet が制限なしに使えたので、root 権限が必要なこと以外は割となんでもできた。

2012年2月

NURS はドメインがサブドメインで分ける方式ではなく全ユーザー共通であり、Cookie の導入がセキュリティ上の理由[1]で躊躇われることからアイネットディー(www.inetd.co.jp)に移転したうえで独自ドメインを取得。月額270円の格安サーバーにしては Cron の制限が緩いのがありがたかった。

2017年1月

常時 TLS の波に乗りさくらの VPS(vps.sakura.ad.jp) に移転。Let's Encrypt(letsencrypt.org)を利用して TLS 対応を行う。

サーバー構成

サーバー
Apache HTTP Server(httpd.apache.org)
アプリケーション
Node.js(nodejs.org)
プロセス・マネージャー
PM2(pm2.keymetrics.io)
データベース (RDBMS)
SQLite(sqlite.org), MySQL(www.mysql.com)
ドメイン一覧
ドメイン 用途 フレームワーク ソースコード 備考
w0s.jp 個人サイト Express, Astro GitHub
blog.w0s.jp ブログ Express GitHub
labs.w0s.jp Web 技術の遊び場 GitHub HTTP でもアクセス可
media.w0s.jp 画像、動画、音声の配信 Express GitHub 画像は URL パラメーターにより動的生成
report.w0s.jp エラーレポート収集 Express GitHub
analytics.w0s.jp アクセス解析 Matomo Analytics(matomo.org) を利用

HTML

  • 静的ファイルを事前に生成するページは、w0s.jp では Astro(astro.build) を使用、一方 blog.w0s.jp では自作の Node.js プログラムにテンプレート言語として EJS(ejs.co) を組み合わせているが、そのままだと HTML のインデントが不揃いになってしまう。周知のとおりブラウザにはソースの表示機能があり、これはユーザースタイルシートを設定する場合などに欠かせない。昨今ではソースを開発者ツールで見るユーザーも多いと思うが、公共施設のパソコンなど開発者ツールの機能が封じられているケースもあるため、生の HTML を綺麗に整形しておくことは重要なことと考えている。さらに事前に圧縮ファイルを生成しておくことで通信量の削減にも繋がるため、ビルドに際して以下の処理を挟んでいる。
    • Prettier(prettier.io)で HTML ファイルを整形
    • brotli-cli(GitHub)で Brotli 圧縮したファイル(e.g. index.html.br)を出力
  • Markuplint(markuplint.dev)を使用したチェックを実施している。Markuplint の設定には公式のプリセット(markuplint.dev)が用意されているが、以下に挙げる理由からあえてそれらを使うことはせず、当サイト向けに改めて定義した設定ファイル(GitHub)を使用している。
    • すべてのルールについてその効果を把握したい(公式のプリセットに頼るとその確認が疎かになってしまう)。
    • markuplint:recommended より厳しいルールを適用させたい箇所が複数存在する。
    • 公式のプリセットは HTML Standard や WAI-ARIA への仕様適合だけではなく、パフォーマンスやセキュリティを含めたベストプラクティス的な考えも取り入れられている。純粋な構文チェックに留まらないところは Markuplint の良い点であるが、そのすべてに賛同できるわけではない。
  • HTML ページの MIME タイプは text/html としている。コンテンツのデータをスクレイピングで活用したいユーザーにとっては XML として処理できた方が便利だろうとの考えで以前は application/xhtml+xml で配信していたが、Google 翻訳(URL 指定)が対応していないなどの外部要因によりデメリットの方が大きくなってきており、今や text/html の方が利便性は高いものと判断する。ちなみに HTML Living Standard では2024年4月より XML 構文の使用は推奨されないとの記述が追加された[2]
  • ウェブページの URL に拡張子が含まれるのは好ましくないと考えているので[3]、拡張子なしの URL でアクセスできるようにしている。

3xx リダイレクトページ

URL が変更になった場合は 301 Moved Permanently、フォームの POST 送信後には 303 See Other などいくつかのケースでは HTTP レスポンス 3xx でリダイレクトを設定している。

RFC 7231 の 6.4. Redirection 3xx(datatracker.ietf.org)では、ステータスコード 3xx で Location ヘッダーフィールドが設定されている場合のユーザーエージェントの挙動としてthe user agent MAY automatically redirect its request to the URIと書かれており(あくまで MAY であることに注目)、実際にブラウザの環境によっては自動リダイレクトが行われず、レスポンスボディの内容が画面に表示されることがある[4]

当サイトで使用している Node.js フレームワークの Express では、res.redirect()(expressjs.com) メソッドでリダイレクト設定を行うことができるが、この場合レスポンスボディは <p>Moved Permanently. Redirecting to <a href="${path}">${path}</a></p> のように、DOCTYPE や <title> 要素のない不正な HTML となってしまう。これについては Issue を上げており(GitHub)、それに対する Pull Request も提出されている(GitHub)が、2024年6月現在マージはされていない。そのためこの機能は使わず、res.send()(expressjs.com) メソッドにて独自の HTML を返すようにカスタマイズしている。

4xx クライアントエラーページ

403 Forbidden および 404 Not Found のクライアントエラーページには JavaScript で以下の機能を組み込んでいる。

  • 同一ドメインのリファラーがあった場合(= サイト内から無効なリンクが張られている)は管理者へ通知する。検知プログラムは GitHub で公開(GitHub)している。
  • 直近の有効な祖先ディレクトリへのリンクを提示する。(e.g. /foo/bar/baz へのリクエストに対し、/foo/bar/baz のレスポンスコードが 404 で /foo/bar/ が 403、/foo/ が 200 の場合、/foo/ へのリンクを提示)

スタイルシート(CSS)

  • CSS ファイルのビルドには PostCSS(postcss.org)を使用している。これはブラウザが対応していない機能の先行使用、あるいはファイルをまとめることによるパフォーマンス向上が目的であり、標準化の見込みのないプラグインは使っていない。将来的には変換前のコードをそのままブラウザに読み込ませても問題ないようにすることが目標である。
  • ビルド過程で CSSNANO(cssnano.github.io)を適用しているが、minify 処理が目的ではなくコメントの除去など最小限の最適化のみを行っている。以前は minify 処理も行っていたが、IE 11 のサポート終了によりすべてのメジャーブラウザが Brotli に対応する状況になったことから、通信量の削減効果は薄くなったと考えている[5]
  • Stylelint(stylelint.io)を使用したチェックを実施している。設定ファイルは GitHub で公開(GitHub)している。

印刷用スタイル

  • ディスプレイ表示の眩しさを低減するため、ページの背景色は白色ではなく、若干黄味がかった色を設定している。印刷時にはそのような配慮は不要なので @print を使って完全な白(#ffffff)に設定している。

リーダー表示

  • 昨今のブラウザはリーダーモードを備えたものも多いが、制作者の意図どおりに表示されるとは限らないため、それとは別個に制作者スタイルシートにてリーダー表示を実装している。
  • ヘッダーやフッターを非表示にする簡易なスタイルシートを用意し、代替スタイルシート(WHATWG)で任意に適用可能な状態としている。PC 版 Firefox ではメニューバーの「表示」→「スタイルシート」から切り替え可能である。Chrome など他ブラウザでも拡張機能が公開されている。

スクリプト(JavaScript)

基本的な考え方として JavaScript は補助的な使用とし、スクリプトの一部ないし全部が動かない環境でもコンテンツの閲覧、操作に支障がないようにしている。フィード全文配信などスクリプトが動かないケースは往々にして存在するため、当サイトのようなテキストと画像による表現が中心の Web サイトは現代においてもスクリプト無効で閲覧できるようにするのはメリットが大きいと考えている。

  • ソースコードは TypeScript による記述とし、コンパイルには Rollup(www.rollupjs.org) を使用している。
  • 外部サービスに関係した機能など一部を除き ES modules で作成しており、本来であれば機能ごとに別れたファイルを import / export を使ってそのままブラウザに読ませることができるが、実際のところ HTTP/2 通信下であっても画面描画への影響が体感できるほど大きかったため、ビルド時にファイル結合を行うようにしている。

エラー検知

作成したスクリプト機能は普段使いのブラウザでの軽い動作確認はしているが、きちんとしたテストは行っていない。というより、サーバーサイドプログラムとは異なりブラウザには様々な設定項目があり、Bot 等も含めたあらゆる環境を想定したテストを行うことなど不可能だと考えている。もとより前述のとおりスクリプトが動かなくてもコンテンツの閲覧には支障がないため、テストはそこそこで良いと割り切り、せめて発生してしまったエラーは把握できるよう、error イベントを検知し通知する機能を組み込んでいる。この検知プログラムは GitHub で公開(GitHub)しているが、簡略化したコード例を下記に示す。

window.addEventListener('error', (ev) => {
	const formData = new FormData();
	formData.append('location', location.toString());
	formData.append('message', ev.message);
	formData.append('filename', ev.filename);
	formData.append('lineno', String(ev.lineno));
	formData.append('colno', String(ev.colno));

	fetch(ENDPOINT, {
		method: 'POST',
		body: new URLSearchParams([...formData]),
	});
});

これにより、事前確認が難しい以下のようなケースで発生したエラーも検知することができる。

  • 古いブラウザ
  • ブラウザの設定やアドオンに起因するもの
  • 検索エンジンのロボットなどブラウザ以外の環境

ビットマップ画像(JPEG, PNG)

昨今、高解像度ディスプレイの普及や WebP, AVIF など新フォーマットの登場により、環境に合わせて画像表示を最適化しようとすれば多くの出し分けが必要になってきている。当サイトでは基本的に以下5種類の画像を <picture> 要素を使って提示している。

  • AVIF
  • AVIF @2x
  • WebP
  • WebP @2x
  • JPEG(WebP 未対応環境は減少しているため JPEG の @2x 画像は用意しない)

これらの画像は /path/to/image.jpg?type=avif;w=360;h=240;quality=30 のように URL パラメーターでサイズや画質を指定して動的生成している。詳細は画像を配置している media.w0s.jp にドキュメントを置いているが、ドキュメントで触れていないポイントについて以下に述べる。

  • URL パラメーターの区切り文字は一般的な & だけでなく ; にも対応しており、原則として後者を利用する。これは HTML4 時代に B.2.2 Ampersands in URI attribute values(W3C)で推奨されていたテクニックである。設定は Express の app.set('query parser', value)(expressjs.com)にカスタムクエリ文字列解析関数を指定することで実現している。
  • 画像生成は sharp(sharp.pixelplumbing.com)を利用しているが、AVIF への変換処理は JPEG や WebP と比較して遅く、多数の画像を埋め込んだページでは問題があるため AVIF の初回リクエストに限っては代替として WebP を動的生成して返し、別途バッチ処理で AVIF を生成するようにしている。この場合、ブラウザ視点では「<source type="image/avif"> の画像をリクエストしたら image/webp が返ってきた」という状態になる。一見違和感があるものの、HTML 仕様における <source> 要素の定義(WHATWG)では The type attribute gives the type of the images in the source set, to allow the user agent to skip to the next source element if it does not support the given type. とされているため問題なく、どのブラウザも正常に動作する。
  • 一般論として GET リクエストでファイル生成という副作用を及ぼすのは好ましいことではなく、可能なら POST や PUT リクエストを使うべきだろう。一方で RFC 7231 の 4.2.1. Safe Methods(www.rfc-editor.org)では This definition of safe methods does not prevent an implementation from including behavior that is potentially harmful, that is not entirely read-only, or that causes side effects while invoking a safe method. と書かれており、
    • 副作用を起こす実装が禁止されているものではない
    • アクセスログのように GET でサーバー内にファイルを追加・更新する機能は一般的に存在する
    • そもそも本機能の要件は GET でないと実現できない
    といった理由から GET リクエストでファイル生成を行うようにしている。

ファビコンfavicon.ico

ファビコンは SVG 形式で提供している。

Internet Explorer 5 の実装(web.archive.org)を発端とする歴史的な経緯により、/favicon.ico のファイルパスにすることでブラウザが自動的に読み取るようになっている。あくまで URL が /favicon.ico であれば良く、ファイルの実体が ICO 形式である必要はないため、SVG 形式のファビコンファイルを /favicon.ico に配置し、MIME タイプを image/svg+xml で返すように設定することで <link rel="icon"> の記述を省略できる[6]。ただし以下のデメリットがある(いずれも2024年6月現在)。

  • Safari は最新のバージョン 17 系においても SVG ファビコンに対応していない
  • Android 版 Firefox も同様に対応しておらず、そればかりか The Open Graph protocol(ogp.me)が設定されているページでは og:image をファビコン代わりに表示してしまう
  • 検索サービス DuckDuckGo(duckduckgo.com)での検索結果においてファビコン表示が行われない

CSP

  • Fetch ディレクティブ(*-src)は Content-Security-Policy-Report-Only で設定。ユーザースタイルシート、ユーザースクリプトによるカスタマイズを妨げないため、あくまで実態調査目的として Report-Only にしている。
  • Trusted Types 関係も現状は Content-Security-Policy-Report-Only で設定している。
  • それ以外のディレクティブは Content-Security-Policy で設定している。
  • Reporting は Report URI(report-uri.com) を利用している。

脚注