http://go/somewhereのようなショートカット
従業員がイントラネット内でネットサーフィンをするのは結構大変だ。
企業のイントラネット内のサイトはインターネット上に公開するわけにもいかず
普通は検索エンジンに乗らない。
当然googleで勤怠と検索しても自社の勤怠管理用のサイトは出てきてくれない。
イントラネット内にDNSサーバーがあれば、
管理者がhttp://kintai.internal.companyA.com
のような形で従業員用の勤怠管理サイトの
名前を登録してくれているかもしれない。
しかし、このようなURLはおぼえられるほど短くないし、
予算管理は、http://internal.companyA.com/cooporate/a/b/c/yosan.html
のような全然別のURLにあるのかもしれない。大きな企業になればなるほどこの辺の統制は
難しいと思う。
結局は昔ながらのリンク集のようなものに頼ることがほとんどなのではないだろうか?
一方で、ソースを忘れてしまったがgoogle社内では http://go/kintai
みたいなURLで社内向けのサイトへのアクセスを
簡単にできるようにしているらしい。(実際は英語だろうけど)
ここまで短ければ容易に覚えられるし、ただのURLのパスなので名前の競合を心配する必要もない。
http://internal.companyA.com/cooporate/a/b/c/yosan.html
のような
コーポレート部門の作った複雑な予算管理Webサイトにもhttp://go/yosan
で重要なところだけアクセスすることが出来る。
“go”がドメイン名として使用できるか?
***.com
や***.co.jp
ではなくgo
なんていうドメイン名をパブリックなDNSに登録するのは
さすがに無理。そもそも、ドットすら入っていない。。。
だが、以下のようなエントリをhostsファイルに登録した状態でブラウザのurlにgo
と入力すると普通にlocalhostに対してリクエストを出してくれた。
127.0.0.1 go
ということは、localhost:80
(go:80
)にサーバーを立ち上げ、何らかの形でgo/somewhere
へのリクエストをどこかに転送すれば、google風のショートカットができるはずだ。
せっかくlocalhost
に立てるので、localhost/bank
(go/bank
)で自分の使っている
銀行口座のログインページに行くようなブックマークのようもできるようにしたい。
Nginxを使う?
go/somewhere=(localhost/somewhere)
へのリクエストをどこか別の場所に
送るだけであれば、NginxやHAProxyなどの設定をいじって立ち上げれば十分にできる。
だが、以下のような制御をいれたいので、Go(プログラミング言語)で作ってみることにした。
go/smewhere
のようなスペルミスをしても大丈夫なようにしたい- どのパスがどのサイトに行くかをyaml(読みやすい)にしたい
- 設定ファイルが変わったら自動で再読み込みしたい
go/
になんか出したい。(リンクの一覧とか)
リバースプロキシ(失敗)
実装の際、最初に思ったのは、このサーバーはgo/a
へのリクエストをwebsiteA.com
、
go/b
へのリクエストをwebsiteB.com
に送信した結果をクライアントに戻す
リバースプロキシのようなものになるんじゃないかということだった。
だが、ちょっと実装してみてダメだということが分かった。
websiteA.com
のページ内での/index.js
へのSame Originのリクエストがgo/index.js
に行ってしまう。
これをwebsiteB.com
から/index.js
へのリクエストと見分ける手段がない。ブラウザがSame Originの
リクエストに対しても必ずRefererヘッダーをつけるようなことをしてくれれば、見分けることが出来るが
ブラウザ側に手をいれるのは、社内のショートカットへの利用を想定するとなるべく避けたい。
リダイレクトを使う
結局go/a
へのリクエストをwebsiteA.com
にリダイレクトするような
サーバーにすることにした。これならずっとシンプルだし、ヘッダーとかの余計なことを
考えなくても済む。
Go(言語)でのリダイレクトは以下に簡単なサンプルがあった。
https://gist.github.com/hSATAC/5343225
この例を少しだけ改造して複数のリダイレクト先をPathに応じて選ぶようにすると以下のようになる。
package main
import (
"log"
"net/http"
"strings"
)
var destinations map[string]string
func init() {
// パスとその時のリダイレクト先
destinations = map[string]string{
"shop": "https://amazon.com",
"bank": "https://www.smbc-card.com/mem/index.js",
}
}
// r.URL.pathは`/shop`だったり、`/shop/`だったりするので、
// ここで`/`をトリムする
func getDestination(path string) (string, bool) {
// Remove slash if exists
d := strings.TrimPrefix(path, "/")
d = strings.TrimSuffix(d, "/")
destination, found := destinations[d]
return destination, found
}
func redirect(w http.ResponseWriter, r *http.Request) {
destination, found := getDestination(r.URL.Path)
if found {
http.Redirect(w, r, destination, 301)
} else {
http.NotFound(w, r)
}
}
func main() {
http.HandleFunc("/", redirect)
err := http.ListenAndServe(":80", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
上のサーバーをlocalhostに立てると、localhost/shop
をamazon.com
にlocalhost/bank
をwww.smbc-card.com/mem/index.js
にリダイレクトしてくれる
リダイレクトのステータスコード
上の例だとリダイレクトのステータスコードに問題があった。ステータスコード301はMoved Permanently
なので、恒久的なサイトの移動があることを指す。
localhost/shop
が一旦amazon.com
に301でリダイレクトすると、ブラウザ(すくなくともChrome)
はlocalhost/shop
への次のアクセス時にlocalhost/shop
へのリクエストを出さずに、
キャッシュされた結果から永久にamazon.com
に直接行ってしまう。これではlocalhost/shop
の行き先を
www.rakuten.co.jp
に変更したい場合などに困ってしまう。
この挙動はCache-Controlヘッダーで制御できないこともないが、意味論的にただしい307(Temporary Redirect)
を用いるのがよさそうだ。307の場合はブラウザは毎回localhost/shop
にアクセスしてくれる。
パスにあいまいさを持たせる
先ほどの例だと、以下のようにリダイレクト先の決定は行き先のMAP(map[string][string]
)
にリクエストのパスがあるかどうかだった。
destination, found := destinations[d]
return destination, found
これだと、localhost/shopp
のような打ち間違いをした場合にエラーになるしかない。
以下のように編集距離が最も短い宛先にリダイレクトするようにしてみた。
(編集距離の導出はgithub.com/agnivade/levenshteinを利用)
var destination string
minDistance := math.MaxInt32
for k, v := range destinations {
distance := levenshtein.ComputeDistance(k, d)
if distance < minDistance {
minDistance = distance
destination = v
}
}
if minDistance < 3 {
return destination, true
} else {
return "", false
}
}
だが、単純な編集距離だと、localhost/shopping
とlocalhost/card
があった際に
localhost/sho
と入力された場合、localhost/shopping
(編集距離:5)よりもlocalhost/card
(編集距離:4)が優先されてしまう。
もうちょっとよいアルゴリズムがありそう。。。
完成
一応最小限の機能がそろったので、github.com/sato-s/gosomewhere
に上げてみた。
(yaml自動リロードや、go/
に何か出すのは未対応)
インストール
go get -u github.com/sato-s/gosomewhere
# 設定ファイルを作る
cat << EOF > config.yaml
port: 80
listen: 0.0.0.0
destinations:
shop: https://www.amazon.com/
credit: https://www.smbc-card.com/mem/index.jsp
search: https://www.google.co.jp
vim: https://vim.rtorr.com/
ascii: https://en.wikipedia.org/wiki/ASCII#Printable_characters
cloud: https://developers.digitalocean.com/documentation/v2/
EOF
起動(port:80を使うから用sudo)
sudo env "PATH=$PATH" gosomewhere config.yaml
確認(事前にhostsにgo
を登録)
curl -v go/shop
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to go (127.0.0.1) port 80 (#0)
> GET /shop HTTP/1.1
> Host: go
> User-Agent: curl/7.61.1
> Accept: */*
>
< HTTP/1.1 307 Temporary Redirect
< Content-Type: text/html; charset=utf-8
< Location: https://www.amazon.com/
< Date: Sun, 09 Feb 2020 04:07:24 GMT
< Content-Length: 59
<
<a href="https://www.amazon.com/">Temporary Redirect</a>.