当サイトの Web 技術
更新
おことわり
-
本ページで記載することは特記のない限り
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 | ||
blog.w0s.jp
|
ブログ | Express | ||
labs.w0s.jp
|
Web 技術の遊び場 | HTTP でもアクセス可 | ||
media.w0s.jp
|
画像、動画、音声の配信 | Express | 画像は URL パラメーターにより動的生成 | |
report.w0s.jp |
エラーレポート収集 | Express | ||
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で Brotli 圧縮したファイル(e.g.
index.html.br
)を出力
- Prettier(
-
Markuplint(
markuplint.dev
)を使用したチェックを実施している。Markuplint の設定には公式のプリセット(markuplint.dev
)が用意されているが、以下に挙げる理由からあえてそれらを使うことはせず、当サイト向けに改めて定義した設定ファイルを使用している。- すべてのルールについてその効果を把握したい(公式のプリセットに頼るとその確認が疎かになってしまう)。
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 を上げており、それに対する Pull Request も提出されているが、2024年6月現在マージはされていない。そのためこの機能は使わず、res.send()
(expressjs.com
) メソッドにて独自の HTML を返すようにカスタマイズしている。
4xx クライアントエラーページ
403 Forbidden
および 404 Not Found
のクライアントエラーページには JavaScript で以下の機能を組み込んでいる。
- 同一ドメインのリファラーがあった場合(= サイト内から無効なリンクが張られている)は管理者へ通知する。検知プログラムは 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 で公開している。
印刷用スタイル
- ディスプレイ表示の眩しさを低減するため、ページの背景色は白色ではなく、若干黄味がかった色を設定している。印刷時にはそのような配慮は不要なので
@print
を使って完全な白(#ffffff
)に設定している。
リーダー表示
- 昨今のブラウザはリーダーモードを備えたものも多いが、制作者の意図どおりに表示されるとは限らないため、それとは別個に制作者スタイルシートにてリーダー表示を実装している。
- ヘッダーやフッターを非表示にする簡易なスタイルシートを用意し、代替スタイルシートで任意に適用可能な状態としている。PC 版 Firefox ではメニューバーの「表示」→「スタイルシート」から切り替え可能である。Chrome など他ブラウザでも拡張機能が公開されている。
スクリプト(JavaScript)
基本的な考え方として JavaScript は補助的な使用とし、スクリプトの一部ないし全部が動かない環境でもコンテンツの閲覧、操作に支障がないようにしている。フィード全文配信などスクリプトが動かないケースは往々にして存在するため、当サイトのようなテキストと画像による表現が中心の Web サイトは現代においてもスクリプト無効で閲覧できるようにするのはメリットが大きいと考えている。
- ソースコードは TypeScript による記述とし、コンパイルには Rollup(
www.rollupjs.org
) を使用している。 - 外部サービスに関係した機能など一部を除き ES modules で作成しており、本来であれば機能ごとに別れたファイルを
import
/export
を使ってそのままブラウザに読ませることができるが、実際のところ HTTP/2 通信下であっても画面描画への影響が体感できるほど大きかったため、ビルド時にファイル結合を行うようにしている。
エラー検知
作成したスクリプト機能は普段使いのブラウザでの軽い動作確認はしているが、きちんとしたテストは行っていない。というより、サーバーサイドプログラムとは異なりブラウザには様々な設定項目があり、Bot 等も含めたあらゆる環境を想定したテストを行うことなど不可能だと考えている。もとより前述のとおりスクリプトが動かなくてもコンテンツの閲覧には支障がないため、テストはそこそこで良いと割り切り、せめて発生してしまったエラーは把握できるよう、error
イベントを検知し通知する機能を組み込んでいる。この検知プログラムは GitHub で公開しているが、簡略化したコード例を下記に示す。
これにより、事前確認が難しい以下のようなケースで発生したエラーも検知することができる。
- 古いブラウザ
- ブラウザの設定やアドオンに起因するもの
- 検索エンジンのロボットなどブラウザ以外の環境
ビットマップ画像(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で推奨されていたテクニックである。設定は 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>
要素の定義では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 でないと実現できない
ファビコン(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
) を利用している。
脚注
-
1.
高木浩光@自宅の日記 - 共用SSLサーバの危険性が理解されていない(
takagi-hiromitsu.jp
)で解説されているように、フレームを利用して他サイトから読み出すことが可能なため。 ↩ 戻る -
2.
詳細はブログ記事 XHTML の終焉と XHTML 1.0 Transitional 時代の思い出(2024年4月)を参照。 ↩ 戻る
-
3.
Tim Berners-Lee による Cool URIs don't changeでも URL にファイル名拡張子を含めることは問題を引き起こすと言われている。 ↩ 戻る
-
4.
Android Firefox ではアプリ連携された URL に 3xx でリダイレクトすると、当該アプリが自動で起動するが、ブラウザでは 3xx のレスポンスボディが表示された状態になる。(2022年2月現在、Android Firefox 96 にて確認) ↩ 戻る
-
5.
詳細はブログ記事 CSS ファイルの最小化を止めた(2022年6月)を参照。 ↩ 戻る
-
6.
詳細はブログ記事 SVG ファビコンのファイル名を
favicon.ico
にして<link rel="icon">
を省略する(2021年10月)を参照。 ↩ 戻る