リンクをクリックしたいだけなんだ

こんにちは。kurumataniです。

この記事は、[NTTドコモ R&D Advent Calendar 2021（カレンダー2）](https://qiita.com/advent-calendar/2021/nttdocomo)の4日目の記事です。

Amazon S3のリソースに対するハイパーリンクを意味あるものにすることで、ちょっとしたフラストレーションを解消するための方法を試行錯誤の経緯とともに解説します。

# 背景と目的

みんな大好きハイパーリンク。Webページを見ながら、関連するページへ移動するためにクリック。スマホを見ながら面白そうなリンクをタップ。一日何回もお世話になっていることでしょう。かくいう私もその一人。何度クリックしているかなんて、数えきれません。

最近ではそれっぽいURLを勝手にハイパーリンク化してくれるアプリケーションもちょくちょく出てきました。たとえばSlack。とりあえずURLを貼り付ければリンクにしてくれます。気が利いていて便利ですよね。

でも、惜しい！と思うときがあります。それは、こんな時です。
![s3:で始まるリンク文字列がブラウザ上に表示されている](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/a2aa1b69-c097-7f5d-69e6-4ce104cce74f.png)
Amazon S3のリソースネーム。リンクをコピーしてコンソールに貼り付ける……といった操作はハイパーリンク化されているおかげで簡単です。でも、視覚的な印象に従ってクリックすると、無情な画面が表示されてしまいます。
![真っ白な画面がブラウザ上に表示されている](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/8cc6935c-180f-cb92-0543-0cf8567f4203.png)
**まっしろ！**これは不便です。なんとかしなくてはなりません。

本稿では、Amazon S3のリソースネームに対するハイパーリンクを開いて残念な気持ちにならずに済むようにすることを目的として、このハイパーリンクが何らかの有意義な機能を持つような環境を作り上げます。なお、実行環境は広く使われているWindows環境を前提とします。
# 何が起こっているのか

設計に先立って、まずは何が起こっているのか、順を追って確認してみましょう。

##  ハイパーリンクで特定のプログラムを開く

ハイパーリンクには、当たり前かもしれませんがURLが関連付けられています。一般的に、URLは`(スキーム名):(リソースを特定する文字列)`という形式をとります。スキーム名は、たいていの場合プロトコル名が利用されます。たとえばこんな具合です。

| URL                             | スキーム名 | リソースを特定する文字列      |
| ------------------------------- | ---------- | ----------------------------- |
| https://www.nttdocomo.co.jp/    | `https`    | `//www.nttdocomo.co.jp`       |
| git://example.net/user/repo.git | `git`      | `//example.net/user/repo.git` |
| mailto:username@example.com     | `mailto`   | `username@example.com`        |

このような定型フォーマットが定められていることで、スキーム名を元に「対応したプログラム」を選ぶことが出来る、という仕組みです。

この仕組みはWindowsの内部でも使われています。試してみましょう。Windowsでは、`ファイル名を指定して実行`というダイアログが用意されています。`Win+R`を押すと開く、アレです。ここにURLを打ち込むと、適切なアプリケーションが立ち上がります。ためしに`https://www.nttdocomo.co.jp`を入れてみます。
![ファイル名を指定して実行ダイアログで名前欄にURLを入力した画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/85e3cf18-a917-1260-14f7-ecc3a291708c.png)
OKを押してみます。
![ブラウザでNTTドコモのWebサイトが開いた画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/d44cca54-0431-5fc2-88fd-7debf9028ebb.png)
無事Webブラウザが立ち上がりました。

内部でURLが使われているなんて本当か？……と、疑り深い人のために、今度は「天気」アプリを開いてみます。Windows内部では、`msnweather`スキームが「天気」アプリに関連付けられていますから、今度は`msnweather:`と打ち込んでみましょう。
![ファイル名を指定して実行ダイアログで名前欄にmsnweather:と入力した画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/76c32ba2-9af6-4efa-5844-291e1948903a.png)
OKを押してみると……
![天気アプリが更新の準備をしている画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/fa7fe729-89b6-0d6c-65ee-52729c15aced.png)
天気アプリが起動しました。普段使っていないこともバレてしまいましたけど、ご愛敬ということで。

この仕組みはWebブラウザからの遷移でも使用されています。ためしにWebブラウザのアドレスバーへ`msnweather:`と打ち込んでみます。
![Webブラウザのアドレスバーにmsnweather:と入力した画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/6b9dde8a-1bc1-17c8-6987-ff97f909fc50.png)
Enterを押して移動するとどうなるでしょうか。
![ウェブサイトが天気アプリケーションを開く許可を求めているというモーダルダイアログがWebブラウザ内で表示されている画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/3bfbe5fe-edb6-da65-de91-8eb46c5c048d.png)
Webブラウザのセキュリティ機能によってダイアログが開いてしまいましたが、「天気」アプリが開けそうです。

この仕掛けを使えば、`s3://bucket-name/object/key`へのハイパーリンクで任意のアプリケーションを立ち上げることが出来そうです。

## スキーム名とプログラムを関連付ける

ここまでで、Windowsが「URLからスキーマを認識し、関連付けられたアプリケーションを起動する機能」を持っていそうだということが確認できました。続いて、s3スキーマを認識させるにはどのようにすれば良いのか、考えてゆきます。

Windows 11ならば`設定 > アプリ > 規定のアプリ > リンクの種類で規定値を選択する`、Windows 10ならば`設定 > アプリ > 規定のアプリ > プロトコルごとに規定のアプリを選ぶ`と進んで下さい。先ほど試した`msnweather`スキーマも、ここで関連付けられていることが確認できます。
![Windows 11のリンクの種類で規定値を選択する画面。MSNWEATHERが天気アプリに関連付けられていることが、URL:msnweatherという補足説明とともに表示されている](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/555bd383-e0d3-91e6-3765-93a549c6a1a3.png)
しかしこの画面、新しいリンクの種類（スキーマ）を定義することは出来ません。そう、この定義はGUIから追加できないのです。

困りました。ならば、直接この定義を追加するしかありません。

Windowsでは、システム設定やユーザ設定などあらゆる情報をレジストリと呼ばれるデータベースに格納しています。レジストリをどのように書き換えれば良いか、探ってみましょう。

:::note alert
レジストリにはWindowsの重要な設定が含まれています。不用意な編集はWindowsが起動しなくなるなどの問題を引き起こしますから、細心の注意を払って慎重に編集して下さい。なお、このエントリを見て生じた問題の責任は負いかねますのでご了承下さい。
:::

Windowsのレジストリは、`コンピューター`をルートとする木構造です。ユーザ別にアプリケーションをインストールされる可能性があり、それぞれ関連付けが定義されるはずですから、何らかの情報がユーザ別の設定を格納する領域である`HKEY_CURRENT_USER`配下に存在すると期待されます。試しにレジストリエディターで`\HKEY_CURRENT_USER\Sosftware\Classes`を見てみます。
![レジストリエディターでmsnweatherの定義を表示した画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/a0e13f3c-48ee-6693-90db-bc771fbd7119.png)
先ほど試した「天気」アプリのスキーマ`msnweather`、発見です[^1]。データ欄に確認できる`URL:msnweather`の文字列も、何やら見覚えがありますね。Microsoftはとてもたくさんの文書を公開していますから、正攻法で進めるならば仕様を調べることとなるわけですが、少々面倒です。真似できるものがないか、もう少し見てみましょう。

もう少し見てゆくと、`slack`が定義されています。「Windows版Slack」プログラムと関連付けられているようです。直下のキーは先ほどと代わり映えしませんが、こちらは`msnweather`と違い子キーが定義されています。開いてみましょう。
![レジストリエディターでslackの定義を表示した画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/f053e661-9607-9115-201d-0a66f26cab1b.png)
`shell\open\command`……すごく、使えそうです。何が規定されているのでしょうか。
![レジストリエディターでslackの下にあるキーを表示した画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/7652ad4b-95db-aa19-f99b-5fab931daf46.png)
起動すべき実行ファイルへのパスや引数の定義がありました！こうやって、スキーマごとに異なるプログラムを関連付けて、起動していたんですね。

# 設計

ここまでで、Windowsは以下のように処理していそうであることがわかりました。

1. URLが実行される
2. URLをパースし、スキームを特定する
3. レジストリから、特定されたスキームの起動方法を探す
4. 見つかった起動方法に従って、プログラムを起動する

1と2は、勝手に行われています。3をレジストリ上に設定し、4の方法を考えれば目的を達成できそうです。

今回は、ブラウザやアプリでリンクをクリックできるようにすることがゴールです。ですから、最後はブラウザ上でS3ファイルを開けるようにしましょう。ブラウザでURLを開く方法は、これまでの調査で見えてきました。httpスキーマのURLに書き換えることさえ出来れば、関連付けられたブラウザを起動してもらうことが出来そうです。

## S3リソースネームをhttpスキーマのURLに変換

さて。Amazon S3のリソースネームは、どのようなルールでhttpスキーマのURLに変換すればよいか考えます。

インターネット上の情報を探してみると、[Getting the URL of an amazon S3 bucket](https://forums.aws.amazon.com/thread.jspa?threadID=93828)という議論をAWSのコミュニティに見つけることができました。AWSのRyanP氏が、以下のように回答しています。

> The following patterns are valid for constructing S3 URLs:
>
> ```
> http(s)://<bucket>.s3.amazonaws.com/<object>
> http(s)://s3.amazonaws.com/<bucket>/<object>
> ```
>
> Because the pattern is so simple, we've never added a get_bucket_url() method.

議論のきっかけは違えど、ここで紹介されている単純なURL変換ロジックは利用できそう……なのですが、そうはうまくゆきません。この投稿は2012年のものです。**古すぎる**のです。

今のAmazon S3にはパブリックアクセス設定という機能が追加されています。そして、初期設定はパブリックアクセスが拒否されるようになっています。安全第一です。でも、今はあまり嬉しくありません。せっかく見つけたこの変換方法で得られるURLは、残念ながら設定次第で開けないURLになってしまっているのですから。

パブリックアクセスが禁じられているケースが初期値であるため、別のURLを使う方が良さそうです。何か使えそうなURLはあるでしょうか。ブラウザで開くとしたら……AWS Webコンソールがありそうです。未ログイン状態ならばログイン画面が開きます。そもそもs3リソースネームのやりとりをしているような環境下では、対象となるAWSアカウントにログインしているケースも多いでしょうから、ログイン済み状態となっていることも多そうです。複数アカウントをログイン/ログアウト繰り返しながら使い分けているケースではうまくゆかないかもしれませんが、ある程度はスムーズに利用できそうです。今回はWebコンソール画面のURLを開くことをゴールとしてみましょう。

さっそく適当なオブジェクトをAWS Webコンソールで開いて、アドレスバーを見てみます。手元の環境では`https://s3.console.aws.amazon.com/s3/buckets/(バケット名)?region=ap-northeast-1&prefix=(オブジェクトキー)&showversions=false`となっていました。表示状態を意味していそうなパラメータなどはなくても初期値が設定されるでしょうから、ちょっと単純化してみます。最低限のクエリに絞った`https://s3.console.aws.amazon.com/s3/buckets/(バケット名)?prefix=(オブジェクトキー)`としても開けそうです。今回はこのURLを開くことにしたいと思います。

## 変換するプログラムの用意

続いてプログラミングのお時間です。

何で実装しても良いですが、ものぐさな私はWindowsへ最初から導入されているものだけで作ってみようと思います。Windowsにも、古き良きバッチファイルからWSH、PowerShellなどいくつかの方法が用意されています。今回は一番新しいPowerShellを使用してみることにしましょう。

今回はS3リソースネームを書き換えてブラウザを起動するだけですから、GUIは要りません。プログラムの起動は`Start-Process`コマンドレットがありますから、こちらも大丈夫。コンソールプログラムとして実行されるPowerShellでも十分実装できそうです。

# 実装

ここまでで、二つやるべきことがあると分かりました。

一つ目は、スクリプトの用意。PowerShellスクリプトを用意する必要があります。このスクリプトは、S3リソースネームを受け取って、httpプロトコルのURLに変換し、そのURLを実行します。

もう一つは、レジストリを編集する方法の確立。前述の通りレジストリは細心の注意を払って慎重に操作する必要がありますが、いずれにせよ操作方法を確立せねばなりません。

## スクリプトの用意

第一引数にS3リソースネームを受け取るように作ってみます。たとえば、こんな感じですね。

```powershell:opens3.ps1
Param( [parameter(mandatory=$true)][string]$s3Path )

$schema, $empty, $bucket, $object = $s3Path.Split( "/", 4 )

if ( $schema -eq 's3:' -and $empty.Length -eq 0 -and $bucket.Length -gt 0 ) {
    $url = 'https://s3.console.aws.amazon.com/s3/buckets/' + $bucket + '?prefix=' + $object
    Start-Process -FilePath $url
}
```

## レジストリの編集

ここまでで、`slack`スキームのURLがどのように処理されるかを確認しました。これを真似して、こんなキーを追加してみましょう。

```reg:s3schema.reg
Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Classes\s3]
@="URL:s3"
"URL Protocol"=""

[HKEY_CURRENT_USER\Software\Classes\s3\shell]

[HKEY_CURRENT_USER\Software\Classes\s3\shell\open]

[HKEY_CURRENT_USER\Software\Classes\s3\shell\open\command]
@="powershell.exe \"C:\\path\\to\\opens3.ps1\" \"%1\""
```

# 実行

さあ、実行してみる時が来ました。

改めて、先ほどのS3リソースネームをクリックしてみましょう。
![Webブラウザ上にs3リソースネームがハイパーリンクとして表示されているSlackのチャット画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/4b42d35c-4103-01b9-6b6e-62c1d2e4fb1b.png)
いい感じです。思った通り、ダイアログが表示されました。
![PowerShellを開こうとしていることモーダルダイアログがWebブラウザ内に表示されている画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/8e580faf-9711-5c2e-c503-c0a70eef78c3.png)
`Windows PowerShell を開く`を押してみます。
一瞬コンソールが立ち上がりますが、それもつかの間。すぐに閉じられて、ブラウザへ遷移します。
![AWSログイン画面が開いたWebブラウザ画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2344450/57a554f6-84be-2d4c-d927-8fa19f5a9380.png)
**やりました！**成功です[^2]。これで、S3リソースネームをクリックしたときに感じるフラストレーションからも、無事解放されました！

OSでs3スキーマのURLを処理できるようにしましたから、Slackプログラムなどほかの箇所でも意図したとおりAWS Webコンソールを開くことができます。

# 終わりに

ほんのちょっとPowerShellでスクリプトを書き、レジストリをいじるだけで、S3リソースネームに対する挙動を定義できました。また、AWS Webコンソールを開くURLに変換することで、直接ブラウザで開けることを確認しました。

ちょっとした不便さや時間のロスは、積み重なると馬鹿に出来ない時間のロスになります。少しずつの改善を積み重ねて、楽しいことに費やす時間を最大化できれば良いなと思います。

[^1]: 勘の良い方は、関連付けられたプログラムに関する情報が定義されていないことに気づかれるかもしれません。私のケースでは個別に設定していないため`HKEY_CURRENT_USER`配下には定義されていないだけで、他の箇所で定義されています。
[^2]: 未ログイン状態のためログイン画面が開いてしまっていますが、ご愛敬というコトで。。