仮説: case-sensitiveなファイルシステム (例: ext4) でのng serve時にファイル名が大文字小文字違いのファイルが同居していると小文字のファイルが404 Not Foundになる?

仮説: case-sensitiveなファイルシステム (例: ext4) でのng serve時にファイル名が大文字小文字違いのファイルが同居していると小文字のファイルが404 Not Foundになる?
Page content

謎解き orz

Node.js初心者・Angular初心者でありつつAngularアプリの動作確認を行う必要があったとき。ng serveするとExpressというウェブサーバがローカル起動されますが、このサーバのファイル名の大文字小文字の扱いに関して、いわゆる“おま環”と思われる現象に遭遇しました。この現象は他の人の環境 (macOS環境) では再現できず、私のメイン環境 (Ubuntu環境) では再現できてしまう。また、私の別環境 (macOS環境) では再現できない。ウェブ検索しても原因がわからなかったのですが、どうやら実行環境の「ファイルシステム」の仕様に関係するような気がして頭を捻った結果、再現手順が作れましたので、下記に掲載します。

具体的にはこれは、記事タイトルに挙げている「case-sensitiveなファイルシステム (例: ext4) でのng serve時にファイル名が大文字小文字違いのファイルが同居していると小文字のファイルが404 Not Foundになる?」という仮説を検証する問題になります。再現手順で用いるAngularアプリとしてはng serveが試せればおそらくなんでも良いわけで、Angular公式リポジトリ https://github.com/angular/examples にある「walk-my-dog」を用いています。

本記事に辿り着いた方のなにかの参考になりましたら幸いです。

Case-1. Ubuntuのext4領域での再現手順

  • ext4: デフォルトではファイル名の大文字小文字を区別するcase-sensitiveなファイルシステム

Step-1. Terminal-A: 環境確認・ext4領域に移動

$ npm version | grep -E 'npm|node'
  npm: '8.19.2',
  node: '16.17.1',
  v8: '9.4.146.26-node.22',
$ cd <ext4領域の適当なディレクトリ>
$ df -Th .
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4  457G  322G  112G  75% /

Step-2. Terminal-A: ソース取得してcase-sensitiveなファイル作成してserve

$ git clone https://github.com/angular/examples
$ cd ./examples/walk-my-dog/
$ ls -al ./src/assets/dog-walker-logo.*
-rw-r--r-- 1 mah mah 2976  3月 25 23:03 ./src/assets/dog-walker-logo.svg
$ cp -ip ./src/assets/dog-walker-logo.{svg,SVG}
$ ls -al ./src/assets/dog-walker-logo.*
-rw-rw-r-- 1 mah mah 2976  3月 25 23:21 ./src/assets/dog-walker-logo.SVG
-rw-rw-r-- 1 mah mah 2976  3月 25 23:21 ./src/assets/dog-walker-logo.svg
$ npm install
$ ng serve

Step-3. Terminal-B: Case-SensitiveなファイルのHTTPステータスを確認

小文字のファイルが「404 Not Found」?! 存在するのになぜ??
$ curl -I http://localhost:4200/assets/dog-walker-logo.svg
HTTP/1.1 404 Not Found
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Security-Policy: default-src 'none'
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 166
Date: Mon, 25 Mar 2024 14:27:49 GMT
Connection: keep-alive
Keep-Alive: timeout=5
大文字のファイルは「200 OK」
$ curl -I http://localhost:4200/assets/dog-walker-logo.SVG
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: image/svg+xml
Accept-Ranges: bytes
Content-Length: 2976
ETag: W/"ba0-06SevQUxlDl5sFFmZNn8+H9NSZE"
Date: Mon, 25 Mar 2024 14:28:14 GMT
Connection: keep-alive
Keep-Alive: timeout=5

Step-4. Terminal-A: 大文字のファイルを削除して再serve

(Ctrl+C でng serveを停止する)
$ rm ./src/assets/dog-walker-logo.SVG
$ ls -al ./src/assets/dog-walker-logo.*
-rw-rw-r-- 1 mah mah 2976  3月 25 23:21 ./src/assets/dog-walker-logo.svg
$ ng serve

Step-5. Terminal-B: HTTPステータスを再確認

小文字のファイルが「200 OK」に変わっている
$ curl -I http://localhost:4200/assets/dog-walker-logo.svg
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: image/svg+xml
Accept-Ranges: bytes
Content-Length: 2976
ETag: W/"ba0-06SevQUxlDl5sFFmZNn8+H9NSZE"
Date: Mon, 25 Mar 2024 14:30:30 GMT
Connection: keep-alive
Keep-Alive: timeout=5

謎です。まるで「ファイル名小文字のファイルへのgetリクエストに、ファイル名大文字のファイルの存在が影響する (ファイル名小文字のファイルへのgetを邪魔する)」かのよう。どうしてこのような挙動をするのだろう?

Case-2. Ubuntuのntfs3領域では

  • ntfs3 (NTFS): ファイル名の大文字小文字を区別するcase-sensitiveなファイルシステム
$ df -Th .
Filesystem     Type   Size  Used Avail Use% Mounted on
/dev/sda1      ntfs3  233G   72M  233G   1% /media/mah/TEST_NTFS

Ubuntuのext4領域での再現手順と同じ結果になる。

参考) 上記再現手順が実施できなかった環境

MacのAPFS領域 (not 「大文字/小文字を区別」)

  • Apple File System (APFS): デフォルトではファイル名の大文字小文字を区別しないcase-insensitiveなファイルシステム
$ npm version | grep -E 'npm|node'
  npm: '10.2.3',
  node: '20.10.0',
  v8: '11.3.244.8-node.25',
$ cd ~/tmp/
$ diskutil info / | grep 'File System'
   File System Personality:   APFS
  • cp時にファイル名の大文字小文字違いが同一視されるためエラーになる
    $ cp -ip ./src/assets/dog-walker-logo.{svg,SVG}
    cp: ./src/assets/dog-walker-logo.SVG and ./src/assets/dog-walker-logo.svg are identical (not copied).
    
  • なお、このcpをpassして進めた場合、npm install, ng serve はノーマルな操作であって特に問題ない

Ubuntuのvfat領域

  • vfat (FAT): ファイル名の大文字小文字を区別しないcase-insensitiveなファイルシステム
$ df -Th .
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/sda1      vfat  233G   28M  233G   1% /media/mah/TEST_FAT
  • cp時にファイル名の大文字小文字違いが同一視されるためエラーになる
    $ cp -ip ./src/assets/dog-walker-logo.{svg,SVG}
    cp: './src/assets/dog-walker-logo.svg''./src/assets/dog-walker-logo.SVG' は同じファイルです
    
  • なお、このcpをpassして進めると、npm installでsymlinkが使用できない旨のエラーが発生してng serveまで進めない
    $ npm install
    (途中省略)
    npm ERR! code EPERM
    npm ERR! syscall symlink
    npm ERR! path ../@angular/cli/bin/ng.js
    npm ERR! dest /media/mah/TEST_FAT/examples/walk-my-dog/node_modules/.bin/ng
    npm ERR! errno -1
    npm ERR! Error: EPERM: operation not permitted, symlink '../@angular/cli/bin/ng.js' -> '/media/mah/TEST_FAT/examples/walk-my-dog/node_modules/.bin/ng'
    npm ERR!  [Error: EPERM: operation not permitted, symlink '../@angular/cli/bin/ng.js' -> '/media/mah/TEST_FAT/examples/walk-my-dog/node_modules/.bin/ng'] {
    npm ERR!   errno: -1,
    npm ERR!   code: 'EPERM',
    npm ERR!   syscall: 'symlink',
    npm ERR!   path: '../@angular/cli/bin/ng.js',
    npm ERR!   dest: '/media/mah/TEST_FAT/examples/walk-my-dog/node_modules/.bin/ng'
    npm ERR! }
    npm ERR! 
    npm ERR! The operation was rejected by your operating system.
    npm ERR! It is likely you do not have the permissions to access this file as the current user
    npm ERR! 
    npm ERR! If you believe this might be a permissions issue, please double-check the
    npm ERR! permissions of the file and its containing directories, or try running
    npm ERR! the command again as root/Administrator.